Skip to content

Commit

Permalink
Fix a long-standing bug with type annotation paths on raw inner class…
Browse files Browse the repository at this point in the history
… types

As described in [JVMS
4.7.20.2](https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.7.20.2),
type annotations on nested types are located with `type_path_kind=1` type path
entries, which describe steps starting with the 'outermost part of the type for
which a type annotation is admissible'.

Turbine represents class types for inner classes as a flat list of 'simple
class types' corresponding to outer and inner classes. The 'canonicalization'
step normalizes type arguments, and also ensures there's exactly one entry for
each nested class and non-static containing class. The former doesn't apply to
raw types (we erase the entire type if any part of it is raw), but the latter
is important to ensure we compute the right number of type path steps for inner
classes.

PiperOrigin-RevId: 576882290
  • Loading branch information
cushon authored and Javac Team committed Oct 26, 2023
1 parent 05fee69 commit 18df6a7
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 3 deletions.
6 changes: 3 additions & 3 deletions java/com/google/turbine/types/Canonicalize.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@ private ClassTy canon(ClassSymbol base, ClassTy ty) {
if (ty.sym().equals(ClassSymbol.ERROR)) {
return ty;
}
if (isRaw(ty)) {
return Erasure.eraseClassTy(ty);
}
// if the first name is a simple name resolved inside a nested class, add explicit qualifiers
// for the enclosing declarations
Iterator<ClassTy.SimpleClassTy> it = ty.classes().iterator();
Expand All @@ -140,6 +137,9 @@ private ClassTy canon(ClassSymbol base, ClassTy ty) {
while (it.hasNext()) {
canon = canonOne(canon, it.next());
}
if (isRaw(canon)) {
canon = Erasure.eraseClassTy(canon);
}
return canon;
}

Expand Down
7 changes: 7 additions & 0 deletions javatests/com/google/turbine/lower/LowerIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
* A test that compiles inputs with both javac and turbine, and asserts that the output is
* equivalent.
*/
@RunWith(Parameterized.class)
public class LowerIntegrationTest {

Expand Down Expand Up @@ -314,6 +318,9 @@ public static Iterable<Object[]> parameters() {
"type_anno_c_array.test",
"type_anno_cstyle_array_dims.test",
"type_anno_hello.test",
"type_anno_nested.test",
"type_anno_nested_generic.test",
"type_anno_nested_raw.test",
"type_anno_order.test",
"type_anno_parameter_index.test",
"type_anno_qual.test",
Expand Down
23 changes: 23 additions & 0 deletions javatests/com/google/turbine/lower/testdata/type_anno_nested.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
=== Annotations.java ===
import static java.lang.annotation.ElementType.TYPE_USE;
import java.lang.annotation.Target;

@Target(TYPE_USE) @interface A {}
@Target(TYPE_USE) @interface B {}
@Target(TYPE_USE) @interface C {}

=== Outer.java ===
class Outer {

@A Outer . @B Middle . @C Inner f;
Outer . @A MiddleStatic . @B Inner g;
Outer . MiddleStatic . @A InnerStatic h;

class Middle {
class Inner {}
}
static class MiddleStatic {
class Inner {}
static class InnerStatic {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
=== Annotations.java ===
import static java.lang.annotation.ElementType.TYPE_USE;
import java.lang.annotation.Target;

@Target(TYPE_USE) @interface A {}
@Target(TYPE_USE) @interface B {}
@Target(TYPE_USE) @interface C {}
@Target(TYPE_USE) @interface D {}

=== Outer.java ===
class Outer {
Outer . Middle<@A Foo . @B Bar> . Inner<@D String @C []> f;

class Middle<T> {
class Inner<U> {}
}

class Foo {
class Bar {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== Annotations.java ===
import static java.lang.annotation.ElementType.TYPE_USE;
import java.lang.annotation.Target;

@Target(TYPE_USE) @interface A {}
@Target(TYPE_USE) @interface B {}
@Target(TYPE_USE) @interface C {}

=== Outer.java ===
import java.util.List;

class Outer {
static class StaticMiddle<T> {
class Inner<U> {}
static class StaticInner<U> {}

// raw types with parameterized enclosing types
@A Inner a;
@A StaticInner b;
}

Outer . StaticMiddle . @A Inner e;
Outer . StaticMiddle . @A StaticInner f;
Outer . StaticMiddle<@A String> . @B Inner<@C String> g;

Outer . StaticMiddle<@A List> . @B Inner<@C List> h;
List<Outer . StaticMiddle . @A StaticInner> i;

// javac rejects these partially raw types
// Outer . StaticMiddle<@A String> . @B Inner j;
// Outer . StaticMiddle . @B Inner<@C String> k;
}

0 comments on commit 18df6a7

Please sign in to comment.