aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcushon <cushon@google.com>2016-10-05 17:05:23 -0700
committerLiam Miller-Cushon <cushon@google.com>2016-10-05 21:12:30 -0700
commit4c1981b86b09d9802ce3f773c059c529546048a5 (patch)
tree0cd55d103d47045c2932a25fc40723630c55bf35
parent5a08d0b091f99653ff51f60b10392340f4718f6c (diff)
downloadturbine-4c1981b86b09d9802ce3f773c059c529546048a5.tar.gz
Type canonicalization
Canonicalize qualified type names so qualifiers are always the declaring class of the qualified type. For example, given: ``` class A<T> { class Inner {} } class B extends A<String> {} ``` The type name `B.Inner` must be canonicalized as `A<String>.Inner` in bytecode. MOE_MIGRATED_REVID=135300804
-rw-r--r--java/com/google/turbine/binder/Binder.java12
-rw-r--r--java/com/google/turbine/binder/CanonicalTypeBinder.java122
-rw-r--r--java/com/google/turbine/binder/ClassPathBinder.java27
-rw-r--r--java/com/google/turbine/binder/TypeBinder.java4
-rw-r--r--java/com/google/turbine/binder/bound/SourceTypeBoundClass.java26
-rw-r--r--java/com/google/turbine/binder/bound/TypeBoundClass.java52
-rw-r--r--java/com/google/turbine/binder/bytecode/BytecodeBinder.java88
-rw-r--r--java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java100
-rw-r--r--java/com/google/turbine/lower/Lower.java2
-rw-r--r--java/com/google/turbine/type/Type.java14
-rw-r--r--java/com/google/turbine/types/Canonicalize.java286
-rw-r--r--javatests/com/google/turbine/binder/ClassPathBinderTest.java3
-rw-r--r--javatests/com/google/turbine/lower/LowerIntegrationTest.java18
-rw-r--r--javatests/com/google/turbine/lower/LowerSignatureTest.java2
-rw-r--r--javatests/com/google/turbine/lower/testdata/bytenoncanon.test22
-rw-r--r--javatests/com/google/turbine/lower/testdata/canon.test11
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon.test45
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon1.test26
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon10.test22
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon2.test28
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon3.test24
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon4.test24
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon5.test30
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon6.test19
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon8.test23
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon9.test22
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon_byte.test50
-rw-r--r--javatests/com/google/turbine/lower/testdata/genericnoncanon_method3.test31
-rw-r--r--javatests/com/google/turbine/lower/testdata/noncanon.test16
-rw-r--r--javatests/com/google/turbine/lower/testdata/rawcanon.test12
-rw-r--r--javatests/com/google/turbine/lower/testdata/wildboundcanon.test12
-rw-r--r--javatests/com/google/turbine/lower/testdata/wildcanon.test12
32 files changed, 1141 insertions, 44 deletions
diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java
index 77e85a7..8877e6f 100644
--- a/java/com/google/turbine/binder/Binder.java
+++ b/java/com/google/turbine/binder/Binder.java
@@ -30,6 +30,7 @@ import com.google.turbine.binder.bound.PackageSourceBoundClass;
import com.google.turbine.binder.bound.SourceBoundClass;
import com.google.turbine.binder.bound.SourceHeaderBoundClass;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
+import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
@@ -82,6 +83,8 @@ public class Binder {
Env<SourceTypeBoundClass> tenv =
bindTypes(syms, henv, CompoundEnv.<HeaderBoundClass>of(classPathEnv).append(henv));
+ tenv = canonicalizeTypes(syms, tenv, CompoundEnv.<TypeBoundClass>of(classPathEnv).append(tenv));
+
ImmutableMap.Builder<ClassSymbol, SourceTypeBoundClass> result = ImmutableMap.builder();
for (ClassSymbol sym : syms) {
result.put(sym, tenv.get(sym));
@@ -199,6 +202,15 @@ public class Binder {
return builder.build();
}
+ private static Env<SourceTypeBoundClass> canonicalizeTypes(
+ ImmutableSet<ClassSymbol> syms, Env<SourceTypeBoundClass> stenv, Env<TypeBoundClass> tenv) {
+ SimpleEnv.Builder<SourceTypeBoundClass> builder = SimpleEnv.builder();
+ for (ClassSymbol sym : syms) {
+ builder.putIfAbsent(sym, CanonicalTypeBinder.bind(sym, stenv.get(sym), tenv));
+ }
+ return builder.build();
+ }
+
/** The result of binding: bound nodes for sources in the compilation, and the classpath. */
public static class BindingResult {
private final ImmutableMap<ClassSymbol, SourceTypeBoundClass> units;
diff --git a/java/com/google/turbine/binder/CanonicalTypeBinder.java b/java/com/google/turbine/binder/CanonicalTypeBinder.java
new file mode 100644
index 0000000..c6a17b6
--- /dev/null
+++ b/java/com/google/turbine/binder/CanonicalTypeBinder.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.turbine.binder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.bound.SourceTypeBoundClass;
+import com.google.turbine.binder.bound.SourceTypeBoundClass.FieldInfo;
+import com.google.turbine.binder.bound.SourceTypeBoundClass.MethodInfo;
+import com.google.turbine.binder.bound.SourceTypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.TyVarSymbol;
+import com.google.turbine.type.Type;
+import com.google.turbine.type.Type.ClassTy;
+import com.google.turbine.types.Canonicalize;
+import java.util.Map;
+
+/**
+ * Canonicalizes all qualified types in a {@link SourceTypeBoundClass} using {@link Canonicalize}.
+ */
+public class CanonicalTypeBinder {
+
+ static SourceTypeBoundClass bind(
+ ClassSymbol sym, SourceTypeBoundClass base, Env<TypeBoundClass> env) {
+ ClassTy superClassType = null;
+ if (base.superClassType() != null) {
+ superClassType = Canonicalize.canonicalizeClassTy(env, sym, base.superClassType());
+ }
+ ImmutableList.Builder<ClassTy> interfaceTypes = ImmutableList.builder();
+ for (ClassTy i : base.interfaceTypes()) {
+ interfaceTypes.add(Canonicalize.canonicalizeClassTy(env, sym, i));
+ }
+ ImmutableMap<TyVarSymbol, TyVarInfo> typParamTypes =
+ typeParameters(env, sym, base.typeParameterTypes());
+ ImmutableList<MethodInfo> methods = methods(env, sym, base.methods());
+ ImmutableList<FieldInfo> fields = fields(env, sym, base.fields());
+ return new SourceTypeBoundClass(
+ interfaceTypes.build(),
+ superClassType,
+ typParamTypes,
+ base.access(),
+ methods,
+ fields,
+ base.owner(),
+ base.kind(),
+ base.children(),
+ base.superclass(),
+ base.interfaces(),
+ base.typeParameters());
+ }
+
+ private static ImmutableList<FieldInfo> fields(
+ Env<TypeBoundClass> env, ClassSymbol sym, ImmutableList<FieldInfo> fields) {
+ ImmutableList.Builder<FieldInfo> result = ImmutableList.builder();
+ for (FieldInfo base : fields) {
+ result.add(
+ new FieldInfo(
+ base.sym(), Canonicalize.canonicalize(env, sym, base.type()), base.access()));
+ }
+ return result.build();
+ }
+
+ private static ImmutableList<MethodInfo> methods(
+ Env<TypeBoundClass> env, ClassSymbol sym, ImmutableList<MethodInfo> methods) {
+ ImmutableList.Builder<MethodInfo> result = ImmutableList.builder();
+ for (MethodInfo base : methods) {
+ ImmutableMap<TyVarSymbol, TyVarInfo> tps = typeParameters(env, sym, base.tyParams());
+ Type ret = Canonicalize.canonicalize(env, sym, base.returnType());
+ ImmutableList.Builder<ParamInfo> parameters = ImmutableList.builder();
+ for (ParamInfo parameter : base.parameters()) {
+ parameters.add(
+ new ParamInfo(
+ Canonicalize.canonicalize(env, sym, parameter.type()), parameter.synthetic()));
+ }
+ ImmutableList<Type> exceptions = canonicalizeList(env, sym, base.exceptions());
+ result.add(
+ new MethodInfo(base.sym(), tps, ret, parameters.build(), exceptions, base.access()));
+ }
+ return result.build();
+ }
+
+ private static ImmutableMap<TyVarSymbol, TyVarInfo> typeParameters(
+ Env<TypeBoundClass> env, ClassSymbol sym, Map<TyVarSymbol, TyVarInfo> tps) {
+ ImmutableMap.Builder<TyVarSymbol, TyVarInfo> result = ImmutableMap.builder();
+ for (Map.Entry<TyVarSymbol, TyVarInfo> e : tps.entrySet()) {
+ TyVarInfo info = e.getValue();
+ Type superClassBound = null;
+ if (info.superClassBound() != null) {
+ superClassBound = Canonicalize.canonicalize(env, sym, info.superClassBound());
+ }
+ ImmutableList<Type> interfaceBounds = canonicalizeList(env, sym, info.interfaceBounds());
+ result.put(e.getKey(), new TyVarInfo(superClassBound, interfaceBounds));
+ }
+ return result.build();
+ }
+
+ private static ImmutableList<Type> canonicalizeList(
+ Env<TypeBoundClass> env, ClassSymbol sym, ImmutableList<Type> types) {
+ ImmutableList.Builder<Type> result = ImmutableList.builder();
+ for (Type type : types) {
+ result.add(Canonicalize.canonicalize(env, sym, type));
+ }
+ return result.build();
+ }
+}
diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java
index 73609fc..70c80ca 100644
--- a/java/com/google/turbine/binder/ClassPathBinder.java
+++ b/java/com/google/turbine/binder/ClassPathBinder.java
@@ -16,8 +16,7 @@
package com.google.turbine.binder;
-import static com.google.turbine.binder.env.SimpleEnv.builder;
-
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
@@ -29,9 +28,12 @@ import java.io.IOError;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+
/** Sets up an environment for symbols on the classpath. */
public class ClassPathBinder {
@@ -51,15 +53,25 @@ public class ClassPathBinder {
private static Env<BytecodeBoundClass> bindClasspath(
TopLevelIndex.Builder tli, Iterable<Path> paths) throws IOException {
- SimpleEnv.Builder<BytecodeBoundClass> result = builder();
+ Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>();
+ Env<BytecodeBoundClass> benv =
+ new Env<BytecodeBoundClass>() {
+ @Override
+ public BytecodeBoundClass get(ClassSymbol sym) {
+ return map.get(sym);
+ }
+ };
for (Path path : paths) {
- bindJar(tli, path, result);
+ bindJar(tli, path, map, benv);
}
- return result.build();
+ return new SimpleEnv<>(ImmutableMap.copyOf(map));
}
private static void bindJar(
- TopLevelIndex.Builder tli, Path path, SimpleEnv.Builder<BytecodeBoundClass> env)
+ TopLevelIndex.Builder tli,
+ Path path,
+ Map<ClassSymbol, BytecodeBoundClass> env,
+ Env<BytecodeBoundClass> benv)
throws IOException {
// TODO(cushon): consider creating a nio-friendly jar reading abstraction for testing,
// that yields something like `Iterable<Pair<String, Supplier<byte[]>>>`
@@ -73,7 +85,8 @@ public class ClassPathBinder {
continue;
}
ClassSymbol sym = new ClassSymbol(name.substring(0, name.length() - ".class".length()));
- if (env.putIfAbsent(sym, new BytecodeBoundClass(sym, () -> toByteArrayOrDie(jf, je)))) {
+ if (!env.containsKey(sym)) {
+ env.put(sym, new BytecodeBoundClass(sym, () -> toByteArrayOrDie(jf, je), benv));
tli.insert(sym);
}
}
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index 162c3f6..219bc1f 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -27,7 +27,7 @@ import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.SourceTypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.SourceTypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.SourceTypeBoundClass.ParamInfo;
-import com.google.turbine.binder.bound.SourceTypeBoundClass.TyVarInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.lookup.CompoundScope;
import com.google.turbine.binder.lookup.LookupKey;
@@ -560,7 +560,7 @@ public class TypeBinder {
} else if (t.upper().isPresent()) {
return new Type.WildUpperBoundedTy(bindTy(env, scope, t.upper().get()));
} else {
- return new Type.WildTy();
+ return Type.WILD_TY;
}
}
}
diff --git a/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java b/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
index bed37ee..8bbf9f0 100644
--- a/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
+++ b/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
@@ -27,7 +27,7 @@ import com.google.turbine.type.Type;
import javax.annotation.Nullable;
/** A HeaderBoundClass for classes compiled from source. */
-public class SourceTypeBoundClass implements HeaderBoundClass {
+public class SourceTypeBoundClass implements TypeBoundClass {
private final TurbineTyKind kind;
private final ClassSymbol owner;
@@ -102,7 +102,6 @@ public class SourceTypeBoundClass implements HeaderBoundClass {
return children;
}
- /** Declared type parameters. */
@Override
public ImmutableMap<String, TyVarSymbol> typeParameters() {
return typeParameters;
@@ -114,6 +113,7 @@ public class SourceTypeBoundClass implements HeaderBoundClass {
}
/** The super-class type. */
+ @Override
public Type.ClassTy superClassType() {
return superClassType;
}
@@ -129,6 +129,7 @@ public class SourceTypeBoundClass implements HeaderBoundClass {
}
/** Declared type parameters. */
+ @Override
public ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes() {
return typeParameterTypes;
}
@@ -249,25 +250,4 @@ public class SourceTypeBoundClass implements HeaderBoundClass {
return access;
}
}
-
- /** A type parameter declaration. */
- public static class TyVarInfo {
- private final Type superClassBound;
- private final ImmutableList<Type> interfaceBounds;
-
- public TyVarInfo(Type superClassBound, ImmutableList<Type> interfaceBounds) {
- this.superClassBound = superClassBound;
- this.interfaceBounds = interfaceBounds;
- }
-
- /** A class bound, or {@code null}. */
- public Type superClassBound() {
- return superClassBound;
- }
-
- /** Interface type bounds. */
- public ImmutableList<Type> interfaceBounds() {
- return interfaceBounds;
- }
- }
}
diff --git a/java/com/google/turbine/binder/bound/TypeBoundClass.java b/java/com/google/turbine/binder/bound/TypeBoundClass.java
new file mode 100644
index 0000000..169ceb2
--- /dev/null
+++ b/java/com/google/turbine/binder/bound/TypeBoundClass.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.turbine.binder.bound;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.sym.TyVarSymbol;
+import com.google.turbine.type.Type;
+
+/** A bound node that augments {@link HeaderBoundClass} with type information. */
+public interface TypeBoundClass extends HeaderBoundClass {
+
+ /** The super-class type. */
+ Type.ClassTy superClassType();
+
+ ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes();
+
+ /** A type parameter declaration. */
+ class TyVarInfo {
+ private final Type superClassBound;
+ private final ImmutableList<Type> interfaceBounds;
+
+ public TyVarInfo(Type superClassBound, ImmutableList<Type> interfaceBounds) {
+ this.superClassBound = superClassBound;
+ this.interfaceBounds = interfaceBounds;
+ }
+
+ /** A class bound, or {@code null}. */
+ public Type superClassBound() {
+ return superClassBound;
+ }
+
+ /** Interface type bounds. */
+ public ImmutableList<Type> interfaceBounds() {
+ return interfaceBounds;
+ }
+ }
+}
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
new file mode 100644
index 0000000..85e26e2
--- /dev/null
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.turbine.binder.bytecode;
+
+import com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.TyVarSymbol;
+import com.google.turbine.bytecode.sig.Sig;
+import com.google.turbine.type.Type;
+import com.google.turbine.type.Type.ConcreteTyArg;
+import com.google.turbine.type.Type.TyVar;
+import com.google.turbine.type.Type.WildLowerBoundedTy;
+import com.google.turbine.type.Type.WildUpperBoundedTy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/** Bind {@link Type}s from bytecode. */
+public class BytecodeBinder {
+
+ static Type.ClassTy bindClassTy(Sig.ClassTySig sig, Function<String, TyVarSymbol> scope) {
+ StringBuilder sb = new StringBuilder(sig.pkg());
+ boolean first = true;
+ List<Type.ClassTy.SimpleClassTy> classes = new ArrayList<>();
+ for (Sig.SimpleClassTySig s : sig.classes()) {
+ sb.append(first ? '/' : '$');
+ sb.append(s.simpleName());
+ ClassSymbol sym = new ClassSymbol(sb.toString());
+
+ ImmutableList.Builder<Type.TyArg> tyArgs = ImmutableList.builder();
+ for (Sig.TyArgSig sig1 : s.tyArgs()) {
+ tyArgs.add(bindTyArg(sig1, scope));
+ }
+
+ classes.add(new Type.ClassTy.SimpleClassTy(sym, tyArgs.build()));
+ first = false;
+ }
+ return new Type.ClassTy(classes);
+ }
+
+ private static Type.TyArg bindTyArg(Sig.TyArgSig sig, Function<String, TyVarSymbol> scope) {
+ switch (sig.kind()) {
+ case UNBOUNDED:
+ return Type.WILD_TY;
+ case LOWER_BOUNDED:
+ return new WildLowerBoundedTy(bindTy(((Sig.LowerBoundTyArgSig) sig).bound(), scope));
+ case UPPER_BOUNDED:
+ return new WildUpperBoundedTy(bindTy(((Sig.UpperBoundTyArgSig) sig).bound(), scope));
+ case CONCRETE:
+ return new ConcreteTyArg(bindTy(((Sig.ConcreteTyArgSig) sig).type(), scope));
+ default:
+ throw new AssertionError(sig.kind());
+ }
+ }
+
+ static Type bindTy(Sig.TySig sig, Function<String, TyVarSymbol> scope) {
+ switch (sig.kind()) {
+ case BASE_TY_SIG:
+ return new Type.PrimTy(((Sig.BaseTySig) sig).type());
+ case CLASS_TY_SIG:
+ return bindClassTy((Sig.ClassTySig) sig, scope);
+ case TY_VAR_SIG:
+ return new TyVar(scope.apply(((Sig.TyVarSig) sig).name()));
+ case ARRAY_TY_SIG:
+ return bindArrayTy((Sig.ArrayTySig) sig, scope);
+ default:
+ throw new AssertionError(sig.kind());
+ }
+ }
+
+ private static Type bindArrayTy(Sig.ArrayTySig arrayTySig, Function<String, TyVarSymbol> scope) {
+ return new Type.ArrayTy(arrayTySig.dimension(), bindTy(arrayTySig.elementType(), scope));
+ }
+}
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index 1e546a7..9964b40 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -24,6 +24,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.BoundClass;
import com.google.turbine.binder.bound.HeaderBoundClass;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.bytecode.ClassFile;
@@ -33,6 +35,10 @@ import com.google.turbine.bytecode.sig.Sig.ClassSig;
import com.google.turbine.bytecode.sig.SigParser;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
+import com.google.turbine.type.Type;
+import com.google.turbine.type.Type.ClassTy;
+import java.util.Map;
+import java.util.function.Function;
import javax.annotation.Nullable;
/**
@@ -43,13 +49,16 @@ import javax.annotation.Nullable;
* resolved and canonicalized so there are no cycles. The laziness also minimizes the amount of work
* done on the classpath.
*/
-public class BytecodeBoundClass implements BoundClass, HeaderBoundClass {
+public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBoundClass {
private final ClassSymbol sym;
+ private final Env<BytecodeBoundClass> env;
private final Supplier<ClassFile> classFile;
- public BytecodeBoundClass(ClassSymbol sym, final Supplier<byte[]> bytes) {
+ public BytecodeBoundClass(
+ ClassSymbol sym, final Supplier<byte[]> bytes, Env<BytecodeBoundClass> env) {
this.sym = sym;
+ this.env = env;
this.classFile =
Suppliers.memoize(
new Supplier<ClassFile>() {
@@ -220,4 +229,91 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass {
public ImmutableList<ClassSymbol> interfaces() {
return interfaces.get();
}
+
+ Supplier<ClassTy> superClassType =
+ Suppliers.memoize(
+ new Supplier<ClassTy>() {
+ @Override
+ public ClassTy get() {
+ if (superclass() == null) {
+ return null;
+ }
+ if (sig.get() == null || sig.get().superClass() == null) {
+ return ClassTy.asNonParametricClassTy(superclass());
+ }
+ return BytecodeBinder.bindClassTy(
+ sig.get().superClass(), makeScope(env, sym, ImmutableMap.of()));
+ }
+ });
+
+ @Override
+ public ClassTy superClassType() {
+ return superClassType.get();
+ }
+
+ Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>> typeParameterTypes =
+ Suppliers.memoize(
+ new Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>>() {
+ @Override
+ public ImmutableMap<TyVarSymbol, TyVarInfo> get() {
+ if (sig.get() == null) {
+ return ImmutableMap.of();
+ }
+ ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder();
+ Function<String, TyVarSymbol> scope = makeScope(env, sym, typeParameters());
+ for (Sig.TyParamSig p : sig.get().tyParams()) {
+ tparams.put(typeParameters().get(p.name()), bindTyParam(p, scope));
+ }
+ return tparams.build();
+ }
+ });
+
+ private static TyVarInfo bindTyParam(Sig.TyParamSig sig, Function<String, TyVarSymbol> scope) {
+ Type superClassBound = null;
+ if (sig.classBound() != null) {
+ superClassBound = BytecodeBinder.bindTy(sig.classBound(), scope);
+ }
+ ImmutableList.Builder<Type> interfaceBounds = ImmutableList.builder();
+ for (Sig.TySig t : sig.interfaceBounds()) {
+ interfaceBounds.add(BytecodeBinder.bindTy(t, scope));
+ }
+ return new TyVarInfo(superClassBound, interfaceBounds.build());
+ }
+
+ @Override
+ public ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes() {
+ return typeParameterTypes.get();
+ }
+
+ /**
+ * Create a scope for resolving type variable symbols declared in the class, and any enclosing
+ * instances.
+ */
+ private static Function<String, TyVarSymbol> makeScope(
+ final Env<BytecodeBoundClass> env,
+ final ClassSymbol sym,
+ final Map<String, TyVarSymbol> typeVariables) {
+ return new Function<String, TyVarSymbol>() {
+ @Override
+ public TyVarSymbol apply(String input) {
+ TyVarSymbol result = typeVariables.get(input);
+ if (result != null) {
+ return result;
+ }
+ ClassSymbol curr = sym;
+ while (curr != null) {
+ BytecodeBoundClass info = env.get(curr);
+ if (info == null) {
+ throw new AssertionError(curr);
+ }
+ result = info.typeParameters().get(input);
+ if (result != null) {
+ return result;
+ }
+ curr = info.owner();
+ }
+ throw new AssertionError(input);
+ }
+ };
+ }
}
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index c759a20..9230e29 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -24,7 +24,7 @@ import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.SourceTypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.SourceTypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.SourceTypeBoundClass.ParamInfo;
-import com.google.turbine.binder.bound.SourceTypeBoundClass.TyVarInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
diff --git a/java/com/google/turbine/type/Type.java b/java/com/google/turbine/type/Type.java
index 004f974..5f9e8f8 100644
--- a/java/com/google/turbine/type/Type.java
+++ b/java/com/google/turbine/type/Type.java
@@ -295,11 +295,11 @@ public interface Type {
}
/** An unbounded wildcard type. */
- class WildTy extends TyArg {
-
- @Override
- public TyArgKind tyArgKind() {
- return TyArgKind.WILD;
- }
- }
+ TyArg WILD_TY =
+ new TyArg() {
+ @Override
+ public TyArgKind tyArgKind() {
+ return TyArgKind.WILD;
+ }
+ };
}
diff --git a/java/com/google/turbine/types/Canonicalize.java b/java/com/google/turbine/types/Canonicalize.java
new file mode 100644
index 0000000..8aa5beb
--- /dev/null
+++ b/java/com/google/turbine/types/Canonicalize.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.turbine.types;
+
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.TyVarSymbol;
+import com.google.turbine.model.TurbineFlag;
+import com.google.turbine.type.Type;
+import com.google.turbine.type.Type.ClassTy;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * Canonicalizes qualified type names so qualifiers are always the declaring class of the qualified
+ * type.
+ *
+ * <p>For example, given:
+ *
+ * <pre>{@code
+ * class A<T> {
+ * class Inner {}
+ * }
+ * class B extends A<String> {
+ * Inner i;
+ * }
+ * }</pre>
+ *
+ * <p>The canonical name of the type of {@code B.i} is {@code A<String>.Inner}, not {@code B.Inner}.
+ */
+public class Canonicalize {
+
+ /** Canonicalizes the given type. */
+ public static Type canonicalize(Env<TypeBoundClass> env, ClassSymbol base, Type type) {
+ switch (type.tyKind()) {
+ case PRIM_TY:
+ case VOID_TY:
+ case TY_VAR:
+ return type;
+ case ARRAY_TY:
+ {
+ Type.ArrayTy arrayTy = (Type.ArrayTy) type;
+ return new Type.ArrayTy(
+ arrayTy.dimension(), canonicalize(env, base, arrayTy.elementType()));
+ }
+ case CLASS_TY:
+ return canonicalizeClassTy(env, base, (ClassTy) type);
+ default:
+ throw new AssertionError(type.tyKind());
+ }
+ }
+
+ /** Canonicalize a qualified class type, excluding type arguments. */
+ private static ClassTy canon(Env<TypeBoundClass> env, ClassSymbol base, ClassTy 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();
+ Collection<ClassTy.SimpleClassTy> lexicalBase = lexicalBase(env, ty.classes.get(0).sym(), base);
+ ClassTy canon =
+ !lexicalBase.isEmpty()
+ ? new ClassTy(lexicalBase)
+ : new ClassTy(Collections.singletonList(it.next()));
+
+ // canonicalize each additional simple name that appeared in source
+ while (it.hasNext()) {
+ canon = canonOne(env, canon, it.next());
+ }
+ return canon;
+ }
+
+ /** Given a base symbol to canonicalize, find any implicit enclosing instances. */
+ private static Collection<ClassTy.SimpleClassTy> lexicalBase(
+ Env<TypeBoundClass> env, ClassSymbol first, ClassSymbol owner) {
+ if ((env.get(first).access() & TurbineFlag.ACC_STATIC) == TurbineFlag.ACC_STATIC) {
+ return Collections.emptyList();
+ }
+ ClassSymbol canonOwner = env.get(first).owner();
+ Deque<ClassTy.SimpleClassTy> result = new ArrayDeque<>();
+ while (canonOwner != null && owner != null) {
+ if (!isSubclass(env, owner, canonOwner)) {
+ owner = env.get(owner).owner();
+ continue;
+ }
+ result.addFirst(uninstantiated(env, owner));
+ if ((env.get(owner).access() & TurbineFlag.ACC_STATIC) == TurbineFlag.ACC_STATIC) {
+ break;
+ }
+ canonOwner = env.get(canonOwner).owner();
+ }
+ return result;
+ }
+
+ private static ClassTy.SimpleClassTy uninstantiated(Env<TypeBoundClass> env, ClassSymbol owner) {
+ ImmutableList.Builder<Type.TyArg> targs = ImmutableList.builder();
+ for (TyVarSymbol p : env.get(owner).typeParameterTypes().keySet()) {
+ targs.add(new Type.ConcreteTyArg(new Type.TyVar(p)));
+ }
+ return new ClassTy.SimpleClassTy(owner, targs.build());
+ }
+
+ // is s a subclass (not interface) of t?
+ static boolean isSubclass(Env<TypeBoundClass> env, ClassSymbol s, ClassSymbol t) {
+ while (s != null) {
+ if (s.equals(t)) {
+ return true;
+ }
+ s = env.get(s).superclass();
+ }
+ return false;
+ }
+
+ /**
+ * Adds a simple class type to an existing canonical base class type, and canonicalizes the
+ * result.
+ */
+ private static ClassTy canonOne(Env<TypeBoundClass> env, ClassTy base, ClassTy.SimpleClassTy ty) {
+ // if the class is static, it has a trivial canonical qualifier with no type arguments
+ if ((env.get(ty.sym()).access() & TurbineFlag.ACC_STATIC) == TurbineFlag.ACC_STATIC) {
+ return new ClassTy(Collections.singletonList(ty));
+ }
+ ImmutableList.Builder<ClassTy.SimpleClassTy> simples = ImmutableList.builder();
+ ClassSymbol owner = env.get(ty.sym()).owner();
+ if (owner.equals(base.sym())) {
+ // if the canonical prefix is the owner the next symbol in the qualified name,
+ // the type is already in canonical form
+ simples.addAll(base.classes);
+ simples.add(ty);
+ return new ClassTy(simples.build());
+ }
+ // ... otherwise, find the supertype the class was inherited from
+ // and instantiate it as a member of the current class
+ ClassTy curr = base;
+ Map<TyVarSymbol, Type.TyArg> mapping = new LinkedHashMap<>();
+ while (curr != null) {
+ for (ClassTy.SimpleClassTy s : curr.classes) {
+ addInstantiation(env, mapping, s);
+ }
+ if (curr.sym().equals(owner)) {
+ for (ClassTy.SimpleClassTy s : curr.classes) {
+ simples.add(instantiate(env, mapping, s.sym()));
+ }
+ break;
+ }
+ curr = canon(env, curr.sym(), env.get(curr.sym()).superClassType());
+ }
+ simples.add(ty);
+ return new ClassTy(simples.build());
+ }
+
+ /** Add the type arguments of a simple class type to a type mapping. */
+ static void addInstantiation(
+ Env<TypeBoundClass> env,
+ Map<TyVarSymbol, Type.TyArg> mapping,
+ ClassTy.SimpleClassTy simpleType) {
+ Collection<TyVarSymbol> symbols = env.get(simpleType.sym()).typeParameters().values();
+ if (simpleType.targs().isEmpty()) {
+ // the type is raw
+ for (TyVarSymbol sym : symbols) {
+ mapping.put(sym, null);
+ }
+ return;
+ }
+ // otherwise, it is an instantiated generic type
+ Verify.verify(symbols.size() == simpleType.targs().size());
+ Iterator<Type.TyArg> typeArguments = simpleType.targs().iterator();
+ for (TyVarSymbol sym : symbols) {
+ Type.TyArg argument = typeArguments.next();
+ if (Objects.equals(tyVarSym(argument), sym)) {
+ continue;
+ }
+ mapping.put(sym, argument);
+ }
+ }
+
+ /** Instantiate a simple class type for the given symbol, and with the given type mapping. */
+ static ClassTy.SimpleClassTy instantiate(
+ Env<TypeBoundClass> env, Map<TyVarSymbol, Type.TyArg> mapping, ClassSymbol classSymbol) {
+ List<Type.TyArg> args = new ArrayList<>();
+ for (TyVarSymbol sym : env.get(classSymbol).typeParameterTypes().keySet()) {
+ if (!mapping.containsKey(sym)) {
+ args.add(new Type.ConcreteTyArg(new Type.TyVar(sym)));
+ continue;
+ }
+ Type.TyArg arg = instantiate(mapping, mapping.get(sym));
+ if (arg == null) {
+ // raw types
+ args.clear();
+ break;
+ }
+ args.add(arg);
+ }
+ return new ClassTy.SimpleClassTy(classSymbol, ImmutableList.copyOf(args));
+ }
+
+ /** Instantiates a type argument using the given mapping. */
+ private static Type.TyArg instantiate(
+ Map<TyVarSymbol, Type.TyArg> mapping, Type.TyArg typeArgument) {
+ if (typeArgument == null) {
+ return null;
+ }
+ TyVarSymbol sym = tyVarSym(typeArgument);
+ if (!mapping.containsKey(sym)) {
+ return typeArgument;
+ }
+ return instantiate(mapping, mapping.get(sym));
+ }
+
+ /**
+ * Returns the type variable symbol for a concrete type argument whose type is a type variable
+ * reference, or else {@code null}.
+ */
+ @Nullable
+ static TyVarSymbol tyVarSym(Type.TyArg typeArgument) {
+ if (typeArgument.tyArgKind() != Type.TyArg.TyArgKind.CONCRETE) {
+ return null;
+ }
+ Type.ConcreteTyArg concrete = (Type.ConcreteTyArg) typeArgument;
+ if (concrete.type().tyKind() != Type.TyKind.TY_VAR) {
+ return null;
+ }
+ return ((Type.TyVar) concrete.type()).sym();
+ }
+
+ public static ClassTy canonicalizeClassTy(Env<TypeBoundClass> env, ClassSymbol base, ClassTy ty) {
+ // canonicalize type arguments first
+ ImmutableList.Builder<ClassTy.SimpleClassTy> args = ImmutableList.builder();
+ for (ClassTy.SimpleClassTy s : ty.classes) {
+ args.add(new ClassTy.SimpleClassTy(s.sym(), canonicalizeTyArgs(s.targs(), base, env)));
+ }
+ ty = new ClassTy(args.build());
+ return canon(env, base, ty);
+ }
+
+ private static ImmutableList<Type.TyArg> canonicalizeTyArgs(
+ ImmutableList<Type.TyArg> targs, ClassSymbol base, Env<TypeBoundClass> env) {
+ ImmutableList.Builder<Type.TyArg> result = ImmutableList.builder();
+ for (Type.TyArg a : targs) {
+ result.add(canonicalizeTyArg(a, base, env));
+ }
+ return result.build();
+ }
+
+ private static Type.TyArg canonicalizeTyArg(
+ Type.TyArg tyArg, ClassSymbol base, Env<TypeBoundClass> env) {
+ switch (tyArg.tyArgKind()) {
+ case CONCRETE:
+ return new Type.ConcreteTyArg(canonicalize(env, base, ((Type.ConcreteTyArg) tyArg).type()));
+ case WILD:
+ return tyArg;
+ case LOWER_WILD:
+ return new Type.WildLowerBoundedTy(
+ canonicalize(env, base, ((Type.WildLowerBoundedTy) tyArg).bound()));
+ case UPPER_WILD:
+ return new Type.WildUpperBoundedTy(
+ canonicalize(env, base, ((Type.WildUpperBoundedTy) tyArg).bound()));
+ default:
+ throw new AssertionError(tyArg.tyArgKind());
+ }
+ }
+}
diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
index 66a1fcc..f494277 100644
--- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java
+++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
@@ -107,7 +107,8 @@ public class ClassPathBinderTest {
} catch (IOException e) {
throw new IOError(e);
}
- });
+ },
+ null);
try {
c.owner();
fail();
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index 7391854..c640359 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -168,6 +168,24 @@ public class LowerIntegrationTest {
"vanillaexception.test",
"varargs.test",
"wild.test",
+ "bytenoncanon.test",
+ "canon.test",
+ "genericnoncanon.test",
+ "genericnoncanon1.test",
+ "genericnoncanon10.test",
+ "genericnoncanon2.test",
+ "genericnoncanon3.test",
+ "genericnoncanon4.test",
+ "genericnoncanon5.test",
+ "genericnoncanon6.test",
+ "genericnoncanon8.test",
+ "genericnoncanon9.test",
+ "genericnoncanon_byte.test",
+ "genericnoncanon_method3.test",
+ "noncanon.test",
+ "rawcanon.test",
+ "wildboundcanon.test",
+ "wildcanon.test",
};
return ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList());
}
diff --git a/javatests/com/google/turbine/lower/LowerSignatureTest.java b/javatests/com/google/turbine/lower/LowerSignatureTest.java
index cfd57cf..6f3e87c 100644
--- a/javatests/com/google/turbine/lower/LowerSignatureTest.java
+++ b/javatests/com/google/turbine/lower/LowerSignatureTest.java
@@ -98,7 +98,7 @@ public class LowerSignatureTest {
new Type.ClassTy.SimpleClassTy(
new ClassSymbol("test/Test"),
ImmutableList.of(
- new Type.WildTy(),
+ Type.WILD_TY,
new Type.WildLowerBoundedTy(Type.ClassTy.OBJECT),
new Type.WildUpperBoundedTy(Type.ClassTy.OBJECT))))))))
.isEqualTo("Ltest/Test<*-Ljava/lang/Object;+Ljava/lang/Object;>;");
diff --git a/javatests/com/google/turbine/lower/testdata/bytenoncanon.test b/javatests/com/google/turbine/lower/testdata/bytenoncanon.test
new file mode 100644
index 0000000..6e5a304
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/bytenoncanon.test
@@ -0,0 +1,22 @@
+%%% lib/A.java %%%
+package lib;
+
+public class A {
+ public static class Inner {
+ }
+}
+
+%%% lib/B.java %%%
+package lib;
+
+public class B extends A {
+}
+
+=== test/Test.java ===
+package test;
+
+import lib.B;
+
+public class Test {
+ B.Inner i;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/canon.test b/javatests/com/google/turbine/lower/testdata/canon.test
new file mode 100644
index 0000000..870364a
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/canon.test
@@ -0,0 +1,11 @@
+=== C.java ===
+
+public class C extends D.I {
+}
+
+=== D.java ===
+
+public class D {
+ public static class I {
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon.test
new file mode 100644
index 0000000..8df812e
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon.test
@@ -0,0 +1,45 @@
+=== test/A.java ===
+package test;
+
+public class A<T> {
+ public class Inner {
+ }
+}
+
+=== test/B.java ===
+package test;
+
+public class B extends A<Test> {
+}
+
+=== test/C.java ===
+package test;
+
+public class C<T> extends A<T> {
+}
+
+=== test/D.java ===
+package test;
+
+public class D<T> {
+ public class Inner extends A<T> {
+ }
+ public class F<T> extends D<T>.Inner {
+ }
+}
+
+=== test/E.java ===
+package test;
+
+public class E<T> extends D<T> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ B.Inner i0;
+ C<Test>.Inner i1;
+ E<Test>.Inner.Inner i2;
+ D<D>.F<Test>.Inner i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon1.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon1.test
new file mode 100644
index 0000000..1a1ad52
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon1.test
@@ -0,0 +1,26 @@
+=== test/A0.java ===
+package test;
+
+public class A0<Z> {
+ public class Inner {
+ }
+}
+
+=== test/A.java ===
+package test;
+
+public class A<N> extends A0<N> {
+}
+
+=== test/B.java ===
+package test;
+
+public class B<U> extends A<U> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ B<Test>.Inner i0;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon10.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon10.test
new file mode 100644
index 0000000..6181131
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon10.test
@@ -0,0 +1,22 @@
+=== test/B.java ===
+package test;
+
+public class B<U> {
+ public class One extends B<Long>.Two {
+ }
+ public class Two extends B<Short>.Three {
+ }
+ public class Three extends B<Float>.Four {
+ public class Inner {
+ }
+ }
+ public class Four {
+ }
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ B<Test>.One.Inner i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon2.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon2.test
new file mode 100644
index 0000000..2a45fd1
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon2.test
@@ -0,0 +1,28 @@
+=== test/A.java ===
+package test;
+
+public class A<T> {
+ public class Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D<T> {
+ public class Inner extends A<T> {
+ }
+}
+
+=== test/E.java ===
+package test;
+
+public class E<T> extends D<T> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ E<Test>.Inner.Inner i2;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon3.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon3.test
new file mode 100644
index 0000000..a49a193
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon3.test
@@ -0,0 +1,24 @@
+=== test/A.java ===
+package test;
+
+public class A<U> {
+ public class Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D<Z> {
+ public class Inner extends A<Z> {
+ }
+ public class F<X> extends D<X>.Inner {
+ }
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ D<D<Test>>.F<Test>.Inner i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon4.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon4.test
new file mode 100644
index 0000000..5193e2b
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon4.test
@@ -0,0 +1,24 @@
+=== test/A.java ===
+package test;
+
+public class A<U> {
+ public class Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D<Z> {
+ public class Inner {
+ }
+ public class F<X> extends D<X>.Inner {
+ }
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ D<D<Test>>.F<Test> i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon5.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon5.test
new file mode 100644
index 0000000..1762ebe
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon5.test
@@ -0,0 +1,30 @@
+=== test/A.java ===
+package test;
+
+public class A<U> {
+ public class Inner {
+ }
+}
+
+=== test/I.java ===
+package test;
+
+public class I<T> {
+ public class Holder extends A<T> {
+ }
+}
+
+=== test/J.java ===
+package test;
+
+public class J extends I<String> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test extends J {
+ public class K extends J.Holder {}
+
+ K.Inner i;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon6.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon6.test
new file mode 100644
index 0000000..9431dfd
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon6.test
@@ -0,0 +1,19 @@
+=== test/A.java ===
+package test;
+
+public class A<T> {
+ public class Inner {
+ }
+}
+
+=== test/B.java ===
+package test;
+
+public class B extends A<String> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test<T extends B.Inner> {
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon8.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon8.test
new file mode 100644
index 0000000..58e5079
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon8.test
@@ -0,0 +1,23 @@
+=== test/A.java ===
+package test;
+
+public class A<U> {
+ public class Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D extends A<String> {
+ public class E extends A<Integer> {
+ }
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ D.Inner i0;
+ D.E.Inner i1;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon9.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon9.test
new file mode 100644
index 0000000..d80f1d6
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon9.test
@@ -0,0 +1,22 @@
+=== test/B.java ===
+package test;
+
+public class B<U> {
+ public class Inner {
+ }
+ public class F<X> extends B<X>.Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D<Z> extends B<Z> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ D<Number>.F<String>i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon_byte.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon_byte.test
new file mode 100644
index 0000000..7b9c4f1
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon_byte.test
@@ -0,0 +1,50 @@
+%%% test/A.java %%%
+package test;
+
+public class A<T> {
+ public class Inner {
+ }
+}
+
+%%% test/Foo.java %%%
+package test;
+
+public class Foo {}
+
+%%% test/B.java %%%
+package test;
+
+public class B extends A<Foo> {
+}
+
+%%% test/C.java %%%
+package test;
+
+public class C<T> extends A<T> {
+}
+
+%%% test/D.java %%%
+package test;
+
+public class D<T> {
+ public class Inner extends A<T> {
+ }
+ public class F<T> extends D<T>.Inner {
+ }
+}
+
+%%% test/E.java %%%
+package test;
+
+public class E<T> extends D<T> {
+}
+
+=== test/Test.java ===
+package test;
+
+public class Test {
+ B.Inner i0;
+ C<Test>.Inner i1;
+ E<Test>.Inner.Inner i2;
+ D<D>.F<Test>.Inner i3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/genericnoncanon_method3.test b/javatests/com/google/turbine/lower/testdata/genericnoncanon_method3.test
new file mode 100644
index 0000000..03bf113
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/genericnoncanon_method3.test
@@ -0,0 +1,31 @@
+=== test/A.java ===
+package test;
+
+public class A<U> {
+ public class Inner {
+ }
+}
+
+=== test/D.java ===
+package test;
+
+public class D<Z> {
+ public class Inner extends A<Z> {
+ }
+ public class F<X> extends D<X>.Inner {
+ }
+}
+
+=== test/Test.java ===
+
+package test;
+
+public class Test<S> {
+ <T extends S> D<Foo>.F<T>.Inner f() { return null; }
+}
+
+=== test/Foo.java ===
+
+package test;
+
+public class Foo {}
diff --git a/javatests/com/google/turbine/lower/testdata/noncanon.test b/javatests/com/google/turbine/lower/testdata/noncanon.test
new file mode 100644
index 0000000..e1bcd17
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/noncanon.test
@@ -0,0 +1,16 @@
+=== C.java ===
+
+public class C extends Sub.I {
+}
+
+=== Sub.java ===
+
+public class Sub extends Sup {
+}
+
+=== Sup.java ===
+
+public class Sup {
+ public static class I {
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/rawcanon.test b/javatests/com/google/turbine/lower/testdata/rawcanon.test
new file mode 100644
index 0000000..c8fe4b1
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/rawcanon.test
@@ -0,0 +1,12 @@
+=== test/A.java ===
+package test;
+public class A<T> {
+ public class Inner<X> {
+ }
+}
+
+=== test/B.java ===
+package test;
+public class B extends A {
+ B.Inner i;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/wildboundcanon.test b/javatests/com/google/turbine/lower/testdata/wildboundcanon.test
new file mode 100644
index 0000000..4e0c263
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/wildboundcanon.test
@@ -0,0 +1,12 @@
+=== test/A.java ===
+package test;
+public class A<T> {
+ public class Inner<X> {
+ }
+}
+
+=== test/B.java ===
+package test;
+public class B<T extends String> extends A<T> {
+ B<?>.Inner<?> i;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/wildcanon.test b/javatests/com/google/turbine/lower/testdata/wildcanon.test
new file mode 100644
index 0000000..3a937d9
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/wildcanon.test
@@ -0,0 +1,12 @@
+=== test/A.java ===
+package test;
+public class A<T> {
+ public class Inner<X> {
+ }
+}
+
+=== test/B.java ===
+package test;
+public class B<T> extends A<T> {
+ B<?>.Inner<?> i;
+}