diff options
author | Adrian Roos <roosa@google.com> | 2020-03-30 11:41:49 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-30 11:41:49 +0000 |
commit | 0d6829d18aedcaf5dd02b6c5f51d721e2e01791f (patch) | |
tree | 8107af79f333219cf2f043078e4e733974edc19c | |
parent | 05724bdb61c1908154d7c68915584a58316e282c (diff) | |
parent | b3b9a496c8200bc0c7db1f5628fdfd2c0269dea2 (diff) | |
download | turbine-0d6829d18aedcaf5dd02b6c5f51d721e2e01791f.tar.gz |
Merge upstream into AOSP am: b3b9a496c8
Change-Id: Ic103c276716a4cbcde8bf4557d482147bf9ec8a0
131 files changed, 13625 insertions, 1130 deletions
diff --git a/.travis.yml b/.travis.yml index 3ea8885..8113f5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java jdk: - - oraclejdk8 - - oraclejdk9 + - openjdk8 + - openjdk9 - openjdk10 - openjdk11 - openjdk-ea diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java index cffe291..0e3f41f 100644 --- a/java/com/google/turbine/binder/Binder.java +++ b/java/com/google/turbine/binder/Binder.java @@ -16,11 +16,13 @@ package com.google.turbine.binder; +import com.google.auto.value.AutoValue; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.turbine.binder.CompUnitPreprocessor.PreprocessedCompUnit; +import com.google.turbine.binder.Processing.ProcessorInfo; import com.google.turbine.binder.Resolve.CanonicalResolver; import com.google.turbine.binder.bound.BoundClass; import com.google.turbine.binder.bound.HeaderBoundClass; @@ -51,6 +53,7 @@ import com.google.turbine.binder.lookup.WildImportIndex; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.ModuleSymbol; +import com.google.turbine.diag.SourceFile; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; import com.google.turbine.diag.TurbineLog; @@ -60,19 +63,56 @@ import com.google.turbine.tree.Tree; import com.google.turbine.tree.Tree.CompUnit; import com.google.turbine.tree.Tree.ModDecl; import com.google.turbine.type.Type; -import java.util.List; +import java.time.Duration; import java.util.Optional; +import javax.annotation.processing.Processor; /** The entry point for analysis. */ public class Binder { /** Binds symbols and types to the given compilation units. */ public static BindingResult bind( - List<CompUnit> units, + ImmutableList<CompUnit> units, ClassPath classpath, ClassPath bootclasspath, Optional<String> moduleVersion) { + return bind(units, classpath, Processing.ProcessorInfo.empty(), bootclasspath, moduleVersion); + } + /** Binds symbols and types to the given compilation units. */ + public static BindingResult bind( + ImmutableList<CompUnit> units, + ClassPath classpath, + ProcessorInfo processorInfo, + ClassPath bootclasspath, + Optional<String> moduleVersion) { + TurbineLog log = new TurbineLog(); + BindingResult br = + bind( + log, + units, + /* generatedSources= */ ImmutableMap.of(), + /* generatedClasses= */ ImmutableMap.of(), + classpath, + bootclasspath, + moduleVersion); + if (!processorInfo.processors().isEmpty() && !units.isEmpty()) { + br = + Processing.process( + log, units, classpath, processorInfo, bootclasspath, br, moduleVersion); + } + log.maybeThrow(); + return br; + } + + static BindingResult bind( + TurbineLog log, + ImmutableList<CompUnit> units, + ImmutableMap<String, SourceFile> generatedSources, + ImmutableMap<String, byte[]> generatedClasses, + ClassPath classpath, + ClassPath bootclasspath, + Optional<String> moduleVersion) { ImmutableList<PreprocessedCompUnit> preProcessedUnits = CompUnitPreprocessor.preprocess(units); SimpleEnv<ClassSymbol, SourceBoundClass> ienv = bindSourceBoundClasses(preProcessedUnits); @@ -91,8 +131,6 @@ public class Binder { CompoundEnv<ModuleSymbol, ModuleInfo> classPathModuleEnv = CompoundEnv.of(classpath.moduleEnv()).append(bootclasspath.moduleEnv()); - TurbineLog log = new TurbineLog(); - BindPackagesResult bindPackagesResult = bindPackages(log, ienv, tli, preProcessedUnits, classPathEnv); @@ -108,11 +146,12 @@ public class Binder { henv, CompoundEnv.<ClassSymbol, HeaderBoundClass>of(classPathEnv).append(henv)); - log.maybeThrow(); - tenv = constants( - syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv)); + syms, + tenv, + CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv), + log); tenv = disambiguateTypeAnnotations( syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv)); @@ -125,13 +164,22 @@ public class Binder { modules, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv), classPathModuleEnv, - moduleVersion); + moduleVersion, + log); ImmutableMap.Builder<ClassSymbol, SourceTypeBoundClass> result = ImmutableMap.builder(); for (ClassSymbol sym : syms) { result.put(sym, tenv.get(sym)); } - return new BindingResult(result.build(), boundModules, classPathEnv); + + return new BindingResult( + result.build(), + boundModules, + classPathEnv, + tli, + generatedSources, + generatedClasses, + Statistics.empty()); } /** Records enclosing declarations of member classes, and group classes by compilation unit. */ @@ -262,7 +310,8 @@ public class Binder { SimpleEnv<ModuleSymbol, PackageSourceBoundModule> modules, CompoundEnv<ClassSymbol, TypeBoundClass> env, CompoundEnv<ModuleSymbol, ModuleInfo> moduleEnv, - Optional<String> moduleVersion) { + Optional<String> moduleVersion, + TurbineLog log) { // Allow resolution of modules in the current compilation. Currently this is only needed for // version strings in requires directives. moduleEnv = @@ -288,7 +337,9 @@ public class Binder { }); ImmutableList.Builder<SourceModuleInfo> bound = ImmutableList.builder(); for (PackageSourceBoundModule module : modules.asMap().values()) { - bound.add(ModuleBinder.bind(module, env, moduleEnv, moduleVersion)); + bound.add( + ModuleBinder.bind( + module, env, moduleEnv, moduleVersion, log.withSource(module.source()))); } return bound.build(); } @@ -296,7 +347,8 @@ public class Binder { private static Env<ClassSymbol, SourceTypeBoundClass> constants( ImmutableSet<ClassSymbol> syms, Env<ClassSymbol, SourceTypeBoundClass> env, - CompoundEnv<ClassSymbol, TypeBoundClass> baseEnv) { + CompoundEnv<ClassSymbol, TypeBoundClass> baseEnv, + TurbineLog log) { // Prepare to lazily evaluate constant fields in each compilation unit. // The laziness is necessary since constant fields can reference other @@ -322,7 +374,8 @@ public class Binder { info.source(), info.scope(), env1, - baseEnv) + baseEnv, + log.withSource(info.source())) .evalFieldInitializer(field.decl().init().get(), field.type()); } catch (LazyEnv.LazyBindingError e) { // fields initializers are allowed to reference the field being initialized, @@ -342,7 +395,9 @@ public class Binder { SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder(); for (ClassSymbol sym : syms) { - builder.put(sym, new ConstBinder(constenv, sym, baseEnv, env.get(sym)).bind()); + SourceTypeBoundClass base = env.get(sym); + builder.put( + sym, new ConstBinder(constenv, sym, baseEnv, base, log.withSource(base.source())).bind()); } return builder.build(); } @@ -387,19 +442,57 @@ public class Binder { return builder.build(); } + /** Statistics about annotation processing. */ + @AutoValue + public abstract static class Statistics { + + /** + * The total elapsed time spent in {@link Processor#init} and {@link Processor#process} across + * all rounds for each annotation processor. + */ + public abstract ImmutableMap<String, Duration> processingTime(); + + /** + * Serialized protos containing processor-specific metrics. Currently only supported for Dagger. + */ + public abstract ImmutableMap<String, byte[]> processorMetrics(); + + public static Statistics create( + ImmutableMap<String, Duration> processingTime, + ImmutableMap<String, byte[]> processorMetrics) { + return new AutoValue_Binder_Statistics(processingTime, processorMetrics); + } + + public static Statistics empty() { + return create(ImmutableMap.of(), ImmutableMap.of()); + } + } + /** The result of binding: bound nodes for sources in the compilation, and the classpath. */ public static class BindingResult { private final ImmutableMap<ClassSymbol, SourceTypeBoundClass> units; private final ImmutableList<SourceModuleInfo> modules; private final CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv; + private final TopLevelIndex tli; + private final ImmutableMap<String, SourceFile> generatedSources; + private final ImmutableMap<String, byte[]> generatedClasses; + private final Statistics statistics; public BindingResult( ImmutableMap<ClassSymbol, SourceTypeBoundClass> units, ImmutableList<SourceModuleInfo> modules, - CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) { + CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv, + TopLevelIndex tli, + ImmutableMap<String, SourceFile> generatedSources, + ImmutableMap<String, byte[]> generatedClasses, + Statistics statistics) { this.units = units; this.modules = modules; this.classPathEnv = classPathEnv; + this.tli = tli; + this.generatedSources = generatedSources; + this.generatedClasses = generatedClasses; + this.statistics = statistics; } /** Bound nodes for sources in the compilation. */ @@ -415,5 +508,36 @@ public class Binder { public CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv() { return classPathEnv; } + + public TopLevelIndex tli() { + return tli; + } + + public ImmutableMap<String, SourceFile> generatedSources() { + return generatedSources; + } + + public ImmutableMap<String, byte[]> generatedClasses() { + return generatedClasses; + } + + public Statistics statistics() { + return statistics; + } + + public BindingResult withGeneratedClasses(ImmutableMap<String, byte[]> generatedClasses) { + return new BindingResult( + units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics); + } + + public BindingResult withGeneratedSources(ImmutableMap<String, SourceFile> generatedSources) { + return new BindingResult( + units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics); + } + + public BindingResult withStatistics(Statistics statistics) { + return new BindingResult( + units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics); + } } } diff --git a/java/com/google/turbine/binder/CanonicalTypeBinder.java b/java/com/google/turbine/binder/CanonicalTypeBinder.java index 934ec54..a2f045a 100644 --- a/java/com/google/turbine/binder/CanonicalTypeBinder.java +++ b/java/com/google/turbine/binder/CanonicalTypeBinder.java @@ -147,8 +147,8 @@ public class CanonicalTypeBinder { ClassSymbol sym, ParamInfo base) { return new ParamInfo( + base.sym(), Canonicalize.canonicalize(source, position, env, sym, base.type()), - base.name(), base.annotations(), base.access()); } @@ -162,8 +162,9 @@ public class CanonicalTypeBinder { ImmutableMap.Builder<TyVarSymbol, TyVarInfo> result = ImmutableMap.builder(); for (Map.Entry<TyVarSymbol, TyVarInfo> e : tps.entrySet()) { TyVarInfo info = e.getValue(); - Type bound = Canonicalize.canonicalize(source, position, env, sym, info.bound()); - result.put(e.getKey(), new TyVarInfo((IntersectionTy) bound, info.annotations())); + IntersectionTy upperBound = + (IntersectionTy) Canonicalize.canonicalize(source, position, env, sym, info.upperBound()); + result.put(e.getKey(), new TyVarInfo(upperBound, /* lowerBound= */ null, info.annotations())); } return result.build(); } diff --git a/java/com/google/turbine/binder/ClassPath.java b/java/com/google/turbine/binder/ClassPath.java index d3461bf..eeea7c5 100644 --- a/java/com/google/turbine/binder/ClassPath.java +++ b/java/com/google/turbine/binder/ClassPath.java @@ -16,6 +16,7 @@ package com.google.turbine.binder; +import com.google.common.base.Supplier; import com.google.turbine.binder.bound.ModuleInfo; import com.google.turbine.binder.bytecode.BytecodeBoundClass; import com.google.turbine.binder.env.Env; @@ -24,7 +25,7 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.ModuleSymbol; /** - * A compilation classpath, e.g. the user or platform class path. Maybe backed by a search path of + * A compilation classpath, e.g. the user or platform class path. May be backed by a search path of * jar files, or a jrtfs filesystem. */ public interface ClassPath { @@ -36,4 +37,6 @@ public interface ClassPath { /** The classpath's top level index. */ TopLevelIndex index(); + + Supplier<byte[]> resource(String path); } diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java index 5d8db86..8aead80 100644 --- a/java/com/google/turbine/binder/ClassPathBinder.java +++ b/java/com/google/turbine/binder/ClassPathBinder.java @@ -53,6 +53,7 @@ public class ClassPathBinder { Map<ClassSymbol, BytecodeBoundClass> transitive = new LinkedHashMap<>(); Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>(); Map<ModuleSymbol, ModuleInfo> modules = new HashMap<>(); + Map<String, Supplier<byte[]>> resources = new HashMap<>(); Env<ClassSymbol, BytecodeBoundClass> benv = new Env<ClassSymbol, BytecodeBoundClass>() { @Override @@ -62,7 +63,7 @@ public class ClassPathBinder { }; for (Path path : paths) { try { - bindJar(path, map, modules, benv, transitive); + bindJar(path, map, modules, benv, transitive, resources); } catch (IOException e) { throw new IOException("error reading " + path, e); } @@ -89,6 +90,11 @@ public class ClassPathBinder { public TopLevelIndex index() { return index; } + + @Override + public Supplier<byte[]> resource(String path) { + return resources.get(path); + } }; } @@ -97,12 +103,14 @@ public class ClassPathBinder { Map<ClassSymbol, BytecodeBoundClass> env, Map<ModuleSymbol, ModuleInfo> modules, Env<ClassSymbol, BytecodeBoundClass> benv, - Map<ClassSymbol, BytecodeBoundClass> transitive) + Map<ClassSymbol, BytecodeBoundClass> transitive, + Map<String, Supplier<byte[]>> resources) throws IOException { // TODO(cushon): don't leak file descriptors for (Zip.Entry ze : new Zip.ZipIterable(path)) { String name = ze.name(); if (!name.endsWith(".class")) { + resources.put(name, toByteArrayOrDie(ze)); continue; } if (name.startsWith(TRANSITIVE_PREFIX)) { diff --git a/java/com/google/turbine/binder/CompUnitPreprocessor.java b/java/com/google/turbine/binder/CompUnitPreprocessor.java index 85ff1c0..ed70e88 100644 --- a/java/com/google/turbine/binder/CompUnitPreprocessor.java +++ b/java/com/google/turbine/binder/CompUnitPreprocessor.java @@ -219,6 +219,7 @@ public class CompUnitPreprocessor { Optional.empty(), ImmutableList.of(), ImmutableList.of(), - TurbineTyKind.INTERFACE); + TurbineTyKind.INTERFACE, + /* javadoc= */ null); } } diff --git a/java/com/google/turbine/binder/ConstBinder.java b/java/com/google/turbine/binder/ConstBinder.java index 0f989dd..3a41e94 100644 --- a/java/com/google/turbine/binder/ConstBinder.java +++ b/java/com/google/turbine/binder/ConstBinder.java @@ -33,6 +33,7 @@ import com.google.turbine.binder.env.Env; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.diag.TurbineLog.TurbineLogWithSource; import com.google.turbine.model.Const; import com.google.turbine.model.Const.ArrayInitValue; import com.google.turbine.model.Const.Kind; @@ -63,19 +64,29 @@ public class ConstBinder { private final SourceTypeBoundClass base; private final CompoundEnv<ClassSymbol, TypeBoundClass> env; private final ConstEvaluator constEvaluator; + private final TurbineLogWithSource log; public ConstBinder( Env<FieldSymbol, Value> constantEnv, ClassSymbol origin, CompoundEnv<ClassSymbol, TypeBoundClass> env, - SourceTypeBoundClass base) { + SourceTypeBoundClass base, + TurbineLogWithSource log) { this.constantEnv = constantEnv; this.origin = origin; this.base = base; this.env = env; + this.log = log; this.constEvaluator = new ConstEvaluator( - origin, origin, base.memberImports(), base.source(), base.scope(), constantEnv, env); + origin, + origin, + base.memberImports(), + base.source(), + base.scope(), + constantEnv, + env, + log); } public SourceTypeBoundClass bind() { @@ -87,7 +98,8 @@ public class ConstBinder { base.source(), base.enclosingScope(), constantEnv, - env) + env, + log) .evaluateAnnotations(base.annotations()); ImmutableList<TypeBoundClass.FieldInfo> fields = fields(base.fields()); ImmutableList<MethodInfo> methods = bindMethods(base.methods()); @@ -149,7 +161,7 @@ public class ConstBinder { private ParamInfo bindParameter(ParamInfo base) { ImmutableList<AnnoInfo> annos = constEvaluator.evaluateAnnotations(base.annotations()); - return new ParamInfo(bindType(base.type()), base.name(), annos, base.access()); + return new ParamInfo(base.sym(), bindType(base.type()), annos, base.access()); } static AnnotationMetadata bindAnnotationMetadata( @@ -161,7 +173,11 @@ public class ConstBinder { ImmutableSet<TurbineElementType> target = null; ClassSymbol repeatable = null; for (AnnoInfo annotation : annotations) { - switch (annotation.sym().binaryName()) { + ClassSymbol sym = annotation.sym(); + if (sym == null) { + continue; + } + switch (sym.binaryName()) { case "java/lang/annotation/Retention": retention = bindRetention(annotation); break; @@ -286,7 +302,8 @@ public class ConstBinder { result.put( entry.getKey(), new TyVarInfo( - (IntersectionTy) bindType(info.bound()), + (IntersectionTy) bindType(info.upperBound()), + /* lowerBound= */ null, constEvaluator.evaluateAnnotations(info.annotations()))); } return result.build(); @@ -318,12 +335,12 @@ public class ConstBinder { return WildLowerBoundedTy.create( bindType(wildTy.bound()), constEvaluator.evaluateAnnotations(wildTy.annotations())); - default: - throw new AssertionError(wildTy.boundKind()); } + throw new AssertionError(wildTy.boundKind()); } case PRIM_TY: case VOID_TY: + case ERROR_TY: return type; case INTERSECTION_TY: return IntersectionTy.create(bindTypes(((IntersectionTy) type).bounds())); diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java index 0e837a4..9d5f042 100644 --- a/java/com/google/turbine/binder/ConstEvaluator.java +++ b/java/com/google/turbine/binder/ConstEvaluator.java @@ -22,8 +22,8 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.turbine.binder.bound.AnnotationValue; import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; @@ -38,8 +38,10 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.Symbol; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineDiagnostic; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; +import com.google.turbine.diag.TurbineLog.TurbineLogWithSource; import com.google.turbine.model.Const; import com.google.turbine.model.Const.ConstCastError; import com.google.turbine.model.Const.Value; @@ -91,14 +93,17 @@ public strictfp class ConstEvaluator { private final Scope scope; + private final TurbineLogWithSource log; + public ConstEvaluator( ClassSymbol origin, ClassSymbol owner, MemberImportIndex memberImports, SourceFile source, Scope scope, - Env<FieldSymbol, Const.Value> values, - CompoundEnv<ClassSymbol, TypeBoundClass> env) { + Env<FieldSymbol, Value> values, + CompoundEnv<ClassSymbol, TypeBoundClass> env, + TurbineLogWithSource log) { this.origin = origin; this.owner = owner; @@ -107,6 +112,7 @@ public strictfp class ConstEvaluator { this.values = values; this.env = env; this.scope = scope; + this.log = log; } /** Evaluates the given expression's value. */ @@ -139,9 +145,8 @@ public strictfp class ConstEvaluator { case SHORT: case BYTE: case NULL: - default: - throw new AssertionError(a.constantTypeKind()); } + throw new AssertionError(a.constantTypeKind()); } case VOID_TY: throw new AssertionError(t.kind()); @@ -162,7 +167,7 @@ public strictfp class ConstEvaluator { case ANNO_EXPR: return evalAnno(((Tree.AnnoExpr) t).value()); default: - throw new AssertionError(t.kind()); + throw error(t.position(), ErrorKind.EXPRESSION_ERROR); } } @@ -178,7 +183,7 @@ public strictfp class ConstEvaluator { case VOID_TY: return Type.VOID; case CLASS_TY: - return Type.ClassTy.asNonParametricClassTy(resolveClass((ClassTy) type)); + return resolveClass((ClassTy) type); case ARR_TY: return Type.ArrayTy.create( evalClassLiteralType(((Tree.ArrTy) type).elem()), ImmutableList.of()); @@ -191,19 +196,20 @@ public strictfp class ConstEvaluator { * Resolves the {@link ClassSymbol} for the given {@link Tree.ClassTy}, with handling for * non-canonical qualified type names. * - * <p>Similar to {@link HierarchyBinder#resolveClass}, except we can't unconditionally consider + * <p>Similar to {@code HierarchyBinder#resolveClass}, except we can't unconditionally consider * members of the current class (e.g. when binding constants inside annotations on that class), * and when we do want to consider members we can rely on them being in the current scope (it * isn't completed during the hierarchy phase). */ - private ClassSymbol resolveClass(ClassTy classTy) { + private Type resolveClass(ClassTy classTy) { ArrayDeque<Ident> flat = new ArrayDeque<>(); for (ClassTy curr = classTy; curr != null; curr = curr.base().orElse(null)) { flat.addFirst(curr.name()); } LookupResult result = scope.lookup(new LookupKey(ImmutableList.copyOf(flat))); if (result == null) { - throw error(classTy.position(), ErrorKind.CANNOT_RESOLVE, flat.peekFirst()); + log.error(classTy.position(), ErrorKind.CANNOT_RESOLVE, flat.peekFirst()); + return Type.ErrorTy.create(flat); } if (result.sym().symKind() != Symbol.Kind.CLASS) { throw error(classTy.position(), ErrorKind.UNEXPECTED_TYPE_PARAMETER, flat.peekFirst()); @@ -212,7 +218,7 @@ public strictfp class ConstEvaluator { for (Ident bit : result.remaining()) { classSym = resolveNext(classTy.position(), classSym, bit); } - return classSym; + return Type.ClassTy.asNonParametricClassTy(classSym); } private ClassSymbol resolveNext(int position, ClassSymbol sym, Ident bit) { @@ -372,41 +378,41 @@ public strictfp class ConstEvaluator { } switch (t.op()) { case NOT: - return unaryNegate(expr); + return unaryNegate(t.position(), expr); case BITWISE_COMP: - return bitwiseComp(expr); + return bitwiseComp(t.position(), expr); case UNARY_PLUS: - return unaryPlus(expr); + return unaryPlus(t.position(), expr); case NEG: - return unaryMinus(expr); + return unaryMinus(t.position(), expr); default: throw new AssertionError(t.op()); } } - private Value unaryNegate(Value expr) { + private Value unaryNegate(int position, Value expr) { switch (expr.constantTypeKind()) { case BOOLEAN: return new Const.BooleanValue(!expr.asBoolean().value()); default: - throw new AssertionError(expr.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); } } - private Value bitwiseComp(Value expr) { - expr = promoteUnary(expr); + private Value bitwiseComp(int position, Value expr) { + expr = promoteUnary(position, expr); switch (expr.constantTypeKind()) { case INT: return new Const.IntValue(~expr.asInteger().value()); case LONG: return new Const.LongValue(~expr.asLong().value()); default: - throw new AssertionError(expr.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); } } - private Value unaryPlus(Value expr) { - expr = promoteUnary(expr); + private Value unaryPlus(int position, Value expr) { + expr = promoteUnary(position, expr); switch (expr.constantTypeKind()) { case INT: return new Const.IntValue(+expr.asInteger().value()); @@ -417,12 +423,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(+expr.asDouble().value()); default: - throw new AssertionError(expr.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); } } - private Value unaryMinus(Value expr) { - expr = promoteUnary(expr); + private Value unaryMinus(int position, Value expr) { + expr = promoteUnary(position, expr); switch (expr.constantTypeKind()) { case INT: return new Const.IntValue(-expr.asInteger().value()); @@ -433,7 +439,7 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(-expr.asDouble().value()); default: - throw new AssertionError(expr.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); } } @@ -460,12 +466,12 @@ public strictfp class ConstEvaluator { } } - static Const.Value add(Const.Value a, Const.Value b) { + private Const.Value add(int position, Const.Value a, Const.Value b) { if (a.constantTypeKind() == TurbineConstantTypeKind.STRING || b.constantTypeKind() == TurbineConstantTypeKind.STRING) { return new Const.StringValue(a.asString().value() + b.asString().value()); } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -478,12 +484,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(a.asDouble().value() + b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value subtract(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value subtract(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -496,12 +502,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(a.asDouble().value() - b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value mult(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value mult(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -514,12 +520,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(a.asDouble().value() * b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value divide(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value divide(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -532,12 +538,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(a.asDouble().value() / b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value mod(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value mod(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -550,17 +556,17 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.DoubleValue(a.asDouble().value() % b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static final int INT_SHIFT_MASK = 0b11111; + private static final int INT_SHIFT_MASK = 0b11111; - static final int LONG_SHIFT_MASK = 0b111111; + private static final int LONG_SHIFT_MASK = 0b111111; - static Const.Value shiftLeft(Const.Value a, Const.Value b) { - a = promoteUnary(a); - b = promoteUnary(b); + private Const.Value shiftLeft(int position, Const.Value a, Const.Value b) { + a = promoteUnary(position, a); + b = promoteUnary(position, b); switch (a.constantTypeKind()) { case INT: return new Const.IntValue( @@ -568,13 +574,13 @@ public strictfp class ConstEvaluator { case LONG: return new Const.LongValue(a.asLong().value() << (b.asInteger().value() & LONG_SHIFT_MASK)); default: - throw new AssertionError(a.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); } } - static Const.Value shiftRight(Const.Value a, Const.Value b) { - a = promoteUnary(a); - b = promoteUnary(b); + private Const.Value shiftRight(int position, Const.Value a, Const.Value b) { + a = promoteUnary(position, a); + b = promoteUnary(position, b); switch (a.constantTypeKind()) { case INT: return new Const.IntValue( @@ -582,13 +588,13 @@ public strictfp class ConstEvaluator { case LONG: return new Const.LongValue(a.asLong().value() >> (b.asInteger().value() & LONG_SHIFT_MASK)); default: - throw new AssertionError(a.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); } } - static Const.Value unsignedShiftRight(Const.Value a, Const.Value b) { - a = promoteUnary(a); - b = promoteUnary(b); + private Const.Value unsignedShiftRight(int position, Const.Value a, Const.Value b) { + a = promoteUnary(position, a); + b = promoteUnary(position, b); switch (a.constantTypeKind()) { case INT: return new Const.IntValue( @@ -597,12 +603,12 @@ public strictfp class ConstEvaluator { return new Const.LongValue( a.asLong().value() >>> (b.asInteger().value() & LONG_SHIFT_MASK)); default: - throw new AssertionError(a.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); } } - static Const.Value lessThan(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value lessThan(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -615,12 +621,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() < b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value lessThanEqual(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value lessThanEqual(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -633,12 +639,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() <= b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value greaterThan(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value greaterThan(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -651,12 +657,12 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() > b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value greaterThanEqual(Const.Value a, Const.Value b) { - TurbineConstantTypeKind type = promoteBinary(a, b); + private Const.Value greaterThanEqual(int position, Const.Value a, Const.Value b) { + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -669,11 +675,11 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() >= b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value equal(Const.Value a, Const.Value b) { + private Const.Value equal(int position, Const.Value a, Const.Value b) { switch (a.constantTypeKind()) { case STRING: return new Const.BooleanValue(a.asString().value().equals(b.asString().value())); @@ -682,7 +688,7 @@ public strictfp class ConstEvaluator { default: break; } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -695,11 +701,11 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() == b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value notEqual(Const.Value a, Const.Value b) { + private Const.Value notEqual(int position, Const.Value a, Const.Value b) { switch (a.constantTypeKind()) { case STRING: return new Const.BooleanValue(!a.asString().value().equals(b.asString().value())); @@ -708,7 +714,7 @@ public strictfp class ConstEvaluator { default: break; } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -721,18 +727,18 @@ public strictfp class ConstEvaluator { case DOUBLE: return new Const.BooleanValue(a.asDouble().value() != b.asDouble().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value bitwiseAnd(Const.Value a, Const.Value b) { + private Const.Value bitwiseAnd(int position, Const.Value a, Const.Value b) { switch (a.constantTypeKind()) { case BOOLEAN: return new Const.BooleanValue(a.asBoolean().value() & b.asBoolean().value()); default: break; } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -741,18 +747,18 @@ public strictfp class ConstEvaluator { case LONG: return new Const.LongValue(a.asLong().value() & b.asLong().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value bitwiseOr(Const.Value a, Const.Value b) { + private Const.Value bitwiseOr(int position, Const.Value a, Const.Value b) { switch (a.constantTypeKind()) { case BOOLEAN: return new Const.BooleanValue(a.asBoolean().value() | b.asBoolean().value()); default: break; } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -761,18 +767,18 @@ public strictfp class ConstEvaluator { case LONG: return new Const.LongValue(a.asLong().value() | b.asLong().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } - static Const.Value bitwiseXor(Const.Value a, Const.Value b) { + private Const.Value bitwiseXor(int position, Const.Value a, Const.Value b) { switch (a.constantTypeKind()) { case BOOLEAN: return new Const.BooleanValue(a.asBoolean().value() ^ b.asBoolean().value()); default: break; } - TurbineConstantTypeKind type = promoteBinary(a, b); + TurbineConstantTypeKind type = promoteBinary(position, a, b); a = coerce(a, type); b = coerce(b, type); switch (type) { @@ -781,7 +787,7 @@ public strictfp class ConstEvaluator { case LONG: return new Const.LongValue(a.asLong().value() ^ b.asLong().value()); default: - throw new AssertionError(type); + throw error(position, ErrorKind.OPERAND_TYPE, type); } } @@ -793,49 +799,49 @@ public strictfp class ConstEvaluator { } switch (t.op()) { case PLUS: - return add(lhs, rhs); + return add(t.position(), lhs, rhs); case MINUS: - return subtract(lhs, rhs); + return subtract(t.position(), lhs, rhs); case MULT: - return mult(lhs, rhs); + return mult(t.position(), lhs, rhs); case DIVIDE: - return divide(lhs, rhs); + return divide(t.position(), lhs, rhs); case MODULO: - return mod(lhs, rhs); + return mod(t.position(), lhs, rhs); case SHIFT_LEFT: - return shiftLeft(lhs, rhs); + return shiftLeft(t.position(), lhs, rhs); case SHIFT_RIGHT: - return shiftRight(lhs, rhs); + return shiftRight(t.position(), lhs, rhs); case UNSIGNED_SHIFT_RIGHT: - return unsignedShiftRight(lhs, rhs); + return unsignedShiftRight(t.position(), lhs, rhs); case LESS_THAN: - return lessThan(lhs, rhs); + return lessThan(t.position(), lhs, rhs); case GREATER_THAN: - return greaterThan(lhs, rhs); + return greaterThan(t.position(), lhs, rhs); case LESS_THAN_EQ: - return lessThanEqual(lhs, rhs); + return lessThanEqual(t.position(), lhs, rhs); case GREATER_THAN_EQ: - return greaterThanEqual(lhs, rhs); + return greaterThanEqual(t.position(), lhs, rhs); case EQUAL: - return equal(lhs, rhs); + return equal(t.position(), lhs, rhs); case NOT_EQUAL: - return notEqual(lhs, rhs); + return notEqual(t.position(), lhs, rhs); case AND: return new Const.BooleanValue(lhs.asBoolean().value() && rhs.asBoolean().value()); case OR: return new Const.BooleanValue(lhs.asBoolean().value() || rhs.asBoolean().value()); case BITWISE_AND: - return bitwiseAnd(lhs, rhs); + return bitwiseAnd(t.position(), lhs, rhs); case BITWISE_XOR: - return bitwiseXor(lhs, rhs); + return bitwiseXor(t.position(), lhs, rhs); case BITWISE_OR: - return bitwiseOr(lhs, rhs); + return bitwiseOr(t.position(), lhs, rhs); default: throw new AssertionError(t.op()); } } - private static Const.Value promoteUnary(Const.Value v) { + private Const.Value promoteUnary(int position, Value v) { switch (v.constantTypeKind()) { case CHAR: case SHORT: @@ -847,13 +853,13 @@ public strictfp class ConstEvaluator { case DOUBLE: return v; default: - throw new AssertionError(v.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, v.constantTypeKind()); } } - private static TurbineConstantTypeKind promoteBinary(Const.Value a, Const.Value b) { - a = promoteUnary(a); - b = promoteUnary(b); + private TurbineConstantTypeKind promoteBinary(int position, Const.Value a, Const.Value b) { + a = promoteUnary(position, a); + b = promoteUnary(position, b); switch (a.constantTypeKind()) { case INT: switch (b.constantTypeKind()) { @@ -863,7 +869,7 @@ public strictfp class ConstEvaluator { case FLOAT: return b.constantTypeKind(); default: - throw new AssertionError(b.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); } case LONG: switch (b.constantTypeKind()) { @@ -874,7 +880,7 @@ public strictfp class ConstEvaluator { case FLOAT: return b.constantTypeKind(); default: - throw new AssertionError(b.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); } case FLOAT: switch (b.constantTypeKind()) { @@ -885,7 +891,7 @@ public strictfp class ConstEvaluator { case DOUBLE: return TurbineConstantTypeKind.DOUBLE; default: - throw new AssertionError(b.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); } case DOUBLE: switch (b.constantTypeKind()) { @@ -895,10 +901,10 @@ public strictfp class ConstEvaluator { case DOUBLE: return TurbineConstantTypeKind.DOUBLE; default: - throw new AssertionError(b.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); } default: - throw new AssertionError(a.constantTypeKind()); + throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); } } @@ -915,9 +921,17 @@ public strictfp class ConstEvaluator { * expression trees. */ AnnoInfo evaluateAnnotation(AnnoInfo info) { + // bail if annotation has not been resolved + if (info.sym() == null) { + return info; + } + Map<String, Type> template = new LinkedHashMap<>(); - for (MethodInfo method : env.get(info.sym()).methods()) { - template.put(method.name(), method.returnType()); + TypeBoundClass annoClass = env.get(info.sym()); + if (annoClass != null) { + for (MethodInfo method : annoClass.methods()) { + template.put(method.name(), method.returnType()); + } } Map<String, Const> values = new LinkedHashMap<>(); @@ -952,7 +966,7 @@ public strictfp class ConstEvaluator { return info.withValues(ImmutableMap.copyOf(values)); } - private AnnotationValue evalAnno(Tree.Anno t) { + private TurbineAnnotationValue evalAnno(Tree.Anno t) { LookupResult result = scope.lookup(new LookupKey(t.name())); if (result == null) { throw error( @@ -968,8 +982,8 @@ public strictfp class ConstEvaluator { if (sym == null) { return null; } - AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, null)); - return new AnnotationValue(annoInfo.sym(), annoInfo.values()); + AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, ImmutableMap.of())); + return new TurbineAnnotationValue(annoInfo); } private Const.ArrayInitValue evalArrayInit(ArrayInit t) { @@ -994,6 +1008,9 @@ public strictfp class ConstEvaluator { } switch (ty.tyKind()) { case PRIM_TY: + if (!(value instanceof Const.Value)) { + throw error(tree.position(), ErrorKind.EXPRESSION_ERROR); + } return coerce((Const.Value) value, ((Type.PrimTy) ty).primkind()); case CLASS_TY: case TY_VAR: @@ -1027,7 +1044,17 @@ public strictfp class ConstEvaluator { return null; } return (Const.Value) cast(type, value); - } catch (TurbineError | ConstCastError error) { + } catch (TurbineError error) { + for (TurbineDiagnostic diagnostic : error.diagnostics()) { + switch (diagnostic.kind()) { + case CANNOT_RESOLVE: + // assume this wasn't a constant + return null; + default: // fall out + } + } + throw error; + } catch (ConstCastError error) { return null; } } diff --git a/java/com/google/turbine/binder/CtSymClassBinder.java b/java/com/google/turbine/binder/CtSymClassBinder.java index 84fa966..a6f1b3d 100644 --- a/java/com/google/turbine/binder/CtSymClassBinder.java +++ b/java/com/google/turbine/binder/CtSymClassBinder.java @@ -105,6 +105,11 @@ public class CtSymClassBinder { public TopLevelIndex index() { return index; } + + @Override + public Supplier<byte[]> resource(String input) { + return null; + } }; } diff --git a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java index 9ad20d2..7e3fbda 100644 --- a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java +++ b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java @@ -18,13 +18,14 @@ package com.google.turbine.binder; import static com.google.common.collect.Iterables.getOnlyElement; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; -import com.google.turbine.binder.bound.AnnotationValue; +import com.google.common.collect.MultimapBuilder; +import com.google.turbine.binder.bound.AnnotationMetadata; import com.google.turbine.binder.bound.SourceTypeBoundClass; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; @@ -44,7 +45,6 @@ import com.google.turbine.type.Type.PrimTy; import com.google.turbine.type.Type.TyVar; import java.util.Collection; import java.util.Map; -import java.util.Set; /** * Disambiguate annotations on field, parameter, and method return types that could be declaration @@ -139,7 +139,7 @@ public class DisambiguateTypeAnnotations { base.type(), base.annotations(), declarationAnnotations); - return new ParamInfo(type, base.name(), declarationAnnotations.build(), base.access()); + return new ParamInfo(base.sym(), type, declarationAnnotations.build(), base.access()); } /** @@ -151,13 +151,13 @@ public class DisambiguateTypeAnnotations { TurbineElementType declarationTarget, Type type, ImmutableList<AnnoInfo> annotations, - Builder<AnnoInfo> declarationAnnotations) { + ImmutableList.Builder<AnnoInfo> declarationAnnotations) { // desugar @Repeatable annotations before disambiguating: annotation containers may target // a subset of the types targeted by their element annotation annotations = groupRepeated(env, annotations); ImmutableList.Builder<AnnoInfo> typeAnnotations = ImmutableList.builder(); for (AnnoInfo anno : annotations) { - Set<TurbineElementType> target = env.get(anno.sym()).annotationMetadata().target(); + ImmutableSet<TurbineElementType> target = getTarget(env, anno); if (target.contains(TurbineElementType.TYPE_USE)) { typeAnnotations.add(anno); } @@ -168,6 +168,23 @@ public class DisambiguateTypeAnnotations { return addAnnotationsToType(type, typeAnnotations.build()); } + private static ImmutableSet<TurbineElementType> getTarget( + Env<ClassSymbol, TypeBoundClass> env, AnnoInfo anno) { + ClassSymbol sym = anno.sym(); + if (sym == null) { + return AnnotationMetadata.DEFAULT_TARGETS; + } + TypeBoundClass info = env.get(sym); + if (info == null) { + return AnnotationMetadata.DEFAULT_TARGETS; + } + AnnotationMetadata metadata = info.annotationMetadata(); + if (metadata == null) { + return AnnotationMetadata.DEFAULT_TARGETS; + } + return metadata.target(); + } + private static ImmutableList<FieldInfo> bindFields( Env<ClassSymbol, TypeBoundClass> env, ImmutableList<FieldInfo> fields) { ImmutableList.Builder<FieldInfo> result = ImmutableList.builder(); @@ -218,6 +235,7 @@ public class DisambiguateTypeAnnotations { TyVar tyVar = (TyVar) type; return Type.TyVar.create(tyVar.sym(), appendAnnotations(tyVar.annos(), extra)); case VOID_TY: + case ERROR_TY: return type; case WILD_TY: throw new AssertionError("unexpected wildcard type outside type argument context"); @@ -242,21 +260,33 @@ public class DisambiguateTypeAnnotations { */ public static ImmutableList<AnnoInfo> groupRepeated( Env<ClassSymbol, TypeBoundClass> env, ImmutableList<AnnoInfo> annotations) { - Multimap<ClassSymbol, AnnoInfo> repeated = ArrayListMultimap.create(); + Multimap<ClassSymbol, AnnoInfo> repeated = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + ImmutableList.Builder<AnnoInfo> result = ImmutableList.builder(); for (AnnoInfo anno : annotations) { + if (anno.sym() == null) { + result.add(anno); + continue; + } repeated.put(anno.sym(), anno); } - Builder<AnnoInfo> result = ImmutableList.builder(); for (Map.Entry<ClassSymbol, Collection<AnnoInfo>> entry : repeated.asMap().entrySet()) { ClassSymbol symbol = entry.getKey(); Collection<AnnoInfo> infos = entry.getValue(); if (infos.size() > 1) { - Builder<Const> elements = ImmutableList.builder(); + ImmutableList.Builder<Const> elements = ImmutableList.builder(); for (AnnoInfo element : infos) { - elements.add(new AnnotationValue(element.sym(), element.values())); + elements.add(new TurbineAnnotationValue(element)); } - ClassSymbol container = env.get(symbol).annotationMetadata().repeatable(); + TypeBoundClass info = env.get(symbol); + if (info == null || info.annotationMetadata() == null) { + continue; + } + ClassSymbol container = info.annotationMetadata().repeatable(); if (container == null) { + if (isKotlinRepeatable(info)) { + continue; + } AnnoInfo anno = infos.iterator().next(); throw TurbineError.format( anno.source(), anno.position(), ErrorKind.NONREPEATABLE_ANNOTATION, symbol); @@ -273,4 +303,18 @@ public class DisambiguateTypeAnnotations { } return result.build(); } + + // Work-around for https://youtrack.jetbrains.net/issue/KT-34189. + // Kotlin stubs include repeated annotations that are valid in Kotlin (i.e. meta-annotated with + // @kotlin.annotation.Repeatable), even though they are invalid Java. + // TODO(b/142002426): kill this with fire + static boolean isKotlinRepeatable(TypeBoundClass info) { + for (AnnoInfo metaAnno : info.annotations()) { + if (metaAnno.sym() != null + && metaAnno.sym().binaryName().equals("kotlin/annotation/Repeatable")) { + return true; + } + } + return false; + } } diff --git a/java/com/google/turbine/binder/HierarchyBinder.java b/java/com/google/turbine/binder/HierarchyBinder.java index 8549d3a..07d255c 100644 --- a/java/com/google/turbine/binder/HierarchyBinder.java +++ b/java/com/google/turbine/binder/HierarchyBinder.java @@ -16,6 +16,7 @@ package com.google.turbine.binder; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.turbine.binder.bound.HeaderBoundClass; @@ -68,6 +69,9 @@ public class HierarchyBinder { ClassSymbol superclass; if (decl.xtnds().isPresent()) { superclass = resolveClass(decl.xtnds().get()); + if (origin.equals(superclass)) { + log.error(decl.xtnds().get().position(), ErrorKind.CYCLIC_HIERARCHY, origin); + } } else { switch (decl.tykind()) { case ENUM: @@ -90,6 +94,9 @@ public class HierarchyBinder { if (result == null) { continue; } + if (origin.equals(result)) { + log.error(i.position(), ErrorKind.CYCLIC_HIERARCHY, origin); + } interfaces.add(result); } } else { @@ -120,7 +127,7 @@ public class HierarchyBinder { // Resolve the base symbol in the qualified name. LookupResult result = lookup(ty, new LookupKey(ImmutableList.copyOf(flat))); if (result == null) { - log.error(ty.position(), ErrorKind.CANNOT_RESOLVE, ty); + log.error(ty.position(), ErrorKind.CANNOT_RESOLVE, Joiner.on('.').join(flat)); return null; } // Resolve pieces in the qualified name referring to member types. diff --git a/java/com/google/turbine/binder/JimageClassBinder.java b/java/com/google/turbine/binder/JimageClassBinder.java index e3d0865..d11dda1 100644 --- a/java/com/google/turbine/binder/JimageClassBinder.java +++ b/java/com/google/turbine/binder/JimageClassBinder.java @@ -33,6 +33,7 @@ import com.google.turbine.binder.bytecode.BytecodeBoundClass; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.lookup.LookupKey; import com.google.turbine.binder.lookup.LookupResult; +import com.google.turbine.binder.lookup.PackageScope; import com.google.turbine.binder.lookup.Scope; import com.google.turbine.binder.lookup.TopLevelIndex; import com.google.turbine.binder.sym.ClassSymbol; @@ -150,7 +151,7 @@ public class JimageClassBinder { String binaryName = modulePath.relativize(path).toString(); binaryName = binaryName.substring(0, binaryName.length() - ".class".length()); ClassSymbol sym = new ClassSymbol(binaryName); - packageClassesBySimpleName.put(packageName, simpleName(sym), sym); + packageClassesBySimpleName.put(packageName, sym.simpleName(), sym); JimageClassBinder.this.env.put( sym, new BytecodeBoundClass(sym, toByteArrayOrDie(path), env, path.toString())); } @@ -176,16 +177,6 @@ public class JimageClassBinder { }); } - private static String simpleName(ClassSymbol sym) { - int idx = sym.binaryName().lastIndexOf('/'); - return idx != -1 ? sym.binaryName().substring(idx + 1) : sym.binaryName(); - } - - private static String packageName(ClassSymbol sym) { - int idx = sym.binaryName().lastIndexOf('/'); - return idx != -1 ? sym.binaryName().substring(0, idx) : ""; - } - private class JimageTopLevelIndex implements TopLevelIndex { final Scope topLevelScope = @@ -222,18 +213,23 @@ public class JimageClassBinder { } @Override - public Scope lookupPackage(ImmutableList<String> name) { + public PackageScope lookupPackage(Iterable<String> name) { String packageName = Joiner.on('/').join(name); if (!initPackage(packageName)) { return null; } - return new Scope() { + return new PackageScope() { @Nullable @Override public LookupResult lookup(LookupKey lookupKey) { ClassSymbol sym = packageClassesBySimpleName.get(packageName, lookupKey.first().value()); return sym != null ? new LookupResult(sym, lookupKey) : null; } + + @Override + public Iterable<ClassSymbol> classes() { + return packageClassesBySimpleName.row(packageName).values(); + } }; } } @@ -247,7 +243,7 @@ public class JimageClassBinder { return new Env<ClassSymbol, BytecodeBoundClass>() { @Override public BytecodeBoundClass get(ClassSymbol sym) { - return initPackage(packageName(sym)) ? env.get(sym) : null; + return initPackage(sym.packageName()) ? env.get(sym) : null; } }; } @@ -266,5 +262,10 @@ public class JimageClassBinder { public TopLevelIndex index() { return index; } + + @Override + public Supplier<byte[]> resource(String input) { + return null; + } } } diff --git a/java/com/google/turbine/binder/ModuleBinder.java b/java/com/google/turbine/binder/ModuleBinder.java index afd8770..748ff39 100644 --- a/java/com/google/turbine/binder/ModuleBinder.java +++ b/java/com/google/turbine/binder/ModuleBinder.java @@ -40,6 +40,7 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.ModuleSymbol; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; +import com.google.turbine.diag.TurbineLog.TurbineLogWithSource; import com.google.turbine.model.TurbineFlag; import com.google.turbine.tree.Tree; import com.google.turbine.tree.Tree.Ident; @@ -60,8 +61,9 @@ public class ModuleBinder { PackageSourceBoundModule module, CompoundEnv<ClassSymbol, TypeBoundClass> env, Env<ModuleSymbol, ModuleInfo> moduleEnv, - Optional<String> moduleVersion) { - return new ModuleBinder(module, env, moduleEnv, moduleVersion).bind(); + Optional<String> moduleVersion, + TurbineLogWithSource log) { + return new ModuleBinder(module, env, moduleEnv, moduleVersion, log).bind(); } private final PackageSourceBoundModule module; @@ -69,16 +71,19 @@ public class ModuleBinder { private final Env<ModuleSymbol, ModuleInfo> moduleEnv; private final Optional<String> moduleVersion; private final CompoundScope scope; + private final TurbineLogWithSource log; public ModuleBinder( PackageSourceBoundModule module, CompoundEnv<ClassSymbol, TypeBoundClass> env, Env<ModuleSymbol, ModuleInfo> moduleEnv, - Optional<String> moduleVersion) { + Optional<String> moduleVersion, + TurbineLogWithSource log) { this.module = module; this.env = env; this.moduleEnv = moduleEnv; this.moduleVersion = moduleVersion; + this.log = log; this.scope = module.scope().toScope(Resolve.resolveFunction(env, /* origin= */ null)); } @@ -92,11 +97,12 @@ public class ModuleBinder { module.source(), scope, /* values= */ new SimpleEnv<>(ImmutableMap.of()), - env); + env, + log); ImmutableList.Builder<AnnoInfo> annoInfos = ImmutableList.builder(); for (Tree.Anno annoTree : module.module().annos()) { ClassSymbol sym = resolve(annoTree.position(), annoTree.name()); - annoInfos.add(new AnnoInfo(module.source(), sym, annoTree, null)); + annoInfos.add(new AnnoInfo(module.source(), sym, annoTree, ImmutableMap.of())); } ImmutableList<AnnoInfo> annos = constEvaluator.evaluateAnnotations(annoInfos.build()); @@ -130,8 +136,6 @@ public class ModuleBinder { case PROVIDES: provides.add(bindProvides((ModProvides) directive)); break; - default: - throw new AssertionError(directive.kind()); } } if (!requiresJavaBase) { @@ -181,11 +185,11 @@ public class ModuleBinder { return new RequireInfo(moduleName, flags, requires != null ? requires.version() : null); } - private ExportInfo bindExports(ModExports directive) { + private static ExportInfo bindExports(ModExports directive) { return new ExportInfo(directive.packageName(), directive.moduleNames()); } - private OpenInfo bindOpens(ModOpens directive) { + private static OpenInfo bindOpens(ModOpens directive) { return new OpenInfo(directive.packageName(), directive.moduleNames()); } diff --git a/java/com/google/turbine/binder/Processing.java b/java/com/google/turbine/binder/Processing.java new file mode 100644 index 0000000..ecdf195 --- /dev/null +++ b/java/com/google/turbine/binder/Processing.java @@ -0,0 +1,556 @@ +/* + * Copyright 2019 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.auto.value.AutoValue; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Stopwatch; +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Sets; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.Binder.Statistics; +import com.google.turbine.binder.bound.SourceTypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineLog; +import com.google.turbine.parse.Parser; +import com.google.turbine.processing.ModelFactory; +import com.google.turbine.processing.TurbineElements; +import com.google.turbine.processing.TurbineFiler; +import com.google.turbine.processing.TurbineMessager; +import com.google.turbine.processing.TurbineProcessingEnvironment; +import com.google.turbine.processing.TurbineRoundEnvironment; +import com.google.turbine.processing.TurbineTypes; +import com.google.turbine.tree.Tree.CompUnit; +import com.google.turbine.type.AnnoInfo; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; +import javax.annotation.processing.Processor; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Top level annotation processing logic, see also {@link Binder}. */ +public class Processing { + + static BindingResult process( + TurbineLog log, + final ImmutableList<CompUnit> initialSources, + final ClassPath classpath, + ProcessorInfo processorInfo, + ClassPath bootclasspath, + BindingResult result, + Optional<String> moduleVersion) { + + Set<String> seen = new HashSet<>(); + for (CompUnit u : initialSources) { + if (u.source() != null) { + seen.add(u.source().path()); + } + } + + TurbineFiler filer = + new TurbineFiler( + seen, + new Function<String, Supplier<byte[]>>() { + @Nullable + @Override + public Supplier<byte[]> apply(@Nullable String input) { + // TODO(cushon): should annotation processors be allowed to generate code with + // dependencies between source and bytecode, or vice versa? + // Currently generated classes are not available on the classpath when compiling + // the compilation sources (including generated sources). + return classpath.resource(input); + } + }, + processorInfo.loader()); + + Env<ClassSymbol, SourceTypeBoundClass> tenv = new SimpleEnv<>(result.units()); + CompoundEnv<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv); + ModelFactory factory = new ModelFactory(env, processorInfo.loader(), result.tli()); + + Map<String, byte[]> statistics = new LinkedHashMap<>(); + + TurbineTypes turbineTypes = new TurbineTypes(factory); + TurbineProcessingEnvironment processingEnv = + new TurbineProcessingEnvironment( + filer, + turbineTypes, + new TurbineElements(factory, turbineTypes), + new TurbineMessager(factory, log), + processorInfo.options(), + processorInfo.sourceVersion(), + processorInfo.loader(), + statistics); + Timers timers = new Timers(); + for (Processor processor : processorInfo.processors()) { + try (Timers.Timer unused = timers.start(processor)) { + processor.init(processingEnv); + } catch (Throwable t) { + reportProcessorCrash(log, processor, t); + } + } + + Map<Processor, Pattern> wanted = new HashMap<>(); + for (Processor processor : processorInfo.processors()) { + List<String> patterns = new ArrayList<>(); + for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) { + // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct + patterns.add(supportedAnnotationType.replace("*", ".*")); + } + wanted.put(processor, Pattern.compile(Joiner.on('|').join(patterns))); + } + + Set<ClassSymbol> allSymbols = new HashSet<>(); + + ImmutableList.Builder<CompUnit> units = + ImmutableList.<CompUnit>builder().addAll(initialSources); + + Set<Processor> toRun = new LinkedHashSet<>(); + + boolean errorRaised = false; + + while (true) { + ImmutableSet<ClassSymbol> syms = + Sets.difference(result.units().keySet(), allSymbols).immutableCopy(); + allSymbols.addAll(syms); + if (syms.isEmpty()) { + break; + } + ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations = getAllAnnotations(env, syms); + TurbineRoundEnvironment roundEnv = null; + for (Processor processor : processorInfo.processors()) { + Set<TypeElement> annotations = new HashSet<>(); + Pattern pattern = wanted.get(processor); + boolean run = toRun.contains(processor); + for (ClassSymbol a : allAnnotations.keys()) { + if (pattern.matcher(a.toString()).matches()) { + annotations.add(factory.typeElement(a)); + run = true; + } + } + if (run) { + toRun.add(processor); + if (roundEnv == null) { + roundEnv = + new TurbineRoundEnvironment(factory, syms, false, errorRaised, allAnnotations); + } + try (Timers.Timer unused = timers.start(processor)) { + // discard the result of Processor#process because 'claiming' annotations is a bad idea + // TODO(cushon): consider disallowing this, or reporting a diagnostic + processor.process(annotations, roundEnv); + } catch (Throwable t) { + reportProcessorCrash(log, processor, t); + } + } + } + Collection<SourceFile> files = filer.finishRound(); + if (files.isEmpty()) { + break; + } + for (SourceFile file : files) { + units.add(Parser.parse(file)); + } + errorRaised = log.errorRaised(); + if (errorRaised) { + log.maybeThrow(); + } + log.clear(); + result = + Binder.bind( + log, + units.build(), + filer.generatedSources(), + filer.generatedClasses(), + classpath, + bootclasspath, + moduleVersion); + tenv = new SimpleEnv<>(result.units()); + env = CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv); + factory.round(env, result.tli()); + } + + TurbineRoundEnvironment roundEnv = null; + for (Processor processor : toRun) { + if (roundEnv == null) { + roundEnv = + new TurbineRoundEnvironment( + factory, + ImmutableSet.of(), + /* processingOver= */ true, + errorRaised, + ImmutableSetMultimap.of()); + } + try (Timers.Timer unused = timers.start(processor)) { + processor.process(ImmutableSet.of(), roundEnv); + } catch (Throwable t) { + reportProcessorCrash(log, processor, t); + } + } + + Collection<SourceFile> files = filer.finishRound(); + if (!files.isEmpty()) { + // processors aren't supposed to generate sources on the final processing round, but javac + // tolerates it anyway + // TODO(cushon): consider disallowing this, or reporting a diagnostic + for (SourceFile file : files) { + units.add(Parser.parse(file)); + } + result = + Binder.bind( + log, + units.build(), + filer.generatedSources(), + filer.generatedClasses(), + classpath, + bootclasspath, + moduleVersion); + log.maybeThrow(); + } + + if (!filer.generatedClasses().isEmpty()) { + // add any generated class files to the output + // TODO(cushon): consider handling generated classes after each round + result = result.withGeneratedClasses(filer.generatedClasses()); + } + if (!filer.generatedSources().isEmpty()) { + result = result.withGeneratedSources(filer.generatedSources()); + } + + result = + result.withStatistics(Statistics.create(timers.build(), ImmutableMap.copyOf(statistics))); + + return result; + } + + private static void reportProcessorCrash(TurbineLog log, Processor processor, Throwable t) { + log.diagnostic( + Diagnostic.Kind.ERROR, + String.format( + "An exception occurred in %s:\n%s", + processor.getClass().getCanonicalName(), Throwables.getStackTraceAsString(t))); + log.maybeThrow(); + } + + /** Returns a map from annotations present in the compilation to the annotated elements. */ + private static ImmutableSetMultimap<ClassSymbol, Symbol> getAllAnnotations( + Env<ClassSymbol, TypeBoundClass> env, Iterable<ClassSymbol> syms) { + ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result = ImmutableSetMultimap.builder(); + for (ClassSymbol sym : syms) { + TypeBoundClass info = env.get(sym); + for (AnnoInfo annoInfo : info.annotations()) { + if (sym.simpleName().equals("package-info")) { + addAnno(result, annoInfo, sym.owner()); + } else { + addAnno(result, annoInfo, sym); + } + } + for (ClassSymbol inheritedAnno : + inheritedAnnotations(new HashSet<>(), info.superclass(), env)) { + result.put(inheritedAnno, sym); + } + for (TypeBoundClass.MethodInfo method : info.methods()) { + for (AnnoInfo annoInfo : method.annotations()) { + addAnno(result, annoInfo, method.sym()); + } + for (TypeBoundClass.ParamInfo param : method.parameters()) { + for (AnnoInfo annoInfo : param.annotations()) { + addAnno(result, annoInfo, param.sym()); + } + } + } + for (FieldInfo field : info.fields()) { + for (AnnoInfo annoInfo : field.annotations()) { + addAnno(result, annoInfo, field.sym()); + } + } + } + return result.build(); + } + + // TODO(cushon): consider memoizing this (or isAnnotationInherited) if they show up in profiles + private static Set<ClassSymbol> inheritedAnnotations( + Set<ClassSymbol> seen, ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env) { + ImmutableSet.Builder<ClassSymbol> result = ImmutableSet.builder(); + ClassSymbol curr = sym; + while (curr != null && seen.add(curr)) { + TypeBoundClass info = env.get(curr); + if (info == null) { + break; + } + for (AnnoInfo anno : info.annotations()) { + ClassSymbol annoSym = anno.sym(); + if (annoSym == null) { + continue; + } + if (isAnnotationInherited(env, annoSym)) { + result.add(annoSym); + } + } + curr = info.superclass(); + } + return result.build(); + } + + private static boolean isAnnotationInherited( + Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) { + TypeBoundClass annoInfo = env.get(sym); + if (annoInfo == null) { + return false; + } + for (AnnoInfo anno : annoInfo.annotations()) { + if (Objects.equals(anno.sym(), ClassSymbol.INHERITED)) { + return true; + } + } + return false; + } + + private static void addAnno( + ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result, AnnoInfo annoInfo, Symbol owner) { + ClassSymbol sym = annoInfo.sym(); + if (sym != null) { + result.put(sym, owner); + } + } + + public static ProcessorInfo initializeProcessors( + ImmutableList<String> javacopts, + ImmutableList<String> processorPath, + ImmutableSet<String> processorNames, + ImmutableSet<String> builtinProcessors) + throws MalformedURLException { + ClassLoader processorLoader = null; + ImmutableList.Builder<Processor> processors = ImmutableList.builder(); + ImmutableMap<String, String> processorOptions; + if (!processorNames.isEmpty() && !javacopts.contains("-proc:none")) { + if (!processorPath.isEmpty()) { + processorLoader = + new URLClassLoader( + toUrls(processorPath), + new ClassLoader(getPlatformClassLoader()) { + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.startsWith("com.sun.source.") + || name.startsWith("com.sun.tools.") + || name.startsWith("com.google.common.collect.") + || name.startsWith("com.google.common.base.") + || name.startsWith("com.google.common.graph.") + || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.") + || name.startsWith("dagger.model.") + || name.startsWith("dagger.spi.") + || name.equals("com.google.turbine.processing.TurbineProcessingEnvironment") + || builtinProcessors.contains(name)) { + return Class.forName(name); + } + throw new ClassNotFoundException(name); + } + }); + } else { + processorLoader = Processing.class.getClassLoader(); + } + for (String processor : processorNames) { + try { + Class<? extends Processor> clazz = + Class.forName(processor, false, processorLoader).asSubclass(Processor.class); + processors.add(clazz.getConstructor().newInstance()); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + processorOptions = processorOptions(javacopts); + } else { + processorOptions = ImmutableMap.of(); + } + SourceVersion sourceVersion = SourceVersion.latestSupported(); + Iterator<String> it = javacopts.iterator(); + while (it.hasNext()) { + String option = it.next(); + switch (option) { + case "-target": + if (it.hasNext()) { + String value = it.next(); + switch (value) { + case "5": + case "1.5": + sourceVersion = SourceVersion.RELEASE_5; + break; + case "6": + case "1.6": + sourceVersion = SourceVersion.RELEASE_6; + break; + case "7": + case "1.7": + sourceVersion = SourceVersion.RELEASE_7; + break; + case "8": + sourceVersion = SourceVersion.RELEASE_8; + break; + default: + break; + } + } + break; + default: + break; + } + } + return ProcessorInfo.create( + processors.build(), processorLoader, processorOptions, sourceVersion); + } + + private static URL[] toUrls(ImmutableList<String> processorPath) throws MalformedURLException { + URL[] urls = new URL[processorPath.size()]; + int i = 0; + for (String path : processorPath) { + urls[i++] = Paths.get(path).toUri().toURL(); + } + return urls; + } + + public static ClassLoader getPlatformClassLoader() { + try { + return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null); + } catch (ReflectiveOperationException e) { + // In earlier releases, set 'null' as the parent to delegate to the boot class loader. + return null; + } + } + + private static ImmutableMap<String, String> processorOptions(ImmutableList<String> javacopts) { + Map<String, String> result = new LinkedHashMap<>(); // ImmutableMap.Builder rejects duplicates + for (String javacopt : javacopts) { + if (javacopt.startsWith("-A")) { + javacopt = javacopt.substring("-A".length()); + int idx = javacopt.indexOf('='); + String key; + String value; + if (idx != -1) { + key = javacopt.substring(0, idx); + value = javacopt.substring(idx + 1); + } else { + key = javacopt; + value = javacopt; + } + result.put(key, value); + } + } + return ImmutableMap.copyOf(result); + } + + /** Information about any annotation processing performed by this compilation. */ + @AutoValue + public abstract static class ProcessorInfo { + + abstract ImmutableList<Processor> processors(); + + /** + * The classloader to use for annotation processor implementations, and any annotations they + * access reflectively. + */ + @Nullable + abstract ClassLoader loader(); + + /** Command line annotation processing options, passed to javac with {@code -Akey=value}. */ + abstract ImmutableMap<String, String> options(); + + public abstract SourceVersion sourceVersion(); + + public static ProcessorInfo create( + ImmutableList<Processor> processors, + @Nullable ClassLoader loader, + ImmutableMap<String, String> options, + SourceVersion sourceVersion) { + return new AutoValue_Processing_ProcessorInfo(processors, loader, options, sourceVersion); + } + + static ProcessorInfo empty() { + return create( + /* processors= */ ImmutableList.of(), + /* loader= */ null, + /* options= */ ImmutableMap.of(), + /* sourceVersion= */ SourceVersion.latest()); + } + } + + private static class Timers { + private final Map<Class<?>, Stopwatch> processorTimers = new LinkedHashMap<>(); + + Timer start(Processor processor) { + Class<? extends Processor> clazz = processor.getClass(); + Stopwatch sw = processorTimers.get(clazz); + if (sw == null) { + sw = Stopwatch.createUnstarted(); + processorTimers.put(clazz, sw); + } + sw.start(); + return new Timer(sw); + } + + private static class Timer implements AutoCloseable { + + private final Stopwatch sw; + + public Timer(Stopwatch sw) { + this.sw = sw; + } + + @Override + public void close() { + sw.stop(); + } + } + + ImmutableMap<String, Duration> build() { + ImmutableMap.Builder<String, Duration> result = ImmutableMap.builder(); + for (Map.Entry<Class<?>, Stopwatch> e : processorTimers.entrySet()) { + result.put(e.getKey().getCanonicalName(), e.getValue().elapsed()); + } + return result.build(); + } + } +} diff --git a/java/com/google/turbine/binder/Resolve.java b/java/com/google/turbine/binder/Resolve.java index 0a61844..28a8be3 100644 --- a/java/com/google/turbine/binder/Resolve.java +++ b/java/com/google/turbine/binder/Resolve.java @@ -136,12 +136,11 @@ public class Resolve { return true; case PROTECTED: case PACKAGE: - return Objects.equals(packageName(sym), packagename); + return Objects.equals(sym.packageName(), packagename); case PRIVATE: return false; - default: - throw new AssertionError(visibility); } + throw new AssertionError(visibility); } } @@ -206,24 +205,12 @@ public class Resolve { case PROTECTED: return true; case PACKAGE: - return Objects.equals(packageName(owner), packageName(origin)); + return Objects.equals(owner.packageName(), origin.packageName()); case PRIVATE: // Private members of lexically enclosing declarations are not handled, // since this visibility check is only used for inherited members. return owner.equals(origin); - default: - throw new AssertionError(visibility); } - } - - private static String packageName(ClassSymbol sym) { - if (sym == null) { - return null; - } - int idx = sym.binaryName().lastIndexOf('/'); - if (idx == -1) { - return null; - } - return sym.binaryName().substring(0, idx); + throw new AssertionError(visibility); } } diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java index 8cf71e1..7b01856 100644 --- a/java/com/google/turbine/binder/TypeBinder.java +++ b/java/com/google/turbine/binder/TypeBinder.java @@ -34,6 +34,7 @@ import com.google.turbine.binder.lookup.Scope; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; import com.google.turbine.binder.sym.Symbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.diag.TurbineError.ErrorKind; @@ -266,22 +267,23 @@ public class TypeBinder { if (hasConstructor()) { return ImmutableList.of(); } + MethodSymbol symbol = new MethodSymbol(-1, owner, "<init>"); ImmutableList<ParamInfo> formals; if (hasEnclosingInstance(base)) { - formals = ImmutableList.of(enclosingInstanceParameter()); + formals = ImmutableList.of(enclosingInstanceParameter(symbol)); } else { formals = ImmutableList.of(); } return ImmutableList.of( - syntheticConstructor(formals, TurbineVisibility.fromAccess(base.access()))); + syntheticConstructor(symbol, formals, TurbineVisibility.fromAccess(base.access()))); } private MethodInfo syntheticConstructor( - ImmutableList<ParamInfo> formals, TurbineVisibility visibility) { + MethodSymbol symbol, ImmutableList<ParamInfo> formals, TurbineVisibility visibility) { int access = visibility.flag(); access |= (base.access() & TurbineFlag.ACC_STRICT); return new MethodInfo( - new MethodSymbol(owner, "<init>"), + symbol, ImmutableMap.of(), Type.VOID, formals, @@ -293,7 +295,7 @@ public class TypeBinder { null); } - private ParamInfo enclosingInstanceParameter() { + private ParamInfo enclosingInstanceParameter(MethodSymbol owner) { int access = TurbineFlag.ACC_FINAL; if ((base.access() & TurbineFlag.ACC_PRIVATE) == TurbineFlag.ACC_PRIVATE) { access |= TurbineFlag.ACC_SYNTHETIC; @@ -311,37 +313,40 @@ public class TypeBinder { sym = info.owner(); } return new ParamInfo( + new ParamSymbol(owner, "this$" + enclosingInstances), Type.ClassTy.asNonParametricClassTy(base.owner()), - "this$" + enclosingInstances, ImmutableList.of(), access); } - private static final ImmutableList<ParamInfo> ENUM_CTOR_PARAMS = - ImmutableList.of( - new ParamInfo( - Type.ClassTy.STRING, - "$enum$name", - ImmutableList.of(), - /*synthetic*/ - TurbineFlag.ACC_SYNTHETIC), - new ParamInfo( - Type.PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), - "$enum$ordinal", - ImmutableList.of(), - /*synthetic*/ - TurbineFlag.ACC_SYNTHETIC)); + private static ImmutableList<ParamInfo> enumCtorParams(MethodSymbol owner) { + return ImmutableList.of( + new ParamInfo( + new ParamSymbol(owner, "$enum$name"), + Type.ClassTy.STRING, + ImmutableList.of(), + /*synthetic*/ + TurbineFlag.ACC_SYNTHETIC), + new ParamInfo( + new ParamSymbol(owner, "$enum$ordinal"), + Type.PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), + ImmutableList.of(), + /*synthetic*/ + TurbineFlag.ACC_SYNTHETIC)); + } private ImmutableList<MethodInfo> syntheticEnumMethods() { ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder(); int access = 0; access |= (base.access() & TurbineFlag.ACC_STRICT); if (!hasConstructor()) { - methods.add(syntheticConstructor(ENUM_CTOR_PARAMS, TurbineVisibility.PRIVATE)); + MethodSymbol symbol = new MethodSymbol(-1, owner, "<init>"); + methods.add(syntheticConstructor(symbol, enumCtorParams(symbol), TurbineVisibility.PRIVATE)); } + MethodSymbol valuesMethod = new MethodSymbol(-2, owner, "values"); methods.add( new MethodInfo( - new MethodSymbol(owner, "values"), + valuesMethod, ImmutableMap.of(), Type.ArrayTy.create(Type.ClassTy.asNonParametricClassTy(owner), ImmutableList.of()), ImmutableList.of(), @@ -351,14 +356,18 @@ public class TypeBinder { null, ImmutableList.of(), null)); + MethodSymbol valueOfMethod = new MethodSymbol(-3, owner, "valueOf"); methods.add( new MethodInfo( - new MethodSymbol(owner, "valueOf"), + valueOfMethod, ImmutableMap.of(), Type.ClassTy.asNonParametricClassTy(owner), ImmutableList.of( new ParamInfo( - Type.ClassTy.STRING, "name", ImmutableList.of(), TurbineFlag.ACC_MANDATED)), + new ParamSymbol(valueOfMethod, "name"), + Type.ClassTy.STRING, + ImmutableList.of(), + TurbineFlag.ACC_MANDATED)), ImmutableList.of(), access | TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_STATIC, null, @@ -387,38 +396,38 @@ public class TypeBinder { for (Tree.TyParam tree : trees) { TyVarSymbol sym = symbols.get(tree.name().value()); ImmutableList.Builder<Type> bounds = ImmutableList.builder(); - if (tree.bounds().isEmpty()) { - bounds.add(Type.ClassTy.OBJECT); - } else { - for (Tree bound : tree.bounds()) { - bounds.add(bindTy(scope, bound)); - } + for (Tree bound : tree.bounds()) { + bounds.add(bindTy(scope, bound)); } ImmutableList<AnnoInfo> annotations = bindAnnotations(scope, tree.annos()); - result.put(sym, new TyVarInfo(IntersectionTy.create(bounds.build()), annotations)); + result.put( + sym, + new TyVarInfo( + IntersectionTy.create(bounds.build()), /* lowerBound= */ null, annotations)); } return result.build(); } private List<MethodInfo> bindMethods(CompoundScope scope, ImmutableList<Tree> members) { List<MethodInfo> methods = new ArrayList<>(); + int idx = 0; for (Tree member : members) { if (member.kind() == Tree.Kind.METH_DECL) { - methods.add(bindMethod(scope, (Tree.MethDecl) member)); + methods.add(bindMethod(idx++, scope, (Tree.MethDecl) member)); } } return methods; } - private MethodInfo bindMethod(CompoundScope scope, Tree.MethDecl t) { + private MethodInfo bindMethod(int idx, CompoundScope scope, Tree.MethDecl t) { - MethodSymbol sym = new MethodSymbol(owner, t.name().value()); + MethodSymbol sym = new MethodSymbol(idx, owner, t.name().value()); ImmutableMap<String, TyVarSymbol> typeParameters; { ImmutableMap.Builder<String, TyVarSymbol> builder = ImmutableMap.builder(); for (Tree.TyParam pt : t.typarams()) { - builder.put(pt.name().value(), new TyVarSymbol(owner, pt.name().value())); + builder.put(pt.name().value(), new TyVarSymbol(sym, pt.name().value())); } typeParameters = builder.build(); } @@ -439,9 +448,9 @@ public class TypeBinder { String name = t.name().value(); if (name.equals("<init>")) { if (hasEnclosingInstance(base)) { - parameters.add(enclosingInstanceParameter()); + parameters.add(enclosingInstanceParameter(sym)); } else if (base.kind() == TurbineTyKind.ENUM && name.equals("<init>")) { - parameters.addAll(ENUM_CTOR_PARAMS); + parameters.addAll(enumCtorParams(sym)); } } ParamInfo receiver = null; @@ -452,8 +461,8 @@ public class TypeBinder { } ParamInfo param = new ParamInfo( + new ParamSymbol(sym, p.name().value()), bindTy(scope, p.ty()), - p.name().value(), bindAnnotations(scope, p.annos()), /*synthetic*/ access); if (p.name().value().equals("this")) { @@ -561,7 +570,7 @@ public class TypeBinder { ImmutableList<Ident> name = tree.name(); LookupResult lookupResult = scope.lookup(new LookupKey(name)); ClassSymbol sym = resolveAnnoSymbol(tree, name, lookupResult); - result.add(new AnnoInfo(base.source(), sym, tree, null)); + result.add(new AnnoInfo(base.source(), sym, tree, ImmutableMap.of())); } return result.build(); } @@ -648,7 +657,7 @@ public class TypeBinder { LookupResult result = scope.lookup(new LookupKey(names)); if (result == null || result.sym() == null) { log.error(names.get(0).position(), ErrorKind.CANNOT_RESOLVE, Joiner.on('.').join(names)); - return Type.ErrorTy.create(); + return Type.ErrorTy.create(names); } Symbol sym = result.sym(); int annoIdx = flat.size() - result.remaining().size() - 1; @@ -660,7 +669,7 @@ public class TypeBinder { case TY_PARAM: if (!result.remaining().isEmpty()) { log.error(t.position(), ErrorKind.TYPE_PARAMETER_QUALIFIER); - return Type.ErrorTy.create(); + return Type.ErrorTy.create(names); } return Type.TyVar.create((TyVarSymbol) sym, annos); default: @@ -684,7 +693,7 @@ public class TypeBinder { Tree.ClassTy curr = flat.get(idx); sym = resolveNext(sym, curr.name()); if (sym == null) { - return Type.ErrorTy.create(); + return Type.ErrorTy.create(bits); } annotations = bindAnnotations(scope, curr.annos()); diff --git a/java/com/google/turbine/binder/bound/AnnotationMetadata.java b/java/com/google/turbine/binder/bound/AnnotationMetadata.java index 9c81a5f..31860b6 100644 --- a/java/com/google/turbine/binder/bound/AnnotationMetadata.java +++ b/java/com/google/turbine/binder/bound/AnnotationMetadata.java @@ -30,7 +30,7 @@ import java.util.EnumSet; */ public class AnnotationMetadata { - private static final ImmutableSet<TurbineElementType> DEFAULT_TARGETS = getDefaultElements(); + public static final ImmutableSet<TurbineElementType> DEFAULT_TARGETS = getDefaultElements(); private static ImmutableSet<TurbineElementType> getDefaultElements() { EnumSet<TurbineElementType> values = EnumSet.allOf(TurbineElementType.class); diff --git a/java/com/google/turbine/binder/bound/EnumConstantValue.java b/java/com/google/turbine/binder/bound/EnumConstantValue.java index 4083ae3..e99c6ed 100644 --- a/java/com/google/turbine/binder/bound/EnumConstantValue.java +++ b/java/com/google/turbine/binder/bound/EnumConstantValue.java @@ -46,4 +46,9 @@ public class EnumConstantValue extends Const { public boolean equals(Object obj) { return obj instanceof EnumConstantValue && sym().equals(((EnumConstantValue) obj).sym()); } + + @Override + public String toString() { + return sym.toString(); + } } diff --git a/java/com/google/turbine/binder/bound/AnnotationValue.java b/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java index fd4ffab..808d603 100644 --- a/java/com/google/turbine/binder/bound/AnnotationValue.java +++ b/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java @@ -19,22 +19,20 @@ package com.google.turbine.binder.bound; import com.google.common.collect.ImmutableMap; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.model.Const; -import java.util.Objects; +import com.google.turbine.type.AnnoInfo; /** An annotation literal constant. */ -public class AnnotationValue extends Const { +public class TurbineAnnotationValue extends Const { - private final ClassSymbol sym; - private final ImmutableMap<String, Const> values; + private final AnnoInfo info; - public AnnotationValue(ClassSymbol sym, ImmutableMap<String, Const> values) { - this.sym = sym; - this.values = values; + public TurbineAnnotationValue(AnnoInfo info) { + this.info = info; } @Override public String toString() { - return String.format("@%s", sym); + return info.toString(); } @Override @@ -44,25 +42,26 @@ public class AnnotationValue extends Const { /** The annotation declaration. */ public ClassSymbol sym() { - return sym; + return info.sym(); } /** The annotation literal's element-value pairs. */ public ImmutableMap<String, Const> values() { - return values; + return info.values(); } @Override public int hashCode() { - return Objects.hash(sym, values); + return info.hashCode(); } @Override public boolean equals(Object obj) { - if (!(obj instanceof AnnotationValue)) { - return false; - } - AnnotationValue that = (AnnotationValue) obj; - return sym().equals(that.sym()) && values().equals(that.values()); + return obj instanceof TurbineAnnotationValue + && info().equals(((TurbineAnnotationValue) obj).info()); + } + + public AnnoInfo info() { + return info; } } diff --git a/java/com/google/turbine/binder/bound/TypeBoundClass.java b/java/com/google/turbine/binder/bound/TypeBoundClass.java index 350d311..e8933ac 100644 --- a/java/com/google/turbine/binder/bound/TypeBoundClass.java +++ b/java/com/google/turbine/binder/bound/TypeBoundClass.java @@ -16,10 +16,12 @@ package com.google.turbine.binder.bound; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.model.Const; import com.google.turbine.model.TurbineFlag; @@ -28,6 +30,8 @@ import com.google.turbine.tree.Tree.MethDecl; import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type; import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.type.Type.MethodTy; +import org.checkerframework.checker.nullness.qual.Nullable; /** A bound node that augments {@link HeaderBoundClass} with type information. */ public interface TypeBoundClass extends HeaderBoundClass { @@ -57,17 +61,29 @@ public interface TypeBoundClass extends HeaderBoundClass { /** A type parameter declaration. */ class TyVarInfo { - private final IntersectionTy bound; + private final IntersectionTy upperBound; + @Nullable private final Type lowerBound; private final ImmutableList<AnnoInfo> annotations; - public TyVarInfo(IntersectionTy bound, ImmutableList<AnnoInfo> annotations) { - this.bound = bound; + public TyVarInfo( + IntersectionTy upperBound, @Nullable Type lowerBound, ImmutableList<AnnoInfo> annotations) { + this.upperBound = upperBound; + if (lowerBound != null) { + throw new IllegalArgumentException("TODO(cushon): support lower bounds"); + } + this.lowerBound = lowerBound; this.annotations = annotations; } - /** The bound. */ - public IntersectionTy bound() { - return bound; + /** The upper bound. */ + public IntersectionTy upperBound() { + return upperBound; + } + + /** The lower bound. */ + @Nullable + public Type lowerBound() { + return lowerBound; } /** Type parameter declaration annotations. */ @@ -148,7 +164,7 @@ public interface TypeBoundClass extends HeaderBoundClass { private final Const defaultValue; private final MethDecl decl; private final ImmutableList<AnnoInfo> annotations; - private final ParamInfo receiver; + private final @Nullable ParamInfo receiver; public MethodInfo( MethodSymbol sym, @@ -160,7 +176,7 @@ public interface TypeBoundClass extends HeaderBoundClass { Const defaultValue, MethDecl decl, ImmutableList<AnnoInfo> annotations, - ParamInfo receiver) { + @Nullable ParamInfo receiver) { this.sym = sym; this.tyParams = tyParams; this.returnType = returnType; @@ -223,26 +239,50 @@ public interface TypeBoundClass extends HeaderBoundClass { return annotations; } - /** Receiver parameter. */ - public ParamInfo receiver() { + /** Receiver parameter (see JLS 8.4.1), or {@code null}. */ + public @Nullable ParamInfo receiver() { return receiver; } + + public MethodTy asType() { + return MethodTy.create( + tyParams.keySet(), + returnType, + receiver != null ? receiver.type() : null, + asTypes(parameters), + exceptions); + } + + private static ImmutableList<Type> asTypes(ImmutableList<ParamInfo> parameters) { + ImmutableList.Builder<Type> result = ImmutableList.builder(); + for (ParamInfo param : parameters) { + if (!param.synthetic()) { + result.add(param.type()); + } + } + return result.build(); + } } /** A formal parameter declaration. */ class ParamInfo { + private final ParamSymbol sym; private final Type type; - private final String name; private final int access; private final ImmutableList<AnnoInfo> annotations; - public ParamInfo(Type type, String name, ImmutableList<AnnoInfo> annotations, int access) { + public ParamInfo(ParamSymbol sym, Type type, ImmutableList<AnnoInfo> annotations, int access) { + this.sym = sym; this.type = type; - this.name = name; this.access = access; this.annotations = annotations; } + /** The parameter's symbol. */ + public ParamSymbol sym() { + return sym; + } + /** The parameter type. */ public Type type() { return type; @@ -263,7 +303,7 @@ public interface TypeBoundClass extends HeaderBoundClass { /** The parameter's name. */ public String name() { - return name; + return sym.name(); } /** The parameter's modifiers. */ diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java index 1d2eecb..66d4cf0 100644 --- a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java +++ b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java @@ -18,9 +18,9 @@ package com.google.turbine.binder.bytecode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.turbine.binder.bound.AnnotationValue; import com.google.turbine.binder.bound.EnumConstantValue; import com.google.turbine.binder.bound.ModuleInfo; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; @@ -81,9 +81,8 @@ public class BytecodeBinder { case UPPER: return Type.WildUpperBoundedTy.create( bindTy(((UpperBoundTySig) sig).bound(), scope), ImmutableList.of()); - default: - throw new AssertionError(sig.boundKind()); } + throw new AssertionError(sig.boundKind()); } static Type bindTy(Sig.TySig sig, Function<String, TyVarSymbol> scope) { @@ -100,23 +99,22 @@ public class BytecodeBinder { return wildTy((WildTySig) sig, scope); case VOID_TY_SIG: return Type.VOID; - default: - throw new AssertionError(sig.kind()); } + throw new AssertionError(sig.kind()); } private static Type bindArrayTy(Sig.ArrayTySig arrayTySig, Function<String, TyVarSymbol> scope) { return Type.ArrayTy.create(bindTy(arrayTySig.elementType(), scope), ImmutableList.of()); } - public static Const bindValue(Type type, ElementValue value) { + public static Const bindValue(ElementValue value) { switch (value.kind()) { case ENUM: return bindEnumValue((EnumConstValue) value); case CONST: - return bindConstValue(type, ((ConstValue) value).value()); + return ((ConstValue) value).value(); case ARRAY: - return bindArrayValue(type, (ArrayValue) value); + return bindArrayValue((ArrayValue) value); case CLASS: return new TurbineClassValue( bindTy( @@ -125,37 +123,45 @@ public class BytecodeBinder { throw new IllegalStateException(x); })); case ANNOTATION: - return bindAnnotationValue(type, ((ElementValue.AnnotationValue) value).annotation()); + return bindAnnotationValue(((ElementValue.ConstTurbineAnnotationValue) value).annotation()); } throw new AssertionError(value.kind()); } - static AnnotationValue bindAnnotationValue(Type type, AnnotationInfo value) { + static TurbineAnnotationValue bindAnnotationValue(AnnotationInfo value) { ClassSymbol sym = asClassSymbol(value.typeName()); ImmutableMap.Builder<String, Const> values = ImmutableMap.builder(); for (Map.Entry<String, ElementValue> e : value.elementValuePairs().entrySet()) { - values.put(e.getKey(), bindValue(type, e.getValue())); + values.put(e.getKey(), bindValue(e.getValue())); } - return new AnnotationValue(sym, values.build()); + return new TurbineAnnotationValue(new AnnoInfo(null, sym, null, values.build())); } static ImmutableList<AnnoInfo> bindAnnotations(List<AnnotationInfo> input) { ImmutableList.Builder<AnnoInfo> result = ImmutableList.builder(); for (AnnotationInfo annotation : input) { - AnnotationValue anno = bindAnnotationValue(Type.VOID, annotation); - result.add(new AnnoInfo(null, anno.sym(), null, anno.values())); + TurbineAnnotationValue anno = bindAnnotationValue(annotation); + if (!shouldSkip(anno)) { + result.add(anno.info()); + } } return result.build(); } + private static boolean shouldSkip(TurbineAnnotationValue anno) { + // ct.sym contains fake annotations without corresponding class files. + return anno.sym().equals(ClassSymbol.PROFILE_ANNOTATION) + || anno.sym().equals(ClassSymbol.PROPRIETARY_ANNOTATION); + } + private static ClassSymbol asClassSymbol(String s) { return new ClassSymbol(s.substring(1, s.length() - 1)); } - private static Const bindArrayValue(Type type, ArrayValue value) { + private static Const bindArrayValue(ArrayValue value) { ImmutableList.Builder<Const> elements = ImmutableList.builder(); for (ElementValue element : value.elements()) { - elements.add(bindValue(type, element)); + elements.add(bindValue(element)); } return new ArrayInitValue(elements.build()); } @@ -164,30 +170,22 @@ public class BytecodeBinder { if (type.tyKind() != Type.TyKind.PRIM_TY) { return value; } + // Deficient numberic types and booleans are all stored as ints in the class file, + // coerce them to the target type. // TODO(b/32626659): this is not bug-compatible with javac switch (((Type.PrimTy) type).primkind()) { case CHAR: return new Const.CharValue(value.asChar().value()); case SHORT: return new Const.ShortValue(value.asShort().value()); - case INT: - return new Const.IntValue(value.asInteger().value()); - case LONG: - return new Const.LongValue(value.asLong().value()); - case FLOAT: - return new Const.FloatValue(value.asFloat().value()); - case DOUBLE: - return new Const.DoubleValue(value.asDouble().value()); case BOOLEAN: // boolean constants are encoded as integers return new Const.BooleanValue(value.asInteger().value() != 0); case BYTE: return new Const.ByteValue(value.asByte().value()); - case STRING: - case NULL: + default: return value; } - throw new AssertionError(type); } private static Const bindEnumValue(EnumConstValue value) { diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java index 6ff8cb4..b992643 100644 --- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java +++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java @@ -32,6 +32,7 @@ import com.google.turbine.binder.env.Env; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.bytecode.ClassFile; import com.google.turbine.bytecode.ClassFile.AnnotationInfo; @@ -332,7 +333,8 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou for (Sig.TySig t : sig.interfaceBounds()) { bounds.add(BytecodeBinder.bindTy(t, scope)); } - return new TyVarInfo(IntersectionTy.create(bounds.build()), ImmutableList.of()); + return new TyVarInfo( + IntersectionTy.create(bounds.build()), /* lowerBound= */ null, ImmutableList.of()); } @Override @@ -350,14 +352,17 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou FieldSymbol fieldSym = new FieldSymbol(sym, cfi.name()); Type type = BytecodeBinder.bindTy( - new SigParser(cfi.descriptor()).parseType(), + new SigParser(firstNonNull(cfi.signature(), cfi.descriptor())).parseType(), makeScope(env, sym, ImmutableMap.of())); int access = cfi.access(); Const.Value value = cfi.value(); if (value != null) { value = BytecodeBinder.bindConstValue(type, value); } - fields.add(new FieldInfo(fieldSym, type, access, ImmutableList.of(), null, value)); + ImmutableList<AnnoInfo> annotations = + BytecodeBinder.bindAnnotations(cfi.annotations()); + fields.add( + new FieldInfo(fieldSym, type, access, annotations, /* decl= */ null, value)); } return fields.build(); } @@ -374,15 +379,16 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou @Override public ImmutableList<MethodInfo> get() { ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder(); + int idx = 0; for (ClassFile.MethodInfo m : classFile.get().methods()) { - methods.add(bindMethod(m)); + methods.add(bindMethod(idx++, m)); } return methods.build(); } }); - private MethodInfo bindMethod(ClassFile.MethodInfo m) { - MethodSymbol methodSymbol = new MethodSymbol(sym, m.name()); + private MethodInfo bindMethod(int methodIdx, ClassFile.MethodInfo m) { + MethodSymbol methodSymbol = new MethodSymbol(methodIdx, sym, m.name()); Sig.MethodSig sig = new SigParser(firstNonNull(m.signature(), m.descriptor())).parseMethodSig(); ImmutableMap<String, TyVarSymbol> tyParams; @@ -414,28 +420,43 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou ImmutableList.Builder<ParamInfo> formals = ImmutableList.builder(); int idx = 0; for (Sig.TySig tySig : sig.params()) { - String name = null; + String name; int access = 0; if (idx < m.parameters().size()) { ParameterInfo paramInfo = m.parameters().get(idx); name = paramInfo.name(); - access = paramInfo.access(); + // ignore parameter modifiers for bug-parity with javac: + // https://bugs.openjdk.java.net/browse/JDK-8226216 + // access = paramInfo.access(); + } else { + name = "arg" + idx; } ImmutableList<AnnoInfo> annotations = (idx < m.parameterAnnotations().size()) ? BytecodeBinder.bindAnnotations(m.parameterAnnotations().get(idx)) : ImmutableList.of(); - formals.add(new ParamInfo(BytecodeBinder.bindTy(tySig, scope), name, annotations, access)); + formals.add( + new ParamInfo( + new ParamSymbol(methodSymbol, name), + BytecodeBinder.bindTy(tySig, scope), + annotations, + access)); idx++; } ImmutableList.Builder<Type> exceptions = ImmutableList.builder(); - for (TySig e : sig.exceptions()) { - exceptions.add(BytecodeBinder.bindTy(e, scope)); + if (!sig.exceptions().isEmpty()) { + for (TySig e : sig.exceptions()) { + exceptions.add(BytecodeBinder.bindTy(e, scope)); + } + } else { + for (String e : m.exceptions()) { + exceptions.add(ClassTy.asNonParametricClassTy(new ClassSymbol(e))); + } } Const defaultValue = - m.defaultValue() != null ? BytecodeBinder.bindValue(ret, m.defaultValue()) : null; + m.defaultValue() != null ? BytecodeBinder.bindValue(m.defaultValue()) : null; ImmutableList<AnnoInfo> annotations = BytecodeBinder.bindAnnotations(m.annotations()); @@ -487,7 +508,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou } }); - private RetentionPolicy bindRetention(AnnotationInfo annotation) { + private static RetentionPolicy bindRetention(AnnotationInfo annotation) { ElementValue val = annotation.elementValuePairs().get("value"); if (val.kind() != Kind.ENUM) { return null; diff --git a/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java index de50a2e..b41edb0 100644 --- a/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java +++ b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java @@ -62,14 +62,14 @@ public class CompoundTopLevelIndex implements TopLevelIndex { } @Override - public Scope lookupPackage(ImmutableList<String> packagename) { + public PackageScope lookupPackage(Iterable<String> packagename) { // When returning package scopes, build up a compound scope containing entries from all // indices with matching packages. - CompoundScope result = null; + PackageScope result = null; for (TopLevelIndex index : indexes) { - Scope packageScope = index.lookupPackage(packagename); + PackageScope packageScope = index.lookupPackage(packagename); if (packageScope != null) { - result = result == null ? CompoundScope.base(packageScope) : result.append(packageScope); + result = result == null ? packageScope : result.append(packageScope); } } return result; diff --git a/java/com/google/turbine/binder/lookup/ImportIndex.java b/java/com/google/turbine/binder/lookup/ImportIndex.java index afa985a..fd57223 100644 --- a/java/com/google/turbine/binder/lookup/ImportIndex.java +++ b/java/com/google/turbine/binder/lookup/ImportIndex.java @@ -142,7 +142,8 @@ public class ImportIndex implements ImportScope { TurbineLogWithSource log, TopLevelIndex cpi, ImportDecl i) { LookupResult base = cpi.scope().lookup(new LookupKey(i.type())); if (base == null) { - log.error(i.position(), ErrorKind.SYMBOL_NOT_FOUND, Joiner.on(".").join(i.type())); + log.error( + i.position(), ErrorKind.SYMBOL_NOT_FOUND, new ClassSymbol(Joiner.on("/").join(i.type()))); return null; } return new ImportScope() { diff --git a/java/com/google/turbine/binder/lookup/PackageScope.java b/java/com/google/turbine/binder/lookup/PackageScope.java new file mode 100644 index 0000000..695e802 --- /dev/null +++ b/java/com/google/turbine/binder/lookup/PackageScope.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.lookup; + +import com.google.common.collect.Iterables; +import com.google.turbine.binder.sym.ClassSymbol; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A scope that corresponds to a particular package, which supports iteration over its enclosed + * classes. + */ +public interface PackageScope extends Scope { + + /** Returns the top-level classes enclosed by this package. */ + Iterable<ClassSymbol> classes(); + + default PackageScope append(PackageScope next) { + return concat(this, next); + } + + static PackageScope concat(PackageScope base, PackageScope next) { + return new PackageScope() { + @Override + public Iterable<ClassSymbol> classes() { + return Iterables.concat(base.classes(), next.classes()); + } + + @Override + public @Nullable LookupResult lookup(LookupKey lookupKey) { + LookupResult result = base.lookup(lookupKey); + if (result != null) { + return result; + } + return next.lookup(lookupKey); + } + }; + } +} diff --git a/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java index 2454319..4ec05bc 100644 --- a/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java +++ b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java @@ -16,6 +16,8 @@ package com.google.turbine.binder.lookup; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.turbine.binder.sym.ClassSymbol; import java.util.HashMap; @@ -157,7 +159,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex { /** Returns a {@link Scope} that performs lookups in the given qualified package name. */ @Override - public Scope lookupPackage(ImmutableList<String> packagename) { + public PackageScope lookupPackage(Iterable<String> packagename) { Node curr = root; for (String bit : packagename) { curr = curr.lookup(bit); @@ -168,7 +170,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex { return new PackageIndex(curr); } - static class PackageIndex implements Scope { + static class PackageIndex implements PackageScope { private final Node node; @@ -184,5 +186,25 @@ public class SimpleTopLevelIndex implements TopLevelIndex { } return null; } + + private final Supplier<ImmutableList<ClassSymbol>> classes = + Suppliers.memoize( + new Supplier<ImmutableList<ClassSymbol>>() { + @Override + public ImmutableList<ClassSymbol> get() { + ImmutableList.Builder<ClassSymbol> result = ImmutableList.builder(); + for (Node child : node.children.values()) { + if (child.sym != null) { + result.add(child.sym); + } + } + return result.build(); + } + }); + + @Override + public Iterable<ClassSymbol> classes() { + return classes.get(); + } } } diff --git a/java/com/google/turbine/binder/lookup/TopLevelIndex.java b/java/com/google/turbine/binder/lookup/TopLevelIndex.java index 95782f5..a364119 100644 --- a/java/com/google/turbine/binder/lookup/TopLevelIndex.java +++ b/java/com/google/turbine/binder/lookup/TopLevelIndex.java @@ -16,7 +16,6 @@ package com.google.turbine.binder.lookup; -import com.google.common.collect.ImmutableList; /** * An index of canonical type names. @@ -36,5 +35,5 @@ public interface TopLevelIndex { Scope scope(); /** Returns a scope to look up members of the given package. */ - Scope lookupPackage(ImmutableList<String> packagename); + PackageScope lookupPackage(Iterable<String> packagename); } diff --git a/java/com/google/turbine/binder/sym/ClassSymbol.java b/java/com/google/turbine/binder/sym/ClassSymbol.java index 2adf15f..20513e7 100644 --- a/java/com/google/turbine/binder/sym/ClassSymbol.java +++ b/java/com/google/turbine/binder/sym/ClassSymbol.java @@ -33,6 +33,23 @@ public class ClassSymbol implements Symbol { public static final ClassSymbol STRING = new ClassSymbol("java/lang/String"); public static final ClassSymbol ENUM = new ClassSymbol("java/lang/Enum"); public static final ClassSymbol ANNOTATION = new ClassSymbol("java/lang/annotation/Annotation"); + public static final ClassSymbol INHERITED = new ClassSymbol("java/lang/annotation/Inherited"); + public static final ClassSymbol CLONEABLE = new ClassSymbol("java/lang/Cloneable"); + public static final ClassSymbol SERIALIZABLE = new ClassSymbol("java/io/Serializable"); + public static final ClassSymbol DEPRECATED = new ClassSymbol("java/lang/Deprecated"); + public static final ClassSymbol PROFILE_ANNOTATION = new ClassSymbol("jdk/Profile+Annotation"); + public static final ClassSymbol PROPRIETARY_ANNOTATION = + new ClassSymbol("sun/Proprietary+Annotation"); + public static final ClassSymbol ERROR = new ClassSymbol("<error>"); + + public static final ClassSymbol CHARACTER = new ClassSymbol("java/lang/Character"); + public static final ClassSymbol SHORT = new ClassSymbol("java/lang/Short"); + public static final ClassSymbol INTEGER = new ClassSymbol("java/lang/Integer"); + public static final ClassSymbol LONG = new ClassSymbol("java/lang/Long"); + public static final ClassSymbol FLOAT = new ClassSymbol("java/lang/Float"); + public static final ClassSymbol DOUBLE = new ClassSymbol("java/lang/Double"); + public static final ClassSymbol BOOLEAN = new ClassSymbol("java/lang/Boolean"); + public static final ClassSymbol BYTE = new ClassSymbol("java/lang/Byte"); private final String className; @@ -64,4 +81,17 @@ public class ClassSymbol implements Symbol { public Kind symKind() { return Kind.CLASS; } + + public String simpleName() { + return binaryName().substring(binaryName().lastIndexOf('/') + 1); + } + + public String packageName() { + int idx = binaryName().lastIndexOf('/'); + return idx != -1 ? binaryName().substring(0, idx) : ""; + } + + public PackageSymbol owner() { + return new PackageSymbol(packageName()); + } } diff --git a/java/com/google/turbine/binder/sym/FieldSymbol.java b/java/com/google/turbine/binder/sym/FieldSymbol.java index 21304e7..d6c3cbc 100644 --- a/java/com/google/turbine/binder/sym/FieldSymbol.java +++ b/java/com/google/turbine/binder/sym/FieldSymbol.java @@ -61,6 +61,6 @@ public class FieldSymbol implements Symbol { @Override public String toString() { - return owner + "#" + name; + return name; } } diff --git a/java/com/google/turbine/binder/sym/MethodSymbol.java b/java/com/google/turbine/binder/sym/MethodSymbol.java index b2050f4..f4b211d 100644 --- a/java/com/google/turbine/binder/sym/MethodSymbol.java +++ b/java/com/google/turbine/binder/sym/MethodSymbol.java @@ -22,10 +22,17 @@ import java.util.Objects; /** A method symbol. */ @Immutable public class MethodSymbol implements Symbol { + /** + * The index of the method in its enclosing element. Used to implement equals and hashCode, since + * methods aren't uniquely identified by their name and owner. + */ + private final int idx; + private final ClassSymbol owner; private final String name; - public MethodSymbol(ClassSymbol owner, String name) { + public MethodSymbol(int idx, ClassSymbol owner, String name) { + this.idx = idx; this.owner = owner; this.name = name; } @@ -56,7 +63,7 @@ public class MethodSymbol implements Symbol { return false; } MethodSymbol other = (MethodSymbol) obj; - return name().equals(other.name()) && owner().equals(other.owner()); + return name().equals(other.name()) && owner().equals(other.owner()) && idx == other.idx; } @Override diff --git a/java/com/google/turbine/binder/sym/PackageSymbol.java b/java/com/google/turbine/binder/sym/PackageSymbol.java new file mode 100644 index 0000000..8354a34 --- /dev/null +++ b/java/com/google/turbine/binder/sym/PackageSymbol.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 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.sym; + +import com.google.errorprone.annotations.Immutable; + +/** A package symbol. */ +@Immutable +public class PackageSymbol implements Symbol { + + final String binaryName; + + public PackageSymbol(String binaryName) { + this.binaryName = binaryName; + } + + @Override + public int hashCode() { + return binaryName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PackageSymbol && binaryName.equals(((PackageSymbol) obj).binaryName); + } + + @Override + public String toString() { + return binaryName.replace('/', '.'); + } + + @Override + public Kind symKind() { + return Kind.PACKAGE; + } + + public String binaryName() { + return binaryName; + } +} diff --git a/java/com/google/turbine/binder/sym/ParamSymbol.java b/java/com/google/turbine/binder/sym/ParamSymbol.java new file mode 100644 index 0000000..328658e --- /dev/null +++ b/java/com/google/turbine/binder/sym/ParamSymbol.java @@ -0,0 +1,66 @@ +/* + * 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.sym; + +import com.google.errorprone.annotations.Immutable; +import java.util.Objects; + +/** A parameter symbol. */ +@Immutable +public class ParamSymbol implements Symbol { + private final MethodSymbol owner; + private final String name; + + public ParamSymbol(MethodSymbol owner, String name) { + this.owner = owner; + this.name = name; + } + + /** The enclosing class. */ + public MethodSymbol owner() { + return owner; + } + + /** The parameter name. */ + public String name() { + return name; + } + + @Override + public Kind symKind() { + return Kind.PARAMETER; + } + + @Override + public int hashCode() { + return Objects.hash(name, owner); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ParamSymbol)) { + return false; + } + ParamSymbol other = (ParamSymbol) obj; + return name().equals(other.name()) && owner().equals(other.owner()); + } + + @Override + public String toString() { + return name; + } +} diff --git a/java/com/google/turbine/binder/sym/Symbol.java b/java/com/google/turbine/binder/sym/Symbol.java index b2a7723..bc142cb 100644 --- a/java/com/google/turbine/binder/sym/Symbol.java +++ b/java/com/google/turbine/binder/sym/Symbol.java @@ -27,7 +27,9 @@ public interface Symbol { TY_PARAM, METHOD, FIELD, - MODULE + PARAMETER, + MODULE, + PACKAGE } /** The symbol kind. */ diff --git a/java/com/google/turbine/binder/sym/TyVarSymbol.java b/java/com/google/turbine/binder/sym/TyVarSymbol.java index a73a79e..1ecec11 100644 --- a/java/com/google/turbine/binder/sym/TyVarSymbol.java +++ b/java/com/google/turbine/binder/sym/TyVarSymbol.java @@ -62,6 +62,6 @@ public class TyVarSymbol implements Symbol { @Override public String toString() { - return owner + "#" + name; + return name; } } diff --git a/java/com/google/turbine/bytecode/AnnotationWriter.java b/java/com/google/turbine/bytecode/AnnotationWriter.java index 868b548..b547971 100644 --- a/java/com/google/turbine/bytecode/AnnotationWriter.java +++ b/java/com/google/turbine/bytecode/AnnotationWriter.java @@ -20,8 +20,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.ByteArrayDataOutput; import com.google.turbine.bytecode.ClassFile.AnnotationInfo; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue; -import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.AnnotationValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ArrayValue; +import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineAnnotationValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineClassValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue; @@ -71,10 +71,8 @@ public class AnnotationWriter { writeArrayElementValue((ArrayValue) value); break; case ANNOTATION: - writeAnnotationElementValue((AnnotationValue) value); + writeAnnotationElementValue((ConstTurbineAnnotationValue) value); break; - default: - throw new AssertionError(value.kind()); } } @@ -136,7 +134,7 @@ public class AnnotationWriter { } } - private void writeAnnotationElementValue(AnnotationValue value) { + private void writeAnnotationElementValue(ConstTurbineAnnotationValue value) { output.writeByte('@'); writeAnnotation(value.annotation()); } @@ -178,8 +176,6 @@ public class AnnotationWriter { output.writeByte(typeParameterBoundTarget.typeParameterIndex()); output.writeByte(typeParameterBoundTarget.boundIndex()); break; - default: - throw new AssertionError(target.kind()); } } } diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java index 5e3ea95..c5ffd16 100644 --- a/java/com/google/turbine/bytecode/AttributeWriter.java +++ b/java/com/google/turbine/bytecode/AttributeWriter.java @@ -87,8 +87,6 @@ public class AttributeWriter { case MODULE: writeModule((Attribute.Module) attribute); break; - default: - throw new AssertionError(attribute.kind()); } } diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java index 54b6983..8ee2aac 100644 --- a/java/com/google/turbine/bytecode/ClassFile.java +++ b/java/com/google/turbine/bytecode/ClassFile.java @@ -368,7 +368,7 @@ public class ClassFile { public interface ElementValue { /** The value kind. */ - Kind kind(); + ElementValue.Kind kind(); /** Element value kinds. */ enum Kind { @@ -391,8 +391,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.ENUM; + public ElementValue.Kind kind() { + return ElementValue.Kind.ENUM; } /** The type of the enum. */ @@ -417,8 +417,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.CONST; + public ElementValue.Kind kind() { + return ElementValue.Kind.CONST; } /** The constant value. */ @@ -437,8 +437,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.ARRAY; + public ElementValue.Kind kind() { + return ElementValue.Kind.ARRAY; } /** The elements of the array. */ @@ -457,8 +457,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.CLASS; + public ElementValue.Kind kind() { + return ElementValue.Kind.CLASS; } /** The class name. */ @@ -468,17 +468,17 @@ public class ClassFile { } /** A nested annotation value. */ - class AnnotationValue implements ElementValue { + class ConstTurbineAnnotationValue implements ElementValue { private final AnnotationInfo annotation; - public AnnotationValue(AnnotationInfo annotation) { + public ConstTurbineAnnotationValue(AnnotationInfo annotation) { this.annotation = annotation; } @Override - public Kind kind() { - return Kind.ANNOTATION; + public ElementValue.Kind kind() { + return ElementValue.Kind.ANNOTATION; } /** The annotation. */ @@ -566,7 +566,7 @@ public class ClassFile { } /** Returns the target info kind. */ - public abstract Kind kind(); + public abstract Target.Kind kind(); } /** A JVMS 4.7.20.1 type_parameter_target. */ @@ -582,8 +582,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.TYPE_PARAMETER; + public Target.Kind kind() { + return Target.Kind.TYPE_PARAMETER; } } @@ -596,8 +596,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.SUPERTYPE; + public Target.Kind kind() { + return Target.Kind.SUPERTYPE; } public int index() { @@ -616,8 +616,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.TYPE_PARAMETER_BOUND; + public Target.Kind kind() { + return Target.Kind.TYPE_PARAMETER_BOUND; } public int typeParameterIndex() { @@ -633,8 +633,8 @@ public class ClassFile { public static final Target EMPTY_TARGET = new Target() { @Override - public Kind kind() { - return Kind.EMPTY; + public Target.Kind kind() { + return Target.Kind.EMPTY; } }; @@ -647,8 +647,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.FORMAL_PARAMETER; + public Target.Kind kind() { + return Target.Kind.FORMAL_PARAMETER; } public int index() { @@ -665,8 +665,8 @@ public class ClassFile { } @Override - public Kind kind() { - return Kind.THROWS; + public Target.Kind kind() { + return Target.Kind.THROWS; } public int index() { @@ -689,22 +689,22 @@ public class ClassFile { /** Adds an array type_path_kind entry. */ public TypePath array() { - return new TypePath(Kind.ARRAY, this); + return new TypePath(TypePath.Kind.ARRAY, this); } /** Adds a nested type type_path_kind entry. */ public TypePath nested() { - return new TypePath(Kind.NESTED, this); + return new TypePath(TypePath.Kind.NESTED, this); } /** Adds a wildcard bound type_path_kind entry. */ public TypePath wild() { - return new TypePath(Kind.WILDCARD_BOUND, this); + return new TypePath(TypePath.Kind.WILDCARD_BOUND, this); } /** Adds a type argument type_path_kind entry. */ public TypePath typeArgument(int idx) { - return new TypePath(idx, Kind.TYPE_ARGUMENT, this); + return new TypePath(idx, TypePath.Kind.TYPE_ARGUMENT, this); } /** A type_path_kind. */ @@ -722,15 +722,15 @@ public class ClassFile { } private final TypePath parent; - private final Kind kind; + private final TypePath.Kind kind; private final int index; - private TypePath(Kind kind, TypePath parent) { + private TypePath(TypePath.Kind kind, TypePath parent) { // JVMS 4.7.20.2: type_argument_index is 0 if the bound kind is not TYPE_ARGUMENT this(0, kind, parent); } - private TypePath(int index, Kind kind, TypePath parent) { + private TypePath(int index, TypePath.Kind kind, TypePath parent) { this.index = index; this.kind = kind; this.parent = parent; diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java index a894997..9c79b42 100644 --- a/java/com/google/turbine/bytecode/ClassReader.java +++ b/java/com/google/turbine/bytecode/ClassReader.java @@ -19,9 +19,10 @@ package com.google.turbine.bytecode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.FormatMethod; import com.google.turbine.bytecode.ClassFile.AnnotationInfo; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue; -import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.AnnotationValue; +import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineAnnotationValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineClassValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue; @@ -33,6 +34,7 @@ import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo; import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo; import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo; import com.google.turbine.model.Const; +import com.google.turbine.model.TurbineFlag; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; @@ -59,6 +61,7 @@ public class ClassReader { this.reader = new ByteReader(bytes, 0); } + @FormatMethod @CheckReturnValue Error error(String format, Object... args) { StringBuilder sb = new StringBuilder(); @@ -72,7 +75,7 @@ public class ClassReader { private ClassFile read() { int magic = reader.u4(); if (magic != 0xcafebabe) { - throw error("bad magic: 0x%x", path, magic); + throw error("bad magic: 0x%x", magic); } int minorVersion = reader.u2(); int majorVersion = reader.u2(); @@ -212,6 +215,11 @@ public class ClassReader { for (int i = 0; i < numParameters; i++) { String name = constantPool.utf8(reader.u2()); int access = reader.u2(); + if ((access & (TurbineFlag.ACC_SYNTHETIC | TurbineFlag.ACC_MANDATED)) != 0) { + // ExecutableElement#getParameters doesn't expect synthetic or mandated + // parameters + continue; + } parameters.add(new ParameterInfo(name, access)); } } @@ -321,17 +329,22 @@ public class ClassReader { int tag = reader.u1(); switch (tag) { case 'B': + return new ConstValue(readConst(constantPool).asByte()); case 'C': + return new ConstValue(readConst(constantPool).asChar()); + case 'S': + return new ConstValue(readConst(constantPool).asShort()); case 'D': case 'F': case 'I': case 'J': - case 'S': - case 'Z': case 's': + return new ConstValue(readConst(constantPool)); + case 'Z': { - int constValueIndex = reader.u2(); - return new ConstValue(constantPool.constant(constValueIndex)); + Const.Value value = readConst(constantPool); + // boolean constants are encoded as integers + return new ConstValue(new Const.BooleanValue(value.asInteger().value() != 0)); } case 'e': { @@ -348,7 +361,7 @@ public class ClassReader { return new ConstTurbineClassValue(className); } case '@': - return new AnnotationValue(readAnnotation(constantPool)); + return new ConstTurbineAnnotationValue(readAnnotation(constantPool)); case '[': { int numValues = reader.u2(); @@ -363,6 +376,11 @@ public class ClassReader { throw new AssertionError(String.format("bad tag value %c", tag)); } + private Const.Value readConst(ConstantPoolReader constantPool) { + int constValueIndex = reader.u2(); + return constantPool.constant(constValueIndex); + } + /** Reads JVMS 4.6 method_infos. */ private List<ClassFile.MethodInfo> readMethods(ConstantPoolReader constantPool) { int methodsCount = reader.u2(); @@ -415,6 +433,10 @@ public class ClassReader { for (ImmutableList.Builder<AnnotationInfo> x : parameterAnnotationsBuilder) { parameterAnnotations.add(x.build()); } + if ((accessFlags & (TurbineFlag.ACC_BRIDGE | TurbineFlag.ACC_SYNTHETIC)) != 0) { + // javac doesn't enter synthetic members for reasons 'lost to history', so we don't either + continue; + } methods.add( new ClassFile.MethodInfo( accessFlags, @@ -454,6 +476,8 @@ public class ClassReader { String desc = constantPool.utf8(descriptorIndex); int attributesCount = reader.u2(); Const.Value value = null; + ImmutableList.Builder<ClassFile.AnnotationInfo> annotations = ImmutableList.builder(); + String signature = null; for (int j = 0; j < attributesCount; j++) { String attributeName = constantPool.utf8(reader.u2()); switch (attributeName) { @@ -461,6 +485,13 @@ public class ClassReader { reader.u4(); // length value = constantPool.constant(reader.u2()); break; + case "RuntimeInvisibleAnnotations": + case "RuntimeVisibleAnnotations": + readAnnotations(annotations, constantPool); + break; + case "Signature": + signature = readSignature(constantPool); + break; default: reader.skip(reader.u4()); break; @@ -471,10 +502,10 @@ public class ClassReader { accessFlags, name, desc, - /*signature*/ null, + signature, value, - ImmutableList.of(), - ImmutableList.of())); + annotations.build(), + /* typeAnnotations= */ ImmutableList.of())); } return fields; } diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java index 4a89ec8..c3490ca 100644 --- a/java/com/google/turbine/bytecode/ClassWriter.java +++ b/java/com/google/turbine/bytecode/ClassWriter.java @@ -110,8 +110,6 @@ public class ClassWriter { case UTF8: output.writeUTF(((StringValue) value).value()); break; - default: - throw new AssertionError(e.kind()); } } } diff --git a/java/com/google/turbine/bytecode/ConstantPool.java b/java/com/google/turbine/bytecode/ConstantPool.java index b423cfc..673102c 100644 --- a/java/com/google/turbine/bytecode/ConstantPool.java +++ b/java/com/google/turbine/bytecode/ConstantPool.java @@ -66,9 +66,8 @@ public class ConstantPool { // "In retrospect, making 8-byte constants take two constant pool entries // was a poor choice." -- JVMS 4.4.5 return 2; - default: - throw new AssertionError(kind); } + throw new AssertionError(kind); } /** A constant pool entry. */ diff --git a/java/com/google/turbine/bytecode/sig/SigWriter.java b/java/com/google/turbine/bytecode/sig/SigWriter.java index dab4f47..3711186 100644 --- a/java/com/google/turbine/bytecode/sig/SigWriter.java +++ b/java/com/google/turbine/bytecode/sig/SigWriter.java @@ -113,8 +113,6 @@ public class SigWriter { sb.append('+'); writeTySig(((UpperBoundTySig) sig).bound()); break; - default: - throw new AssertionError(sig.kind()); } } @@ -198,8 +196,6 @@ public class SigWriter { case WILD_TY_SIG: wildTyArgSig((WildTySig) p); break; - default: - throw new AssertionError(p.kind()); } } diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java index f76efe7..92193e8 100644 --- a/java/com/google/turbine/deps/Dependencies.java +++ b/java/com/google/turbine/deps/Dependencies.java @@ -22,14 +22,22 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPath; +import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; +import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; +import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; import com.google.turbine.binder.bytecode.BytecodeBoundClass; import com.google.turbine.binder.env.CompoundEnv; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.lower.Lower.Lowered; +import com.google.turbine.model.Const; import com.google.turbine.proto.DepsProto; +import com.google.turbine.type.AnnoInfo; +import com.google.turbine.type.Type; import java.io.BufferedInputStream; import java.io.IOError; import java.io.IOException; @@ -82,13 +90,59 @@ public class Dependencies { Env<ClassSymbol, TypeBoundClass> env = CompoundEnv.<ClassSymbol, TypeBoundClass>of(new SimpleEnv<>(bound.units())) .append(bound.classPathEnv()); - Set<ClassSymbol> closure = new LinkedHashSet<>(); + Set<ClassSymbol> closure = new LinkedHashSet<>(lowered.symbols()); for (ClassSymbol sym : lowered.symbols()) { - addSuperTypes(closure, env, sym); + TypeBoundClass info = env.get(sym); + addAnnotations(closure, info.annotations()); + for (MethodInfo method : info.methods()) { + addAnnotations(closure, method.annotations()); + } + for (FieldInfo field : info.fields()) { + addAnnotations(closure, field.annotations()); + } + addSuperTypes(closure, env, info); } return closure; } + private static void addAnnotations( + Set<ClassSymbol> closure, ImmutableList<AnnoInfo> annotations) { + for (AnnoInfo annoInfo : annotations) { + addAnnotation(closure, annoInfo); + } + } + + private static void addAnnotation(Set<ClassSymbol> closure, AnnoInfo annoInfo) { + closure.add(annoInfo.sym()); + for (Const c : annoInfo.values().values()) { + addConst(closure, c); + } + } + + private static void addConst(Set<ClassSymbol> closure, Const c) { + switch (c.kind()) { + case ARRAY: + for (Const e : ((Const.ArrayInitValue) c).elements()) { + addConst(closure, e); + } + break; + case CLASS_LITERAL: + Type t = ((TurbineClassValue) c).type(); + if (t.tyKind() == Type.TyKind.CLASS_TY) { + closure.add(((Type.ClassTy) t).sym()); + } + break; + case ENUM_CONSTANT: + closure.add(((EnumConstantValue) c).sym().owner()); + break; + case ANNOTATION: + addAnnotation(closure, ((TurbineAnnotationValue) c).info()); + break; + case PRIMITIVE: + // continue below + } + } + private static void addSuperTypes( Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) { if (!closure.add(sym)) { @@ -98,6 +152,11 @@ public class Dependencies { if (info == null) { return; } + addSuperTypes(closure, env, info); + } + + private static void addSuperTypes( + Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass info) { if (info.superclass() != null) { addSuperTypes(closure, env, info.superclass()); } @@ -109,11 +168,11 @@ public class Dependencies { private static void addPackageInfos(Set<ClassSymbol> closure, BindingResult bound) { Set<ClassSymbol> packages = new LinkedHashSet<>(); for (ClassSymbol sym : closure) { - int idx = sym.binaryName().lastIndexOf('/'); - if (idx == -1) { + String packageName = sym.packageName(); + if (packageName.isEmpty()) { continue; } - packages.add(new ClassSymbol(sym.binaryName().substring(0, idx) + "/package-info")); + packages.add(new ClassSymbol(packageName + "/package-info")); } for (ClassSymbol pkg : packages) { if (bound.classPathEnv().get(pkg) != null) { @@ -134,6 +193,7 @@ public class Dependencies { ImmutableList<String> depsArtifacts) { if (directJars.isEmpty()) { // the compilation doesn't support strict deps (e.g. proto libraries) + // TODO(cushon): make this a usage error return transitiveClasspath; } Set<String> reduced = new HashSet<>(directJars); @@ -153,8 +213,6 @@ public class Dependencies { case INCOMPLETE: case UNUSED: break; - default: - throw new AssertionError(dep.getKind()); } } } diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java index f9a29a1..8b0d44d 100644 --- a/java/com/google/turbine/deps/Transitive.java +++ b/java/com/google/turbine/deps/Transitive.java @@ -17,7 +17,6 @@ package com.google.turbine.deps; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPath; @@ -65,7 +64,7 @@ public class Transitive { */ public static ClassFile trimClass(ClassFile cf) { // drop non-constant fields - Builder<FieldInfo> fields = ImmutableList.builder(); + ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder(); for (FieldInfo f : cf.fields()) { if (f.value() != null) { fields.add(f); @@ -75,7 +74,7 @@ public class Transitive { // To do this for javac, we would have to scan all remaining signatures and preserve attributes // for reachable inner classes, but turbine only needs the attributes for the immediate // children or parent of the current class. - Builder<InnerClass> innerClasses = ImmutableList.builder(); + ImmutableList.Builder<InnerClass> innerClasses = ImmutableList.builder(); for (InnerClass i : cf.innerClasses()) { if (i.innerClass().equals(cf.name()) || i.outerClass().equals(cf.name())) { innerClasses.add(i); diff --git a/java/com/google/turbine/diag/SourceFile.java b/java/com/google/turbine/diag/SourceFile.java index cb4133b..3868252 100644 --- a/java/com/google/turbine/diag/SourceFile.java +++ b/java/com/google/turbine/diag/SourceFile.java @@ -16,12 +16,25 @@ package com.google.turbine.diag; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import java.util.Objects; + /** A source file. */ public class SourceFile { private final String path; private final String source; + private final Supplier<LineMap> lineMap = + Suppliers.memoize( + new Supplier<LineMap>() { + @Override + public LineMap get() { + return LineMap.create(source); + } + }); + public SourceFile(String path, String source) { this.path = path; this.source = source; @@ -36,4 +49,22 @@ public class SourceFile { public String source() { return source; } + + LineMap lineMap() { + return lineMap.get(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SourceFile)) { + return false; + } + SourceFile that = (SourceFile) obj; + return Objects.equals(path, that.path) && source.equals(that.source); + } + + @Override + public int hashCode() { + return path != null ? path.hashCode() : 0; + } } diff --git a/java/com/google/turbine/diag/TurbineDiagnostic.java b/java/com/google/turbine/diag/TurbineDiagnostic.java index 0404a8e..ccbaa7f 100644 --- a/java/com/google/turbine/diag/TurbineDiagnostic.java +++ b/java/com/google/turbine/diag/TurbineDiagnostic.java @@ -16,7 +16,6 @@ package com.google.turbine.diag; -import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static java.util.Objects.requireNonNull; @@ -27,18 +26,29 @@ import com.google.common.collect.ImmutableList; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.diag.TurbineError.ErrorKind; import java.util.Objects; +import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.Nullable; /** A compilation error. */ public class TurbineDiagnostic { + private final Diagnostic.Kind severity; private final ErrorKind kind; - private final String diagnostic; private final ImmutableList<Object> args; + private final @Nullable SourceFile source; + private final int position; - private TurbineDiagnostic(ErrorKind kind, String diagnostic, ImmutableList<Object> args) { + private TurbineDiagnostic( + Diagnostic.Kind severity, + ErrorKind kind, + ImmutableList<Object> args, + @Nullable SourceFile source, + int position) { + this.severity = requireNonNull(severity); this.kind = requireNonNull(kind); - this.diagnostic = requireNonNull(diagnostic); this.args = requireNonNull(args); + this.source = source; + this.position = position; } /** The diagnostic kind. */ @@ -46,9 +56,28 @@ public class TurbineDiagnostic { return kind; } + /** + * The diagnostic severity (error, warning, ...). Turbine only produces errors, non-error + * diagnostics are only ever created by annotation processors. + */ + public Diagnostic.Kind severity() { + return severity; + } + /** The diagnostic message. */ public String diagnostic() { - return diagnostic; + StringBuilder sb = new StringBuilder(path()); + if (line() != -1) { + sb.append(':').append(line()); + } + sb.append(": error: "); + sb.append(message().trim()).append(System.lineSeparator()); + if (line() != -1 && column() != -1) { + sb.append(CharMatcher.breakingWhitespace().trimTrailingFrom(source.lineMap().line(position))) + .append(System.lineSeparator()); + sb.append(Strings.repeat(" ", column() - 1)).append('^'); + } + return sb.toString(); } /** The diagnostic arguments. */ @@ -57,20 +86,28 @@ public class TurbineDiagnostic { } private static TurbineDiagnostic create( - ErrorKind kind, String diagnostic, ImmutableList<Object> args) { + Diagnostic.Kind severity, + ErrorKind kind, + ImmutableList<Object> args, + SourceFile source, + int position) { switch (kind) { case SYMBOL_NOT_FOUND: { checkArgument( args.size() == 1 && getOnlyElement(args) instanceof ClassSymbol, - "diagnostic (%s) has invalid argument args %s", - diagnostic, + "diagnostic (%s) has invalid argument %s", + kind, args); break; } default: // fall out } - return new TurbineDiagnostic(kind, diagnostic, args); + return new TurbineDiagnostic(severity, kind, args, source, position); + } + + public static TurbineDiagnostic format(Diagnostic.Kind severity, ErrorKind kind, String message) { + return create(severity, kind, ImmutableList.of(message), null, -1); } /** @@ -81,10 +118,7 @@ public class TurbineDiagnostic { * @param args format args */ public static TurbineDiagnostic format(SourceFile source, ErrorKind kind, Object... args) { - String path = firstNonNull(source.path(), "<>"); - String message = kind.format(args); - String diagnostic = path + ": error: " + message.trim() + System.lineSeparator(); - return create(kind, diagnostic, ImmutableList.copyOf(args)); + return create(Diagnostic.Kind.ERROR, kind, ImmutableList.copyOf(args), source, -1); } /** @@ -95,26 +129,13 @@ public class TurbineDiagnostic { * @param args format args */ public static TurbineDiagnostic format( - SourceFile source, int position, ErrorKind kind, Object... args) { - String path = firstNonNull(source.path(), "<>"); - LineMap lineMap = LineMap.create(source.source()); - int lineNumber = lineMap.lineNumber(position); - int column = lineMap.column(position); - String message = kind.format(args); - - StringBuilder sb = new StringBuilder(path).append(":"); - sb.append(lineNumber).append(": error: "); - sb.append(message.trim()).append(System.lineSeparator()); - sb.append(CharMatcher.breakingWhitespace().trimTrailingFrom(lineMap.line(position))) - .append(System.lineSeparator()); - sb.append(Strings.repeat(" ", column)).append('^'); - String diagnostic = sb.toString(); - return create(kind, diagnostic, ImmutableList.copyOf(args)); + Diagnostic.Kind severity, SourceFile source, int position, ErrorKind kind, Object... args) { + return create(severity, kind, ImmutableList.copyOf(args), source, position); } @Override public int hashCode() { - return Objects.hash(diagnostic, kind); + return Objects.hash(kind, source, position); } @Override @@ -123,6 +144,26 @@ public class TurbineDiagnostic { return false; } TurbineDiagnostic that = (TurbineDiagnostic) obj; - return diagnostic.equals(that.diagnostic) && kind.equals(that.kind); + return severity.equals(that.severity) + && kind.equals(that.kind) + && args.equals(that.args) + && Objects.equals(source, that.source) + && position == that.position; + } + + public String path() { + return source != null && source.path() != null ? source.path() : "<>"; + } + + public int line() { + return position != -1 ? source.lineMap().lineNumber(position) : -1; + } + + public int column() { + return position != -1 ? source.lineMap().column(position) + 1 : -1; + } + + public String message() { + return kind.format(args.toArray()); } } diff --git a/java/com/google/turbine/diag/TurbineError.java b/java/com/google/turbine/diag/TurbineError.java index b8b2a54..39244b5 100644 --- a/java/com/google/turbine/diag/TurbineError.java +++ b/java/com/google/turbine/diag/TurbineError.java @@ -19,6 +19,7 @@ package com.google.turbine.diag; import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableList; +import javax.tools.Diagnostic; /** A compilation error. */ public class TurbineError extends Error { @@ -31,6 +32,7 @@ public class TurbineError extends Error { UNTERMINATED_STRING("unterminated string literal"), UNTERMINATED_CHARACTER_LITERAL("unterminated char literal"), UNTERMINATED_EXPRESSION("unterminated expression, expected ';' not found"), + INVALID_UNICODE("illegal unicode escape"), EMPTY_CHARACTER_LITERAL("empty char literal"), EXPECTED_TOKEN("expected token %s"), INVALID_LITERAL("invalid literal: %s"), @@ -42,11 +44,14 @@ public class TurbineError extends Error { INVALID_ANNOTATION_ARGUMENT("invalid annotation argument"), CANNOT_RESOLVE("could not resolve %s"), EXPRESSION_ERROR("could not evaluate constant expression"), + OPERAND_TYPE("bad operand type %s"), CYCLIC_HIERARCHY("cycle in class hierarchy: %s"), NOT_AN_ANNOTATION("%s is not an annotation"), NONREPEATABLE_ANNOTATION("%s is not @Repeatable"), DUPLICATE_DECLARATION("duplicate declaration of %s"), - BAD_MODULE_INFO("unexpected declaration found in module-info"); + BAD_MODULE_INFO("unexpected declaration found in module-info"), + UNCLOSED_COMMENT("unclosed comment"), + PROC("%s"); private final String message; @@ -80,16 +85,21 @@ public class TurbineError extends Error { public static TurbineError format( SourceFile source, int position, ErrorKind kind, Object... args) { return new TurbineError( - ImmutableList.of(TurbineDiagnostic.format(source, position, kind, args))); + ImmutableList.of( + TurbineDiagnostic.format(Diagnostic.Kind.ERROR, source, position, kind, args))); } private final ImmutableList<TurbineDiagnostic> diagnostics; public TurbineError(ImmutableList<TurbineDiagnostic> diagnostics) { - super(diagnostics.stream().map(d -> d.diagnostic()).collect(joining("\n"))); this.diagnostics = diagnostics; } + @Override + public String getMessage() { + return diagnostics.stream().map(d -> d.diagnostic()).collect(joining(System.lineSeparator())); + } + public ImmutableList<TurbineDiagnostic> diagnostics() { return diagnostics; } diff --git a/java/com/google/turbine/diag/TurbineLog.java b/java/com/google/turbine/diag/TurbineLog.java index fd8fc38..b336e25 100644 --- a/java/com/google/turbine/diag/TurbineLog.java +++ b/java/com/google/turbine/diag/TurbineLog.java @@ -18,8 +18,10 @@ package com.google.turbine.diag; import com.google.common.collect.ImmutableList; import com.google.turbine.diag.TurbineError.ErrorKind; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; +import javax.tools.Diagnostic; /** A log that collects diagnostics. */ public class TurbineLog { @@ -31,11 +33,51 @@ public class TurbineLog { } public void maybeThrow() { - if (!errors.isEmpty()) { + if (anyErrors()) { throw new TurbineError(ImmutableList.copyOf(errors)); } } + private boolean anyErrors() { + for (TurbineDiagnostic error : errors) { + if (error.severity().equals(Diagnostic.Kind.ERROR)) { + return true; + } + } + return false; + } + + /** + * Returns true if a non-deferrable error was raised during annotation processing, i.e. an error + * reported by an annotation processor. + * + * <p>Errors reported by turbine (e.g. missing symbols) are non-fatal, since they may be fixed by + * code generated in later processing rounds. + */ + public boolean errorRaised() { + for (TurbineDiagnostic error : errors) { + if (error.kind().equals(ErrorKind.PROC) && error.severity().equals(Diagnostic.Kind.ERROR)) { + return true; + } + } + return false; + } + + /** Reset the log between annotation processing rounds. */ + public void clear() { + Iterator<TurbineDiagnostic> it = errors.iterator(); + while (it.hasNext()) { + if (it.next().severity().equals(Diagnostic.Kind.ERROR)) { + it.remove(); + } + } + } + + /** Reports an annotation processing diagnostic with no position information. */ + public void diagnostic(Diagnostic.Kind severity, String message) { + errors.add(TurbineDiagnostic.format(severity, ErrorKind.PROC, message)); + } + /** A log for a specific source file. */ public class TurbineLogWithSource { @@ -45,12 +87,12 @@ public class TurbineLog { this.source = source; } - public void error(ErrorKind kind, Object... args) { - errors.add(TurbineDiagnostic.format(source, kind, args)); + public void diagnostic(Diagnostic.Kind severity, int position, ErrorKind kind, Object... args) { + errors.add(TurbineDiagnostic.format(severity, source, position, kind, args)); } public void error(int position, ErrorKind kind, Object... args) { - errors.add(TurbineDiagnostic.format(source, position, kind, args)); + diagnostic(Diagnostic.Kind.ERROR, position, kind, args); } } } diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java index 16447ab..0f7bb90 100644 --- a/java/com/google/turbine/lower/Lower.java +++ b/java/com/google/turbine/lower/Lower.java @@ -21,10 +21,8 @@ import static com.google.turbine.binder.DisambiguateTypeAnnotations.groupRepeate import com.google.common.base.Function; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.turbine.binder.bound.AnnotationValue; import com.google.turbine.binder.bound.EnumConstantValue; import com.google.turbine.binder.bound.ModuleInfo.ExportInfo; import com.google.turbine.binder.bound.ModuleInfo.OpenInfo; @@ -33,6 +31,7 @@ import com.google.turbine.binder.bound.ModuleInfo.RequireInfo; import com.google.turbine.binder.bound.ModuleInfo.UseInfo; import com.google.turbine.binder.bound.SourceModuleInfo; import com.google.turbine.binder.bound.SourceTypeBoundClass; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; @@ -264,10 +263,10 @@ public class Lower { ImmutableList<AnnotationInfo> annotations = lowerAnnotations(info.annotations()); - ImmutableList<ClassFile.InnerClass> inners = collectInnerClasses(info.source(), sym, info); - ImmutableList<TypeAnnotationInfo> typeAnnotations = classTypeAnnotations(info); + ImmutableList<ClassFile.InnerClass> inners = collectInnerClasses(info.source(), sym, info); + ClassFile classfile = new ClassFile( access, @@ -525,9 +524,8 @@ public class Lower { return true; case SOURCE: return null; - default: - throw new AssertionError(retention); } + throw new AssertionError(retention); } private ImmutableMap<String, ElementValue> annotationValues(ImmutableMap<String, Const> values) { @@ -563,12 +561,12 @@ public class Lower { } case ANNOTATION: { - AnnotationValue annotationValue = (AnnotationValue) value; + TurbineAnnotationValue annotationValue = (TurbineAnnotationValue) value; Boolean visible = isVisible(annotationValue.sym()); if (visible == null) { visible = true; } - return new ElementValue.AnnotationValue( + return new ElementValue.ConstTurbineAnnotationValue( new AnnotationInfo( sig.objectType(annotationValue.sym()), visible, @@ -576,9 +574,8 @@ public class Lower { } case PRIMITIVE: return new ElementValue.ConstValue((Const.Value) value); - default: - throw new AssertionError(value.kind()); } + throw new AssertionError(value.kind()); } /** Lower type annotations in a class declaration's signature. */ @@ -656,7 +653,7 @@ public class Lower { * or on bounds. */ private void typeParameterAnnotations( - Builder<TypeAnnotationInfo> result, + ImmutableList.Builder<TypeAnnotationInfo> result, Iterable<TyVarInfo> typeParameters, TargetType targetType, TargetType boundTargetType) { @@ -675,7 +672,7 @@ public class Lower { info)); } int boundIndex = 0; - for (Type i : p.bound().bounds()) { + for (Type i : p.upperBound().bounds()) { if (boundIndex == 0 && isInterface(i, env)) { // super class bound index is always 0; interface bounds start at 1 boundIndex++; @@ -696,7 +693,10 @@ public class Lower { } private void lowerTypeAnnotations( - Builder<TypeAnnotationInfo> result, Type type, TargetType targetType, Target target) { + ImmutableList.Builder<TypeAnnotationInfo> result, + Type type, + TargetType targetType, + Target target) { new LowerTypeAnnotations(result, targetType, target) .lowerTypeAnnotations(type, TypePath.root()); } @@ -707,7 +707,7 @@ public class Lower { private final Target target; public LowerTypeAnnotations( - Builder<TypeAnnotationInfo> result, TargetType targetType, Target target) { + ImmutableList.Builder<TypeAnnotationInfo> result, TargetType targetType, Target target) { this.result = result; this.targetType = targetType; this.target = target; @@ -764,8 +764,6 @@ public class Lower { lowerTypeAnnotations(type.annotations(), path); lowerTypeAnnotations(type.bound(), path.wild()); break; - default: - throw new AssertionError(type.boundKind()); } } diff --git a/java/com/google/turbine/lower/LowerSignature.java b/java/com/google/turbine/lower/LowerSignature.java index fe9b912..13a7b9f 100644 --- a/java/com/google/turbine/lower/LowerSignature.java +++ b/java/com/google/turbine/lower/LowerSignature.java @@ -92,17 +92,8 @@ public class LowerSignature { while (curr.targs().isEmpty() && it.hasNext()) { curr = it.next(); } - String pkg; - String name; - int idx = curr.sym().binaryName().lastIndexOf('/'); - if (idx == -1) { - pkg = ""; - name = curr.sym().binaryName(); - } else { - pkg = curr.sym().binaryName().substring(0, idx); - name = curr.sym().binaryName().substring(idx + 1); - } - classes.add(new Sig.SimpleClassTySig(name, tyArgSigs(curr))); + String pkg = curr.sym().packageName(); + classes.add(new Sig.SimpleClassTySig(curr.sym().simpleName(), tyArgSigs(curr))); while (it.hasNext()) { SimpleClassTy outer = curr; curr = it.next(); @@ -128,9 +119,8 @@ public class LowerSignature { return new UpperBoundTySig(signature(((Type.WildUpperBoundedTy) ty).bound())); case LOWER: return new LowerBoundTySig(signature(((Type.WildLowerBoundedTy) ty).bound())); - default: - throw new AssertionError(ty.boundKind()); } + throw new AssertionError(ty.boundKind()); } /** @@ -284,13 +274,13 @@ public class LowerSignature { String identifier = sym.name(); Sig.TySig cbound = null; ImmutableList.Builder<Sig.TySig> ibounds = ImmutableList.builder(); - if (info.bound().bounds().isEmpty()) { + if (info.upperBound().bounds().isEmpty()) { cbound = new ClassTySig( "java/lang", ImmutableList.of(new SimpleClassTySig("Object", ImmutableList.of()))); } else { boolean first = true; - for (Type bound : info.bound().bounds()) { + for (Type bound : info.upperBound().bounds()) { TySig sig = signature(bound); if (first) { if (!isInterface(bound, env)) { diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java index 34421e1..1e60ae6 100644 --- a/java/com/google/turbine/main/Main.java +++ b/java/com/google/turbine/main/Main.java @@ -19,15 +19,21 @@ package com.google.turbine.main; import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.hash.Hashing; import com.google.common.io.MoreFiles; import com.google.turbine.binder.Binder; import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.Binder.Statistics; import com.google.turbine.binder.ClassPath; import com.google.turbine.binder.ClassPathBinder; import com.google.turbine.binder.CtSymClassBinder; import com.google.turbine.binder.JimageClassBinder; +import com.google.turbine.binder.Processing; +import com.google.turbine.binder.bound.SourceTypeBoundClass; +import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.deps.Dependencies; import com.google.turbine.deps.Transitive; import com.google.turbine.diag.SourceFile; @@ -35,9 +41,12 @@ import com.google.turbine.diag.TurbineError; import com.google.turbine.lower.Lower; import com.google.turbine.lower.Lower.Lowered; import com.google.turbine.options.TurbineOptions; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; import com.google.turbine.options.TurbineOptionsParser; import com.google.turbine.parse.Parser; import com.google.turbine.proto.DepsProto; +import com.google.turbine.proto.ManifestProto; +import com.google.turbine.proto.ManifestProto.CompilationUnit; import com.google.turbine.tree.Tree.CompUnit; import com.google.turbine.zip.Zip; import java.io.BufferedOutputStream; @@ -75,7 +84,8 @@ public class Main { public static void main(String[] args) throws IOException { boolean ok; try { - ok = compile(args); + compile(args); + ok = true; } catch (TurbineError | UsageException e) { System.err.println(e.getMessage()); ok = false; @@ -86,56 +96,182 @@ public class Main { System.exit(ok ? 0 : 1); } - public static boolean compile(String[] args) throws IOException { - TurbineOptions options = TurbineOptionsParser.parse(Arrays.asList(args)); - return compile(options); + /** The result of a turbine invocation. */ + @AutoValue + public abstract static class Result { + /** Returns {@code true} if transitive classpath fallback occurred. */ + public abstract boolean transitiveClasspathFallback(); + + /** The length of the transitive classpath. */ + public abstract int transitiveClasspathLength(); + + /** + * The length of the reduced classpath, or {@link #transitiveClasspathLength} if classpath + * reduction is not supported. + */ + public abstract int reducedClasspathLength(); + + public abstract Statistics processorStatistics(); + + static Result create( + boolean transitiveClasspathFallback, + int transitiveClasspathLength, + int reducedClasspathLength, + Statistics processorStatistics) { + return new AutoValue_Main_Result( + transitiveClasspathFallback, + transitiveClasspathLength, + reducedClasspathLength, + processorStatistics); + } + } + + public static void compile(String[] args) throws IOException { + compile(TurbineOptionsParser.parse(Arrays.asList(args))); } - public static boolean compile(TurbineOptions options) throws IOException { + public static Result compile(TurbineOptions options) throws IOException { usage(options); ImmutableList<CompUnit> units = parseAll(options); ClassPath bootclasspath = bootclasspath(options); - Collection<String> reducedClasspath = - Dependencies.reduceClasspath( - options.classPath(), options.directJars(), options.depsArtifacts()); - ClassPath classpath = ClassPathBinder.bindClasspath(toPaths(reducedClasspath)); + BindingResult bound; + ReducedClasspathMode reducedClasspathMode = options.reducedClasspathMode(); + if (reducedClasspathMode == ReducedClasspathMode.JAVABUILDER_REDUCED + && options.directJars().isEmpty()) { + // the compilation doesn't support reduced classpaths + // TODO(cushon): make this a usage error, see TODO in Dependencies.reduceClasspath + reducedClasspathMode = ReducedClasspathMode.NONE; + } + boolean transitiveClasspathFallback = false; + ImmutableList<String> classPath = options.classPath(); + int transitiveClasspathLength = classPath.size(); + int reducedClasspathLength = classPath.size(); + switch (reducedClasspathMode) { + case NONE: + bound = bind(options, units, bootclasspath, classPath); + break; + case BAZEL_FALLBACK: + reducedClasspathLength = options.reducedClasspathLength(); + bound = bind(options, units, bootclasspath, classPath); + transitiveClasspathFallback = true; + break; + case JAVABUILDER_REDUCED: + Collection<String> reducedClasspath = + Dependencies.reduceClasspath(classPath, options.directJars(), options.depsArtifacts()); + reducedClasspathLength = reducedClasspath.size(); + try { + bound = bind(options, units, bootclasspath, reducedClasspath); + } catch (TurbineError e) { + bound = fallback(options, units, bootclasspath, classPath); + transitiveClasspathFallback = true; + } + break; + case BAZEL_REDUCED: + transitiveClasspathLength = options.fullClasspathLength(); + try { + bound = bind(options, units, bootclasspath, classPath); + } catch (TurbineError e) { + writeJdepsForFallback(options); + return Result.create( + /* transitiveClasspathFallback= */ true, + /* transitiveClasspathLength= */ transitiveClasspathLength, + /* reducedClasspathLength= */ reducedClasspathLength, + Statistics.empty()); + } + break; + default: + throw new AssertionError(reducedClasspathMode); + } - BindingResult bound = - Binder.bind(units, classpath, bootclasspath, /* moduleVersion=*/ Optional.empty()); + if (options.outputDeps().isPresent() + || options.output().isPresent() + || options.outputManifest().isPresent()) { + // TODO(cushon): parallelize + Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()); - // TODO(cushon): parallelize - Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()); + if (options.outputDeps().isPresent()) { + DepsProto.Dependencies deps = + Dependencies.collectDeps(options.targetLabel(), bootclasspath, bound, lowered); + try (OutputStream os = + new BufferedOutputStream( + Files.newOutputStream(Paths.get(options.outputDeps().get())))) { + deps.writeTo(os); + } + } + if (options.output().isPresent()) { + Map<String, byte[]> transitive = Transitive.collectDeps(bootclasspath, bound); + writeOutput(options, bound.generatedClasses(), lowered.bytes(), transitive); + } + if (options.outputManifest().isPresent()) { + writeManifestProto(options, bound.units(), bound.generatedSources()); + } + } - Map<String, byte[]> transitive = Transitive.collectDeps(bootclasspath, bound); + writeSources(options, bound.generatedSources()); + writeResources(options, bound.generatedClasses()); + return Result.create( + /* transitiveClasspathFallback= */ transitiveClasspathFallback, + /* transitiveClasspathLength= */ transitiveClasspathLength, + /* reducedClasspathLength= */ reducedClasspathLength, + bound.statistics()); + } - if (options.outputDeps().isPresent()) { - DepsProto.Dependencies deps = - Dependencies.collectDeps(options.targetLabel(), bootclasspath, bound, lowered); - try (OutputStream os = - new BufferedOutputStream(Files.newOutputStream(Paths.get(options.outputDeps().get())))) { - deps.writeTo(os); - } + // don't inline this; we want it to show up in profiles + private static BindingResult fallback( + TurbineOptions options, + ImmutableList<CompUnit> units, + ClassPath bootclasspath, + ImmutableList<String> classPath) + throws IOException { + return bind(options, units, bootclasspath, classPath); + } + + /** + * Writes a jdeps proto that indiciates to Blaze that the transitive classpath compilation failed, + * and it should fall back to the transitive classpath. Used only when {@link + * ReducedClasspathMode#BAZEL_REDUCED}. + */ + public static void writeJdepsForFallback(TurbineOptions options) throws IOException { + try (OutputStream os = + new BufferedOutputStream(Files.newOutputStream(Paths.get(options.outputDeps().get())))) { + DepsProto.Dependencies.newBuilder() + .setRuleLabel(options.targetLabel().get()) + .setRequiresReducedClasspathFallback(true) + .build() + .writeTo(os); } + } - writeOutput(options, lowered.bytes(), transitive); - return true; + private static BindingResult bind( + TurbineOptions options, + ImmutableList<CompUnit> units, + ClassPath bootclasspath, + Collection<String> classpath) + throws IOException { + return Binder.bind( + units, + ClassPathBinder.bindClasspath(toPaths(classpath)), + Processing.initializeProcessors( + /* javacopts= */ options.javacOpts(), + /* processorPath= */ options.processorPath(), + /* processorNames= */ options.processors(), + /* builtinProcessors= */ options.builtinProcessors()), + bootclasspath, + /* moduleVersion=*/ Optional.empty()); } private static void usage(TurbineOptions options) { - if (!options.processors().isEmpty()) { - throw new UsageException("--processors is not supported"); - } - if (options.sources().isEmpty() && options.sourceJars().isEmpty()) { - throw new UsageException("no sources were provided"); - } if (options.help()) { throw new UsageException(); } - if (!options.output().isPresent()) { - throw new UsageException("--output is required"); + if (!options.output().isPresent() + && !options.gensrcOutput().isPresent() + && !options.resourceOutput().isPresent()) { + throw new UsageException( + "at least one of --output, --gensrc_output, or --resource_output is required"); } } @@ -188,9 +324,46 @@ public class Main { return units.build(); } - /** Write bytecode to the output jar. */ + /** Writes source files generated by annotation processors. */ + private static void writeSources( + TurbineOptions options, ImmutableMap<String, SourceFile> generatedSources) + throws IOException { + if (!options.gensrcOutput().isPresent()) { + return; + } + Path path = Paths.get(options.gensrcOutput().get()); + try (OutputStream os = Files.newOutputStream(path); + BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE); + JarOutputStream jos = new JarOutputStream(bos)) { + for (SourceFile source : generatedSources.values()) { + addEntry(jos, source.path(), source.source().getBytes(UTF_8)); + } + writeManifest(jos, manifest()); + } + } + + /** Writes resource files generated by annotation processors. */ + private static void writeResources( + TurbineOptions options, ImmutableMap<String, byte[]> generatedResources) throws IOException { + if (!options.resourceOutput().isPresent()) { + return; + } + Path path = Paths.get(options.resourceOutput().get()); + try (OutputStream os = Files.newOutputStream(path); + BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE); + JarOutputStream jos = new JarOutputStream(bos)) { + for (Map.Entry<String, byte[]> resource : generatedResources.entrySet()) { + addEntry(jos, resource.getKey(), resource.getValue()); + } + } + } + + /** Writes bytecode to the output jar. */ private static void writeOutput( - TurbineOptions options, Map<String, byte[]> lowered, Map<String, byte[]> transitive) + TurbineOptions options, + Map<String, byte[]> generated, + Map<String, byte[]> lowered, + Map<String, byte[]> transitive) throws IOException { Path path = Paths.get(options.output().get()); try (OutputStream os = Files.newOutputStream(path); @@ -199,17 +372,42 @@ public class Main { for (Map.Entry<String, byte[]> entry : lowered.entrySet()) { addEntry(jos, entry.getKey() + ".class", entry.getValue()); } + for (Map.Entry<String, byte[]> entry : generated.entrySet()) { + addEntry(jos, entry.getKey(), entry.getValue()); + } for (Map.Entry<String, byte[]> entry : transitive.entrySet()) { addEntry( jos, ClassPathBinder.TRANSITIVE_PREFIX + entry.getKey() + ".class", entry.getValue()); } if (options.targetLabel().isPresent()) { - addEntry(jos, MANIFEST_DIR, new byte[] {}); - addEntry(jos, MANIFEST_NAME, manifestContent(options)); + writeManifest(jos, manifest(options)); } } } + private static void writeManifestProto( + TurbineOptions options, + ImmutableMap<ClassSymbol, SourceTypeBoundClass> units, + ImmutableMap<String, SourceFile> generatedSources) + throws IOException { + ManifestProto.Manifest.Builder manifest = ManifestProto.Manifest.newBuilder(); + for (Map.Entry<ClassSymbol, SourceTypeBoundClass> e : units.entrySet()) { + manifest.addCompilationUnit( + CompilationUnit.newBuilder() + .setPath(e.getValue().source().path()) + .setPkg(e.getKey().packageName()) + .addTopLevel(e.getKey().simpleName()) + .setGeneratedByAnnotationProcessor( + generatedSources.containsKey(e.getValue().source().path())) + .build()); + } + try (OutputStream os = + new BufferedOutputStream( + Files.newOutputStream(Paths.get(options.outputManifest().get())))) { + manifest.build().writeTo(os); + } + } + /** Normalize timestamps. */ static final long DEFAULT_TIMESTAMP = LocalDateTime.of(2010, 1, 1, 0, 0, 0) @@ -228,7 +426,15 @@ public class Main { jos.write(bytes); } - private static byte[] manifestContent(TurbineOptions turbineOptions) throws IOException { + private static void writeManifest(JarOutputStream jos, Manifest manifest) throws IOException { + addEntry(jos, MANIFEST_DIR, new byte[] {}); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + addEntry(jos, MANIFEST_NAME, out.toByteArray()); + } + + /** Creates a default {@link Manifest}. */ + private static Manifest manifest() { Manifest manifest = new Manifest(); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); @@ -236,15 +442,20 @@ public class Main { if (attributes.getValue(createdBy) == null) { attributes.put(createdBy, "bazel"); } + return manifest; + } + + /** Creates a {@link Manifest} that includes the target label and injecting rule kind. */ + private static Manifest manifest(TurbineOptions turbineOptions) { + Manifest manifest = manifest(); + Attributes attributes = manifest.getMainAttributes(); if (turbineOptions.targetLabel().isPresent()) { attributes.put(TARGET_LABEL, turbineOptions.targetLabel().get()); } if (turbineOptions.injectingRuleKind().isPresent()) { attributes.put(INJECTING_RULE_KIND, turbineOptions.injectingRuleKind().get()); } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - manifest.write(out); - return out.toByteArray(); + return manifest; } private static ImmutableList<Path> toPaths(Iterable<String> paths) { diff --git a/java/com/google/turbine/model/Const.java b/java/com/google/turbine/model/Const.java index 6e41bd2..ed4b072 100644 --- a/java/com/google/turbine/model/Const.java +++ b/java/com/google/turbine/model/Const.java @@ -18,6 +18,9 @@ package com.google.turbine.model; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.escape.SourceCodeEscapers; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; /** * Compile-time constant expressions, including literals of primitive or String type, class @@ -31,6 +34,9 @@ public abstract class Const { @Override public abstract boolean equals(Object obj); + @Override + public abstract String toString(); + /** The constant kind. */ public abstract Kind kind(); @@ -51,7 +57,7 @@ public abstract class Const { } /** Subtypes of {@link Const} for primitive and String literals. */ - public abstract static class Value extends Const { + public abstract static class Value extends Const implements AnnotationValue { public abstract TurbineConstantTypeKind constantTypeKind(); @Override @@ -110,6 +116,11 @@ public abstract class Const { } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitBoolean(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.BOOLEAN; } @@ -119,6 +130,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public BooleanValue asBoolean() { return this; } @@ -154,6 +170,11 @@ public abstract class Const { } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitInt(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.INT; } @@ -163,6 +184,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return this; } @@ -227,6 +253,11 @@ public abstract class Const { } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitLong(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.LONG; } @@ -236,6 +267,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -296,7 +332,12 @@ public abstract class Const { @Override public String toString() { - return "'" + value + "'"; + return "'" + SourceCodeEscapers.javaCharEscaper().escape(String.valueOf(value)) + "'"; + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitChar(value, p); } @Override @@ -309,6 +350,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -369,10 +415,18 @@ public abstract class Const { @Override public String toString() { + if (Float.isNaN(value)) { + return "0.0f/0.0f"; + } return value + "f"; } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitFloat(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.FLOAT; } @@ -382,6 +436,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -442,10 +501,24 @@ public abstract class Const { @Override public String toString() { + if (Double.isNaN(value)) { + return "0.0/0.0"; + } + if (value == Double.POSITIVE_INFINITY) { + return "1.0/0.0"; + } + if (value == Double.NEGATIVE_INFINITY) { + return "-1.0/0.0"; + } return String.valueOf(value); } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitDouble(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.DOUBLE; } @@ -455,6 +528,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -515,7 +593,12 @@ public abstract class Const { @Override public String toString() { - return String.format("\"%s\"", value); + return '"' + SourceCodeEscapers.javaCharEscaper().escape(value) + '"'; + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitString(value, p); } @Override @@ -528,6 +611,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public StringValue asString() { return this; } @@ -557,6 +645,11 @@ public abstract class Const { } @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitShort(value, p); + } + + @Override public TurbineConstantTypeKind constantTypeKind() { return TurbineConstantTypeKind.SHORT; } @@ -566,6 +659,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -635,6 +733,11 @@ public abstract class Const { } @Override + public Object getValue() { + return value; + } + + @Override public IntValue asInteger() { return new IntValue((int) value); } @@ -686,7 +789,12 @@ public abstract class Const { @Override public String toString() { - return String.valueOf(value); + return String.format("(byte)0x%02x", value); + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitByte(value, p); } } diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java index 0ce0c13..4dcc408 100644 --- a/java/com/google/turbine/options/TurbineOptions.java +++ b/java/com/google/turbine/options/TurbineOptions.java @@ -16,104 +16,59 @@ package com.google.turbine.options; -import static com.google.common.base.Preconditions.checkNotNull; - +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; /** Header compilation options. */ -public class TurbineOptions { - - private final Optional<String> output; - private final ImmutableList<String> classPath; - private final ImmutableSet<String> bootClassPath; - private final Optional<String> release; - private final Optional<String> system; - private final ImmutableList<String> sources; - private final ImmutableList<String> processorPath; - private final ImmutableSet<String> processors; - private final ImmutableList<String> sourceJars; - private final Optional<String> outputDeps; - private final ImmutableSet<String> directJars; - private final Optional<String> targetLabel; - private final Optional<String> injectingRuleKind; - private final ImmutableList<String> depsArtifacts; - private final boolean javacFallback; - private final boolean help; - private final ImmutableList<String> javacOpts; - private final boolean shouldReduceClassPath; - - private TurbineOptions( - @Nullable String output, - ImmutableList<String> classPath, - ImmutableSet<String> bootClassPath, - @Nullable String release, - @Nullable String system, - ImmutableList<String> sources, - ImmutableList<String> processorPath, - ImmutableSet<String> processors, - ImmutableList<String> sourceJars, - @Nullable String outputDeps, - ImmutableSet<String> directJars, - @Nullable String targetLabel, - @Nullable String injectingRuleKind, - ImmutableList<String> depsArtifacts, - boolean javacFallback, - boolean help, - ImmutableList<String> javacOpts, - boolean shouldReduceClassPath) { - this.output = Optional.ofNullable(output); - this.classPath = checkNotNull(classPath, "classPath must not be null"); - this.bootClassPath = checkNotNull(bootClassPath, "bootClassPath must not be null"); - this.release = Optional.ofNullable(release); - this.system = Optional.ofNullable(system); - this.sources = checkNotNull(sources, "sources must not be null"); - this.processorPath = checkNotNull(processorPath, "processorPath must not be null"); - this.processors = checkNotNull(processors, "processors must not be null"); - this.sourceJars = checkNotNull(sourceJars, "sourceJars must not be null"); - this.outputDeps = Optional.ofNullable(outputDeps); - this.directJars = checkNotNull(directJars, "directJars must not be null"); - this.targetLabel = Optional.ofNullable(targetLabel); - this.injectingRuleKind = Optional.ofNullable(injectingRuleKind); - this.depsArtifacts = checkNotNull(depsArtifacts, "depsArtifacts must not be null"); - this.javacFallback = javacFallback; - this.help = help; - this.javacOpts = checkNotNull(javacOpts, "javacOpts must not be null"); - this.shouldReduceClassPath = shouldReduceClassPath; +@AutoValue +public abstract class TurbineOptions { + + /** + * This modes controls how a probablistic Java classpath reduction is used. For each mode except + * {@code NONE} a speculative compilation is performed against a subset of the original classpath. + * If it fails due to a missing symbol, it is retried with the original transitive classpath. + */ + public enum ReducedClasspathMode { + /** + * Bazel performs classpath reduction, and invokes turbine passing only the reduced classpath. + * If the compilation fails and requires fallback, turbine finishes with exit code 0 but records + * that the reduced classpath compilation failed in the jdeps proto. + */ + BAZEL_REDUCED, + /** + * Indicates that the reduced classpath compilation failed when Bazel previously invoked + * turbine, and that we are retrying with a transitive classpath. + */ + BAZEL_FALLBACK, + /** + * Turbine implements reduced classpaths locally, with in-process fallback if the compilation + * fails. + */ + JAVABUILDER_REDUCED, + /** Reduced classpaths are disabled, and a full transitive classpath is used. */ + NONE } /** Paths to the Java source files to compile. */ - public ImmutableList<String> sources() { - return sources; - } + public abstract ImmutableList<String> sources(); /** Paths to classpath artifacts. */ - public ImmutableList<String> classPath() { - return classPath; - } + public abstract ImmutableList<String> classPath(); /** Paths to compilation bootclasspath artifacts. */ - public ImmutableSet<String> bootClassPath() { - return bootClassPath; - } + public abstract ImmutableSet<String> bootClassPath(); /** The target platform version. */ - public Optional<String> release() { - return release; - } + public abstract Optional<String> release(); /** The target platform's system modules. */ - public Optional<String> system() { - return system; - } + public abstract Optional<String> system(); /** The output jar. */ - @Nullable - public Optional<String> output() { - return output; - } + public abstract Optional<String> output(); /** * The output jar. @@ -123,214 +78,187 @@ public class TurbineOptions { @Deprecated @Nullable public String outputFile() { - return output.orElse(null); + return output().orElse(null); } /** Paths to annotation processor artifacts. */ - public ImmutableList<String> processorPath() { - return processorPath; - } + public abstract ImmutableList<String> processorPath(); /** Annotation processor class names. */ - public ImmutableSet<String> processors() { - return processors; - } + public abstract ImmutableSet<String> processors(); + + /** Class names of annotation processor that are built in. */ + public abstract ImmutableSet<String> builtinProcessors(); /** Source jars for compilation. */ - public ImmutableList<String> sourceJars() { - return sourceJars; - } + public abstract ImmutableList<String> sourceJars(); /** Output jdeps file. */ - public Optional<String> outputDeps() { - return outputDeps; - } + public abstract Optional<String> outputDeps(); + + /** Output manifest file. */ + public abstract Optional<String> outputManifest(); /** The direct dependencies. */ - public ImmutableSet<String> directJars() { - return directJars; - } + public abstract ImmutableSet<String> directJars(); /** The label of the target being compiled. */ - public Optional<String> targetLabel() { - return targetLabel; - } + public abstract Optional<String> targetLabel(); /** * If present, the name of the rule that injected an aspect that compiles this target. * * <p>Note that this rule will have a completely different label to {@link #targetLabel} above. */ - public Optional<String> injectingRuleKind() { - return injectingRuleKind; - } + public abstract Optional<String> injectingRuleKind(); /** The .jdeps artifacts for direct dependencies. */ - public ImmutableList<String> depsArtifacts() { - return depsArtifacts; - } - - /** Fall back to javac-turbine for error reporting. */ - public boolean javacFallback() { - return javacFallback; - } + public abstract ImmutableList<String> depsArtifacts(); /** Print usage information. */ - public boolean help() { - return help; - } + public abstract boolean help(); /** Additional Java compiler flags. */ - public ImmutableList<String> javacOpts() { - return javacOpts; - } + public abstract ImmutableList<String> javacOpts(); - /** Returns true if the reduced classpath optimization is enabled. */ - public boolean shouldReduceClassPath() { - return shouldReduceClassPath; - } + /** The reduced classpath optimization mode. */ + public abstract ReducedClasspathMode reducedClasspathMode(); + + /** An optional path for profiling output. */ + public abstract Optional<String> profile(); + + /** An optional path for generated source output. */ + public abstract Optional<String> gensrcOutput(); + + /** An optional path for generated resource output. */ + public abstract Optional<String> resourceOutput(); + + public abstract int fullClasspathLength(); + + public abstract int reducedClasspathLength(); public static Builder builder() { - return new Builder(); + return new AutoValue_TurbineOptions.Builder() + .setSources(ImmutableList.of()) + .setClassPath(ImmutableList.of()) + .setBootClassPath(ImmutableList.of()) + .setProcessorPath(ImmutableList.of()) + .setProcessors(ImmutableList.of()) + .setBuiltinProcessors(ImmutableList.of()) + .setSourceJars(ImmutableList.of()) + .setDirectJars(ImmutableList.of()) + .setDepsArtifacts(ImmutableList.of()) + .addAllJavacOpts(ImmutableList.of()) + .setReducedClasspathMode(ReducedClasspathMode.NONE) + .setHelp(false) + .setFullClasspathLength(0) + .setReducedClasspathLength(0); } /** A {@link Builder} for {@link TurbineOptions}. */ - public static class Builder { - - private String output; - private final ImmutableList.Builder<String> classPath = ImmutableList.builder(); - private final ImmutableList.Builder<String> sources = ImmutableList.builder(); - private final ImmutableList.Builder<String> processorPath = ImmutableList.builder(); - private final ImmutableSet.Builder<String> processors = ImmutableSet.builder(); - private final ImmutableList.Builder<String> sourceJars = ImmutableList.builder(); - private final ImmutableSet.Builder<String> bootClassPath = ImmutableSet.builder(); - @Nullable private String release; - @Nullable private String system; - private String outputDeps; - private final ImmutableSet.Builder<String> directJars = ImmutableSet.builder(); - @Nullable private String targetLabel; - @Nullable private String injectingRuleKind; - private final ImmutableList.Builder<String> depsArtifacts = ImmutableList.builder(); - private boolean javacFallback = true; - private boolean help = false; - private final ImmutableList.Builder<String> javacOpts = ImmutableList.builder(); - private boolean shouldReduceClassPath = true; - - public TurbineOptions build() { - return new TurbineOptions( - output, - classPath.build(), - bootClassPath.build(), - release, - system, - sources.build(), - processorPath.build(), - processors.build(), - sourceJars.build(), - outputDeps, - directJars.build(), - targetLabel, - injectingRuleKind, - depsArtifacts.build(), - javacFallback, - help, - javacOpts.build(), - shouldReduceClassPath); + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setOutput(String output); + + /** @deprecated use {@link #setClassPath(ImmutableList)} instead. */ + @Deprecated + public Builder addClassPathEntries(Iterable<String> sources) { + return setClassPath(ImmutableList.copyOf(sources)); } - public Builder setOutput(String output) { - this.output = output; - return this; - } + public abstract Builder setClassPath(ImmutableList<String> classPath); - public Builder addClassPathEntries(Iterable<String> classPath) { - this.classPath.addAll(classPath); - return this; - } + public abstract Builder setBootClassPath(ImmutableList<String> bootClassPath); - public Builder addBootClassPathEntries(Iterable<String> bootClassPath) { - this.bootClassPath.addAll(bootClassPath); - return this; + /** @deprecated use {@link #setBootClassPath(ImmutableList)} instead. */ + @Deprecated + public Builder addBootClassPathEntries(Iterable<String> sources) { + return setBootClassPath(ImmutableList.copyOf(sources)); } - public Builder setRelease(String release) { - this.release = release; - return this; - } + public abstract Builder setRelease(String release); - public Builder setSystem(String system) { - this.system = system; - return this; - } + public abstract Builder setSystem(String system); + public abstract Builder setSources(ImmutableList<String> sources); + + /** @deprecated use {@link #setSources(ImmutableList)} instead. */ + @Deprecated public Builder addSources(Iterable<String> sources) { - this.sources.addAll(sources); - return this; + return setSources(ImmutableList.copyOf(sources)); } + /** @deprecated use {@link #setProcessorPath(ImmutableList)} instead. */ + @Deprecated public Builder addProcessorPathEntries(Iterable<String> processorPath) { - this.processorPath.addAll(processorPath); - return this; + return setProcessorPath(ImmutableList.copyOf(processorPath)); } + public abstract Builder setProcessorPath(ImmutableList<String> processorPath); + + /** @deprecated use {@link #setProcessors(ImmutableList)} instead. */ + @Deprecated public Builder addProcessors(Iterable<String> processors) { - this.processors.addAll(processors); - return this; + return setProcessors(ImmutableList.copyOf(processors)); } - // TODO(cushon): remove this when turbine dependency is updated - public Builder setTempDir(String tempDir) { - return this; - } + public abstract Builder setProcessors(ImmutableList<String> processors); - public Builder setSourceJars(Iterable<String> sourceJars) { - this.sourceJars.addAll(sourceJars); - return this; + /** @deprecated use {@link #setBuiltinProcessors(ImmutableList)} instead. */ + @Deprecated + public Builder addBuiltinProcessors(Iterable<String> builtinProcessors) { + return setBuiltinProcessors(ImmutableList.copyOf(builtinProcessors)); } - public Builder setOutputDeps(String outputDeps) { - this.outputDeps = outputDeps; - return this; - } + public abstract Builder setBuiltinProcessors(ImmutableList<String> builtinProcessors); - public Builder setTargetLabel(String targetLabel) { - this.targetLabel = targetLabel; - return this; - } + public abstract Builder setSourceJars(ImmutableList<String> sourceJars); - public Builder setInjectingRuleKind(String injectingRuleKind) { - this.injectingRuleKind = injectingRuleKind; - return this; - } + public abstract Builder setOutputDeps(String outputDeps); + + public abstract Builder setOutputManifest(String outputManifest); + public abstract Builder setTargetLabel(String targetLabel); + + public abstract Builder setInjectingRuleKind(String injectingRuleKind); + + /** @deprecated use {@link #setDepsArtifacts(ImmutableList)} instead. */ + @Deprecated public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) { - this.depsArtifacts.addAll(depsArtifacts); - return this; + return setDepsArtifacts(ImmutableList.copyOf(depsArtifacts)); } - public Builder setJavacFallback(boolean javacFallback) { - this.javacFallback = javacFallback; - return this; - } + public abstract Builder setDepsArtifacts(ImmutableList<String> depsArtifacts); - public Builder setHelp(boolean help) { - this.help = help; - return this; - } + public abstract Builder setHelp(boolean help); + + abstract ImmutableList.Builder<String> javacOptsBuilder(); public Builder addAllJavacOpts(Iterable<String> javacOpts) { - this.javacOpts.addAll(javacOpts); + javacOptsBuilder().addAll(javacOpts); return this; } - public Builder setShouldReduceClassPath(boolean shouldReduceClassPath) { - this.shouldReduceClassPath = shouldReduceClassPath; - return this; - } + public abstract Builder setReducedClasspathMode(ReducedClasspathMode reducedClasspathMode); - public Builder addDirectJars(ImmutableList<String> jars) { - this.directJars.addAll(jars); - return this; + /** @deprecated use {@link #setDirectJars(ImmutableList)} instead. */ + @Deprecated + public Builder addDirectJars(Iterable<String> directJars) { + return setDirectJars(ImmutableList.copyOf(directJars)); } + + public abstract Builder setDirectJars(ImmutableList<String> jars); + + public abstract Builder setProfile(String profile); + + public abstract Builder setGensrcOutput(String gensrcOutput); + + public abstract Builder setResourceOutput(String resourceOutput); + + public abstract Builder setFullClasspathLength(int fullClasspathLength); + + public abstract Builder setReducedClasspathLength(int reducedClasspathLength); + + public abstract TurbineOptions build(); } } diff --git a/java/com/google/turbine/options/TurbineOptionsParser.java b/java/com/google/turbine/options/TurbineOptionsParser.java index 55697ca..17d4bf6 100644 --- a/java/com/google/turbine/options/TurbineOptionsParser.java +++ b/java/com/google/turbine/options/TurbineOptionsParser.java @@ -22,6 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -69,16 +70,19 @@ public class TurbineOptionsParser { readOne(argumentDeque); break; case "--processors": - builder.addProcessors(readList(argumentDeque)); + builder.setProcessors(readList(argumentDeque)); + break; + case "--builtin_processors": + builder.setBuiltinProcessors(readList(argumentDeque)); break; case "--processorpath": - builder.addProcessorPathEntries(readList(argumentDeque)); + builder.setProcessorPath(readList(argumentDeque)); break; case "--classpath": - builder.addClassPathEntries(readList(argumentDeque)); + builder.setClassPath(readList(argumentDeque)); break; case "--bootclasspath": - builder.addBootClassPathEntries(readList(argumentDeque)); + builder.setBootClassPath(readList(argumentDeque)); break; case "--release": builder.setRelease(readOne(argumentDeque)); @@ -94,16 +98,19 @@ public class TurbineOptionsParser { break; } case "--sources": - builder.addSources(readList(argumentDeque)); + builder.setSources(readList(argumentDeque)); break; case "--output_deps": builder.setOutputDeps(readOne(argumentDeque)); break; + case "--output_manifest_proto": + builder.setOutputManifest(readOne(argumentDeque)); + break; case "--direct_dependencies": - builder.addDirectJars(readList(argumentDeque)); + builder.setDirectJars(readList(argumentDeque)); break; case "--deps_artifacts": - builder.addAllDepsArtifacts(readList(argumentDeque)); + builder.setDepsArtifacts(readList(argumentDeque)); break; case "--target_label": builder.setTargetLabel(readOne(argumentDeque)); @@ -112,16 +119,32 @@ public class TurbineOptionsParser { builder.setInjectingRuleKind(readOne(argumentDeque)); break; case "--javac_fallback": - builder.setJavacFallback(true); - break; case "--nojavac_fallback": - builder.setJavacFallback(false); + // TODO(cushon): remove this case once blaze stops passing the flag break; case "--reduce_classpath": - builder.setShouldReduceClassPath(true); + builder.setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED); break; case "--noreduce_classpath": - builder.setShouldReduceClassPath(false); + builder.setReducedClasspathMode(ReducedClasspathMode.NONE); + break; + case "--reduce_classpath_mode": + builder.setReducedClasspathMode(ReducedClasspathMode.valueOf(readOne(argumentDeque))); + break; + case "--full_classpath_length": + builder.setFullClasspathLength(Integer.parseInt(readOne(argumentDeque))); + break; + case "--reduced_classpath_length": + builder.setReducedClasspathLength(Integer.parseInt(readOne(argumentDeque))); + break; + case "--profile": + builder.setProfile(readOne(argumentDeque)); + break; + case "--gensrc_output": + builder.setGensrcOutput(readOne(argumentDeque)); + break; + case "--resource_output": + builder.setResourceOutput(readOne(argumentDeque)); break; case "--help": builder.setHelp(true); diff --git a/java/com/google/turbine/parse/ConstExpressionParser.java b/java/com/google/turbine/parse/ConstExpressionParser.java index b3666a4..e49d51c 100644 --- a/java/com/google/turbine/parse/ConstExpressionParser.java +++ b/java/com/google/turbine/parse/ConstExpressionParser.java @@ -96,7 +96,7 @@ public class ConstExpressionParser { } } - private Tree.Expression primary(boolean negate) { + private Tree.@Nullable Expression primary(boolean negate) { switch (token) { case INT_LITERAL: return finishLiteral(TurbineConstantTypeKind.INT, negate); @@ -133,8 +133,9 @@ public class ConstExpressionParser { case LPAREN: return maybeCast(); case LBRACE: + int pos = position; eat(); - return arrayInitializer(); + return arrayInitializer(pos); case IDENT: return qualIdent(); case BYTE: @@ -244,10 +245,10 @@ public class ConstExpressionParser { position = lexer.position(); } - private Tree.Expression arrayInitializer() { + private Tree.Expression arrayInitializer(int pos) { if (token == Token.RBRACE) { eat(); - return new Tree.ArrayInit(position, ImmutableList.<Tree.Expression>of()); + return new Tree.ArrayInit(pos, ImmutableList.<Tree.Expression>of()); } ImmutableList.Builder<Tree.Expression> exprs = ImmutableList.builder(); @@ -273,11 +274,12 @@ public class ConstExpressionParser { return null; } } - return new Tree.ArrayInit(position, exprs.build()); + return new Tree.ArrayInit(pos, exprs.build()); } /** Finish hex, decimal, octal, and binary integer literals (see JLS 3.10.1). */ private Tree.Expression finishLiteral(TurbineConstantTypeKind kind, boolean negate) { + int pos = position; String text = ident().value(); Const.Value value; switch (kind) { @@ -354,7 +356,7 @@ public class ConstExpressionParser { throw new AssertionError(kind); } eat(); - return new Tree.Literal(position, kind, value); + return new Tree.Literal(pos, kind, value); } static boolean isOctal(String text) { @@ -561,12 +563,16 @@ public class ConstExpressionParser { return new Tree.TypeCast(position, new Tree.PrimTy(position, ImmutableList.of(), ty), rhs); } - private Tree.AnnoExpr annotation() { + private Tree.@Nullable AnnoExpr annotation() { if (token != Token.AT) { throw new AssertionError(); } eat(); - ImmutableList<Ident> name = ((Tree.ConstVarName) qualIdent()).name(); + Tree.ConstVarName constVarName = (Tree.ConstVarName) qualIdent(); + if (constVarName == null) { + return null; + } + ImmutableList<Ident> name = constVarName.name(); ImmutableList.Builder<Tree.Expression> args = ImmutableList.builder(); if (token == Token.LPAREN) { eat(); diff --git a/java/com/google/turbine/parse/IteratorLexer.java b/java/com/google/turbine/parse/IteratorLexer.java index 823f9bb..1b7a9d0 100644 --- a/java/com/google/turbine/parse/IteratorLexer.java +++ b/java/com/google/turbine/parse/IteratorLexer.java @@ -56,7 +56,11 @@ public class IteratorLexer implements Lexer { @Override public int position() { - // TODO(cushon): test expression position EOF handling - return curr != null ? curr.position : -1; + return curr.position; + } + + @Override + public String javadoc() { + return null; } } diff --git a/java/com/google/turbine/parse/Lexer.java b/java/com/google/turbine/parse/Lexer.java index 2d8422a..992bef1 100644 --- a/java/com/google/turbine/parse/Lexer.java +++ b/java/com/google/turbine/parse/Lexer.java @@ -31,4 +31,7 @@ public interface Lexer { /** Returns the source file for diagnostics. */ SourceFile source(); + + /** Returns a saved javadoc comment. */ + String javadoc(); } diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java index ff3cc3e..4a090b3 100644 --- a/java/com/google/turbine/parse/Parser.java +++ b/java/com/google/turbine/parse/Parser.java @@ -23,6 +23,7 @@ import static com.google.turbine.parse.Token.RPAREN; import static com.google.turbine.parse.Token.SEMI; import static com.google.turbine.tree.TurbineModifier.PROTECTED; import static com.google.turbine.tree.TurbineModifier.PUBLIC; +import static com.google.turbine.tree.TurbineModifier.VARARGS; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -149,13 +150,14 @@ public class Parser { break; case AT: { + int pos = position; next(); if (token == INTERFACE) { decls.add(annotationDeclaration(access, annos.build())); access = EnumSet.noneOf(TurbineModifier.class); annos = ImmutableList.builder(); } else { - annos.add(annotation()); + annos.add(annotation(pos)); } break; } @@ -213,6 +215,7 @@ public class Parser { } private TyDecl interfaceDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) { + String javadoc = lexer.javadoc(); eat(Token.INTERFACE); int pos = position; Ident name = eatIdent(); @@ -241,10 +244,12 @@ public class Parser { Optional.<ClassTy>empty(), interfaces.build(), members, - TurbineTyKind.INTERFACE); + TurbineTyKind.INTERFACE, + javadoc); } private TyDecl annotationDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) { + String javadoc = lexer.javadoc(); eat(Token.INTERFACE); int pos = position; Ident name = eatIdent(); @@ -260,10 +265,12 @@ public class Parser { Optional.<ClassTy>empty(), ImmutableList.<ClassTy>of(), members, - TurbineTyKind.ANNOTATION); + TurbineTyKind.ANNOTATION, + javadoc); } private TyDecl enumDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) { + String javadoc = lexer.javadoc(); eat(Token.ENUM); int pos = position; Ident name = eatIdent(); @@ -287,7 +294,8 @@ public class Parser { Optional.<ClassTy>empty(), interfaces.build(), members, - TurbineTyKind.ENUM); + TurbineTyKind.ENUM, + javadoc); } private String moduleName() { @@ -340,7 +348,7 @@ public class Parser { return new ModDecl(pos, annos, open, moduleName, directives.build()); } - private String flatname(char join, ImmutableList<Ident> idents) { + private static String flatname(char join, ImmutableList<Ident> idents) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Ident ident : idents) { @@ -466,7 +474,8 @@ public class Parser { ImmutableList.<Type>of(), ImmutableList.of()), name, - Optional.<Expression>empty())); + Optional.<Expression>empty(), + null)); annos = ImmutableList.builder(); break; } @@ -478,8 +487,9 @@ public class Parser { annos = ImmutableList.builder(); break OUTER; case AT: + int pos = position; next(); - annos.add(annotation()); + annos.add(annotation(pos)); break; default: throw error(token); @@ -489,6 +499,7 @@ public class Parser { } private TyDecl classDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) { + String javadoc = lexer.javadoc(); eat(Token.CLASS); int pos = position; Ident name = eatIdent(); @@ -520,7 +531,8 @@ public class Parser { Optional.ofNullable(xtnds), interfaces.build(), members, - TurbineTyKind.CLASS); + TurbineTyKind.CLASS, + javadoc); } private ImmutableList<Tree> classMembers() { @@ -580,13 +592,14 @@ public class Parser { case AT: { // TODO(cushon): de-dup with top-level parsing + int pos = position; next(); if (token == INTERFACE) { acc.add(annotationDeclaration(access, annos.build())); access = EnumSet.noneOf(TurbineModifier.class); annos = ImmutableList.builder(); } else { - annos.add(annotation()); + annos.add(annotation(pos)); } break; } @@ -769,8 +782,9 @@ public class Parser { } ImmutableList.Builder<Anno> builder = ImmutableList.builder(); while (token == Token.AT) { + int pos = position; next(); - builder.add(annotation()); + builder.add(annotation(pos)); } return builder.build(); } @@ -807,6 +821,7 @@ public class Parser { ImmutableList<Anno> annos, Type baseTy, Ident name) { + String javadoc = lexer.javadoc(); ImmutableList.Builder<Tree> result = ImmutableList.builder(); VariableInitializerParser initializerParser = new VariableInitializerParser(token, lexer); List<List<SavedToken>> bits = initializerParser.parseInitializers(); @@ -831,7 +846,7 @@ public class Parser { if (init != null && init.kind() == Tree.Kind.ARRAY_INIT) { init = null; } - result.add(new VarDecl(pos, access, annos, ty, name, Optional.ofNullable(init))); + result.add(new VarDecl(pos, access, annos, ty, name, Optional.ofNullable(init), javadoc)); } if (token != SEMI) { throw TurbineError.format(lexer.source(), expressionStart, ErrorKind.UNTERMINATED_EXPRESSION); @@ -847,6 +862,7 @@ public class Parser { ImmutableList<TyParam> typaram, Type result, Ident name) { + String javadoc = lexer.javadoc(); eat(Token.LPAREN); ImmutableList.Builder<VarDecl> formals = ImmutableList.builder(); formalParams(formals, access); @@ -873,8 +889,9 @@ public class Parser { Tree expr = cparser.expression(); token = cparser.token; if (expr == null && token == Token.AT) { + int annoPos = position; next(); - expr = annotation(); + expr = annotation(annoPos); } if (expr == null) { throw error(token); @@ -898,7 +915,8 @@ public class Parser { name, formals.build(), exceptions.build(), - Optional.ofNullable(defaultValue)); + Optional.ofNullable(defaultValue), + javadoc); } /** @@ -928,6 +946,10 @@ public class Parser { if (extra.isEmpty()) { return type; } + if (type == null) { + // trailing dims without a type, e.g. for a constructor declaration + throw error(token); + } if (type.kind() == Kind.ARR_TY) { ArrTy arrTy = (ArrTy) type; return new ArrTy(arrTy.position(), arrTy.annos(), extraDims(arrTy.elem(), extra)); @@ -962,13 +984,29 @@ public class Parser { private VarDecl formalParam() { ImmutableList.Builder<Anno> annos = ImmutableList.builder(); EnumSet<TurbineModifier> access = modifiersAndAnnotations(annos); - Type ty = referenceType(ImmutableList.of()); + Type ty = referenceTypeWithoutDims(ImmutableList.of()); ImmutableList<Anno> typeAnnos = maybeAnnos(); - if (maybe(Token.ELLIPSIS)) { - access.add(TurbineModifier.VARARGS); - ty = new ArrTy(position, typeAnnos, ty); - } else { - ty = maybeDims(typeAnnos, ty); + OUTER: + while (true) { + switch (token) { + case LBRACK: + next(); + eat(Token.RBRACK); + ty = new ArrTy(position, typeAnnos, ty); + typeAnnos = maybeAnnos(); + break; + case ELLIPSIS: + next(); + access.add(VARARGS); + ty = new ArrTy(position, typeAnnos, ty); + typeAnnos = ImmutableList.of(); + break OUTER; + default: + break OUTER; + } + } + if (!typeAnnos.isEmpty()) { + throw error(token); } // the parameter name is `this` for receiver parameters, and a qualified this expression // for inner classes @@ -979,7 +1017,8 @@ public class Parser { name = identOrThis(); } ty = extraDims(ty); - return new VarDecl(position, access, annos.build(), ty, name, Optional.<Expression>empty()); + return new VarDecl( + position, access, annos.build(), ty, name, Optional.<Expression>empty(), null); } private Ident identOrThis() { @@ -1041,13 +1080,14 @@ public class Parser { OUTER: while (true) { ImmutableList<Anno> annotations = maybeAnnos(); + int pos = position; Ident name = eatIdent(); ImmutableList<Tree> bounds = ImmutableList.of(); if (token == Token.EXTENDS) { next(); bounds = tybounds(); } - acc.add(new TyParam(position, name, bounds, annotations)); + acc.add(new TyParam(pos, name, bounds, annotations)); switch (token) { case COMMA: eat(Token.COMMA); @@ -1166,49 +1206,42 @@ public class Parser { return acc.build(); } - private Type referenceType(ImmutableList<Anno> typeAnnos) { - Type ty; + private Type referenceTypeWithoutDims(ImmutableList<Anno> typeAnnos) { switch (token) { case IDENT: - ty = classty(null, typeAnnos); - break; + return classty(null, typeAnnos); case BOOLEAN: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BOOLEAN); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BOOLEAN); case BYTE: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BYTE); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BYTE); case SHORT: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.SHORT); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.SHORT); case INT: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.INT); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.INT); case LONG: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.LONG); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.LONG); case CHAR: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.CHAR); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.CHAR); case DOUBLE: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.DOUBLE); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.DOUBLE); case FLOAT: next(); - ty = new PrimTy(position, typeAnnos, TurbineConstantTypeKind.FLOAT); - break; + return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.FLOAT); default: throw error(token); } - ty = maybeDims(maybeAnnos(), ty); - return ty; + } + + private Type referenceType(ImmutableList<Anno> typeAnnos) { + Type ty = referenceTypeWithoutDims(typeAnnos); + return maybeDims(maybeAnnos(), ty); } private Type maybeDims(ImmutableList<Anno> typeAnnos, Type ty) { @@ -1269,8 +1302,9 @@ public class Parser { access.add(TurbineModifier.STRICTFP); break; case AT: + int pos = position; next(); - annos.add(annotation()); + annos.add(annotation(pos)); break; default: return access; @@ -1318,8 +1352,7 @@ public class Parser { return name.build(); } - private Anno annotation() { - int pos = position; + private Anno annotation(int pos) { ImmutableList<Ident> name = qualIdent(); ImmutableList.Builder<Expression> args = ImmutableList.builder(); diff --git a/java/com/google/turbine/parse/StreamLexer.java b/java/com/google/turbine/parse/StreamLexer.java index 74b0ce8..2e20c26 100644 --- a/java/com/google/turbine/parse/StreamLexer.java +++ b/java/com/google/turbine/parse/StreamLexer.java @@ -16,9 +16,9 @@ package com.google.turbine.parse; +import static com.google.common.base.Verify.verify; import static com.google.turbine.parse.UnicodeEscapePreprocessor.ASCII_SUB; -import com.google.common.base.Verify; import com.google.turbine.diag.SourceFile; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; @@ -40,6 +40,9 @@ public class StreamLexer implements Lexer { /** The value of the current string or character literal token. */ private String value = null; + /** A saved javadoc comment. */ + private String javadoc = null; + public StreamLexer(UnicodeEscapePreprocessor reader) { this.reader = reader; eat(); @@ -62,6 +65,17 @@ public class StreamLexer implements Lexer { } @Override + public String javadoc() { + String result = javadoc; + javadoc = null; + if (result == null) { + return null; + } + verify(result.endsWith("*/"), result); + return result.substring(0, result.length() - "*/".length()); + } + + @Override public String stringValue() { if (value != null) { return value; @@ -111,30 +125,51 @@ public class StreamLexer implements Lexer { } eat(); break; + default: // fall out } } case '*': + eat(); boolean sawStar = false; - while (true) { + boolean isJavadoc = false; + if (ch == '*') { eat(); + // handle empty non-javadoc comments: `/**/` + if (ch == '/') { + eat(); + continue OUTER; + } + isJavadoc = true; + readFrom(); + } + while (true) { switch (ch) { case '*': + eat(); sawStar = true; break; case '/': + eat(); if (sawStar) { - eat(); + if (isJavadoc) { + // Save the comment, excluding the leading `/**` and including + // the trailing `/*`. The comment is trimmed and normalized later. + javadoc = stringValue(); + } continue OUTER; } sawStar = false; break; case ASCII_SUB: if (reader.done()) { - return Token.EOF; + throw TurbineError.format( + reader.source(), position, ErrorKind.UNCLOSED_COMMENT); } eat(); + sawStar = false; break; default: + eat(); sawStar = false; break; } @@ -205,7 +240,9 @@ public class StreamLexer implements Lexer { return identifier(); case ASCII_SUB: - Verify.verify(reader.done()); + if (!reader.done()) { + throw error(ErrorKind.UNEXPECTED_EOF); + } return Token.EOF; case '-': @@ -512,6 +549,7 @@ public class StreamLexer implements Lexer { eat(); signedInteger(); break; + default: // fall out } return floatTypeSuffix(); } @@ -526,6 +564,7 @@ public class StreamLexer implements Lexer { eat(); signedInteger(); break; + default: // fall out } return floatTypeSuffix(); } @@ -989,7 +1028,7 @@ public class StreamLexer implements Lexer { return makeIdent(stringValue()); } - private Token makeIdent(String s) { + private static Token makeIdent(String s) { switch (s) { case "abstract": return Token.ABSTRACT; diff --git a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java index 58f129d..3f38561 100644 --- a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java +++ b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java @@ -16,7 +16,10 @@ package com.google.turbine.parse; +import com.google.errorprone.annotations.CheckReturnValue; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineError.ErrorKind; /** Preprocesses Unicode escape characters in Java source code, as described in JLS §3.3. */ public class UnicodeEscapePreprocessor { @@ -85,7 +88,7 @@ public class UnicodeEscapePreprocessor { } /** Consumes a hex digit. */ - private static int hexDigit(char d) { + private int hexDigit(char d) { switch (d) { case '0': case '1': @@ -113,9 +116,9 @@ public class UnicodeEscapePreprocessor { case 'f': return ((d - 'a') + 10); case ASCII_SUB: - throw new AssertionError("unexpected end of input"); + throw error(ErrorKind.UNEXPECTED_EOF); default: - throw new AssertionError(String.format("unexpected hex digit: 0x%x", (int) d)); + throw error(ErrorKind.INVALID_UNICODE); } } @@ -134,4 +137,10 @@ public class UnicodeEscapePreprocessor { public SourceFile source() { return source; } + + @CheckReturnValue + private TurbineError error(ErrorKind kind, Object... args) { + throw TurbineError.format( + source(), Math.min(position(), source().source().length() - 1), kind, args); + } } diff --git a/java/com/google/turbine/parse/VariableInitializerParser.java b/java/com/google/turbine/parse/VariableInitializerParser.java index a39e9e8..4ad9272 100644 --- a/java/com/google/turbine/parse/VariableInitializerParser.java +++ b/java/com/google/turbine/parse/VariableInitializerParser.java @@ -149,8 +149,6 @@ public class VariableInitializerParser { case START: case TYPE: break OUTER; - default: - break; } save(); next(); @@ -163,8 +161,6 @@ public class VariableInitializerParser { case TYPE: commas.add(tokens.size()); break; - default: - break; } break; case DOT: @@ -209,14 +205,14 @@ public class VariableInitializerParser { result.add( ImmutableList.<SavedToken>builder() .addAll(tokens.subList(start, idx - 1)) - .add(new SavedToken(Token.EOF, null, -1)) + .add(new SavedToken(Token.EOF, null, tokens.get(idx - 1).position)) .build()); start = idx; } result.add( ImmutableList.<SavedToken>builder() .addAll(tokens.subList(start, tokens.size())) - .add(new SavedToken(Token.EOF, null, -1)) + .add(new SavedToken(Token.EOF, null, lexer.position())) .build()); return result; } @@ -284,6 +280,9 @@ public class VariableInitializerParser { int lastType = -1; int lastComma = -1; for (int i = 0; i < many; i++) { + if (ltIndices.isEmpty()) { + throw error(ErrorKind.UNEXPECTED_TOKEN, ">"); + } lastType = ltIndices.removeLast(); lastComma = commaIndices.removeLast(); } diff --git a/java/com/google/turbine/processing/ClassHierarchy.java b/java/com/google/turbine/processing/ClassHierarchy.java new file mode 100644 index 0000000..50c929c --- /dev/null +++ b/java/com/google/turbine/processing/ClassHierarchy.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019 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.processing; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineError.ErrorKind; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.TyKind; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * A representation of the class hierarchy, with logic for performing search between subtypes and + * their supertypes. + */ +public class ClassHierarchy { + + private final Map<ClassSymbol, HierarchyNode> cache = new HashMap<>(); + private Env<ClassSymbol, ? extends TypeBoundClass> env; + + ClassHierarchy(Env<ClassSymbol, ? extends TypeBoundClass> env) { + this.env = env; + } + + public void round(CompoundEnv<ClassSymbol, TypeBoundClass> env) { + cache.clear(); + this.env = env; + } + + /** A linked list between two types in the hierarchy. */ + private static class PathNode { + + /** The class for this node. */ + final ClassTy type; + + /** The node corresponding to a direct super-type of this class, or {@code null}. */ + final PathNode ancestor; + + PathNode(ClassTy type, PathNode ancestor) { + this.type = type; + this.ancestor = ancestor; + } + } + + /** + * A node in the type hierarchy, corresponding to a class symbol U. For each type V in the + * transitive supertype hierarchy of U, we save a mapping from the class symbol for V to the path + * from U to V in the type hierarchy. + */ + private class HierarchyNode { + + private final ClassSymbol sym; + private final Map<ClassSymbol, PathNode> ancestors = new LinkedHashMap<>(); + + HierarchyNode(ClassSymbol sym) { + this.sym = sym; + } + + /** Adds a child (direct supertype) of this node. */ + private void add(Type type) { + if (type.tyKind() != TyKind.CLASS_TY) { + // ignore any erroneous types that ended up in the hierarchy + return; + } + ClassTy classTy = (ClassTy) type; + HierarchyNode child = get(classTy.sym()); + // add a new edge to the direct supertype + PathNode existing = ancestors.putIfAbsent(child.sym, new PathNode(classTy, null)); + if (existing != null) { + // if this child has already been added don't re-process its ancestors + return; + } + // copy and extend edges for the transitive supertypes + for (Map.Entry<ClassSymbol, PathNode> n : child.ancestors.entrySet()) { + ancestors.putIfAbsent(n.getKey(), new PathNode(classTy, n.getValue())); + } + } + + /** The supertype closure of this node. */ + private Set<ClassSymbol> closure() { + return ancestors.keySet(); + } + } + + private HierarchyNode compute(ClassSymbol sym) { + HierarchyNode node = new HierarchyNode(sym); + TypeBoundClass info = env.get(sym); + if (info == null) { + throw TurbineError.format(/* source= */ null, ErrorKind.SYMBOL_NOT_FOUND, sym); + } + if (info.superClassType() != null) { + node.add(info.superClassType()); + } + for (Type type : info.interfaceTypes()) { + node.add(type); + } + return node; + } + + private HierarchyNode get(ClassSymbol sym) { + // dont use computeIfAbsent, to support re-entrant lookups + HierarchyNode result = cache.get(sym); + if (result != null) { + return result; + } + result = compute(sym); + cache.put(sym, result); + return result; + } + + /** + * Returns a list of types on the path between the given type {@code t} and a transitive + * superclass {@code s}, or an empty list if no such path exists. + */ + ImmutableList<ClassTy> search(Type t, ClassSymbol s) { + if (t.tyKind() != TyKind.CLASS_TY) { + return ImmutableList.of(); + } + ClassTy classTy = (ClassTy) t; + if (classTy.sym().equals(s)) { + return ImmutableList.of(classTy); + } + HierarchyNode node = get(classTy.sym()); + PathNode path = node.ancestors.get(s); + if (path == null) { + return ImmutableList.of(); + } + ImmutableList.Builder<ClassTy> result = ImmutableList.builder(); + result.add(classTy); + while (path != null) { + result.add(path.type); + path = path.ancestor; + } + return result.build().reverse(); + } + + /** + * Returns all classes in the transitive supertype hierarchy of the given class, including the + * class itself. + * + * <p>The iteration order of the results is undefined, and in particular no guarantees are made + * about the ordering of sub-types and super-types. + */ + public Iterable<ClassSymbol> transitiveSupertypes(ClassSymbol s) { + return Iterables.concat(ImmutableList.of(s), get(s).closure()); + } +} diff --git a/java/com/google/turbine/processing/ModelFactory.java b/java/com/google/turbine/processing/ModelFactory.java new file mode 100644 index 0000000..9b782cd --- /dev/null +++ b/java/com/google/turbine/processing/ModelFactory.java @@ -0,0 +1,391 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; +import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; +import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo; +import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.lookup.LookupKey; +import com.google.turbine.binder.lookup.LookupResult; +import com.google.turbine.binder.lookup.TopLevelIndex; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.ParamSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.processing.TurbineElement.TurbineExecutableElement; +import com.google.turbine.processing.TurbineElement.TurbineFieldElement; +import com.google.turbine.processing.TurbineElement.TurbineNoTypeElement; +import com.google.turbine.processing.TurbineElement.TurbinePackageElement; +import com.google.turbine.processing.TurbineElement.TurbineParameterElement; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.processing.TurbineElement.TurbineTypeParameterElement; +import com.google.turbine.processing.TurbineTypeMirror.TurbineArrayType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineDeclaredType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineErrorType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineExecutableType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineIntersectionType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineNoType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineNullType; +import com.google.turbine.processing.TurbineTypeMirror.TurbinePackageType; +import com.google.turbine.processing.TurbineTypeMirror.TurbinePrimitiveType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineTypeVariable; +import com.google.turbine.processing.TurbineTypeMirror.TurbineVoidType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineWildcardType; +import com.google.turbine.tree.Tree.Ident; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.ArrayTy; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ErrorTy; +import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.type.Type.MethodTy; +import com.google.turbine.type.Type.PrimTy; +import com.google.turbine.type.Type.TyVar; +import com.google.turbine.type.Type.WildTy; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; + +/** + * A factoy for turbine's implementations of {@link Element} and {@link TypeMirror}. + * + * <p>The model provided by those interfaces contains cycles between types and elements, e.g. {@link + * Element#asType} and {@link javax.lang.model.type.DeclaredType#asElement}. Turbine's internal + * model uses an immutable representation of classes and types which cannot represent cycles + * directly. Instead, the implementations in {@link TurbineElement} and {@link TurbineTypeMirror} + * maintain a reference to this class, and use it to lazily construct edges in the type and element + * graph. + */ +public class ModelFactory { + + public Env<ClassSymbol, ? extends TypeBoundClass> env; + + private final AtomicInteger round = new AtomicInteger(0); + + public void round(CompoundEnv<ClassSymbol, TypeBoundClass> env, TopLevelIndex tli) { + this.env = env; + this.tli = tli; + round.getAndIncrement(); + cha.round(env); + } + + private final HashMap<Type, TurbineTypeMirror> typeCache = new HashMap<>(); + + private final Map<FieldSymbol, TurbineFieldElement> fieldCache = new HashMap<>(); + private final Map<MethodSymbol, TurbineExecutableElement> methodCache = new HashMap<>(); + private final Map<ClassSymbol, TurbineTypeElement> classCache = new HashMap<>(); + private final Map<ParamSymbol, TurbineParameterElement> paramCache = new HashMap<>(); + private final Map<TyVarSymbol, TurbineTypeParameterElement> tyParamCache = new HashMap<>(); + private final Map<PackageSymbol, TurbinePackageElement> packageCache = new HashMap<>(); + + private final HashMap<CharSequence, ClassSymbol> inferSymbolCache = new HashMap<>(); + + private final ClassHierarchy cha; + private final ClassLoader processorLoader; + + private TopLevelIndex tli; + + public ModelFactory( + Env<ClassSymbol, ? extends TypeBoundClass> env, + ClassLoader processorLoader, + TopLevelIndex tli) { + this.env = requireNonNull(env); + this.cha = new ClassHierarchy(env); + this.processorLoader = requireNonNull(processorLoader); + this.tli = requireNonNull(tli); + } + + TypeMirror asTypeMirror(Type type) { + return typeCache.computeIfAbsent(type, this::createTypeMirror); + } + + /** + * Returns a supplier that memoizes the result of the input supplier. + * + * <p>It ensures that the results are invalidated after each annotation processing round, to + * support computations that depend on information in the current round and which might change in + * future, e.g. as additional types are generated. + */ + <T> Supplier<T> memoize(Supplier<T> s) { + return new Supplier<T>() { + T v; + int initializedInRound = -1; + + @Override + public T get() { + int r = round.get(); + if (initializedInRound != r) { + v = s.get(); + initializedInRound = r; + } + return v; + } + }; + } + + /** Creates a {@link TurbineTypeMirror} backed by a {@link Type}. */ + private TurbineTypeMirror createTypeMirror(Type type) { + switch (type.tyKind()) { + case PRIM_TY: + if (((PrimTy) type).primkind() == TurbineConstantTypeKind.STRING) { + return new TurbineDeclaredType(this, ClassTy.STRING); + } + return new TurbinePrimitiveType(this, (PrimTy) type); + case CLASS_TY: + return new TurbineDeclaredType(this, (ClassTy) type); + case ARRAY_TY: + return new TurbineArrayType(this, (ArrayTy) type); + case VOID_TY: + return new TurbineVoidType(this); + case WILD_TY: + return new TurbineWildcardType(this, (WildTy) type); + case TY_VAR: + return new TurbineTypeVariable(this, (TyVar) type); + case INTERSECTION_TY: + IntersectionTy intersectionTy = (IntersectionTy) type; + switch (intersectionTy.bounds().size()) { + case 0: + return createTypeMirror(ClassTy.OBJECT); + case 1: + return createTypeMirror(getOnlyElement(intersectionTy.bounds())); + default: + return new TurbineIntersectionType(this, intersectionTy); + } + case NONE_TY: + return new TurbineNoType(this); + case METHOD_TY: + return new TurbineExecutableType(this, (MethodTy) type); + case ERROR_TY: + return new TurbineErrorType(this, (ErrorTy) type); + } + throw new AssertionError(type.tyKind()); + } + + /** Creates a list of {@link TurbineTypeMirror}s backed by the given {@link Type}s. */ + ImmutableList<TypeMirror> asTypeMirrors(Iterable<? extends Type> types) { + ImmutableList.Builder<TypeMirror> result = ImmutableList.builder(); + for (Type type : types) { + result.add(asTypeMirror(type)); + } + return result.build(); + } + + NoType noType() { + return (NoType) asTypeMirror(Type.NONE); + } + + NoType packageType(PackageSymbol symbol) { + return new TurbinePackageType(this, symbol); + } + + public NullType nullType() { + return new TurbineNullType(this); + } + + /** Creates an {@link Element} backed by the given {@link Symbol}. */ + Element element(Symbol symbol) { + switch (symbol.symKind()) { + case CLASS: + return typeElement((ClassSymbol) symbol); + case TY_PARAM: + return typeParameterElement((TyVarSymbol) symbol); + case METHOD: + return executableElement((MethodSymbol) symbol); + case FIELD: + return fieldElement((FieldSymbol) symbol); + case PARAMETER: + return parameterElement((ParamSymbol) symbol); + case PACKAGE: + return packageElement((PackageSymbol) symbol); + case MODULE: + break; + } + throw new AssertionError(symbol.symKind()); + } + + Element noElement(String name) { + return new TurbineNoTypeElement(this, name); + } + + TurbineFieldElement fieldElement(FieldSymbol symbol) { + return fieldCache.computeIfAbsent(symbol, k -> new TurbineFieldElement(this, symbol)); + } + + TurbineExecutableElement executableElement(MethodSymbol symbol) { + return methodCache.computeIfAbsent(symbol, k -> new TurbineExecutableElement(this, symbol)); + } + + public TurbineTypeElement typeElement(ClassSymbol symbol) { + Verify.verify(!symbol.simpleName().equals("package-info"), "%s", symbol); + return classCache.computeIfAbsent(symbol, k -> new TurbineTypeElement(this, symbol)); + } + + TurbinePackageElement packageElement(PackageSymbol symbol) { + return packageCache.computeIfAbsent(symbol, k -> new TurbinePackageElement(this, symbol)); + } + + VariableElement parameterElement(ParamSymbol sym) { + return paramCache.computeIfAbsent(sym, k -> new TurbineParameterElement(this, sym)); + } + + TurbineTypeParameterElement typeParameterElement(TyVarSymbol sym) { + return tyParamCache.computeIfAbsent(sym, k -> new TurbineTypeParameterElement(this, sym)); + } + + ImmutableSet<Element> elements(ImmutableSet<? extends Symbol> symbols) { + ImmutableSet.Builder<Element> result = ImmutableSet.builder(); + for (Symbol symbol : symbols) { + result.add(element(symbol)); + } + return result.build(); + } + + public ClassSymbol inferSymbol(CharSequence name) { + return inferSymbolCache.computeIfAbsent(name, key -> inferSymbolImpl(name)); + } + + private ClassSymbol inferSymbolImpl(CharSequence name) { + LookupResult lookup = tli.scope().lookup(new LookupKey(asIdents(name))); + if (lookup == null) { + return null; + } + ClassSymbol sym = (ClassSymbol) lookup.sym(); + for (Ident bit : lookup.remaining()) { + sym = getSymbol(sym).children().get(bit.value()); + if (sym == null) { + return null; + } + } + return sym; + } + + private static ImmutableList<Ident> asIdents(CharSequence name) { + ImmutableList.Builder<Ident> result = ImmutableList.builder(); + for (String bit : Splitter.on('.').split(name)) { + result.add(new Ident(-1, bit)); + } + return result.build(); + } + + /** + * Returns the {@link TypeBoundClass} for the given {@link ClassSymbol} from the current + * environment. + */ + TypeBoundClass getSymbol(ClassSymbol sym) { + return env.get(sym); + } + + MethodInfo getMethodInfo(MethodSymbol method) { + TypeBoundClass info = getSymbol(method.owner()); + for (MethodInfo m : info.methods()) { + if (m.sym().equals(method)) { + return m; + } + } + return null; + } + + ParamInfo getParamInfo(ParamSymbol sym) { + MethodInfo info = getMethodInfo(sym.owner()); + for (ParamInfo p : info.parameters()) { + if (p.sym().equals(sym)) { + return p; + } + } + return null; + } + + FieldInfo getFieldInfo(FieldSymbol symbol) { + TypeBoundClass info = getSymbol(symbol.owner()); + requireNonNull(info, symbol.owner().toString()); + for (FieldInfo field : info.fields()) { + if (field.sym().equals(symbol)) { + return field; + } + } + throw new AssertionError(symbol); + } + + TyVarInfo getTyVarInfo(TyVarSymbol tyVar) { + Symbol owner = tyVar.owner(); + Verify.verifyNotNull(owner); // TODO(cushon): capture variables + ImmutableMap<TyVarSymbol, TyVarInfo> tyParams; + switch (owner.symKind()) { + case METHOD: + tyParams = getMethodInfo((MethodSymbol) owner).tyParams(); + break; + case CLASS: + tyParams = getSymbol((ClassSymbol) owner).typeParameterTypes(); + break; + default: + throw new AssertionError(owner.symKind()); + } + return tyParams.get(tyVar); + } + + static ClassSymbol enclosingClass(Symbol sym) { + switch (sym.symKind()) { + case CLASS: + return (ClassSymbol) sym; + case TY_PARAM: + return enclosingClass(((TyVarSymbol) sym).owner()); + case METHOD: + return ((MethodSymbol) sym).owner(); + case FIELD: + return ((FieldSymbol) sym).owner(); + case PARAMETER: + return ((ParamSymbol) sym).owner().owner(); + case PACKAGE: + case MODULE: + throw new IllegalArgumentException(sym.toString()); + } + throw new AssertionError(sym.symKind()); + } + + ClassHierarchy cha() { + return cha; + } + + ClassLoader processorLoader() { + return processorLoader; + } + + TopLevelIndex tli() { + return tli; + } +} diff --git a/java/com/google/turbine/processing/TurbineAnnotationMirror.java b/java/com/google/turbine/processing/TurbineAnnotationMirror.java new file mode 100644 index 0000000..5ea3de1 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineAnnotationMirror.java @@ -0,0 +1,349 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getLast; + +import com.google.common.base.Joiner; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; +import com.google.turbine.binder.bound.TurbineClassValue; +import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; +import com.google.turbine.model.Const; +import com.google.turbine.model.Const.ArrayInitValue; +import com.google.turbine.processing.TurbineElement.TurbineExecutableElement; +import com.google.turbine.processing.TurbineElement.TurbineFieldElement; +import com.google.turbine.type.AnnoInfo; +import com.google.turbine.type.Type.ErrorTy; +import com.google.turbine.type.Type.TyKind; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.TypeMirror; + +/** + * An implementation of {@link AnnotationMirror} and {@link AnnotationValue} backed by {@link + * AnnoInfo} and {@link Const.Value}. + */ +class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, AnnotationMirror { + + static TurbineAnnotationValueMirror annotationValue(ModelFactory factory, Const value) { + switch (value.kind()) { + case ARRAY: + return new TurbineArrayConstant(factory, (ArrayInitValue) value); + case PRIMITIVE: + return new TurbinePrimitiveConstant((Const.Value) value); + case CLASS_LITERAL: + return new TurbineClassConstant(factory, (TurbineClassValue) value); + case ENUM_CONSTANT: + return new TurbineEnumConstant(factory, (EnumConstantValue) value); + case ANNOTATION: + return new TurbineAnnotationMirror(factory, (TurbineAnnotationValue) value); + } + throw new AssertionError(value.kind()); + } + + static TurbineAnnotationMirror create(ModelFactory factory, AnnoInfo anno) { + return new TurbineAnnotationMirror(factory, new TurbineAnnotationValue(anno)); + } + + private final TurbineAnnotationValue value; + private final AnnoInfo anno; + private final Supplier<DeclaredType> type; + private final Supplier<ImmutableMap<String, MethodInfo>> elements; + private final Supplier<ImmutableMap<ExecutableElement, AnnotationValue>> elementValues; + private final Supplier<ImmutableMap<ExecutableElement, AnnotationValue>> + elementValuesWithDefaults; + + private TurbineAnnotationMirror(ModelFactory factory, TurbineAnnotationValue value) { + this.value = value; + this.anno = value.info(); + this.type = + factory.memoize( + new Supplier<DeclaredType>() { + @Override + public DeclaredType get() { + if (anno.sym() == null) { + return (ErrorType) + factory.asTypeMirror(ErrorTy.create(getLast(anno.tree().name()).value())); + } + return (DeclaredType) factory.typeElement(anno.sym()).asType(); + } + }); + this.elements = + factory.memoize( + new Supplier<ImmutableMap<String, MethodInfo>>() { + @Override + public ImmutableMap<String, MethodInfo> get() { + ImmutableMap.Builder<String, MethodInfo> result = ImmutableMap.builder(); + for (MethodInfo m : factory.getSymbol(anno.sym()).methods()) { + checkState(m.parameters().isEmpty()); + result.put(m.name(), m); + } + return result.build(); + } + }); + this.elementValues = + factory.memoize( + new Supplier<ImmutableMap<ExecutableElement, AnnotationValue>>() { + @Override + public ImmutableMap<ExecutableElement, AnnotationValue> get() { + ImmutableMap.Builder<ExecutableElement, AnnotationValue> result = + ImmutableMap.builder(); + for (Map.Entry<String, Const> value : anno.values().entrySet()) { + result.put( + factory.executableElement(elements.get().get(value.getKey()).sym()), + annotationValue(factory, value.getValue())); + } + return result.build(); + } + }); + this.elementValuesWithDefaults = + factory.memoize( + new Supplier<ImmutableMap<ExecutableElement, AnnotationValue>>() { + @Override + public ImmutableMap<ExecutableElement, AnnotationValue> get() { + Map<ExecutableElement, AnnotationValue> result = new LinkedHashMap<>(); + result.putAll(getElementValues()); + for (MethodInfo method : elements.get().values()) { + if (method.defaultValue() == null) { + continue; + } + TurbineExecutableElement element = factory.executableElement(method.sym()); + if (result.containsKey(element)) { + continue; + } + result.put(element, annotationValue(factory, method.defaultValue())); + } + return ImmutableMap.copyOf(result); + } + }); + } + + @Override + public int hashCode() { + return anno.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineAnnotationMirror + && anno.equals(((TurbineAnnotationMirror) obj).anno); + } + + @Override + public String toString() { + return anno.toString(); + } + + @Override + public DeclaredType getAnnotationType() { + return type.get(); + } + + public Map<? extends ExecutableElement, ? extends AnnotationValue> + getElementValuesWithDefaults() { + return elementValuesWithDefaults.get(); + } + + @Override + public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { + return elementValues.get(); + } + + @Override + public AnnotationMirror getValue() { + return this; + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitAnnotation(getValue(), p); + } + + public AnnoInfo anno() { + return anno; + } + + @Override + public Const value() { + return value; + } + + private static class TurbineArrayConstant implements TurbineAnnotationValueMirror { + + private final ArrayInitValue value; + private final Supplier<ImmutableList<AnnotationValue>> elements; + + private TurbineArrayConstant(ModelFactory factory, ArrayInitValue value) { + this.value = value; + this.elements = + factory.memoize( + new Supplier<ImmutableList<AnnotationValue>>() { + @Override + public ImmutableList<AnnotationValue> get() { + ImmutableList.Builder<AnnotationValue> values = ImmutableList.builder(); + for (Const element : value.elements()) { + values.add(annotationValue(factory, element)); + } + return values.build(); + } + }); + } + + @Override + public List<AnnotationValue> getValue() { + return elements.get(); + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitArray(elements.get(), p); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + Joiner.on(", ").appendTo(sb, elements.get()); + sb.append("}"); + return sb.toString(); + } + + @Override + public Const value() { + return value; + } + } + + private static class TurbineClassConstant implements TurbineAnnotationValueMirror { + + private final TurbineClassValue value; + private final TypeMirror typeMirror; + + private TurbineClassConstant(ModelFactory factory, TurbineClassValue value) { + this.value = value; + this.typeMirror = factory.asTypeMirror(value.type()); + } + + @Override + public TypeMirror getValue() { + return typeMirror; + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + if (value.type().tyKind() == TyKind.ERROR_TY) { + // represent unresolvable class literals as the string value "<error>" for compatibility + // with javac: https://bugs.openjdk.java.net/browse/JDK-8229535 + return v.visitString("<error>", p); + } else { + return v.visitType(getValue(), p); + } + } + + @Override + public String toString() { + return typeMirror + ".class"; + } + + @Override + public Const value() { + return value; + } + } + + private static class TurbineEnumConstant implements TurbineAnnotationValueMirror { + + private final EnumConstantValue value; + private final TurbineFieldElement fieldElement; + + private TurbineEnumConstant(ModelFactory factory, EnumConstantValue value) { + this.value = value; + this.fieldElement = factory.fieldElement(value.sym()); + } + + @Override + public Object getValue() { + return fieldElement; + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return v.visitEnumConstant(fieldElement, p); + } + + @Override + public String toString() { + return fieldElement.getEnclosingElement() + "." + fieldElement.getSimpleName(); + } + + @Override + public Const value() { + return value; + } + } + + private static class TurbinePrimitiveConstant implements TurbineAnnotationValueMirror { + + private final Const.Value value; + + public TurbinePrimitiveConstant(Const.Value value) { + this.value = value; + } + + @Override + public Object getValue() { + return value.getValue(); + } + + @Override + public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { + return value.accept(v, p); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbinePrimitiveConstant + && value.equals(((TurbinePrimitiveConstant) obj).value); + } + + @Override + public Const value() { + return value; + } + } +} diff --git a/java/com/google/turbine/processing/TurbineAnnotationProxy.java b/java/com/google/turbine/processing/TurbineAnnotationProxy.java new file mode 100644 index 0000000..c39f310 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineAnnotationProxy.java @@ -0,0 +1,176 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; +import com.google.turbine.binder.bound.TurbineClassValue; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.model.Const; +import com.google.turbine.model.Const.ArrayInitValue; +import com.google.turbine.model.Const.Value; +import com.google.turbine.type.AnnoInfo; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeMirror; + +/** An {@link InvocationHandler} for reflectively accessing annotations. */ +class TurbineAnnotationProxy implements InvocationHandler { + + static <A extends Annotation> A create( + ModelFactory factory, Class<A> annotationType, AnnoInfo anno) { + ClassLoader loader = annotationType.getClassLoader(); + if (loader == null) { + // annotation was loaded from the system classloader, e.g. java.lang.annotation.* + loader = factory.processorLoader(); + } + return annotationType.cast( + Proxy.newProxyInstance( + loader, + new Class<?>[] {annotationType}, + new TurbineAnnotationProxy(factory, loader, annotationType, anno))); + } + + private final ModelFactory factory; + private final ClassLoader loader; + private final Class<?> annotationType; + private final AnnoInfo anno; + + TurbineAnnotationProxy( + ModelFactory factory, ClassLoader loader, Class<?> annotationType, AnnoInfo anno) { + this.factory = factory; + this.loader = loader; + this.annotationType = annotationType; + this.anno = anno; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + switch (method.getName()) { + case "hashCode": + checkArgument(args == null); + return anno.hashCode(); + case "annotationType": + checkArgument(args == null); + return annotationType; + case "equals": + checkArgument(args.length == 1); + return proxyEquals(args[0]); + case "toString": + checkArgument(args == null); + return anno.toString(); + default: + break; + } + Const value = anno.values().get(method.getName()); + if (value != null) { + return constValue(method.getReturnType(), factory, loader, value); + } + for (TypeBoundClass.MethodInfo m : factory.getSymbol(anno.sym()).methods()) { + if (m.name().contentEquals(method.getName())) { + return constValue(method.getReturnType(), factory, loader, m.defaultValue()); + } + } + throw new NoSuchMethodError(method.getName()); + } + + public boolean proxyEquals(Object other) { + if (!annotationType.isInstance(other)) { + return false; + } + if (!Proxy.isProxyClass(other.getClass())) { + return false; + } + InvocationHandler handler = Proxy.getInvocationHandler(other); + if (!(handler instanceof TurbineAnnotationProxy)) { + return false; + } + TurbineAnnotationProxy that = (TurbineAnnotationProxy) handler; + return anno.equals(that.anno); + } + + static Object constValue( + Class<?> returnType, ModelFactory factory, ClassLoader loader, Const value) { + switch (value.kind()) { + case PRIMITIVE: + return ((Value) value).getValue(); + case ARRAY: + return constArrayValue(returnType, factory, loader, (Const.ArrayInitValue) value); + case ENUM_CONSTANT: + return constEnumValue(loader, (EnumConstantValue) value); + case ANNOTATION: + return constAnnotationValue(factory, loader, (TurbineAnnotationValue) value); + case CLASS_LITERAL: + return constClassValue(factory, (TurbineClassValue) value); + } + throw new AssertionError(value.kind()); + } + + private static Object constArrayValue( + Class<?> returnType, ModelFactory factory, ClassLoader loader, ArrayInitValue value) { + if (returnType.getComponentType().equals(Class.class)) { + List<TypeMirror> result = new ArrayList<>(); + for (Const element : value.elements()) { + result.add(factory.asTypeMirror(((TurbineClassValue) element).type())); + } + throw new MirroredTypesException(result); + } + Object result = Array.newInstance(returnType.getComponentType(), value.elements().size()); + int idx = 0; + for (Const element : value.elements()) { + Object v = constValue(returnType, factory, loader, element); + Array.set(result, idx++, v); + } + return result; + } + + @SuppressWarnings("unchecked") // Enum.class + private static Object constEnumValue(ClassLoader loader, EnumConstantValue value) { + Class<?> clazz; + try { + clazz = loader.loadClass(value.sym().owner().toString()); + } catch (ClassNotFoundException e) { + throw new LinkageError(e.getMessage(), e); + } + return Enum.valueOf(clazz.asSubclass(Enum.class), value.sym().name()); + } + + private static Object constAnnotationValue( + ModelFactory factory, ClassLoader loader, TurbineAnnotationValue value) { + try { + String name = value.sym().binaryName().replace('/', '.'); + Class<? extends Annotation> clazz = + Class.forName(name, false, loader).asSubclass(Annotation.class); + return create(factory, clazz, value.info()); + } catch (ClassNotFoundException e) { + throw new LinkageError(e.getMessage(), e); + } + } + + private static Object constClassValue(ModelFactory factory, TurbineClassValue value) { + throw new MirroredTypeException(factory.asTypeMirror(value.type())); + } +} diff --git a/java/com/google/turbine/processing/TurbineAnnotationValueMirror.java b/java/com/google/turbine/processing/TurbineAnnotationValueMirror.java new file mode 100644 index 0000000..a196750 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineAnnotationValueMirror.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 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.processing; + +import com.google.turbine.model.Const; +import javax.lang.model.element.AnnotationValue; + +/** An {@link AnnotationValue} backed by a {@link Const}. */ +public interface TurbineAnnotationValueMirror extends AnnotationValue { + Const value(); +} diff --git a/java/com/google/turbine/processing/TurbineElement.java b/java/com/google/turbine/processing/TurbineElement.java new file mode 100644 index 0000000..c22a442 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineElement.java @@ -0,0 +1,1298 @@ +/* + * Copyright 2019 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.processing; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.turbine.binder.bound.AnnotationMetadata; +import com.google.turbine.binder.bound.SourceTypeBoundClass; +import com.google.turbine.binder.bound.TurbineAnnotationValue; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; +import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; +import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo; +import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; +import com.google.turbine.binder.lookup.PackageScope; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.ParamSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineError.ErrorKind; +import com.google.turbine.model.Const; +import com.google.turbine.model.Const.ArrayInitValue; +import com.google.turbine.model.TurbineFlag; +import com.google.turbine.model.TurbineTyKind; +import com.google.turbine.tree.Tree.MethDecl; +import com.google.turbine.tree.Tree.TyDecl; +import com.google.turbine.tree.Tree.VarDecl; +import com.google.turbine.type.AnnoInfo; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.ErrorTy; +import java.lang.annotation.Annotation; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** An {@link Element} implementation backed by a {@link Symbol}. */ +public abstract class TurbineElement implements Element { + + public abstract Symbol sym(); + + public abstract String javadoc(); + + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object obj); + + protected final ModelFactory factory; + private final Supplier<ImmutableList<AnnotationMirror>> annotationMirrors; + + protected <T> Supplier<T> memoize(Supplier<T> supplier) { + return factory.memoize(supplier); + } + + protected TurbineElement(ModelFactory factory) { + this.factory = requireNonNull(factory); + this.annotationMirrors = + factory.memoize( + new Supplier<ImmutableList<AnnotationMirror>>() { + @Override + public ImmutableList<AnnotationMirror> get() { + ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder(); + for (AnnoInfo anno : annos()) { + result.add(TurbineAnnotationMirror.create(factory, anno)); + } + return result.build(); + } + }); + } + + static AnnoInfo getAnnotation(Iterable<AnnoInfo> annos, ClassSymbol sym) { + for (AnnoInfo anno : annos) { + if (Objects.equals(anno.sym(), sym)) { + return anno; + } + } + return null; + } + + @Override + public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + ClassSymbol sym = new ClassSymbol(annotationType.getName().replace('.', '/')); + TypeBoundClass info = factory.getSymbol(sym); + if (info == null) { + return null; + } + AnnoInfo anno = getAnnotation(annos(), sym); + if (anno == null) { + return null; + } + return TurbineAnnotationProxy.create(factory, annotationType, anno); + } + + @Override + public final <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) { + ClassSymbol sym = new ClassSymbol(annotationType.getName().replace('.', '/')); + TypeBoundClass info = factory.getSymbol(sym); + if (info == null) { + return null; + } + AnnotationMetadata metadata = info.annotationMetadata(); + if (metadata == null) { + return null; + } + List<A> result = new ArrayList<>(); + for (AnnoInfo anno : annos()) { + if (anno.sym().equals(sym)) { + result.add(TurbineAnnotationProxy.create(factory, annotationType, anno)); + continue; + } + if (anno.sym().equals(metadata.repeatable())) { + ArrayInitValue arrayValue = (ArrayInitValue) anno.values().get("value"); + for (Const element : arrayValue.elements()) { + result.add( + TurbineAnnotationProxy.create( + factory, annotationType, ((TurbineAnnotationValue) element).info())); + } + } + } + return Iterables.toArray(result, annotationType); + } + + @Override + public final List<? extends AnnotationMirror> getAnnotationMirrors() { + return annotationMirrors.get(); + } + + List<? extends AnnotationMirror> getAllAnnotationMirrors() { + return getAnnotationMirrors(); + } + + protected abstract ImmutableList<AnnoInfo> annos(); + + /** A {@link TypeElement} implementation backed by a {@link ClassSymbol}. */ + static class TurbineTypeElement extends TurbineElement implements TypeElement { + + @Override + public int hashCode() { + return sym.hashCode(); + } + + private final ClassSymbol sym; + private final Supplier<TypeBoundClass> info; + + TurbineTypeElement(ModelFactory factory, ClassSymbol sym) { + super(factory); + this.sym = requireNonNull(sym); + this.info = + memoize( + new Supplier<TypeBoundClass>() { + @Override + public TypeBoundClass get() { + return factory.getSymbol(sym); + } + }); + } + + @Nullable + TypeBoundClass info() { + return info.get(); + } + + TypeBoundClass infoNonNull() { + TypeBoundClass info = info(); + if (info == null) { + throw TurbineError.format(/* source= */ null, ErrorKind.SYMBOL_NOT_FOUND, sym); + } + return info; + } + + @Override + public NestingKind getNestingKind() { + TypeBoundClass info = info(); + return (info != null && info.owner() != null) ? NestingKind.MEMBER : NestingKind.TOP_LEVEL; + } + + private final Supplier<TurbineName> qualifiedName = + memoize( + new Supplier<TurbineName>() { + @Override + public TurbineName get() { + TypeBoundClass info = info(); + if (info == null || info.owner() == null) { + return new TurbineName(sym.toString()); + } + ClassSymbol sym = sym(); + Deque<String> flat = new ArrayDeque<>(); + while (info.owner() != null) { + flat.addFirst(sym.binaryName().substring(info.owner().binaryName().length() + 1)); + sym = info.owner(); + info = factory.getSymbol(sym); + } + flat.addFirst(sym.toString()); + return new TurbineName(Joiner.on('.').join(flat)); + } + }); + + @Override + public Name getQualifiedName() { + return qualifiedName.get(); + } + + private final Supplier<TypeMirror> superclass = + memoize( + new Supplier<TypeMirror>() { + @Override + public TypeMirror get() { + TypeBoundClass info = infoNonNull(); + switch (info.kind()) { + case CLASS: + case ENUM: + if (info.superclass() != null) { + return factory.asTypeMirror(info.superClassType()); + } + if (info instanceof SourceTypeBoundClass) { + // support simple name for stuff that doesn't exist + TyDecl decl = ((SourceTypeBoundClass) info).decl(); + if (decl.xtnds().isPresent()) { + return factory.asTypeMirror( + ErrorTy.create(decl.xtnds().get().name().value())); + } + } + return factory.noType(); + case INTERFACE: + case ANNOTATION: + return factory.noType(); + } + throw new AssertionError(info.kind()); + } + }); + + @Override + public TypeMirror getSuperclass() { + return superclass.get(); + } + + @Override + public String toString() { + return getQualifiedName().toString(); + } + + private final Supplier<List<TypeMirror>> interfaces = + memoize( + new Supplier<List<TypeMirror>>() { + @Override + public List<TypeMirror> get() { + return factory.asTypeMirrors(infoNonNull().interfaceTypes()); + } + }); + + @Override + public List<? extends TypeMirror> getInterfaces() { + return interfaces.get(); + } + + private final Supplier<ImmutableList<TypeParameterElement>> typeParameters = + memoize( + new Supplier<ImmutableList<TypeParameterElement>>() { + @Override + public ImmutableList<TypeParameterElement> get() { + ImmutableList.Builder<TypeParameterElement> result = ImmutableList.builder(); + for (TyVarSymbol p : infoNonNull().typeParameters().values()) { + result.add(factory.typeParameterElement(p)); + } + return result.build(); + } + }); + + @Override + public List<? extends TypeParameterElement> getTypeParameters() { + return typeParameters.get(); + } + + private final Supplier<TypeMirror> type = + memoize( + new Supplier<TypeMirror>() { + @Override + public TypeMirror get() { + return factory.asTypeMirror(asGenericType(sym)); + } + + ClassTy asGenericType(ClassSymbol symbol) { + TypeBoundClass info = info(); + if (info == null) { + return ClassTy.asNonParametricClassTy(symbol); + } + Deque<Type.ClassTy.SimpleClassTy> simples = new ArrayDeque<>(); + simples.addFirst(simple(symbol, info)); + while (info.owner() != null && (info.access() & TurbineFlag.ACC_STATIC) == 0) { + symbol = info.owner(); + info = factory.getSymbol(symbol); + simples.addFirst(simple(symbol, info)); + } + return ClassTy.create(ImmutableList.copyOf(simples)); + } + + private SimpleClassTy simple(ClassSymbol sym, TypeBoundClass info) { + ImmutableList.Builder<Type> args = ImmutableList.builder(); + for (TyVarSymbol t : info.typeParameters().values()) { + args.add(Type.TyVar.create(t, ImmutableList.of())); + } + return SimpleClassTy.create(sym, args.build(), ImmutableList.of()); + } + }); + + @Override + public TypeMirror asType() { + return type.get(); + } + + @Override + public ElementKind getKind() { + TypeBoundClass info = infoNonNull(); + switch (info.kind()) { + case CLASS: + return ElementKind.CLASS; + case INTERFACE: + return ElementKind.INTERFACE; + case ENUM: + return ElementKind.ENUM; + case ANNOTATION: + return ElementKind.ANNOTATION_TYPE; + } + throw new AssertionError(info.kind()); + } + + @Override + public Set<Modifier> getModifiers() { + return asModifierSet(ModifierOwner.TYPE, infoNonNull().access() & ~TurbineFlag.ACC_SUPER); + } + + private final Supplier<TurbineName> simpleName = + memoize( + new Supplier<TurbineName>() { + @Override + public TurbineName get() { + TypeBoundClass info = info(); + if (info == null || info.owner() == null) { + return new TurbineName(sym.simpleName()); + } + return new TurbineName( + sym.binaryName().substring(info.owner().binaryName().length() + 1)); + } + }); + + @Override + public Name getSimpleName() { + return simpleName.get(); + } + + private final Supplier<Element> enclosing = + memoize( + new Supplier<Element>() { + @Override + public Element get() { + return getNestingKind().equals(NestingKind.TOP_LEVEL) + ? factory.packageElement(sym.owner()) + : factory.typeElement(info().owner()); + } + }); + + @Override + public Element getEnclosingElement() { + return enclosing.get(); + } + + private final Supplier<ImmutableList<Element>> enclosed = + memoize( + new Supplier<ImmutableList<Element>>() { + @Override + public ImmutableList<Element> get() { + TypeBoundClass info = infoNonNull(); + ImmutableList.Builder<Element> result = ImmutableList.builder(); + for (FieldInfo field : info.fields()) { + result.add(factory.fieldElement(field.sym())); + } + for (MethodInfo method : info.methods()) { + result.add(factory.executableElement(method.sym())); + } + for (ClassSymbol child : info.children().values()) { + result.add(factory.typeElement(child)); + } + return result.build(); + } + }); + + @Override + public List<? extends Element> getEnclosedElements() { + return enclosed.get(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitType(this, p); + } + + @Override + public ClassSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + TypeBoundClass info = info(); + if (!(info instanceof SourceTypeBoundClass)) { + return null; + } + return ((SourceTypeBoundClass) info).decl().javadoc(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineTypeElement && sym.equals(((TurbineTypeElement) obj).sym); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return infoNonNull().annotations(); + } + + @Override + public final <A extends Annotation> A getAnnotation(Class<A> annotationType) { + ClassSymbol sym = new ClassSymbol(annotationType.getName().replace('.', '/')); + AnnoInfo anno = getAnnotation(annos(), sym); + if (anno != null) { + return TurbineAnnotationProxy.create(factory, annotationType, anno); + } + if (!isAnnotationInherited(sym)) { + return null; + } + ClassSymbol superclass = infoNonNull().superclass(); + while (superclass != null) { + TypeBoundClass info = factory.getSymbol(superclass); + if (info == null) { + break; + } + anno = getAnnotation(info.annotations(), sym); + if (anno != null) { + return TurbineAnnotationProxy.create(factory, annotationType, anno); + } + superclass = info.superclass(); + } + return null; + } + + @Override + List<? extends AnnotationMirror> getAllAnnotationMirrors() { + Map<ClassSymbol, AnnotationMirror> result = new LinkedHashMap<>(); + for (AnnoInfo anno : annos()) { + result.put(anno.sym(), TurbineAnnotationMirror.create(factory, anno)); + } + ClassSymbol superclass = infoNonNull().superclass(); + while (superclass != null) { + TypeBoundClass i = factory.getSymbol(superclass); + if (i == null) { + break; + } + for (AnnoInfo anno : i.annotations()) { + addAnnotationFromSuper(result, anno); + } + superclass = i.superclass(); + } + return ImmutableList.copyOf(result.values()); + } + + private void addAnnotationFromSuper(Map<ClassSymbol, AnnotationMirror> result, AnnoInfo anno) { + if (!isAnnotationInherited(anno.sym())) { + return; + } + if (result.containsKey(anno.sym())) { + // if the same inherited annotation is present on multiple supertypes, only return one + return; + } + result.put(anno.sym(), TurbineAnnotationMirror.create(factory, anno)); + } + + private boolean isAnnotationInherited(ClassSymbol sym) { + TypeBoundClass annoInfo = factory.getSymbol(sym); + if (annoInfo == null) { + return false; + } + for (AnnoInfo anno : annoInfo.annotations()) { + if (anno.sym().equals(ClassSymbol.INHERITED)) { + return true; + } + } + return false; + } + } + + /** A {@link TypeParameterElement} implementation backed by a {@link TyVarSymbol}. */ + static class TurbineTypeParameterElement extends TurbineElement implements TypeParameterElement { + + @Override + public int hashCode() { + return sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineTypeParameterElement + && sym.equals(((TurbineTypeParameterElement) obj).sym); + } + + private final TyVarSymbol sym; + + public TurbineTypeParameterElement(ModelFactory factory, TyVarSymbol sym) { + super(factory); + this.sym = sym; + } + + private final Supplier<TyVarInfo> info = + memoize( + new Supplier<TyVarInfo>() { + @Override + public TyVarInfo get() { + return factory.getTyVarInfo(sym); + } + }); + + @Nullable + private TyVarInfo info() { + return info.get(); + } + + @Override + public String toString() { + return sym.name(); + } + + @Override + public Element getGenericElement() { + return factory.element(sym.owner()); + } + + @Override + public List<? extends TypeMirror> getBounds() { + ImmutableList<Type> bounds = info().upperBound().bounds(); + return factory.asTypeMirrors(bounds.isEmpty() ? ImmutableList.of(ClassTy.OBJECT) : bounds); + } + + @Override + public TypeMirror asType() { + return factory.asTypeMirror(Type.TyVar.create(sym, info().annotations())); + } + + @Override + public ElementKind getKind() { + return ElementKind.TYPE_PARAMETER; + } + + @Override + public Set<Modifier> getModifiers() { + return ImmutableSet.of(); + } + + @Override + public Name getSimpleName() { + return new TurbineName(sym.name()); + } + + @Override + public Element getEnclosingElement() { + return getGenericElement(); + } + + @Override + public List<? extends Element> getEnclosedElements() { + return ImmutableList.of(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitTypeParameter(this, p); + } + + @Override + public TyVarSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + return null; + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return info().annotations(); + } + } + + /** An {@link ExecutableElement} implementation backed by a {@link MethodSymbol}. */ + static class TurbineExecutableElement extends TurbineElement implements ExecutableElement { + + private final MethodSymbol sym; + + private final Supplier<MethodInfo> info = + memoize( + new Supplier<MethodInfo>() { + @Override + public MethodInfo get() { + return factory.getMethodInfo(sym); + } + }); + + @Nullable + MethodInfo info() { + return info.get(); + } + + TurbineExecutableElement(ModelFactory factory, MethodSymbol sym) { + super(factory); + this.sym = sym; + } + + @Override + public MethodSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + MethDecl decl = info().decl(); + return decl != null ? decl.javadoc() : null; + } + + @Override + public int hashCode() { + return sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineExecutableElement + && sym.equals(((TurbineExecutableElement) obj).sym); + } + + @Override + public List<? extends TypeParameterElement> getTypeParameters() { + ImmutableList.Builder<TurbineTypeParameterElement> result = ImmutableList.builder(); + for (Map.Entry<TyVarSymbol, TyVarInfo> p : info().tyParams().entrySet()) { + result.add(factory.typeParameterElement(p.getKey())); + } + return result.build(); + } + + @Override + public TypeMirror getReturnType() { + return factory.asTypeMirror(info().returnType()); + } + + private final Supplier<ImmutableList<VariableElement>> parameters = + memoize( + new Supplier<ImmutableList<VariableElement>>() { + @Override + public ImmutableList<VariableElement> get() { + ImmutableList.Builder<VariableElement> result = ImmutableList.builder(); + for (ParamInfo param : info().parameters()) { + if (param.synthetic()) { + // ExecutableElement#getParameters doesn't expect synthetic or mandated + // parameters + continue; + } + result.add(factory.parameterElement(param.sym())); + } + return result.build(); + } + }); + + @Override + public List<? extends VariableElement> getParameters() { + return parameters.get(); + } + + @Override + public String toString() { + MethodInfo info = info(); + StringBuilder sb = new StringBuilder(); + if (!info.tyParams().isEmpty()) { + sb.append('<'); + Joiner.on(',').appendTo(sb, info.tyParams().keySet()); + sb.append('>'); + } + if (getKind() == ElementKind.CONSTRUCTOR) { + sb.append(info.sym().owner().simpleName()); + } else { + sb.append(info.sym().name()); + } + sb.append('('); + boolean first = true; + for (ParamInfo p : info.parameters()) { + if (!first) { + sb.append(','); + } + sb.append(p.type()); + first = false; + } + sb.append(')'); + return sb.toString(); + } + + @Override + public TypeMirror getReceiverType() { + return info().receiver() != null + ? factory.asTypeMirror(info().receiver().type()) + : factory.noType(); + } + + @Override + public boolean isVarArgs() { + return (info().access() & TurbineFlag.ACC_VARARGS) == TurbineFlag.ACC_VARARGS; + } + + @Override + public boolean isDefault() { + return (info().access() & TurbineFlag.ACC_DEFAULT) == TurbineFlag.ACC_DEFAULT; + } + + @Override + public List<? extends TypeMirror> getThrownTypes() { + return factory.asTypeMirrors(info().exceptions()); + } + + @Override + public AnnotationValue getDefaultValue() { + return info().defaultValue() != null + ? TurbineAnnotationMirror.annotationValue(factory, info().defaultValue()) + : null; + } + + @Override + public TypeMirror asType() { + return factory.asTypeMirror(info().asType()); + } + + @Override + public ElementKind getKind() { + return info().name().equals("<init>") ? ElementKind.CONSTRUCTOR : ElementKind.METHOD; + } + + @Override + public Set<Modifier> getModifiers() { + int access = info().access(); + if (factory.getSymbol(info().sym().owner()).kind() == TurbineTyKind.INTERFACE) { + if ((access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) { + access |= TurbineFlag.ACC_DEFAULT; + } + } + return asModifierSet(ModifierOwner.METHOD, access); + } + + @Override + public Name getSimpleName() { + return new TurbineName(info().sym().name()); + } + + @Override + public Element getEnclosingElement() { + return factory.typeElement(info().sym().owner()); + } + + @Override + public List<? extends Element> getEnclosedElements() { + return ImmutableList.of(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitExecutable(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return info().annotations(); + } + } + + /** An {@link VariableElement} implementation backed by a {@link FieldSymbol}. */ + static class TurbineFieldElement extends TurbineElement implements VariableElement { + + @Override + public String toString() { + return sym.name(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineFieldElement && sym.equals(((TurbineFieldElement) obj).sym); + } + + @Override + public int hashCode() { + return sym.hashCode(); + } + + private final FieldSymbol sym; + + @Override + public FieldSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + VarDecl decl = info().decl(); + return decl != null ? decl.javadoc() : null; + } + + private final Supplier<FieldInfo> info = + memoize( + new Supplier<FieldInfo>() { + @Override + public FieldInfo get() { + return factory.getFieldInfo(sym); + } + }); + + @Nullable + FieldInfo info() { + return info.get(); + } + + TurbineFieldElement(ModelFactory factory, FieldSymbol sym) { + super(factory); + this.sym = sym; + } + + @Override + public Object getConstantValue() { + if (info().value() == null) { + return null; + } + return info().value().getValue(); + } + + @Override + public TypeMirror asType() { + return factory.asTypeMirror(info().type()); + } + + @Override + public ElementKind getKind() { + return ((info().access() & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM) + ? ElementKind.ENUM_CONSTANT + : ElementKind.FIELD; + } + + @Override + public Set<Modifier> getModifiers() { + return asModifierSet(ModifierOwner.FIELD, info().access()); + } + + @Override + public Name getSimpleName() { + return new TurbineName(sym.name()); + } + + @Override + public Element getEnclosingElement() { + return factory.typeElement(sym.owner()); + } + + @Override + public List<? extends Element> getEnclosedElements() { + return ImmutableList.of(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitVariable(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return info().annotations(); + } + } + + private enum ModifierOwner { + TYPE, + PARAMETER, + FIELD, + METHOD + } + + private static ImmutableSet<Modifier> asModifierSet(ModifierOwner modifierOwner, int access) { + EnumSet<Modifier> modifiers = EnumSet.noneOf(Modifier.class); + if ((access & TurbineFlag.ACC_PUBLIC) == TurbineFlag.ACC_PUBLIC) { + modifiers.add(Modifier.PUBLIC); + } + if ((access & TurbineFlag.ACC_PROTECTED) == TurbineFlag.ACC_PROTECTED) { + modifiers.add(Modifier.PROTECTED); + } + if ((access & TurbineFlag.ACC_PRIVATE) == TurbineFlag.ACC_PRIVATE) { + modifiers.add(Modifier.PRIVATE); + } + if ((access & TurbineFlag.ACC_ABSTRACT) == TurbineFlag.ACC_ABSTRACT) { + modifiers.add(Modifier.ABSTRACT); + } + if ((access & TurbineFlag.ACC_FINAL) == TurbineFlag.ACC_FINAL) { + modifiers.add(Modifier.FINAL); + } + if ((access & TurbineFlag.ACC_DEFAULT) == TurbineFlag.ACC_DEFAULT) { + modifiers.add(Modifier.DEFAULT); + } + if ((access & TurbineFlag.ACC_STATIC) == TurbineFlag.ACC_STATIC) { + modifiers.add(Modifier.STATIC); + } + if ((access & TurbineFlag.ACC_TRANSIENT) == TurbineFlag.ACC_TRANSIENT) { + switch (modifierOwner) { + case METHOD: + case PARAMETER: + // varargs and transient use the same bits + break; + default: + modifiers.add(Modifier.TRANSIENT); + } + } + if ((access & TurbineFlag.ACC_VOLATILE) == TurbineFlag.ACC_VOLATILE) { + modifiers.add(Modifier.VOLATILE); + } + if ((access & TurbineFlag.ACC_SYNCHRONIZED) == TurbineFlag.ACC_SYNCHRONIZED) { + modifiers.add(Modifier.SYNCHRONIZED); + } + if ((access & TurbineFlag.ACC_NATIVE) == TurbineFlag.ACC_NATIVE) { + modifiers.add(Modifier.NATIVE); + } + if ((access & TurbineFlag.ACC_STRICT) == TurbineFlag.ACC_STRICT) { + modifiers.add(Modifier.STRICTFP); + } + + return Sets.immutableEnumSet(modifiers); + } + + /** A {@link PackageElement} implementation backed by a {@link PackageSymbol}. */ + static class TurbinePackageElement extends TurbineElement implements PackageElement { + + private final PackageSymbol sym; + + public TurbinePackageElement(ModelFactory factory, PackageSymbol sym) { + super(factory); + this.sym = sym; + } + + @Override + public Name getQualifiedName() { + return new TurbineName(sym.toString()); + } + + @Override + public boolean isUnnamed() { + return sym.binaryName().isEmpty(); + } + + @Override + public TypeMirror asType() { + return factory.packageType(sym); + } + + @Override + public ElementKind getKind() { + return ElementKind.PACKAGE; + } + + @Override + public Set<Modifier> getModifiers() { + return ImmutableSet.of(); + } + + @Override + public Name getSimpleName() { + return new TurbineName(sym.binaryName().substring(sym.binaryName().lastIndexOf('/') + 1)); + } + + @Override + public Element getEnclosingElement() { + // a package is not enclosed by another element + return null; + } + + @Override + public List<TurbineTypeElement> getEnclosedElements() { + ImmutableSet.Builder<TurbineTypeElement> result = ImmutableSet.builder(); + PackageScope scope = factory.tli().lookupPackage(Splitter.on('/').split(sym.binaryName())); + for (ClassSymbol key : scope.classes()) { + if (key.binaryName().contains("$") && factory.getSymbol(key).owner() != null) { + // Skip member classes: only top-level classes are enclosed by the package. + // The initial check for '$' is an optimization. + continue; + } + if (key.simpleName().equals("package-info")) { + continue; + } + result.add(factory.typeElement(key)); + } + return result.build().asList(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitPackage(this, p); + } + + @Override + public PackageSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + return null; + } + + @Override + public int hashCode() { + return sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbinePackageElement && sym.equals(((TurbinePackageElement) obj).sym); + } + + private final Supplier<ImmutableList<AnnoInfo>> annos = + memoize( + new Supplier<ImmutableList<AnnoInfo>>() { + @Override + public ImmutableList<AnnoInfo> get() { + TypeBoundClass info = + factory.getSymbol(new ClassSymbol(sym.binaryName() + "/package-info")); + return info != null ? info.annotations() : ImmutableList.of(); + } + }); + + @Override + protected ImmutableList<AnnoInfo> annos() { + return annos.get(); + } + + @Override + public String toString() { + return sym.toString(); + } + } + + /** A {@link VariableElement} implementation backed by a {@link ParamSymbol}. */ + static class TurbineParameterElement extends TurbineElement implements VariableElement { + + @Override + public ParamSymbol sym() { + return sym; + } + + @Override + public String javadoc() { + return null; + } + + @Override + public int hashCode() { + return sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineParameterElement + && sym.equals(((TurbineParameterElement) obj).sym); + } + + private final ParamSymbol sym; + + private final Supplier<ParamInfo> info = + memoize( + new Supplier<ParamInfo>() { + @Override + public ParamInfo get() { + return factory.getParamInfo(sym); + } + }); + + @Nullable + ParamInfo info() { + return info.get(); + } + + public TurbineParameterElement(ModelFactory factory, ParamSymbol sym) { + super(factory); + this.sym = sym; + } + + @Override + public Object getConstantValue() { + return null; + } + + private final Supplier<TypeMirror> type = + memoize( + new Supplier<TypeMirror>() { + @Override + public TypeMirror get() { + return factory.asTypeMirror(info().type()); + } + }); + + @Override + public TypeMirror asType() { + return type.get(); + } + + @Override + public ElementKind getKind() { + return ElementKind.PARAMETER; + } + + @Override + public Set<Modifier> getModifiers() { + return asModifierSet(ModifierOwner.PARAMETER, info().access()); + } + + @Override + public Name getSimpleName() { + return new TurbineName(sym.name()); + } + + @Override + public Element getEnclosingElement() { + return factory.executableElement(sym.owner()); + } + + @Override + public List<? extends Element> getEnclosedElements() { + return ImmutableList.of(); + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitVariable(this, p); + } + + @Override + public String toString() { + return String.valueOf(sym.name()); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return info().annotations(); + } + } + + static class TurbineNoTypeElement implements TypeElement { + + private final ModelFactory factory; + private final String name; + + public TurbineNoTypeElement(ModelFactory factory, String name) { + this.factory = factory; + this.name = requireNonNull(name); + } + + @Override + public TypeMirror asType() { + return factory.noType(); + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @Override + public Set<Modifier> getModifiers() { + return ImmutableSet.of(); + } + + @Override + public Name getSimpleName() { + return new TurbineName(name.substring(name.lastIndexOf('.') + 1)); + } + + @Override + public TypeMirror getSuperclass() { + return factory.noType(); + } + + @Override + public List<? extends TypeMirror> getInterfaces() { + return ImmutableList.of(); + } + + @Override + public List<? extends TypeParameterElement> getTypeParameters() { + return ImmutableList.of(); + } + + @Override + public Element getEnclosingElement() { + int idx = name.lastIndexOf('.'); + String packageName; + if (idx == -1) { + packageName = ""; + } else { + packageName = name.substring(0, idx).replace('.', '/'); + } + return factory.packageElement(new PackageSymbol(packageName)); + } + + @Override + public List<? extends Element> getEnclosedElements() { + return ImmutableList.of(); + } + + @Override + public NestingKind getNestingKind() { + return NestingKind.TOP_LEVEL; + } + + @Override + public Name getQualifiedName() { + return new TurbineName(name); + } + + @Override + public List<? extends AnnotationMirror> getAnnotationMirrors() { + return ImmutableList.of(); + } + + @Override + public <A extends Annotation> A getAnnotation(Class<A> aClass) { + return null; + } + + @Override + public <A extends Annotation> A[] getAnnotationsByType(Class<A> aClass) { + return null; + } + + @Override + public <R, P> R accept(ElementVisitor<R, P> elementVisitor, P p) { + return elementVisitor.visitType(this, p); + } + + @Override + public String toString() { + return getSimpleName().toString(); + } + } +} diff --git a/java/com/google/turbine/processing/TurbineElements.java b/java/com/google/turbine/processing/TurbineElements.java new file mode 100644 index 0000000..9da210e --- /dev/null +++ b/java/com/google/turbine/processing/TurbineElements.java @@ -0,0 +1,360 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.model.Const; +import com.google.turbine.model.TurbineVisibility; +import com.google.turbine.processing.TurbineElement.TurbineExecutableElement; +import com.google.turbine.processing.TurbineElement.TurbineFieldElement; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.processing.TurbineTypeMirror.TurbineExecutableType; +import com.google.turbine.type.AnnoInfo; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + +/** An implementation of {@link Elements} backed by turbine's {@link Element}. */ +public class TurbineElements implements Elements { + + private final ModelFactory factory; + private final TurbineTypes types; + + public TurbineElements(ModelFactory factory, TurbineTypes types) { + this.factory = factory; + this.types = types; + } + + private static Symbol asSymbol(Element element) { + if (!(element instanceof TurbineElement)) { + throw new IllegalArgumentException(element.toString()); + } + return ((TurbineElement) element).sym(); + } + + @Override + public PackageElement getPackageElement(CharSequence name) { + ImmutableList<String> packageName = ImmutableList.copyOf(Splitter.on('.').split(name)); + if (factory.tli().lookupPackage(packageName) == null) { + return null; + } + return factory.packageElement(new PackageSymbol(Joiner.on('/').join(packageName))); + } + + @Override + public TypeElement getTypeElement(CharSequence name) { + ClassSymbol sym = factory.inferSymbol(name); + if (sym == null) { + return null; + } + if (factory.getSymbol(sym) == null) { + return null; + } + return factory.typeElement(sym); + } + + @Override + public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults( + AnnotationMirror a) { + return ((TurbineAnnotationMirror) a).getElementValuesWithDefaults(); + } + + @Override + public String getDocComment(Element e) { + if (!(e instanceof TurbineElement)) { + throw new IllegalArgumentException(e.toString()); + } + String comment = ((TurbineElement) e).javadoc(); + if (comment == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String line : Splitter.on('\n').split(comment)) { + int start = 0; + if (!first) { + sb.append('\n'); + while (start < line.length() && CharMatcher.whitespace().matches(line.charAt(start))) { + start++; + } + while (start < line.length() && line.charAt(start) == '*') { + start++; + } + } + sb.append(line, start, line.length()); + first = false; + } + return sb.toString(); + } + + @Override + public boolean isDeprecated(Element element) { + if (!(element instanceof TurbineElement)) { + throw new IllegalArgumentException(element.toString()); + } + for (AnnoInfo a : ((TurbineTypeElement) element).annos()) { + if (a.sym().equals(ClassSymbol.DEPRECATED)) { + return true; + } + } + return false; + } + + @Override + public Name getBinaryName(TypeElement element) { + if (!(element instanceof TurbineTypeElement)) { + throw new IllegalArgumentException(element.toString()); + } + return getName(((TurbineTypeElement) element).sym().binaryName().replace('/', '.')); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException for module elements + */ + @Override + public PackageElement getPackageOf(Element element) { + Symbol sym = asSymbol(element); + return factory.packageElement(packageSymbol(sym)); + } + + private static PackageSymbol packageSymbol(Symbol sym) { + if (sym.symKind().equals(Symbol.Kind.PACKAGE)) { + return (PackageSymbol) sym; + } + return ModelFactory.enclosingClass(sym).owner(); + } + + @Override + public List<? extends Element> getAllMembers(TypeElement type) { + ClassSymbol s = (ClassSymbol) asSymbol(type); + PackageSymbol from = packageSymbol(s); + + // keep track of processed methods grouped by their names, to handle overrides more efficiently + Multimap<String, TurbineExecutableElement> methods = + MultimapBuilder.linkedHashKeys().linkedHashSetValues().build(); + + // collect all members of each transitive supertype of the input + ImmutableList.Builder<Element> results = ImmutableList.builder(); + for (ClassSymbol superType : factory.cha().transitiveSupertypes(s)) { + // Most of JSR-269 is implemented on top of turbine's model, instead of the Element and + // TypeMirror wrappers. We don't do that here because we need most of the Elements returned + // by getEnclosedElements anyways, and the work below benefits from some of the caching done + // by TurbineElement. + for (Element el : factory.typeElement(superType).getEnclosedElements()) { + Symbol sym = asSymbol(el); + switch (sym.symKind()) { + case METHOD: + TurbineExecutableElement m = (TurbineExecutableElement) el; + if (shouldAdd(s, from, methods, m)) { + methods.put(m.info().name(), m); + results.add(el); + } + break; + case FIELD: + if (shouldAdd(s, from, (TurbineFieldElement) el)) { + results.add(el); + } + break; + default: + results.add(el); + } + } + } + return results.build(); + } + + private boolean shouldAdd( + ClassSymbol s, + PackageSymbol from, + Multimap<String, TurbineExecutableElement> methods, + TurbineExecutableElement m) { + if (m.sym().owner().equals(s)) { + // always include methods (and constructors) declared in the given type + return true; + } + if (m.getKind() == ElementKind.CONSTRUCTOR) { + // skip constructors from super-types, because the spec says so + return false; + } + if (!isVisible(from, packageSymbol(m.sym()), TurbineVisibility.fromAccess(m.info().access()))) { + // skip invisible methods in supers + return false; + } + // otherwise check if we've seen methods that override, or are overridden by, the + // current method + Set<TurbineExecutableElement> overrides = new HashSet<>(); + Set<TurbineExecutableElement> overridden = new HashSet<>(); + String name = m.info().name(); + for (TurbineExecutableElement other : methods.get(name)) { + if (overrides(m, other, (TypeElement) m.getEnclosingElement())) { + overrides.add(other); + continue; + } + if (overrides(other, m, (TypeElement) other.getEnclosingElement())) { + overridden.add(other); + continue; + } + } + if (!overridden.isEmpty()) { + // We've already processed method(s) that override this one; nothing to do here. + // If that's true, and we've *also* processed a methods that this one overrides, + // something has gone terribly wrong: since overriding is transitive the results + // contain a pair of methods that override each other. + checkState(overrides.isEmpty()); + return false; + } + // Add this method, and remove any methods we've already processed that it overrides. + for (TurbineExecutableElement override : overrides) { + methods.remove(name, override); + } + return true; + } + + private static boolean shouldAdd(ClassSymbol s, PackageSymbol from, TurbineFieldElement f) { + FieldSymbol sym = f.sym(); + if (sym.owner().equals(s)) { + // always include fields declared in the given type + return true; + } + if (!isVisible(from, packageSymbol(sym), TurbineVisibility.fromAccess(f.info().access()))) { + // skip invisible fields in supers + return false; + } + return true; + } + + /** + * Returns true if an element with the given {@code visibility} and located in package {@from} is + * visible to elements in package {@code to}. + */ + private static boolean isVisible( + PackageSymbol from, PackageSymbol to, TurbineVisibility visibility) { + switch (visibility) { + case PUBLIC: + case PROTECTED: + break; + case PACKAGE: + return from.equals(to); + case PRIVATE: + return false; + } + return true; + } + + @Override + public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element element) { + return ((TurbineElement) element).getAllAnnotationMirrors(); + } + + @Override + public boolean hides(Element hider, Element hidden) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean overrides( + ExecutableElement overrider, ExecutableElement overridden, TypeElement type) { + if (!overrider.getSimpleName().contentEquals(overridden.getSimpleName())) { + return false; + } + TypeMirror a = overrider.asType(); + TypeMirror b = types.asMemberOf((DeclaredType) type.asType(), overridden); + if (b == null) { + return false; + } + if (!types.isSubsignature((TurbineExecutableType) a, (TurbineExecutableType) b)) { + return false; + } + return isVisible( + packageSymbol(asSymbol(overrider)), + packageSymbol(asSymbol(overridden)), + TurbineVisibility.fromAccess(((TurbineExecutableElement) overridden).info().access())); + } + + @Override + public String getConstantExpression(Object value) { + if (value instanceof Byte) { + return new Const.ByteValue((Byte) value).toString(); + } + if (value instanceof Long) { + return new Const.LongValue((Long) value).toString(); + } + if (value instanceof Float) { + return new Const.FloatValue((Float) value).toString(); + } + if (value instanceof Double) { + return new Const.DoubleValue((Double) value).toString(); + } + if (value instanceof Short) { + // Special-case short for consistency with javac, see: + // https://bugs.openjdk.java.net/browse/JDK-8227617 + return String.format("(short)%d", (Short) value); + } + if (value instanceof String) { + return new Const.StringValue((String) value).toString(); + } + if (value instanceof Character) { + return new Const.CharValue((Character) value).toString(); + } + return String.valueOf(value); + } + + @Override + public void printElements(Writer w, Element... elements) { + PrintWriter pw = new PrintWriter(w, true); + for (Element element : elements) { + pw.println(element.toString()); + } + } + + @Override + public Name getName(CharSequence cs) { + return new TurbineName(cs.toString()); + } + + @Override + public boolean isFunctionalInterface(TypeElement type) { + throw new UnsupportedOperationException(); + } +} diff --git a/java/com/google/turbine/processing/TurbineFiler.java b/java/com/google/turbine/processing/TurbineFiler.java new file mode 100644 index 0000000..186eb7f --- /dev/null +++ b/java/com/google/turbine/processing/TurbineFiler.java @@ -0,0 +1,435 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; +import com.google.turbine.diag.SourceFile; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.Filer; +import javax.annotation.processing.FilerException; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.FileObject; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardLocation; + +/** Turbine's implementation of {@link Filer}. */ +public class TurbineFiler implements Filer { + + /** + * Existing paths of file objects that cannot be regenerated, including the original compilation + * inputs and source or class files generated during any annotation processing round. + */ + private final Set<String> seen; + + /** + * File objects generated during the current processing round. Each entry has a unique path, which + * is enforced by {@link #seen}. + */ + private final List<TurbineJavaFileObject> files = new ArrayList<>(); + + /** Loads resources from the classpath. */ + private final Function<String, Supplier<byte[]>> classPath; + + /** The {@link ClassLoader} for the annotation processor path, for loading resources. */ + private final ClassLoader loader; + + private final Map<String, SourceFile> generatedSources = new LinkedHashMap<>(); + private final Map<String, byte[]> generatedClasses = new LinkedHashMap<>(); + + /** Generated source file objects from all rounds. */ + public ImmutableMap<String, SourceFile> generatedSources() { + return ImmutableMap.copyOf(generatedSources); + } + + /** Generated class file objects from all rounds. */ + public ImmutableMap<String, byte[]> generatedClasses() { + return ImmutableMap.copyOf(generatedClasses); + } + + public TurbineFiler( + Set<String> seen, Function<String, Supplier<byte[]>> classPath, ClassLoader loader) { + this.seen = seen; + this.classPath = classPath; + this.loader = loader; + } + + /** + * Called when the current annotation processing round is complete, and returns the sources + * generated in that round. + */ + public Collection<SourceFile> finishRound() { + Map<String, SourceFile> roundSources = new LinkedHashMap<>(); + for (TurbineJavaFileObject e : files) { + String path = e.getName(); + switch (e.getKind()) { + case SOURCE: + roundSources.put(path, new SourceFile(path, e.contents())); + break; + case CLASS: + generatedClasses.put(path, e.bytes()); + break; + case OTHER: + switch (e.location()) { + case CLASS_OUTPUT: + generatedClasses.put(path, e.bytes()); + break; + case SOURCE_OUTPUT: + this.generatedSources.put(path, new SourceFile(path, e.contents())); + break; + default: + throw new AssertionError(e.location()); + } + break; + case HTML: + throw new UnsupportedOperationException(String.valueOf(e.getKind())); + } + } + files.clear(); + this.generatedSources.putAll(roundSources); + return roundSources.values(); + } + + @Override + public JavaFileObject createSourceFile(CharSequence n, Element... originatingElements) + throws IOException { + String name = n.toString(); + checkArgument(!name.contains("/"), "invalid type name: %s", name); + return create(StandardLocation.SOURCE_OUTPUT, Kind.SOURCE, name.replace('.', '/') + ".java"); + } + + @Override + public JavaFileObject createClassFile(CharSequence n, Element... originatingElements) + throws IOException { + String name = n.toString(); + checkArgument(!name.contains("/"), "invalid type name: %s", name); + return create(StandardLocation.CLASS_OUTPUT, Kind.CLASS, name.replace('.', '/') + ".class"); + } + + @Override + public FileObject createResource( + Location location, CharSequence p, CharSequence r, Element... originatingElements) + throws IOException { + checkArgument(location instanceof StandardLocation, "%s", location); + String pkg = p.toString(); + String relativeName = r.toString(); + checkArgument(!pkg.contains("/"), "invalid package: %s", pkg); + String path = packageRelativePath(pkg, relativeName); + return create((StandardLocation) location, Kind.OTHER, path); + } + + private JavaFileObject create(StandardLocation location, Kind kind, String path) + throws FilerException { + checkArgument(location.isOutputLocation()); + if (!seen.add(path)) { + throw new FilerException("already created " + path); + } + TurbineJavaFileObject result = new TurbineJavaFileObject(location, kind, path); + files.add(result); + return result; + } + + @Override + public FileObject getResource(Location location, CharSequence p, CharSequence r) + throws IOException { + String pkg = p.toString(); + String relativeName = r.toString(); + checkArgument(!pkg.contains("/"), "invalid package: %s", pkg); + checkArgument(location instanceof StandardLocation, "unsupported location %s", location); + StandardLocation standardLocation = (StandardLocation) location; + String path = packageRelativePath(pkg, relativeName); + switch (standardLocation) { + case CLASS_OUTPUT: + byte[] generated = generatedClasses.get(path); + if (generated == null) { + throw new FileNotFoundException(path); + } + return new BytesFileObject(path, Suppliers.ofInstance(generated)); + case SOURCE_OUTPUT: + SourceFile source = generatedSources.get(path); + if (source == null) { + throw new FileNotFoundException(path); + } + return new SourceFileObject(path, source.source()); + case ANNOTATION_PROCESSOR_PATH: + if (loader.getResource(path) == null) { + throw new FileNotFoundException(path); + } + return new ResourceFileObject(loader, path); + case CLASS_PATH: + Supplier<byte[]> bytes = classPath.apply(path); + if (bytes == null) { + throw new FileNotFoundException(path); + } + return new BytesFileObject(path, bytes); + default: + throw new IllegalArgumentException(standardLocation.getName()); + } + } + + private static String packageRelativePath(String pkg, String relativeName) { + if (pkg.isEmpty()) { + return relativeName; + } + return pkg.replace('.', '/') + '/' + relativeName; + } + + private abstract static class ReadOnlyFileObject implements FileObject { + + protected final String path; + + public ReadOnlyFileObject(String path) { + this.path = path; + } + + @Override + public final String getName() { + return path; + } + + @Override + public URI toUri() { + return URI.create("file://" + path); + } + + @Override + public final OutputStream openOutputStream() { + throw new IllegalStateException(); + } + + @Override + public final Writer openWriter() { + throw new IllegalStateException(); + } + + @Override + public final long getLastModified() { + return 0; + } + + @Override + public final boolean delete() { + throw new IllegalStateException(); + } + } + + private abstract static class WriteOnlyFileObject implements FileObject { + + @Override + public final InputStream openInputStream() { + throw new IllegalStateException(); + } + + @Override + public final Reader openReader(boolean ignoreEncodingErrors) { + throw new IllegalStateException(); + } + + @Override + public final CharSequence getCharContent(boolean ignoreEncodingErrors) { + throw new IllegalStateException(); + } + } + + private static class TurbineJavaFileObject extends WriteOnlyFileObject implements JavaFileObject { + + private final StandardLocation location; + private final Kind kind; + private final CharSequence name; + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + public TurbineJavaFileObject(StandardLocation location, Kind kind, CharSequence name) { + this.location = location; + this.kind = kind; + this.name = name; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + throw new UnsupportedOperationException(); + } + + @Override + public NestingKind getNestingKind() { + throw new UnsupportedOperationException(); + } + + @Override + public Modifier getAccessLevel() { + throw new UnsupportedOperationException(); + } + + @Override + public URI toUri() { + return URI.create("file://" + name + kind.extension); + } + + @Override + public String getName() { + return name.toString(); + } + + @Override + public OutputStream openOutputStream() { + return baos; + } + + @Override + public Writer openWriter() { + return new OutputStreamWriter(openOutputStream(), UTF_8); + } + + @Override + public long getLastModified() { + return 0; + } + + @Override + public boolean delete() { + throw new IllegalStateException(); + } + + public byte[] bytes() { + return baos.toByteArray(); + } + + public String contents() { + return new String(baos.toByteArray(), UTF_8); + } + + public StandardLocation location() { + return location; + } + } + + private static class ResourceFileObject extends ReadOnlyFileObject { + + private final ClassLoader loader; + + public ResourceFileObject(ClassLoader loader, String path) { + super(path); + this.loader = loader; + } + + @Override + public URI toUri() { + try { + return loader.getResource(path).toURI(); + } catch (URISyntaxException e) { + throw new AssertionError(e); + } + } + + @Override + public InputStream openInputStream() { + return loader.getResourceAsStream(path); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + return new InputStreamReader(openInputStream(), UTF_8); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return new String(ByteStreams.toByteArray(openInputStream()), UTF_8); + } + } + + private static class BytesFileObject extends ReadOnlyFileObject { + + private final Supplier<byte[]> bytes; + + public BytesFileObject(String path, Supplier<byte[]> bytes) { + super(path); + this.bytes = bytes; + } + + @Override + public InputStream openInputStream() { + return new ByteArrayInputStream(bytes.get()); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + return new InputStreamReader(openInputStream(), UTF_8); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return new String(bytes.get(), UTF_8); + } + } + + private static class SourceFileObject extends ReadOnlyFileObject { + + private final String source; + + public SourceFileObject(String path, String source) { + super(path); + this.source = source; + } + + @Override + public InputStream openInputStream() { + return new ByteArrayInputStream(source.getBytes(UTF_8)); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + return new StringReader(source); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + } +} diff --git a/java/com/google/turbine/processing/TurbineMessager.java b/java/com/google/turbine/processing/TurbineMessager.java new file mode 100644 index 0000000..9c333b2 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineMessager.java @@ -0,0 +1,252 @@ +/* + * Copyright 2019 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.processing; + +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.TurbineAnnotationValue; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; +import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineLog; +import com.google.turbine.model.Const; +import com.google.turbine.processing.TurbineElement.TurbineNoTypeElement; +import com.google.turbine.tree.Tree; +import com.google.turbine.type.AnnoInfo; +import java.util.Iterator; +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Turbine's implementation of {@link Messager}. */ +public class TurbineMessager implements Messager { + private final ModelFactory factory; + private final TurbineLog log; + + public TurbineMessager(ModelFactory factory, TurbineLog log) { + this.factory = factory; + this.log = log; + } + + @Override + public void printMessage(Diagnostic.Kind kind, CharSequence msg) { + // TODO(cushon): null-check `msg` after fixing affected processors + log.diagnostic(kind, String.valueOf(msg)); + } + + @Override + public void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e) { + if (e == null || e instanceof TurbineNoTypeElement) { + printMessage(kind, msg); + return; + } + Symbol sym = ((TurbineElement) e).sym(); + SourceFile source = getSource(sym); + int position = getPosition(sym); + log.withSource(source).diagnostic(kind, position, TurbineError.ErrorKind.PROC, msg); + } + + @Override + public void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a) { + if (a == null || e == null || e instanceof TurbineNoTypeElement) { + printMessage(kind, msg, e); + return; + } + SourceFile source = getSource(((TurbineElement) e).sym()); + int position = ((TurbineAnnotationMirror) a).anno().tree().position(); + log.withSource(source).diagnostic(kind, position, TurbineError.ErrorKind.PROC, msg); + } + + @Override + public void printMessage( + Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) { + if (a == null || e == null || e instanceof TurbineNoTypeElement || v == null) { + printMessage(kind, msg, e, a); + return; + } + SourceFile source = getSource(((TurbineElement) e).sym()); + AnnoInfo anno = ((TurbineAnnotationMirror) a).anno(); + int position = locateInAnnotation(((TurbineAnnotationValueMirror) v).value(), anno); + if (position == -1) { + position = anno.tree().position(); + } + log.withSource(source).diagnostic(kind, position, TurbineError.ErrorKind.PROC, msg); + } + + /** + * Returns the {@link SourceFile} that contains the declaration of the given {@link Symbol}, or + * {@code null} if the symbol was not compiled from source. + */ + @Nullable + private SourceFile getSource(Symbol sym) { + ClassSymbol encl = ModelFactory.enclosingClass(sym); + TypeBoundClass info = factory.getSymbol(encl); + if (!(info instanceof SourceTypeBoundClass)) { + return null; + } + return ((SourceTypeBoundClass) info).source(); + } + + /** + * Returns the position of the given {@link Symbol}'s declaration, or {@code null} if it was not + * compiled from source. + */ + private int getPosition(Symbol sym) { + switch (sym.symKind()) { + case CLASS: + return classPosition((ClassSymbol) sym); + case TY_PARAM: + return tyParamPosition((TyVarSymbol) sym); + case METHOD: + return methodPosition((MethodSymbol) sym); + case FIELD: + return fieldPosition((FieldSymbol) sym); + case PARAMETER: + return paramPosition((ParamSymbol) sym); + case MODULE: + case PACKAGE: + break; + } + throw new AssertionError(sym.symKind()); + } + + private int fieldPosition(FieldSymbol sym) { + Tree.VarDecl decl = factory.getFieldInfo(sym).decl(); + return decl != null ? decl.position() : -1; + } + + private int paramPosition(ParamSymbol sym) { + MethodInfo minfo = factory.getMethodInfo(sym.owner()); + if (minfo.decl() == null) { + return -1; + } + int idx = minfo.parameters().indexOf(factory.getParamInfo(sym)); + return minfo.decl().params().get(idx).position(); + } + + private int methodPosition(MethodSymbol sym) { + MethodInfo methodInfo = factory.getMethodInfo(sym); + Tree.MethDecl decl = methodInfo.decl(); + if (decl != null) { + return decl.position(); + } + // use the enclosing class position for synthetic methods + int position = classPosition(sym.owner()); + if (position == -1) { + return -1; + } + // TODO(b/139079081): change diagnostic position of declarations instead of the -= 6 fixup + position -= 6; // adjust to start of `class ` for parity with javac + return position; + } + + private int classPosition(ClassSymbol owner) { + TypeBoundClass symbol = factory.getSymbol(owner); + if (!(symbol instanceof SourceTypeBoundClass)) { + return -1; + } + return ((SourceTypeBoundClass) symbol).decl().position(); + } + + private int tyParamPosition(TyVarSymbol sym) { + TyVarSymbol tyVarSymbol = sym; + Symbol owner = tyVarSymbol.owner(); + ImmutableMap<TyVarSymbol, TyVarInfo> tyVars; + ImmutableList<Tree.TyParam> trees; + switch (owner.symKind()) { + case CLASS: + TypeBoundClass cinfo = factory.getSymbol((ClassSymbol) owner); + if (!(cinfo instanceof SourceTypeBoundClass)) { + return -1; + } + tyVars = cinfo.typeParameterTypes(); + trees = ((SourceTypeBoundClass) cinfo).decl().typarams(); + break; + case METHOD: + MethodInfo minfo = factory.getMethodInfo((MethodSymbol) owner); + if (minfo.decl() == null) { + return -1; + } + tyVars = minfo.tyParams(); + trees = minfo.decl().typarams(); + break; + default: + throw new AssertionError(owner.symKind()); + } + return trees.get(tyVars.keySet().asList().indexOf(tyVarSymbol)).position(); + } + + /** Returns the position of the given annotation value {@code v} in the given annotation. */ + private static int locateInAnnotation(Const v, AnnoInfo anno) { + return locate(v, anno.values().values().asList(), anno.tree().args()); + } + + /** + * Returns the position of the given annotation value {@code toFind} within the given constant + * {@code v} (which may be a compound value, i.e. a nested annotation or array value), and given + * the corresponding expression tree. + */ + private static int locate(Const toFind, Const v, Tree.Expression t) { + // the element name can be omitted for `value`, e.g. in `@A({1, 2, 3})` + t = t.kind().equals(Tree.Kind.ASSIGN) ? ((Tree.Assign) t).expr() : t; + if (toFind.equals(v)) { + return t.position(); + } + switch (v.kind()) { + case ARRAY: + ImmutableList<Tree.Expression> elements = + t.kind().equals(Tree.Kind.ARRAY_INIT) + ? ((Tree.ArrayInit) t).exprs() + : ImmutableList.of(t); + return locate(toFind, ((Const.ArrayInitValue) v).elements(), elements); + case ANNOTATION: + return locateInAnnotation(toFind, ((TurbineAnnotationValue) v).info()); + default: + return -1; + } + } + + /** + * Returns the position of the given annotation value {@code toFind}, given a list of annotation + * values corresponding to the element values of a (possibly nested) annotation or an array value, + * and the corresponding expression trees. + */ + private static int locate( + Const toFind, ImmutableList<Const> vx, ImmutableList<Tree.Expression> tx) { + Iterator<Const> vi = vx.iterator(); + Iterator<Tree.Expression> ti = tx.iterator(); + while (vi.hasNext()) { + int result = locate(toFind, vi.next(), ti.next()); + if (result != -1) { + return result; + } + } + return -1; + } +} diff --git a/java/com/google/turbine/processing/TurbineName.java b/java/com/google/turbine/processing/TurbineName.java new file mode 100644 index 0000000..584b1b1 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineName.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 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.processing; + +import static java.util.Objects.requireNonNull; + +import javax.lang.model.element.Name; + +/** An implementation of {@link Name} backed by a {@link CharSequence}. */ +public class TurbineName implements Name { + + private final String name; + + public TurbineName(String name) { + requireNonNull(name); + this.name = name; + } + + @Override + public boolean contentEquals(CharSequence cs) { + return name.contentEquals(cs); + } + + @Override + public int length() { + return name.length(); + } + + @Override + public char charAt(int index) { + return name.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return name.subSequence(start, end); + } + + @Override + public String toString() { + return name; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineName && contentEquals(((TurbineName) obj).name); + } +} diff --git a/java/com/google/turbine/processing/TurbineProcessingEnvironment.java b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java new file mode 100644 index 0000000..726d075 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 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.processing; + +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Turbine's {@link ProcessingEnvironment). */ +public class TurbineProcessingEnvironment implements ProcessingEnvironment { + + private final Filer filer; + private final Types types; + private final Map<String, String> processorOptions; + private final Elements elements; + private final Map<String, byte[]> statistics; + private final SourceVersion sourceVersion; + private final Messager messager; + private final ClassLoader processorLoader; + + public TurbineProcessingEnvironment( + Filer filer, + Types types, + Elements elements, + Messager messager, + Map<String, String> processorOptions, + SourceVersion sourceVersion, + @Nullable ClassLoader processorLoader, + Map<String, byte[]> statistics) { + this.filer = filer; + this.types = types; + this.processorOptions = processorOptions; + this.sourceVersion = sourceVersion; + this.elements = elements; + this.statistics = statistics; + this.messager = messager; + this.processorLoader = processorLoader; + } + + @Override + public Map<String, String> getOptions() { + return processorOptions; + } + + @Override + public Messager getMessager() { + return messager; + } + + @Override + public Filer getFiler() { + return filer; + } + + @Override + public Elements getElementUtils() { + return elements; + } + + @Override + public Types getTypeUtils() { + return types; + } + + @Override + public SourceVersion getSourceVersion() { + return sourceVersion; + } + + @Override + public Locale getLocale() { + return Locale.ENGLISH; + } + + public ClassLoader processorLoader() { + return processorLoader; + } + + public void addStatistics(String key, byte[] extension) { + byte[] existing = statistics.put(key, extension); + if (existing != null) { + throw new IllegalStateException("duplicate statistics reported for " + key); + } + } +} diff --git a/java/com/google/turbine/processing/TurbineRoundEnvironment.java b/java/com/google/turbine/processing/TurbineRoundEnvironment.java new file mode 100644 index 0000000..d63a4e8 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineRoundEnvironment.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019 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.processing; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +/** A {@link RoundEnvironment}. */ +public class TurbineRoundEnvironment implements RoundEnvironment { + + private final ModelFactory factory; + private final ImmutableSet<ClassSymbol> syms; + private final boolean processingOver; + private final boolean errorRaised; + private final ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations; + + // the round environment doesn't outlive the round, so don't worry about resetting this cache + private final Supplier<ImmutableSet<TurbineTypeElement>> rootElements = + Suppliers.memoize( + new Supplier<ImmutableSet<TurbineTypeElement>>() { + @Override + public ImmutableSet<TurbineTypeElement> get() { + ImmutableSet.Builder<TurbineTypeElement> result = ImmutableSet.builder(); + for (ClassSymbol sym : syms) { + if (sym.simpleName().contains("$") && factory.getSymbol(sym).owner() != null) { + continue; + } + if (sym.simpleName().equals("package-info")) { + continue; + } + result.add(factory.typeElement(sym)); + } + return result.build(); + } + }); + + public TurbineRoundEnvironment( + ModelFactory factory, + ImmutableSet<ClassSymbol> syms, + boolean processingOver, + boolean errorRaised, + ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations) { + this.factory = factory; + this.syms = syms; + this.processingOver = processingOver; + this.errorRaised = errorRaised; + this.allAnnotations = allAnnotations; + } + + @Override + public boolean processingOver() { + return processingOver; + } + + @Override + public boolean errorRaised() { + return errorRaised; + } + + @Override + public ImmutableSet<TurbineTypeElement> getRootElements() { + return rootElements.get(); + } + + @Override + public Set<? extends Element> getElementsAnnotatedWith(TypeElement a) { + return factory.elements(allAnnotations.get(((TurbineTypeElement) a).sym())); + } + + @Override + public Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a) { + return getElementsAnnotatedWith( + factory.typeElement(new ClassSymbol(a.getName().replace('.', '/')))); + } +} diff --git a/java/com/google/turbine/processing/TurbineTypeMirror.java b/java/com/google/turbine/processing/TurbineTypeMirror.java new file mode 100644 index 0000000..e94672c --- /dev/null +++ b/java/com/google/turbine/processing/TurbineTypeMirror.java @@ -0,0 +1,770 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.Iterables.getLast; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Ascii; +import com.google.common.base.Joiner; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.model.TurbineFlag; +import com.google.turbine.model.TurbineTyKind; +import com.google.turbine.type.AnnoInfo; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.ArrayTy; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ErrorTy; +import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.type.Type.MethodTy; +import com.google.turbine.type.Type.PrimTy; +import com.google.turbine.type.Type.TyVar; +import com.google.turbine.type.Type.WildTy; +import com.google.turbine.type.Type.WildTy.BoundKind; +import java.lang.annotation.Annotation; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.type.WildcardType; + +/** A {@link TypeMirror} implementation backed by a {@link Type}. */ +public abstract class TurbineTypeMirror implements TypeMirror { + + protected final ModelFactory factory; + + protected TurbineTypeMirror(ModelFactory factory) { + this.factory = requireNonNull(factory); + } + + protected abstract ImmutableList<AnnoInfo> annos(); + + @Override + public final List<? extends AnnotationMirror> getAnnotationMirrors() { + ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder(); + for (AnnoInfo anno : annos()) { + result.add(TurbineAnnotationMirror.create(factory, anno)); + } + return result.build(); + } + + @Override + public final <A extends Annotation> A getAnnotation(Class<A> annotationType) { + throw new AssertionError(); + } + + @Override + public final <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) { + throw new AssertionError(); + } + + public abstract Type asTurbineType(); + + @Override + public String toString() { + return asTurbineType().toString(); + } + + /** A {@link PrimitiveType} implementation backed by a {@link PrimTy}. */ + static class TurbinePrimitiveType extends TurbineTypeMirror implements PrimitiveType { + + @Override + public String toString() { + return Ascii.toLowerCase(type.primkind().toString()); + } + + @Override + public Type asTurbineType() { + return type; + } + + public final PrimTy type; + + TurbinePrimitiveType(ModelFactory factory, PrimTy type) { + super(factory); + if (type.primkind() == TurbineConstantTypeKind.STRING) { + throw new AssertionError(type); + } + this.type = type; + } + + @Override + public TypeKind getKind() { + switch (type.primkind()) { + case CHAR: + return TypeKind.CHAR; + case SHORT: + return TypeKind.SHORT; + case INT: + return TypeKind.INT; + case LONG: + return TypeKind.LONG; + case FLOAT: + return TypeKind.FLOAT; + case DOUBLE: + return TypeKind.DOUBLE; + case BOOLEAN: + return TypeKind.BOOLEAN; + case BYTE: + return TypeKind.BYTE; + case NULL: + return TypeKind.NULL; + case STRING: + } + throw new AssertionError(type.primkind()); + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitPrimitive(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return type.annos(); + } + } + + /** A {@link DeclaredType} implementation backed by a {@link ClassTy}. */ + static class TurbineDeclaredType extends TurbineTypeMirror implements DeclaredType { + + @Override + public int hashCode() { + return type.sym().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineDeclaredType && type.equals(((TurbineDeclaredType) obj).type); + } + + @Override + public ClassTy asTurbineType() { + return type; + } + + private final ClassTy type; + + TurbineDeclaredType(ModelFactory factory, ClassTy type) { + super(factory); + this.type = type; + } + + @Override + public String toString() { + return type.toString(); + } + + final Supplier<Element> element = + factory.memoize( + new Supplier<Element>() { + @Override + public Element get() { + return factory.typeElement(type.sym()); + } + }); + + @Override + public Element asElement() { + return element.get(); + } + + final Supplier<TypeMirror> enclosing = + factory.memoize( + new Supplier<TypeMirror>() { + @Override + public TypeMirror get() { + TypeBoundClass info = factory.getSymbol(type.sym()); + if (info != null + && info.owner() != null + && ((info.access() & TurbineFlag.ACC_STATIC) == 0) + && info.kind() == TurbineTyKind.CLASS) { + if (type.classes().size() > 1) { + return factory.asTypeMirror( + ClassTy.create(type.classes().subList(0, type.classes().size() - 1))); + } + return factory.asTypeMirror(ClassTy.asNonParametricClassTy(info.owner())); + } + return factory.noType(); + } + }); + + @Override + public TypeMirror getEnclosingType() { + return enclosing.get(); + } + + final Supplier<ImmutableList<TypeMirror>> typeArguments = + factory.memoize( + new Supplier<ImmutableList<TypeMirror>>() { + @Override + public ImmutableList<TypeMirror> get() { + return factory.asTypeMirrors(getLast(type.classes()).targs()); + } + }); + + @Override + public List<? extends TypeMirror> getTypeArguments() { + return typeArguments.get(); + } + + @Override + public TypeKind getKind() { + return TypeKind.DECLARED; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitDeclared(this, p); + } + + public ClassTy type() { + return type; + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return getLast(type.classes()).annos(); + } + } + + /** An {@link ArrayType} implementation backed by a {@link ArrayTy}. */ + static class TurbineArrayType extends TurbineTypeMirror implements ArrayType { + + @Override + public Type asTurbineType() { + return type; + } + + private final ArrayTy type; + + TurbineArrayType(ModelFactory factory, ArrayTy type) { + super(factory); + this.type = type; + } + + @Override + public TypeMirror getComponentType() { + return factory.asTypeMirror(type.elementType()); + } + + @Override + public TypeKind getKind() { + return TypeKind.ARRAY; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitArray(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return type.annos(); + } + } + + /** An {@link ErrorType} implementation backed by a {@link ErrorTy}. */ + static class TurbineErrorType extends TurbineTypeMirror implements ErrorType { + + private final ErrorTy type; + + public TurbineErrorType(ModelFactory factory, ErrorTy type) { + super(factory); + this.type = type; + } + + @Override + public TypeKind getKind() { + return TypeKind.ERROR; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitError(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + + @Override + public Type asTurbineType() { + return type; + } + + @Override + public Element asElement() { + return factory.noElement(type.name()); + } + + @Override + public TypeMirror getEnclosingType() { + return factory.noType(); + } + + @Override + public List<? extends TypeMirror> getTypeArguments() { + return ImmutableList.of(); + } + + @Override + public String toString() { + return type.toString(); + } + } + + /** A 'package type' implementation backed by a {@link PackageSymbol}. */ + static class TurbinePackageType extends TurbineTypeMirror implements NoType { + + @Override + public Type asTurbineType() { + throw new UnsupportedOperationException(); + } + + final PackageSymbol symbol; + + TurbinePackageType(ModelFactory factory, PackageSymbol symbol) { + super(factory); + this.symbol = symbol; + } + + @Override + public TypeKind getKind() { + return TypeKind.PACKAGE; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitNoType(this, p); + } + + @Override + public String toString() { + return symbol.toString(); + } + + @Override + public boolean equals(Object other) { + return other instanceof TurbinePackageType + && symbol.equals(((TurbinePackageType) other).symbol); + } + + @Override + public int hashCode() { + return symbol.hashCode(); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + } + + /** The absence of a type, {@see javax.lang.model.util.Types#getNoType}. */ + static class TurbineNoType extends TurbineTypeMirror implements NoType { + + @Override + public Type asTurbineType() { + return Type.NONE; + } + + TurbineNoType(ModelFactory factory) { + super(factory); + } + + @Override + public TypeKind getKind() { + return TypeKind.NONE; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitNoType(this, p); + } + + @Override + public String toString() { + return "none"; + } + + @Override + public boolean equals(Object other) { + return other instanceof TurbineNoType; + } + + @Override + public int hashCode() { + return getKind().hashCode(); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + } + + /** A void type, {@see javax.lang.model.util.Types#getNoType}. */ + static class TurbineVoidType extends TurbineTypeMirror implements NoType { + + @Override + public Type asTurbineType() { + return Type.VOID; + } + + TurbineVoidType(ModelFactory factory) { + super(factory); + } + + @Override + public TypeKind getKind() { + return TypeKind.VOID; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitNoType(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + } + + /** A {@link TypeVariable} implementation backed by a {@link TyVar}. */ + static class TurbineTypeVariable extends TurbineTypeMirror implements TypeVariable { + + @Override + public int hashCode() { + return type.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineTypeVariable && type.equals(((TurbineTypeVariable) obj).type); + } + + @Override + public Type asTurbineType() { + return type; + } + + private final TyVar type; + + private final Supplier<TyVarInfo> info = + factory.memoize( + new Supplier<TyVarInfo>() { + @Override + public TyVarInfo get() { + return factory.getTyVarInfo(type.sym()); + } + }); + + private TyVarInfo info() { + return info.get(); + } + + TurbineTypeVariable(ModelFactory factory, Type.TyVar type) { + super(factory); + this.type = type; + } + + @Override + public TypeKind getKind() { + return TypeKind.TYPEVAR; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitTypeVariable(this, p); + } + + @Override + public Element asElement() { + return factory.typeParameterElement(type.sym()); + } + + @Override + public TypeMirror getUpperBound() { + return factory.asTypeMirror(info().upperBound()); + } + + @Override + public TypeMirror getLowerBound() { + return info().lowerBound() != null + ? factory.asTypeMirror(info().lowerBound()) + : factory.noType(); + } + + @Override + public String toString() { + return type.toString(); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return type.annos(); + } + } + + /** A {@link WildcardType} implementation backed by a {@link WildTy}. */ + static class TurbineWildcardType extends TurbineTypeMirror implements WildcardType { + + @Override + public Type asTurbineType() { + return type; + } + + private final WildTy type; + + public TurbineWildcardType(ModelFactory factory, WildTy type) { + super(factory); + this.type = type; + } + + @Override + public TypeKind getKind() { + return TypeKind.WILDCARD; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitWildcard(this, p); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineWildcardType && type.equals(((TurbineWildcardType) obj).type); + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + @Override + public TypeMirror getExtendsBound() { + return type.boundKind() == BoundKind.UPPER ? factory.asTypeMirror(type.bound()) : null; + } + + @Override + public TypeMirror getSuperBound() { + return type.boundKind() == BoundKind.LOWER ? factory.asTypeMirror(type.bound()) : null; + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return type.annotations(); + } + } + + /** A {@link IntersectionType} implementation backed by a {@link IntersectionTy}. */ + static class TurbineIntersectionType extends TurbineTypeMirror implements IntersectionType { + + @Override + public Type asTurbineType() { + return type; + } + + private final IntersectionTy type; + + TurbineIntersectionType(ModelFactory factory, IntersectionTy type) { + super(factory); + this.type = type; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineIntersectionType + && type.equals(((TurbineIntersectionType) obj).type); + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + @Override + public TypeKind getKind() { + return TypeKind.INTERSECTION; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitIntersection(this, p); + } + + final Supplier<ImmutableList<TypeMirror>> bounds = + factory.memoize( + new Supplier<ImmutableList<TypeMirror>>() { + @Override + public ImmutableList<TypeMirror> get() { + return factory.asTypeMirrors(TurbineTypes.getBounds(factory, type)); + } + }); + + @Override + public List<? extends TypeMirror> getBounds() { + return bounds.get(); + } + + @Override + public String toString() { + return Joiner.on('&').join(getBounds()); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + } + + /** A {@link NullType} implementation. */ + public static class TurbineNullType extends TurbineTypeMirror implements NullType { + + @Override + public Type asTurbineType() { + return Type.PrimTy.create(TurbineConstantTypeKind.NULL, ImmutableList.of()); + } + + public TurbineNullType(ModelFactory factory) { + super(factory); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof NullType; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public TypeKind getKind() { + return TypeKind.NULL; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitNull(this, p); + } + } + + /** An {@link ExecutableType} implementation backed by a {@link MethodTy}. */ + public static class TurbineExecutableType extends TurbineTypeMirror implements ExecutableType { + + @Override + public String toString() { + return type.toString(); + } + + @Override + public MethodTy asTurbineType() { + return type; + } + + public final MethodTy type; + + TurbineExecutableType(ModelFactory factory, MethodTy type) { + super(factory); + this.type = type; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TurbineExecutableType + && type.equals(((TurbineExecutableType) obj).type); + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + @Override + public List<? extends TypeVariable> getTypeVariables() { + ImmutableList.Builder<TypeVariable> result = ImmutableList.builder(); + for (TyVarSymbol tyVar : type.tyParams()) { + result.add((TypeVariable) factory.asTypeMirror(TyVar.create(tyVar, ImmutableList.of()))); + } + return result.build(); + } + + @Override + public TypeMirror getReturnType() { + return factory.asTypeMirror(type.returnType()); + } + + @Override + public List<? extends TypeMirror> getParameterTypes() { + return factory.asTypeMirrors(type.parameters()); + } + + @Override + public TypeMirror getReceiverType() { + return type.receiverType() != null + ? factory.asTypeMirror(type.receiverType()) + : factory.noType(); + } + + @Override + public List<? extends TypeMirror> getThrownTypes() { + return factory.asTypeMirrors(type.thrown()); + } + + @Override + public TypeKind getKind() { + return TypeKind.EXECUTABLE; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitExecutable(this, p); + } + + @Override + protected ImmutableList<AnnoInfo> annos() { + return ImmutableList.of(); + } + } +} diff --git a/java/com/google/turbine/processing/TurbineTypes.java b/java/com/google/turbine/processing/TurbineTypes.java new file mode 100644 index 0000000..f65f921 --- /dev/null +++ b/java/com/google/turbine/processing/TurbineTypes.java @@ -0,0 +1,1132 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; +import com.google.turbine.binder.sym.Symbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.model.TurbineTyKind; +import com.google.turbine.processing.TurbineElement.TurbineExecutableElement; +import com.google.turbine.processing.TurbineElement.TurbineFieldElement; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.processing.TurbineElement.TurbineTypeParameterElement; +import com.google.turbine.processing.TurbineTypeMirror.TurbineDeclaredType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineErrorType; +import com.google.turbine.processing.TurbineTypeMirror.TurbineTypeVariable; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.ArrayTy; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.type.Type.MethodTy; +import com.google.turbine.type.Type.PrimTy; +import com.google.turbine.type.Type.TyKind; +import com.google.turbine.type.Type.TyVar; +import com.google.turbine.type.Type.WildTy; +import com.google.turbine.type.Type.WildTy.BoundKind; +import com.google.turbine.type.Type.WildUnboundedTy; +import com.google.turbine.types.Erasure; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** An implementation of {@link Types} backed by turbine's {@link TypeMirror}. */ +public class TurbineTypes implements Types { + + private final ModelFactory factory; + + public TurbineTypes(ModelFactory factory) { + this.factory = factory; + } + + private static Type asTurbineType(TypeMirror typeMirror) { + if (!(typeMirror instanceof TurbineTypeMirror)) { + throw new IllegalArgumentException(typeMirror.toString()); + } + return ((TurbineTypeMirror) typeMirror).asTurbineType(); + } + + @Override + public Element asElement(TypeMirror t) { + switch (t.getKind()) { + case DECLARED: + return ((TurbineDeclaredType) t).asElement(); + case TYPEVAR: + return ((TurbineTypeVariable) t).asElement(); + case ERROR: + return ((TurbineErrorType) t).asElement(); + default: + return null; + } + } + + @Override + public boolean isSameType(TypeMirror a, TypeMirror b) { + Type t1 = asTurbineType(a); + Type t2 = asTurbineType(b); + if (t1.tyKind() == TyKind.WILD_TY || t2.tyKind() == TyKind.WILD_TY) { + // wild card types that appear at the top-level are never equal to each other. + // Note that generics parameterized by wildcards may be equal, so the recursive + // `isSameType(Type, Type)` below does handle wildcards. + return false; + } + return isSameType(t1, t2); + } + + private boolean isSameType(Type a, Type b) { + switch (a.tyKind()) { + case PRIM_TY: + return b.tyKind() == TyKind.PRIM_TY && ((PrimTy) a).primkind() == ((PrimTy) b).primkind(); + case VOID_TY: + return b.tyKind() == TyKind.VOID_TY; + case NONE_TY: + return b.tyKind() == TyKind.NONE_TY; + case CLASS_TY: + return isSameClassType((ClassTy) a, b); + case ARRAY_TY: + return b.tyKind() == TyKind.ARRAY_TY + && isSameType(((ArrayTy) a).elementType(), ((ArrayTy) b).elementType()); + case TY_VAR: + return b.tyKind() == TyKind.TY_VAR && ((TyVar) a).sym().equals(((TyVar) b).sym()); + case WILD_TY: + return isSameWildType((WildTy) a, b); + case INTERSECTION_TY: + return b.tyKind() == TyKind.INTERSECTION_TY + && isSameIntersectionType((IntersectionTy) a, (IntersectionTy) b); + case METHOD_TY: + return b.tyKind() == TyKind.METHOD_TY && isSameMethodType((MethodTy) a, (MethodTy) b); + case ERROR_TY: + return false; + } + throw new AssertionError(a.tyKind()); + } + + /** + * Returns true if the given method types are equivalent. + * + * <p>Receiver parameters are ignored, regardless of whether they were explicitly specified in + * source. Thrown exception types are also ignored. + */ + private boolean isSameMethodType(MethodTy a, MethodTy b) { + ImmutableMap<TyVarSymbol, Type> mapping = getMapping(a, b); + if (mapping == null) { + return false; + } + if (!sameTypeParameterBounds(a, b, mapping)) { + return false; + } + if (!isSameType(a.returnType(), subst(b.returnType(), mapping))) { + return false; + } + if (!isSameTypes(a.parameters(), substAll(b.parameters(), mapping))) { + return false; + } + return true; + } + + private boolean sameTypeParameterBounds( + MethodTy a, MethodTy b, ImmutableMap<TyVarSymbol, Type> mapping) { + if (a.tyParams().size() != b.tyParams().size()) { + return false; + } + Iterator<TyVarSymbol> ax = a.tyParams().iterator(); + Iterator<TyVarSymbol> bx = b.tyParams().iterator(); + while (ax.hasNext()) { + TyVarSymbol x = ax.next(); + TyVarSymbol y = bx.next(); + if (!isSameType( + factory.getTyVarInfo(x).upperBound(), + subst(factory.getTyVarInfo(y).upperBound(), mapping))) { + return false; + } + } + return true; + } + + private boolean isSameTypes(ImmutableList<Type> a, ImmutableList<Type> b) { + if (a.size() != b.size()) { + return false; + } + Iterator<Type> ax = a.iterator(); + Iterator<Type> bx = b.iterator(); + while (ax.hasNext()) { + if (!isSameType(ax.next(), bx.next())) { + return false; + } + } + return true; + } + + private boolean isSameIntersectionType(IntersectionTy a, IntersectionTy b) { + return isSameTypes(getBounds(a), getBounds(b)); + } + + private ImmutableList<Type> getBounds(IntersectionTy a) { + return getBounds(factory, a); + } + + static ImmutableList<Type> getBounds(ModelFactory factory, IntersectionTy type) { + ImmutableList<Type> bounds = type.bounds(); + if (implicitObjectBound(factory, bounds)) { + return ImmutableList.<Type>builder().add(ClassTy.OBJECT).addAll(bounds).build(); + } + return bounds; + } + + private static boolean implicitObjectBound(ModelFactory factory, ImmutableList<Type> bounds) { + if (bounds.isEmpty()) { + return true; + } + ClassTy first = (ClassTy) bounds.get(0); + return factory.getSymbol(first.sym()).kind().equals(TurbineTyKind.INTERFACE); + } + + private boolean isSameWildType(WildTy a, Type other) { + switch (other.tyKind()) { + case WILD_TY: + break; + case CLASS_TY: + // `? super Object` = Object + return ((ClassTy) other).sym().equals(ClassSymbol.OBJECT) + && a.boundKind() == BoundKind.LOWER + && a.bound().tyKind() == TyKind.CLASS_TY + && ((ClassTy) a.bound()).sym().equals(ClassSymbol.OBJECT); + default: + return false; + } + WildTy b = (WildTy) other; + switch (a.boundKind()) { + case NONE: + switch (b.boundKind()) { + case UPPER: + // `?` = `? extends Object` + return isObjectType(b.bound()); + case LOWER: + return false; + case NONE: + return true; + } + break; + case UPPER: + switch (b.boundKind()) { + case UPPER: + return isSameType(a.bound(), b.bound()); + case LOWER: + return false; + case NONE: + // `? extends Object` = `?` + return isObjectType(a.bound()); + } + break; + case LOWER: + return b.boundKind() == BoundKind.LOWER && isSameType(a.bound(), b.bound()); + } + throw new AssertionError(a.boundKind()); + } + + private boolean isSameClassType(ClassTy a, Type other) { + switch (other.tyKind()) { + case CLASS_TY: + break; + case WILD_TY: + WildTy w = (WildTy) other; + return a.sym().equals(ClassSymbol.OBJECT) + && w.boundKind() == BoundKind.LOWER + && w.bound().tyKind() == TyKind.CLASS_TY + && ((ClassTy) w.bound()).sym().equals(ClassSymbol.OBJECT); + default: + return false; + } + ClassTy b = (ClassTy) other; + if (!a.sym().equals(b.sym())) { + return false; + } + Iterator<SimpleClassTy> ax = a.classes().reverse().iterator(); + Iterator<SimpleClassTy> bx = b.classes().reverse().iterator(); + while (ax.hasNext() && bx.hasNext()) { + if (!isSameSimpleClassType(ax.next(), bx.next())) { + return false; + } + } + // The class type may be in non-canonical form, e.g. may or may not have entries in 'classes' + // corresponding to enclosing instances. Don't require the enclosing instances' representations + // to be identical unless one of them has type arguments. + if (hasTyArgs(ax) || hasTyArgs(bx)) { + return false; + } + return true; + } + + /** Returns true if any {@link SimpleClassTy} in the given iterator has type arguments. */ + private static boolean hasTyArgs(Iterator<SimpleClassTy> it) { + while (it.hasNext()) { + if (!it.next().targs().isEmpty()) { + return true; + } + } + return false; + } + + private boolean isSameSimpleClassType(SimpleClassTy a, SimpleClassTy b) { + return a.sym().equals(b.sym()) && isSameTypes(a.targs(), b.targs()); + } + + /** Returns true if type {@code a} is a subtype of type {@code b}. See JLS 4.1.0, 'subtyping'. */ + @Override + public boolean isSubtype(TypeMirror a, TypeMirror b) { + return isSubtype(asTurbineType(a), asTurbineType(b), /* strict= */ true); + } + + /** + * Returns true if type {@code a} is a subtype of type {@code b}. See JLS 4.1.0, 'subtyping'. + * + * @param strict true if raw types should not be considered subtypes of parameterized types. See + * also {@link #isAssignable}, which sets {@code strict} to {@code false} to handle unchecked + * conversions. + */ + private boolean isSubtype(Type a, Type b, boolean strict) { + if (b.tyKind() == TyKind.INTERSECTION_TY) { + for (Type bound : getBounds((IntersectionTy) b)) { + // TODO(cushon): javac rejects e.g. `|List| isAssignable Serializable&ArrayList<?>`, + // i.e. it does a strict subtype test against the intersection type. Is that a bug? + if (!isSubtype(a, bound, /* strict= */ true)) { + return false; + } + } + return true; + } + switch (a.tyKind()) { + case CLASS_TY: + return isClassSubtype((ClassTy) a, b, strict); + case PRIM_TY: + return isPrimSubtype((PrimTy) a, b); + case ARRAY_TY: + return isArraySubtype((ArrayTy) a, b, strict); + case TY_VAR: + return isTyVarSubtype((TyVar) a, b, strict); + case INTERSECTION_TY: + return isIntersectionSubtype((IntersectionTy) a, b, strict); + case VOID_TY: + return b.tyKind() == TyKind.VOID_TY; + case NONE_TY: + return b.tyKind() == TyKind.NONE_TY; + case WILD_TY: + // TODO(cushon): javac takes wildcards as input to isSubtype and sometimes returns `true`, + // see JDK-8039198 + return false; + case ERROR_TY: + // for compatibility with javac, treat error as bottom + return true; + case METHOD_TY: + return false; + } + throw new AssertionError(a.tyKind()); + } + + private boolean isTyVarSubtype(TyVar a, Type b, boolean strict) { + if (b.tyKind() == TyKind.TY_VAR) { + return a.sym().equals(((TyVar) b).sym()); + } + TyVarInfo tyVarInfo = factory.getTyVarInfo(a.sym()); + return isSubtype(tyVarInfo.upperBound(), b, strict); + } + + private boolean isIntersectionSubtype(IntersectionTy a, Type b, boolean strict) { + for (Type bound : getBounds(a)) { + if (isSubtype(bound, b, strict)) { + return true; + } + } + return false; + } + + // see JLS 4.10.3, 'subtyping among array types' + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10.3 + private boolean isArraySubtype(ArrayTy a, Type b, boolean strict) { + switch (b.tyKind()) { + case ARRAY_TY: + Type ae = a.elementType(); + Type be = ((ArrayTy) b).elementType(); + if (ae.tyKind() == TyKind.PRIM_TY) { + return isSameType(ae, be); + } + return isSubtype(ae, be, strict); + case CLASS_TY: + ClassSymbol bsym = ((ClassTy) b).sym(); + switch (bsym.binaryName()) { + case "java/lang/Object": + case "java/lang/Cloneable": + case "java/io/Serializable": + return true; + default: + return false; + } + default: + return false; + } + } + + // see JLS 4.10.1, 'subtyping among primitive types' + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10.1 + private static boolean isPrimSubtype(PrimTy a, Type other) { + if (other.tyKind() != TyKind.PRIM_TY) { + return false; + } + PrimTy b = (PrimTy) other; + switch (a.primkind()) { + case CHAR: + switch (b.primkind()) { + case CHAR: + case INT: + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case BYTE: + switch (b.primkind()) { + case BYTE: + case SHORT: + case INT: + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case SHORT: + switch (b.primkind()) { + case SHORT: + case INT: + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case INT: + switch (b.primkind()) { + case INT: + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case LONG: + switch (b.primkind()) { + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case FLOAT: + switch (b.primkind()) { + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } + case DOUBLE: + case STRING: + case BOOLEAN: + return a.primkind() == b.primkind(); + case NULL: + break; + } + throw new AssertionError(a.primkind()); + } + + private boolean isClassSubtype(ClassTy a, Type other, boolean strict) { + if (other.tyKind() != TyKind.CLASS_TY) { + return false; + } + ClassTy b = (ClassTy) other; + if (!a.sym().equals(b.sym())) { + // find a path from a to b in the type hierarchy + ImmutableList<ClassTy> path = factory.cha().search(a, b.sym()); + if (path.isEmpty()) { + return false; + } + // perform repeated type substitution to get an instance of B with the type arguments + // provided by A + a = path.get(0); + for (ClassTy ty : path) { + ImmutableMap<TyVarSymbol, Type> mapping = getMapping(ty); + if (mapping == null) { + // if we encounter a raw type on the path from A to B the result is erased + a = (ClassTy) erasure(a); + break; + } + a = substClassTy(a, mapping); + } + } + Iterator<SimpleClassTy> ax = a.classes().reverse().iterator(); + Iterator<SimpleClassTy> bx = b.classes().reverse().iterator(); + while (ax.hasNext() && bx.hasNext()) { + if (!tyArgsContains(ax.next(), bx.next(), strict)) { + return false; + } + } + return !hasTyArgs(ax) && !hasTyArgs(bx); + } + + /** + * Given two parameterizations of the same {@link SimpleClassTy}, {@code a} and {@code b}, teturns + * true if the type arguments of {@code a} are pairwise contained by the type arguments of {@code + * b}. + * + * @see {@link #contains} and JLS 4.5.1. + */ + private boolean tyArgsContains(SimpleClassTy a, SimpleClassTy b, boolean strict) { + verify(a.sym().equals(b.sym())); + Iterator<Type> ax = a.targs().iterator(); + Iterator<Type> bx = b.targs().iterator(); + while (ax.hasNext() && bx.hasNext()) { + if (!containedBy(ax.next(), bx.next(), strict)) { + return false; + } + } + // C<F1, ..., FN> <= |C|, but |C| is not a subtype of C<F1, ..., FN> + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.8 + if (strict) { + return !bx.hasNext(); + } + return true; + } + + private Type subst(Type type, Map<TyVarSymbol, Type> mapping) { + switch (type.tyKind()) { + case CLASS_TY: + return substClassTy((ClassTy) type, mapping); + case ARRAY_TY: + return substArrayTy((ArrayTy) type, mapping); + case TY_VAR: + return substTyVar((TyVar) type, mapping); + case PRIM_TY: + case VOID_TY: + case NONE_TY: + case ERROR_TY: + return type; + case METHOD_TY: + return substMethod((MethodTy) type, mapping); + case INTERSECTION_TY: + return substIntersectionTy((IntersectionTy) type, mapping); + case WILD_TY: + return substWildTy((WildTy) type, mapping); + } + throw new AssertionError(type.tyKind()); + } + + private Type substWildTy(WildTy type, Map<TyVarSymbol, Type> mapping) { + switch (type.boundKind()) { + case NONE: + return type; + case UPPER: + return Type.WildUpperBoundedTy.create(subst(type.bound(), mapping), ImmutableList.of()); + case LOWER: + return Type.WildLowerBoundedTy.create(subst(type.bound(), mapping), ImmutableList.of()); + } + throw new AssertionError(type.boundKind()); + } + + private Type substIntersectionTy(IntersectionTy type, Map<TyVarSymbol, Type> mapping) { + return IntersectionTy.create(substAll(getBounds(type), mapping)); + } + + private MethodTy substMethod(MethodTy method, Map<TyVarSymbol, Type> mapping) { + return MethodTy.create( + method.tyParams(), + subst(method.returnType(), mapping), + method.receiverType() != null ? subst(method.receiverType(), mapping) : null, + substAll(method.parameters(), mapping), + substAll(method.thrown(), mapping)); + } + + private ImmutableList<Type> substAll( + ImmutableList<? extends Type> types, Map<TyVarSymbol, Type> mapping) { + ImmutableList.Builder<Type> result = ImmutableList.builder(); + for (Type type : types) { + result.add(subst(type, mapping)); + } + return result.build(); + } + + private Type substTyVar(TyVar type, Map<TyVarSymbol, Type> mapping) { + return mapping.getOrDefault(type.sym(), type); + } + + private Type substArrayTy(ArrayTy type, Map<TyVarSymbol, Type> mapping) { + return ArrayTy.create(subst(type.elementType(), mapping), type.annos()); + } + + private ClassTy substClassTy(ClassTy type, Map<TyVarSymbol, Type> mapping) { + ImmutableList.Builder<SimpleClassTy> simples = ImmutableList.builder(); + for (SimpleClassTy simple : type.classes()) { + ImmutableList.Builder<Type> args = ImmutableList.builder(); + for (Type arg : simple.targs()) { + args.add(subst(arg, mapping)); + } + simples.add(SimpleClassTy.create(simple.sym(), args.build(), simple.annos())); + } + return ClassTy.create(simples.build()); + } + + /** + * Returns a mapping that can be used to adapt the signature 'b' to the type parameters of 'a', or + * {@code null} if no such mapping exists. + */ + @Nullable + private static ImmutableMap<TyVarSymbol, Type> getMapping(MethodTy a, MethodTy b) { + if (a.tyParams().size() != b.tyParams().size()) { + return null; + } + Iterator<TyVarSymbol> ax = a.tyParams().iterator(); + Iterator<TyVarSymbol> bx = b.tyParams().iterator(); + ImmutableMap.Builder<TyVarSymbol, Type> mapping = ImmutableMap.builder(); + while (ax.hasNext()) { + TyVarSymbol s = ax.next(); + TyVarSymbol t = bx.next(); + mapping.put(t, TyVar.create(s, ImmutableList.of())); + } + return mapping.build(); + } + + /** + * Returns a map from formal type parameters to their arguments for a given class type, or an + * empty map for non-parameterized types, or {@code null} for raw types. + */ + @Nullable + private ImmutableMap<TyVarSymbol, Type> getMapping(ClassTy ty) { + ImmutableMap.Builder<TyVarSymbol, Type> mapping = ImmutableMap.builder(); + for (SimpleClassTy s : ty.classes()) { + TypeBoundClass info = factory.getSymbol(s.sym()); + if (s.targs().isEmpty() && !info.typeParameters().isEmpty()) { + return null; // rawtypes + } + Iterator<TyVarSymbol> ax = info.typeParameters().values().iterator(); + Iterator<Type> bx = s.targs().iterator(); + while (ax.hasNext()) { + mapping.put(ax.next(), bx.next()); + } + verify(!bx.hasNext()); + } + return mapping.build(); + } + + @Override + public boolean isAssignable(TypeMirror a1, TypeMirror a2) { + return isAssignable(asTurbineType(a1), asTurbineType(a2)); + } + + private boolean isAssignable(Type t1, Type t2) { + switch (t1.tyKind()) { + case PRIM_TY: + if (t2.tyKind() == TyKind.CLASS_TY) { + ClassSymbol boxed = boxedClass(((PrimTy) t1).primkind()); + t1 = ClassTy.asNonParametricClassTy(boxed); + } + break; + case CLASS_TY: + switch (t2.tyKind()) { + case PRIM_TY: + TurbineConstantTypeKind unboxed = unboxedType((ClassTy) t1); + if (unboxed == null) { + return false; + } + t1 = PrimTy.create(unboxed, ImmutableList.of()); + break; + case CLASS_TY: + break; + default: // fall out + } + break; + default: // fall out + } + return isSubtype(t1, t2, /* strict= */ false); + } + + private static boolean isObjectType(Type type) { + return type.tyKind() == TyKind.CLASS_TY && ((ClassTy) type).sym().equals(ClassSymbol.OBJECT); + } + + @Override + public boolean contains(TypeMirror a, TypeMirror b) { + return contains(asTurbineType(a), asTurbineType(b), /* strict= */ true); + } + + private boolean contains(Type t1, Type t2, boolean strict) { + return containedBy(t2, t1, strict); + } + + // See JLS 4.5.1, 'type containment' + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1 + private boolean containedBy(Type t1, Type t2, boolean strict) { + if (t1.tyKind() == TyKind.WILD_TY) { + WildTy w1 = (WildTy) t1; + Type t; + switch (w1.boundKind()) { + case UPPER: + t = w1.bound(); + if (t2.tyKind() == TyKind.WILD_TY) { + WildTy w2 = (WildTy) t2; + switch (w2.boundKind()) { + case UPPER: + // ? extends T <= ? extends S if T <: S + return isSubtype(t, w2.bound(), strict); + case NONE: + // ? extends T <= ? [extends Object] if T <: Object + return true; + case LOWER: + // ? extends T <= ? super S + return false; + } + throw new AssertionError(w1.boundKind()); + } + return false; + case LOWER: + t = w1.bound(); + if (t2.tyKind() == TyKind.WILD_TY) { + WildTy w2 = (WildTy) t2; + switch (w2.boundKind()) { + case LOWER: + // ? super T <= ? super S if S <: T + return isSubtype(w2.bound(), t, strict); + case NONE: + // ? super T <= ? [extends Object] + return true; + case UPPER: + // ? super T <= ? extends Object + return isObjectType(w2.bound()); + } + throw new AssertionError(w2.boundKind()); + } + // ? super Object <= Object + return isObjectType(t2) && isObjectType(t); + case NONE: + if (t2.tyKind() == TyKind.WILD_TY) { + WildTy w2 = (WildTy) t2; + switch (w2.boundKind()) { + case NONE: + // ? [extends Object] <= ? extends Object + return true; + case LOWER: + // ? [extends Object] <= ? super S + return false; + case UPPER: + // ? [extends Object] <= ? extends S if Object <: S + return isObjectType(w2.bound()); + } + throw new AssertionError(w2.boundKind()); + } + return false; + } + throw new AssertionError(w1.boundKind()); + } + if (t2.tyKind() == TyKind.WILD_TY) { + WildTy w2 = (WildTy) t2; + switch (w2.boundKind()) { + case LOWER: + // T <= ? super S + return isSubtype(w2.bound(), t1, strict); + case UPPER: + // T <= ? extends S + return isSubtype(t1, w2.bound(), strict); + case NONE: + // T <= ? [extends Object] + return true; + } + throw new AssertionError(w2.boundKind()); + } + if (isSameType(t1, t2)) { + return true; + } + return false; + } + + @Override + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) { + return isSubsignature((MethodTy) asTurbineType(m1), (MethodTy) asTurbineType(m2)); + } + + private boolean isSubsignature(MethodTy a, MethodTy b) { + return isSameSignature(a, b) || isSameSignature(a, (MethodTy) erasure(b)); + } + + private boolean isSameSignature(MethodTy a, MethodTy b) { + if (a.parameters().size() != b.parameters().size()) { + return false; + } + ImmutableMap<TyVarSymbol, Type> mapping = getMapping(a, b); + if (mapping == null) { + return false; + } + if (!sameTypeParameterBounds(a, b, mapping)) { + return false; + } + Iterator<Type> ax = a.parameters().iterator(); + // adapt the formal parameter types of 'b' to the type parameters of 'a' + Iterator<Type> bx = substAll(b.parameters(), mapping).iterator(); + while (ax.hasNext()) { + if (!isSameType(ax.next(), bx.next())) { + return false; + } + } + return true; + } + + @Override + public List<? extends TypeMirror> directSupertypes(TypeMirror m) { + return factory.asTypeMirrors(directSupertypes(asTurbineType(m))); + } + + public ImmutableList<Type> directSupertypes(Type t) { + switch (t.tyKind()) { + case CLASS_TY: + return directSupertypes((ClassTy) t); + case INTERSECTION_TY: + return ((IntersectionTy) t).bounds(); + case TY_VAR: + return getBounds(factory.getTyVarInfo(((TyVar) t).sym()).upperBound()); + case ARRAY_TY: + return directSupertypes((ArrayTy) t); + case PRIM_TY: + case VOID_TY: + case WILD_TY: + case ERROR_TY: + case NONE_TY: + return ImmutableList.of(); + case METHOD_TY: + break; + } + throw new IllegalArgumentException(t.tyKind().name()); + } + + private ImmutableList<Type> directSupertypes(ArrayTy t) { + Type elem = t.elementType(); + if (elem.tyKind() == TyKind.PRIM_TY || isObjectType(elem)) { + return ImmutableList.of( + IntersectionTy.create( + ImmutableList.of(ClassTy.OBJECT, ClassTy.SERIALIZABLE, ClassTy.CLONEABLE))); + } + ImmutableList<Type> ex = directSupertypes(elem); + return ImmutableList.of(ArrayTy.create(ex.iterator().next(), ImmutableList.of())); + } + + private ImmutableList<Type> directSupertypes(ClassTy t) { + if (t.sym().equals(ClassSymbol.OBJECT)) { + return ImmutableList.of(); + } + TypeBoundClass info = factory.getSymbol(t.sym()); + Map<TyVarSymbol, Type> mapping = getMapping(t); + boolean raw = mapping == null; + ImmutableList.Builder<Type> builder = ImmutableList.builder(); + if (info.superClassType() != null) { + builder.add(raw ? erasure(info.superClassType()) : subst(info.superClassType(), mapping)); + } else { + builder.add(ClassTy.OBJECT); + } + for (Type interfaceType : info.interfaceTypes()) { + builder.add(raw ? erasure(interfaceType) : subst(interfaceType, mapping)); + } + return builder.build(); + } + + @Override + public TypeMirror erasure(TypeMirror typeMirror) { + return factory.asTypeMirror(erasure(asTurbineType(typeMirror))); + } + + private Type erasure(Type type) { + return Erasure.erase( + type, + new Function<TyVarSymbol, TyVarInfo>() { + @Override + public TyVarInfo apply(TyVarSymbol input) { + return factory.getTyVarInfo(input); + } + }); + } + + @Override + public TypeElement boxedClass(PrimitiveType p) { + return factory.typeElement(boxedClass(((PrimTy) asTurbineType(p)).primkind())); + } + + static ClassSymbol boxedClass(TurbineConstantTypeKind kind) { + switch (kind) { + case CHAR: + return ClassSymbol.CHARACTER; + case SHORT: + return ClassSymbol.SHORT; + case INT: + return ClassSymbol.INTEGER; + case LONG: + return ClassSymbol.LONG; + case FLOAT: + return ClassSymbol.FLOAT; + case DOUBLE: + return ClassSymbol.DOUBLE; + case BOOLEAN: + return ClassSymbol.BOOLEAN; + case BYTE: + return ClassSymbol.BYTE; + case STRING: + case NULL: + break; + } + throw new AssertionError(kind); + } + + @Override + public PrimitiveType unboxedType(TypeMirror typeMirror) { + Type type = asTurbineType(typeMirror); + if (type.tyKind() != TyKind.CLASS_TY) { + throw new IllegalArgumentException(type.toString()); + } + TurbineConstantTypeKind unboxed = unboxedType((ClassTy) type); + if (unboxed == null) { + throw new IllegalArgumentException(type.toString()); + } + return (PrimitiveType) factory.asTypeMirror(PrimTy.create(unboxed, ImmutableList.of())); + } + + private static TurbineConstantTypeKind unboxedType(ClassTy classTy) { + switch (classTy.sym().binaryName()) { + case "java/lang/Boolean": + return TurbineConstantTypeKind.BOOLEAN; + case "java/lang/Byte": + return TurbineConstantTypeKind.BYTE; + case "java/lang/Short": + return TurbineConstantTypeKind.SHORT; + case "java/lang/Integer": + return TurbineConstantTypeKind.INT; + case "java/lang/Long": + return TurbineConstantTypeKind.LONG; + case "java/lang/Character": + return TurbineConstantTypeKind.CHAR; + case "java/lang/Float": + return TurbineConstantTypeKind.FLOAT; + case "java/lang/Double": + return TurbineConstantTypeKind.DOUBLE; + default: + return null; + } + } + + @Override + public TypeMirror capture(TypeMirror typeMirror) { + throw new UnsupportedOperationException(); + } + + @Override + public PrimitiveType getPrimitiveType(TypeKind kind) { + checkArgument(kind.isPrimitive(), "%s is not a primitive type", kind); + return (PrimitiveType) + factory.asTypeMirror(PrimTy.create(primitiveType(kind), ImmutableList.of())); + } + + private static TurbineConstantTypeKind primitiveType(TypeKind kind) { + switch (kind) { + case BOOLEAN: + return TurbineConstantTypeKind.BOOLEAN; + case BYTE: + return TurbineConstantTypeKind.BYTE; + case SHORT: + return TurbineConstantTypeKind.SHORT; + case INT: + return TurbineConstantTypeKind.INT; + case LONG: + return TurbineConstantTypeKind.LONG; + case CHAR: + return TurbineConstantTypeKind.CHAR; + case FLOAT: + return TurbineConstantTypeKind.FLOAT; + case DOUBLE: + return TurbineConstantTypeKind.DOUBLE; + default: + throw new IllegalArgumentException(kind + " is not a primitive type"); + } + } + + @Override + public NullType getNullType() { + return factory.nullType(); + } + + @Override + public NoType getNoType(TypeKind kind) { + switch (kind) { + case VOID: + return (NoType) factory.asTypeMirror(Type.VOID); + case NONE: + return factory.noType(); + default: + throw new IllegalArgumentException(kind.toString()); + } + } + + @Override + public ArrayType getArrayType(TypeMirror componentType) { + return (ArrayType) + factory.asTypeMirror(ArrayTy.create(asTurbineType(componentType), ImmutableList.of())); + } + + @Override + public WildcardType getWildcardType(TypeMirror extendsBound, TypeMirror superBound) { + WildTy type; + if (extendsBound != null) { + type = WildTy.WildUpperBoundedTy.create(asTurbineType(extendsBound), ImmutableList.of()); + } else if (superBound != null) { + type = WildTy.WildLowerBoundedTy.create(asTurbineType(superBound), ImmutableList.of()); + } else { + type = WildUnboundedTy.create(ImmutableList.of()); + } + return (WildcardType) factory.asTypeMirror(type); + } + + @Override + public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) { + requireNonNull(typeElem); + ImmutableList.Builder<Type> args = ImmutableList.builder(); + for (TypeMirror t : typeArgs) { + args.add(asTurbineType(t)); + } + TurbineTypeElement element = (TurbineTypeElement) typeElem; + return (DeclaredType) + factory.asTypeMirror( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create(element.sym(), args.build(), ImmutableList.of())))); + } + + @Override + public DeclaredType getDeclaredType( + DeclaredType containing, TypeElement typeElem, TypeMirror... typeArgs) { + if (containing == null) { + return getDeclaredType(typeElem, typeArgs); + } + requireNonNull(typeElem); + ClassTy base = (ClassTy) asTurbineType(containing); + TurbineTypeElement element = (TurbineTypeElement) typeElem; + ImmutableList.Builder<Type> args = ImmutableList.builder(); + for (TypeMirror t : typeArgs) { + args.add(asTurbineType(t)); + } + return (DeclaredType) + factory.asTypeMirror( + ClassTy.create( + ImmutableList.<SimpleClassTy>builder() + .addAll(base.classes()) + .add(SimpleClassTy.create(element.sym(), args.build(), ImmutableList.of())) + .build())); + } + + private static ClassSymbol enclosingClass(Symbol symbol) { + switch (symbol.symKind()) { + case CLASS: + return (ClassSymbol) symbol; + case TY_PARAM: + return enclosingClass(((TyVarSymbol) symbol).owner()); + case METHOD: + return ((MethodSymbol) symbol).owner(); + case FIELD: + return ((FieldSymbol) symbol).owner(); + case PARAMETER: + return ((ParamSymbol) symbol).owner().owner(); + case MODULE: + case PACKAGE: + throw new IllegalArgumentException(symbol.symKind().toString()); + } + throw new AssertionError(symbol.symKind()); + } + + private static Type type(Element element) { + switch (element.getKind()) { + case TYPE_PARAMETER: + return TyVar.create(((TurbineTypeParameterElement) element).sym(), ImmutableList.of()); + case FIELD: + return ((TurbineFieldElement) element).info().type(); + case METHOD: + case CONSTRUCTOR: + return ((TurbineExecutableElement) element).info().asType(); + default: + throw new UnsupportedOperationException(element.toString()); + } + } + + /** + * Returns the {@link TypeMirror} of the given {@code element} as a member of {@code containing}, + * or else {@code null} if it is not a member. + * + * <p>e.g. given a class {@code MyStringList} that implements {@code List<String>}, the type of + * {@code List.add} would be {@code add(String)}. + */ + @Override + public TypeMirror asMemberOf(DeclaredType containing, Element element) { + ClassTy c = ((TurbineDeclaredType) containing).asTurbineType(); + ClassSymbol symbol = enclosingClass(((TurbineElement) element).sym()); + ImmutableList<ClassTy> path = factory.cha().search(c, enclosingClass(symbol)); + if (path.isEmpty()) { + return null; + } + Type type = type(element); + for (ClassTy ty : path) { + ImmutableMap<TyVarSymbol, Type> mapping = getMapping(ty); + if (mapping == null) { + type = erasure(type); + break; + } + type = subst(type, mapping); + } + return factory.asTypeMirror(type); + } +} diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java index 2b9374e..b693a42 100644 --- a/java/com/google/turbine/tree/Pretty.java +++ b/java/com/google/turbine/tree/Pretty.java @@ -501,8 +501,6 @@ public class Pretty implements Tree.Visitor<Void, Void> { case ACC_SYNTHETIC: case ACC_BRIDGE: break; - default: - throw new AssertionError(mod); } } } diff --git a/java/com/google/turbine/tree/Tree.java b/java/com/google/turbine/tree/Tree.java index a20b106..d36c3ab 100644 --- a/java/com/google/turbine/tree/Tree.java +++ b/java/com/google/turbine/tree/Tree.java @@ -661,6 +661,7 @@ public abstract class Tree { private final Tree ty; private final Ident name; private final Optional<Expression> init; + private final String javadoc; public VarDecl( int position, @@ -668,13 +669,15 @@ public abstract class Tree { ImmutableList<Anno> annos, Tree ty, Ident name, - Optional<Expression> init) { + Optional<Expression> init, + String javadoc) { super(position); this.mods = ImmutableSet.copyOf(mods); this.annos = annos; this.ty = ty; this.name = name; this.init = init; + this.javadoc = javadoc; } @Override @@ -706,6 +709,14 @@ public abstract class Tree { public Optional<Expression> init() { return init; } + + /** + * A javadoc comment, excluding the opening and closing delimiters but including all interior + * characters and whitespace. + */ + public String javadoc() { + return javadoc; + } } /** A JLS 8.4 method declaration. */ @@ -718,6 +729,7 @@ public abstract class Tree { private final ImmutableList<VarDecl> params; private final ImmutableList<ClassTy> exntys; private final Optional<Tree> defaultValue; + private final String javadoc; public MethDecl( int position, @@ -728,7 +740,8 @@ public abstract class Tree { Ident name, ImmutableList<VarDecl> params, ImmutableList<ClassTy> exntys, - Optional<Tree> defaultValue) { + Optional<Tree> defaultValue, + String javadoc) { super(position); this.mods = ImmutableSet.copyOf(mods); this.annos = annos; @@ -738,6 +751,7 @@ public abstract class Tree { this.params = params; this.exntys = exntys; this.defaultValue = defaultValue; + this.javadoc = javadoc; } @Override @@ -781,6 +795,13 @@ public abstract class Tree { public Optional<Tree> defaultValue() { return defaultValue; } + /** + * A javadoc comment, excluding the opening and closing delimiters but including all interior + * characters and whitespace. + */ + public String javadoc() { + return javadoc; + } } /** A JLS 9.7 annotation. */ @@ -852,6 +873,7 @@ public abstract class Tree { private final ImmutableList<ClassTy> impls; private final ImmutableList<Tree> members; private final TurbineTyKind tykind; + private final String javadoc; public TyDecl( int position, @@ -862,7 +884,8 @@ public abstract class Tree { Optional<ClassTy> xtnds, ImmutableList<ClassTy> impls, ImmutableList<Tree> members, - TurbineTyKind tykind) { + TurbineTyKind tykind, + String javadoc) { super(position); this.mods = ImmutableSet.copyOf(mods); this.annos = annos; @@ -872,6 +895,7 @@ public abstract class Tree { this.impls = impls; this.members = members; this.tykind = tykind; + this.javadoc = javadoc; } @Override @@ -915,6 +939,13 @@ public abstract class Tree { public TurbineTyKind tykind() { return tykind; } + /** + * A javadoc comment, excluding the opening and closing delimiters but including all interior + * characters and whitespace. + */ + public String javadoc() { + return javadoc; + } } /** A JLS 4.4. type variable declaration. */ diff --git a/java/com/google/turbine/type/AnnoInfo.java b/java/com/google/turbine/type/AnnoInfo.java index 9c907aa..ff902b3 100644 --- a/java/com/google/turbine/type/AnnoInfo.java +++ b/java/com/google/turbine/type/AnnoInfo.java @@ -16,6 +16,9 @@ package com.google.turbine.type; +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.util.Objects.requireNonNull; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.turbine.binder.sym.ClassSymbol; @@ -24,6 +27,7 @@ import com.google.turbine.model.Const; import com.google.turbine.tree.Tree; import com.google.turbine.tree.Tree.Anno; import com.google.turbine.tree.Tree.Expression; +import java.util.Map; import java.util.Objects; /** An annotation use. */ @@ -38,7 +42,7 @@ public class AnnoInfo { this.source = source; this.sym = sym; this.tree = tree; - this.values = values; + this.values = requireNonNull(values); } /** The annotation's source, for diagnostics. */ @@ -66,6 +70,10 @@ public class AnnoInfo { return sym; } + public Tree.Anno tree() { + return tree; + } + public AnnoInfo withValues(ImmutableMap<String, Const> values) { return new AnnoInfo(source, sym, tree, values); } @@ -83,4 +91,27 @@ public class AnnoInfo { AnnoInfo that = (AnnoInfo) obj; return sym.equals(that.sym) && values.equals(that.values); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('@').append(sym.binaryName().replace('/', '.').replace('$', '.')); + boolean first = true; + if (values != null && !values.isEmpty()) { + sb.append('('); + if (values.size() == 1 && values.containsKey("value")) { + sb.append(getOnlyElement(values.values())); + } else { + for (Map.Entry<String, Const> e : values.entrySet()) { + if (!first) { + sb.append(", "); + } + sb.append(e.getKey()).append('=').append(e.getValue()); + first = false; + } + } + sb.append(')'); + } + return sb.toString(); + } } diff --git a/java/com/google/turbine/type/Type.java b/java/com/google/turbine/type/Type.java index 8950ec0..daba2ae 100644 --- a/java/com/google/turbine/type/Type.java +++ b/java/com/google/turbine/type/Type.java @@ -16,14 +16,23 @@ package com.google.turbine.type; +import static com.google.common.collect.Iterables.getLast; +import static java.util.Objects.requireNonNull; + import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.tree.Tree; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; /** JLS 4 types. */ public interface Type { @@ -48,8 +57,11 @@ public interface Type { WILD_TY, /** An intersection type. */ INTERSECTION_TY, + /** A method type. */ + METHOD_TY, - ERROR_TY + ERROR_TY, + NONE_TY, } /** The type kind. */ @@ -62,9 +74,34 @@ public interface Type { public TyKind tyKind() { return TyKind.VOID_TY; } + + @Override + public final String toString() { + return "void"; + } + }; + + /** The void type. */ + Type NONE = + new Type() { + @Override + public TyKind tyKind() { + return TyKind.NONE_TY; + } + + @Override + public final String toString() { + return "none"; + } }; - /** A class type. */ + /** + * A class type. + * + * <p>Qualified types (e.g. {@code OuterClass<Foo>.InnerClass<Bar>}) are repesented as a list + * {@link SimpleClassTy}s (enclosing types first), each of which contains a {@link ClassSymbol} + * and an optional list of type arguments. + */ @AutoValue abstract class ClassTy implements Type { @@ -77,19 +114,17 @@ public interface Type { /** The {@link ClassTy} for {@code java.lang.String}. */ public static final ClassTy STRING = asNonParametricClassTy(ClassSymbol.STRING); + public static final ClassTy CLONEABLE = asNonParametricClassTy(ClassSymbol.CLONEABLE); + public static final ClassTy SERIALIZABLE = asNonParametricClassTy(ClassSymbol.SERIALIZABLE); + /** Returns a {@link ClassTy} with no type arguments for the given {@link ClassSymbol}. */ public static ClassTy asNonParametricClassTy(ClassSymbol i) { - return create(Arrays.asList(SimpleClassTy.create(i, ImmutableList.of(), ImmutableList.of()))); + return ClassTy.create( + Arrays.asList(SimpleClassTy.create(i, ImmutableList.of(), ImmutableList.of()))); } public abstract ImmutableList<SimpleClassTy> classes(); - /** - * A class type. Qualified types are repesented as a list tuples, each of which contains a - * {@link ClassSymbol} and an optional list of type arguments. - * - * @param classes components of a qualified class type, possibly with type arguments. - */ public static ClassTy create(Iterable<SimpleClassTy> classes) { return new AutoValue_Type_ClassTy(ImmutableList.copyOf(classes)); } @@ -101,7 +136,7 @@ public interface Type { /** The class symbol. */ public ClassSymbol sym() { - return Iterables.getLast(classes()).sym(); + return getLast(classes()).sym(); } @Override @@ -109,11 +144,15 @@ public interface Type { StringBuilder sb = new StringBuilder(); boolean first = true; for (SimpleClassTy c : classes()) { + for (AnnoInfo anno : c.annos()) { + sb.append(anno); + sb.append(' '); + } if (!first) { sb.append('.'); sb.append(c.sym().binaryName().substring(c.sym().binaryName().lastIndexOf('$') + 1)); } else { - sb.append(c.sym().binaryName()); + sb.append(c.sym().binaryName().replace('/', '.').replace('$', '.')); } if (!c.targs().isEmpty()) { sb.append('<'); @@ -142,6 +181,46 @@ public interface Type { /** The type annotations. */ public abstract ImmutableList<AnnoInfo> annos(); + + @Memoized + @Override + public abstract int hashCode(); + } + + @Memoized + @Override + public int hashCode() { + return Iterables.getLast(classes()).hashCode(); + } + + @Override + public final boolean equals(Object obj) { + if (!(obj instanceof ClassTy)) { + return false; + } + ClassTy that = (ClassTy) obj; + int i = this.classes().size() - 1; + int j = that.classes().size() - 1; + for (; i >= 0 && j >= 0; i--, j--) { + if (!this.classes().get(i).equals(that.classes().get(j))) { + return false; + } + } + // don't rely on canonical form for simple class names + if (hasTargs(this.classes(), i) || hasTargs(that.classes(), j)) { + return false; + } + return true; + } + + private static boolean hasTargs(ImmutableList<SimpleClassTy> classes, int idx) { + for (; idx >= 0; idx--) { + SimpleClassTy simple = classes.get(idx); + if (!simple.targs().isEmpty() || !simple.annos().isEmpty()) { + return true; + } + } + return false; } } @@ -163,6 +242,22 @@ public interface Type { /** The type annotations. */ public abstract ImmutableList<AnnoInfo> annos(); + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annos()) { + sb.append(anno); + sb.append(' '); + } + sb.append(elementType()); + sb.append("[]"); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); } /** A type variable. */ @@ -183,11 +278,21 @@ public interface Type { @Override public final String toString() { - return sym().owner() + "#" + sym().name(); + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annos()) { + sb.append(anno); + sb.append(' '); + } + sb.append(sym().name()); + return sb.toString(); } /** The type annotations. */ public abstract ImmutableList<AnnoInfo> annos(); + + @Memoized + @Override + public abstract int hashCode(); } /** A primitive type. */ @@ -208,6 +313,21 @@ public interface Type { /** The type annotations. */ public abstract ImmutableList<AnnoInfo> annos(); + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annos()) { + sb.append(anno); + sb.append(' '); + } + sb.append(primkind()); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); } /** A wildcard type, valid only inside (possibly nested) type arguments. */ @@ -248,6 +368,22 @@ public interface Type { public BoundKind boundKind() { return BoundKind.UPPER; } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annotations()) { + sb.append(anno); + sb.append(' '); + } + sb.append("? extends "); + sb.append(bound()); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); } /** An lower-bounded wildcard type. */ @@ -266,6 +402,22 @@ public interface Type { public BoundKind boundKind() { return BoundKind.LOWER; } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annotations()) { + sb.append(anno); + sb.append(' '); + } + sb.append("? super "); + sb.append(bound()); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); } /** An unbounded wildcard type. */ @@ -285,6 +437,21 @@ public interface Type { public Type bound() { throw new IllegalStateException(); } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + for (AnnoInfo anno : annotations()) { + sb.append(anno); + sb.append(' '); + } + sb.append('?'); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); } /** An intersection type. */ @@ -301,18 +468,121 @@ public interface Type { public TyKind tyKind() { return TyKind.INTERSECTION_TY; } + + @Memoized + @Override + public abstract int hashCode(); + + @Override + public final String toString() { + return Joiner.on('&').join(bounds()); + } } - /** An error type. */ + /** A method type. */ @AutoValue - abstract class ErrorTy implements Type { - public static ErrorTy create() { - return new AutoValue_Type_ErrorTy(); + abstract class MethodTy implements Type { + + public abstract ImmutableSet<TyVarSymbol> tyParams(); + + public abstract Type returnType(); + + /** The type of the receiver parameter (see JLS 8.4.1). */ + @Nullable + public abstract Type receiverType(); + + public abstract ImmutableList<Type> parameters(); + + public abstract ImmutableList<Type> thrown(); + + public static MethodTy create( + ImmutableSet<TyVarSymbol> tyParams, + Type returnType, + Type receiverType, + ImmutableList<Type> parameters, + ImmutableList<Type> thrown) { + return new AutoValue_Type_MethodTy(tyParams, returnType, receiverType, parameters, thrown); + } + + @Override + public TyKind tyKind() { + return TyKind.METHOD_TY; + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + if (!tyParams().isEmpty()) { + sb.append('<'); + Joiner.on(',').appendTo(sb, tyParams()); + sb.append('>'); + } + sb.append('('); + Joiner.on(',').appendTo(sb, parameters()); + sb.append(')'); + sb.append(returnType()); + return sb.toString(); + } + + @Memoized + @Override + public abstract int hashCode(); + } + + /** An error type. */ + final class ErrorTy implements Type { + + private final String name; + + private ErrorTy(String name) { + this.name = requireNonNull(name); + } + + /** + * Best-effort syntactic context for use in diagnostics or by annotation processors. This may be + * a simple or qualified name; it is not a canonical qualified name. + */ + public String name() { + return name; + } + + public static ErrorTy create(Iterable<Tree.Ident> names) { + List<String> bits = new ArrayList<>(); + for (Tree.Ident ident : names) { + bits.add(ident.value()); + } + return create(Joiner.on('.').join(bits)); + } + + public static ErrorTy create(String name) { + return new ErrorTy(name); } @Override public TyKind tyKind() { return TyKind.ERROR_TY; } + + @Override + public final String toString() { + return name(); + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + @Override + public final boolean equals(Object other) { + // The name associated with an error type is context for use in diagnostics or by annotations + // processors. Two error types with the same name don't necessarily represent the same type. + + // TODO(cushon): should error types compare equal to themselves if they correspond to the same + // source location? Investigate storing the source position for this type, or replacing with + // `this == other` (and removing interning in ModelFactory). + + return false; + } } } diff --git a/java/com/google/turbine/types/Canonicalize.java b/java/com/google/turbine/types/Canonicalize.java index ab73618..22df069 100644 --- a/java/com/google/turbine/types/Canonicalize.java +++ b/java/com/google/turbine/types/Canonicalize.java @@ -100,6 +100,7 @@ public class Canonicalize { case PRIM_TY: case VOID_TY: case TY_VAR: + case ERROR_TY: return type; case WILD_TY: return canonicalizeWildTy(base, (WildTy) type); @@ -118,6 +119,9 @@ public class Canonicalize { } private ClassTy canon(ClassSymbol base, ClassTy ty) { + if (ty.sym().equals(ClassSymbol.ERROR)) { + return ty; + } if (isRaw(ty)) { return Erasure.eraseClassTy(ty); } @@ -277,7 +281,7 @@ public class Canonicalize { } /** Instantiates a type argument using the given mapping. */ - private Type instantiate(Map<TyVarSymbol, Type> mapping, Type type) { + private static Type instantiate(Map<TyVarSymbol, Type> mapping, Type type) { if (type == null) { return null; } @@ -286,6 +290,7 @@ public class Canonicalize { return instantiateWildTy(mapping, (WildTy) type); case PRIM_TY: case VOID_TY: + case ERROR_TY: return type; case CLASS_TY: return instantiateClassTy(mapping, (ClassTy) type); @@ -304,7 +309,7 @@ public class Canonicalize { } } - private Type instantiateWildTy(Map<TyVarSymbol, Type> mapping, WildTy type) { + private static Type instantiateWildTy(Map<TyVarSymbol, Type> mapping, WildTy type) { switch (type.boundKind()) { case NONE: return type; @@ -314,12 +319,11 @@ public class Canonicalize { case LOWER: return Type.WildLowerBoundedTy.create( instantiate(mapping, type.bound()), type.annotations()); - default: - throw new AssertionError(type.boundKind()); } + throw new AssertionError(type.boundKind()); } - private Type instantiateClassTy(Map<TyVarSymbol, Type> mapping, ClassTy type) { + private static Type instantiateClassTy(Map<TyVarSymbol, Type> mapping, ClassTy type) { ImmutableList.Builder<SimpleClassTy> simples = ImmutableList.builder(); for (SimpleClassTy simple : type.classes()) { ImmutableList.Builder<Type> args = ImmutableList.builder(); @@ -336,7 +340,7 @@ public class Canonicalize { * reference, or else {@code null}. */ @Nullable - private TyVarSymbol tyVarSym(Type type) { + private static TyVarSymbol tyVarSym(Type type) { if (type.tyKind() == TyKind.TY_VAR) { return ((TyVar) type).sym(); } diff --git a/java/com/google/turbine/types/Erasure.java b/java/com/google/turbine/types/Erasure.java index e2c7d8f..9042897 100644 --- a/java/com/google/turbine/types/Erasure.java +++ b/java/com/google/turbine/types/Erasure.java @@ -18,6 +18,7 @@ package com.google.turbine.types; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.turbine.binder.bound.SourceTypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; import com.google.turbine.binder.sym.TyVarSymbol; @@ -26,15 +27,14 @@ import com.google.turbine.type.Type.ArrayTy; import com.google.turbine.type.Type.ClassTy; import com.google.turbine.type.Type.ClassTy.SimpleClassTy; import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.type.Type.MethodTy; import com.google.turbine.type.Type.TyVar; +import com.google.turbine.type.Type.WildTy; /** Generic type erasure. */ public class Erasure { public static Type erase(Type ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) { switch (ty.tyKind()) { - case PRIM_TY: - case VOID_TY: - return ty; case CLASS_TY: return eraseClassTy((Type.ClassTy) ty); case ARRAY_TY: @@ -43,20 +43,37 @@ public class Erasure { return eraseTyVar((TyVar) ty, tenv); case INTERSECTION_TY: return eraseIntersectionTy((Type.IntersectionTy) ty, tenv); - default: - throw new AssertionError(ty.tyKind()); + case WILD_TY: + return eraseWildTy((Type.WildTy) ty, tenv); + case METHOD_TY: + return erasureMethodTy((Type.MethodTy) ty, tenv); + case PRIM_TY: + case VOID_TY: + case ERROR_TY: + case NONE_TY: + return ty; + } + throw new AssertionError(ty.tyKind()); + } + + private static ImmutableList<Type> erase( + ImmutableList<Type> types, Function<TyVarSymbol, TyVarInfo> tenv) { + ImmutableList.Builder<Type> result = ImmutableList.builder(); + for (Type type : types) { + result.add(erase(type, tenv)); } + return result.build(); } private static Type eraseIntersectionTy( IntersectionTy ty, Function<TyVarSymbol, TyVarInfo> tenv) { - return erase(ty.bounds().get(0), tenv); + return ty.bounds().isEmpty() ? ClassTy.OBJECT : erase(ty.bounds().get(0), tenv); } private static Type eraseTyVar( TyVar ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) { SourceTypeBoundClass.TyVarInfo info = tenv.apply(ty.sym()); - return erase(info.bound(), tenv); + return erase(info.upperBound(), tenv); } private static Type.ArrayTy eraseArrayTy( @@ -75,4 +92,24 @@ public class Erasure { } return ClassTy.create(classes.build()); } + + private static Type eraseWildTy(WildTy ty, Function<TyVarSymbol, TyVarInfo> tenv) { + switch (ty.boundKind()) { + case NONE: + case LOWER: + return ClassTy.OBJECT; + case UPPER: + return erase(ty.bound(), tenv); + } + throw new AssertionError(ty.boundKind()); + } + + private static Type erasureMethodTy(MethodTy ty, Function<TyVarSymbol, TyVarInfo> tenv) { + return MethodTy.create( + /* tyParams= */ ImmutableSet.of(), + erase(ty.returnType(), tenv), + ty.receiverType() != null ? erase(ty.receiverType(), tenv) : null, + erase(ty.parameters(), tenv), + erase(ty.thrown(), tenv)); + } } diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java index 5a4d97e..15b54eb 100644 --- a/javatests/com/google/turbine/binder/BinderErrorTest.java +++ b/javatests/com/google/turbine/binder/BinderErrorTest.java @@ -22,11 +22,19 @@ import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Processing.ProcessorInfo; import com.google.turbine.diag.TurbineError; import com.google.turbine.parse.Parser; import com.google.turbine.tree.Tree.CompUnit; import java.util.Arrays; import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -259,7 +267,7 @@ public class BinderErrorTest { { "<>:2: error: java.lang.Object is not an annotation", // " @Object int x;", - " ^", + " ^", }, }, { @@ -271,7 +279,7 @@ public class BinderErrorTest { { "<>:2: error: java.lang.Deprecated is not @Repeatable", // " @Deprecated @Deprecated int x;", - " ^", + " ^", }, }, { @@ -283,7 +291,7 @@ public class BinderErrorTest { { "<>:2: error: could not resolve NoSuch.NoSuch", // " @NoSuch.NoSuch int x;", - " ^", + " ^", }, }, { @@ -417,6 +425,9 @@ public class BinderErrorTest { "}", }, { + "<>:1: error: cycle in class hierarchy: Cycle", + "class Cycle extends Cycle {", + " ^", "<>:2: error: could not resolve NoSuch", // " NoSuch f;", " ^", @@ -501,7 +512,7 @@ public class BinderErrorTest { " ^", "<>:3: error: could not resolve NoSuchAnno", "@NoSuchAnno", - " ^", + "^", }, }, { @@ -568,7 +579,127 @@ public class BinderErrorTest { "@One.A(b = {@One.NoSuch})", " ^", }, - } + }, + { + { + "public class Test {", // + " @interface Anno {", + " Class<?> value() default Object.class;", + " }", + " @Anno(NoSuch.class) int x;", + " @Anno(NoSuch.class) int y;", + "}", + }, + { + "<>:5: error: could not resolve NoSuch", + " @Anno(NoSuch.class) int x;", + " ^", + "<>:6: error: could not resolve NoSuch", + " @Anno(NoSuch.class) int y;", + " ^", + }, + }, + { + { + "public class Test {", // + " @A @B void f() {}", + "}", + }, + { + "<>:2: error: could not resolve A", + " @A @B void f() {}", + " ^", + "<>:2: error: could not resolve B", + " @A @B void f() {}", + " ^", + }, + }, + { + { + "public class Test {", // + " @A(\"bar\") void f() {}", + "}", + }, + { + "<>:2: error: could not resolve A", // + " @A(\"bar\") void f() {}", + " ^", + }, + }, + { + { + "@NoSuch", + "@interface A {", // + "}", + }, + { + "<>:1: error: could not resolve NoSuch", // + "@NoSuch", + "^", + }, + }, + { + { + "public class Test {", // + " @String @String int x;", + "}", + }, + { + "<>:2: error: java.lang.String is not an annotation", + " @String @String int x;", + " ^", + "<>:2: error: java.lang.String is not an annotation", + " @String @String int x;", + " ^", + }, + }, + { + { + "@interface Anno {", + " int value();", + "}", + "enum E {", + " ONE", + "}", + "@Anno(value = E.ONE)", + "interface Test {}", + }, + { + "<>:7: error: could not evaluate constant expression", // + "@Anno(value = E.ONE)", + " ^", + }, + }, + { + { + "class T extends T {}", + }, + { + "<>:1: error: cycle in class hierarchy: T", "class T extends T {}", " ^", + }, + }, + { + { + "class T implements T {}", + }, + { + "<>:1: error: cycle in class hierarchy: T", + "class T implements T {}", + " ^", + }, + }, + { + { + "class T {", // + " static final String s = \"a\" + + \"b\";", + "}", + }, + { + "<>:2: error: bad operand type String", + " static final String s = \"a\" + + \"b\";", + " ^", + }, + }, }; return Arrays.asList((Object[][]) testCases); } @@ -596,6 +727,41 @@ public class BinderErrorTest { } } + @SupportedAnnotationTypes("*") + static class HelloWorldProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return false; + } + } + + // exercise error reporting with annotation enabled, which should be identical + @Test + public void testWithProcessors() throws Exception { + try { + Binder.bind( + ImmutableList.of(parseLines(source)), + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new HelloWorldProcessor()), + /* loader= */ getClass().getClassLoader(), + /* options= */ ImmutableMap.of(), + SourceVersion.latestSupported()), + TURBINE_BOOTCLASSPATH, + /* moduleVersion=*/ Optional.empty()) + .units(); + fail(Joiner.on('\n').join(source)); + } catch (TurbineError e) { + assertThat(e).hasMessageThat().isEqualTo(lines(expected)); + } + } + private static CompUnit parseLines(String... lines) { return Parser.parse(lines(lines)); } diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java index 4b1e890..e238ee0 100644 --- a/javatests/com/google/turbine/binder/BinderTest.java +++ b/javatests/com/google/turbine/binder/BinderTest.java @@ -36,8 +36,6 @@ import com.google.turbine.tree.Tree; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.jar.JarEntry; @@ -55,22 +53,21 @@ public class BinderTest { @Test public void hello() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "public class A {", - " public class Inner1 extends b.B {", - " }", - " public class Inner2 extends A.Inner1 {", - " }", - "}")); - units.add( - parseLines( - "package b;", // - "import a.A;", - "public class B extends A {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "public class A {", + " public class Inner1 extends b.B {", + " }", + " public class Inner2 extends A.Inner1 {", + " }", + "}"), + parseLines( + "package b;", // + "import a.A;", + "public class B extends A {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -103,20 +100,19 @@ public class BinderTest { @Test public void interfaces() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.i;", // - "public interface I {", - " public class IInner {", - " }", - "}")); - units.add( - parseLines( - "package b;", // - "class B implements com.i.I {", - " class BInner extends IInner {}", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.i;", // + "public interface I {", + " public class IInner {", + " }", + "}"), + parseLines( + "package b;", // + "class B implements com.i.I {", + " class BInner extends IInner {}", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -143,20 +139,19 @@ public class BinderTest { @Test public void imports() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.test;", // - "public class Test {", - " public static class Inner {}", - "}")); - units.add( - parseLines( - "package other;", // - "import com.test.Test.Inner;", - "import no.such.Class;", // imports are resolved lazily on-demand - "public class Foo extends Inner {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.test;", // + "public class Test {", + " public static class Inner {}", + "}"), + parseLines( + "package other;", // + "import com.test.Test.Inner;", + "import no.such.Class;", // imports are resolved lazily on-demand + "public class Foo extends Inner {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -172,21 +167,20 @@ public class BinderTest { @Test public void cycle() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "import b.B;", - "public class A extends B.Inner {", - " class Inner {}", - "}")); - units.add( - parseLines( - "package b;", // - "import a.A;", - "public class B extends A.Inner {", - " class Inner {}", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "import b.B;", + "public class A extends B.Inner {", + " class Inner {}", + "}"), + parseLines( + "package b;", // + "import a.A;", + "public class B extends A.Inner {", + " class Inner {}", + "}")); try { Binder.bind( @@ -202,12 +196,12 @@ public class BinderTest { @Test public void annotationDeclaration() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.test;", // - "public @interface Annotation {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.test;", // + "public @interface Annotation {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -230,13 +224,13 @@ public class BinderTest { @Test public void helloBytecode() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "import java.util.Map.Entry;", - "public class A implements Entry {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "import java.util.Map.Entry;", + "public class A implements Entry {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -268,15 +262,15 @@ public class BinderTest { jos.write(lib.get("B")); } - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "import java.lang.annotation.Target;", - "import java.lang.annotation.ElementType;", - "public class C implements B {", - " @Target(ElementType.TYPE_USE)", - " @interface A {};", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "import java.lang.annotation.Target;", + "import java.lang.annotation.ElementType;", + "public class C implements B {", + " @Target(ElementType.TYPE_USE)", + " @interface A {};", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -294,13 +288,13 @@ public class BinderTest { // (Error reporting is deferred to javac.) @Test public void invalidConst() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "public class A {", - " public static final boolean b = true == 42;", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "public class A {", + " public static final boolean b = true == 42;", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java index 3f41706..c11d814 100644 --- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java +++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java @@ -44,7 +44,10 @@ import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type.ClassTy; import java.io.IOError; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -161,4 +164,18 @@ public class ClassPathBinderTest { assertThat(e).hasMessageThat().contains("NOT_A_JAR"); } } + + @Test + public void resources() throws Exception { + Path path = temporaryFolder.newFile("tmp.jar").toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) { + jos.putNextEntry(new JarEntry("foo/bar/hello.txt")); + jos.write("hello".getBytes(UTF_8)); + jos.putNextEntry(new JarEntry("foo/bar/Baz.class")); + jos.write("goodbye".getBytes(UTF_8)); + } + ClassPath classPath = ClassPathBinder.bindClasspath(ImmutableList.of(path)); + assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello"); + assertThat(classPath.resource("foo/bar/Baz.class")).isNull(); + } } diff --git a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java index 2a0de48..3e841a5 100644 --- a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java +++ b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java @@ -22,14 +22,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; import static java.util.Objects.requireNonNull; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.io.ByteStreams; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; import com.google.turbine.binder.env.CompoundEnv; import com.google.turbine.binder.env.Env; -import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.type.Type; import com.google.turbine.type.Type.ClassTy; @@ -37,7 +37,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UncheckedIOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -84,6 +86,10 @@ public class BytecodeBoundClassTest { <X, Y extends X, Z extends Throwable> X foo(@Deprecated X bar, Y baz) throws IOException, Z { return null; } + + void baz() throws IOException { + throw new IOException(); + } } @Test @@ -97,6 +103,12 @@ public class BytecodeBoundClassTest { assertThat(m.parameters().get(0).annotations()).hasSize(1); assertThat(m.parameters().get(0).name()).isEqualTo("bar"); assertThat(m.exceptions()).hasSize(2); + + MethodInfo b = + getBytecodeBoundClass(HasMethod.class).methods().stream() + .filter(x -> x.name().equals("baz")) + .collect(onlyElement()); + assertThat(b.exceptions()).hasSize(1); } @interface VoidAnno { @@ -116,6 +128,53 @@ public class BytecodeBoundClassTest { .isEqualTo(Type.TyKind.ARRAY_TY); } + static class HasField { + @Deprecated List<String> foo; + } + + @Test + public void fieldTypes() { + FieldInfo f = + getBytecodeBoundClass(HasField.class).fields().stream() + .filter(x -> x.name().equals("foo")) + .collect(onlyElement()); + + assertThat(Iterables.getLast(((ClassTy) f.type()).classes()).targs()).hasSize(1); + assertThat(f.annotations()).hasSize(1); + } + + interface Y { + Object f(); + } + + interface X extends Y { + String f(); + } + + @Test + public void covariantBridges() { + assertThat(getBytecodeBoundClass(X.class, Y.class).methods()).hasSize(1); + } + + interface A<T> { + void f(T t); + } + + interface B<T extends Number> extends A<T> { + @Override + void f(T t); + } + + interface C<T extends Integer> extends B<T> { + @Override + void f(T t); + } + + @Test + public void genericBridges() { + assertThat(getBytecodeBoundClass(C.class, B.class, A.class).methods()).hasSize(1); + } + private static byte[] toByteArrayOrDie(InputStream is) { try { return ByteStreams.toByteArray(is); @@ -135,15 +194,29 @@ public class BytecodeBoundClassTest { "test.jar"); } - private BytecodeBoundClass getBytecodeBoundClass(Class<?> clazz) { - Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env(); - env = - CompoundEnv.of(env) + private BytecodeBoundClass getBytecodeBoundClass(Class<?> clazz, Class<?>... classpath) { + Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>(); + Env<ClassSymbol, BytecodeBoundClass> env = + CompoundEnv.of(TURBINE_BOOTCLASSPATH.env()) .append( - new SimpleEnv<>( - ImmutableMap.of( - new ClassSymbol(BytecodeBoundClass.class.getName().replace('.', '/')), - getBytecodeBoundClass(env, BytecodeBoundClassTest.class)))); + new Env<ClassSymbol, BytecodeBoundClass>() { + @Override + public BytecodeBoundClass get(ClassSymbol sym) { + return map.get(sym); + } + }); + addClass(clazz, map, env); + addClass(BytecodeBoundClassTest.class, map, env); + for (Class<?> c : classpath) { + addClass(c, map, env); + } return getBytecodeBoundClass(env, clazz); } + + private void addClass( + Class<?> clazz, + Map<ClassSymbol, BytecodeBoundClass> map, + Env<ClassSymbol, BytecodeBoundClass> env) { + map.put(new ClassSymbol(clazz.getName().replace('.', '/')), getBytecodeBoundClass(env, clazz)); + } } diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java index dda29ac..fb64541 100644 --- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java +++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.ModuleVisitor; import org.objectweb.asm.Opcodes; @@ -152,7 +153,8 @@ public class ClassReaderTest { "<X:Ljava/lang/Object;>Ljava/lang/Object;", "java/lang/Object", null); - cw.visitField(Opcodes.ACC_PUBLIC, "x", "I", null, null); + FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, "x", "I", null, null); + fv.visitAnnotation("Ljava/lang/Deprecated;", true); cw.visitField( Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "y", @@ -173,7 +175,9 @@ public class ClassReaderTest { assertThat(x.descriptor()).isEqualTo("I"); assertThat(x.signature()).isNull(); assertThat(x.value()).isNull(); - assertThat(x.annotations()).isEmpty(); + assertThat(x.annotations()).hasSize(1); + ClassFile.AnnotationInfo annotation = Iterables.getOnlyElement(x.annotations()); + assertThat(annotation.typeName()).isEqualTo("Ljava/lang/Deprecated;"); ClassFile.FieldInfo y = classFile.fields().get(1); assertThat(y.access()) @@ -182,12 +186,13 @@ public class ClassReaderTest { assertThat(y.descriptor()).isEqualTo("I"); assertThat(y.value().constantTypeKind()).isEqualTo(TurbineConstantTypeKind.INT); assertThat(((Const.IntValue) y.value()).value()).isEqualTo(42); + assertThat(y.annotations()).isEmpty(); ClassFile.FieldInfo z = classFile.fields().get(2); assertThat(z.name()).isEqualTo("z"); assertThat(z.descriptor()).isEqualTo("Ljava/util/List;"); - // don't bother reading signatures for fields; we only care about constants - assertThat(z.signature()).isNull(); + assertThat(z.signature()).isEqualTo("Ljava/util/List<TX;>;"); + assertThat(z.annotations()).isEmpty(); } @Test diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java index e544c15..71cf356 100644 --- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java +++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java @@ -17,6 +17,7 @@ package com.google.turbine.bytecode; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -84,7 +85,7 @@ public class ClassWriterTest { /* classes= */ null, fileManager.getJavaFileObjects(path)); - assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue(); + assertWithMessage(collector.getDiagnostics().toString()).that(task.call()).isTrue(); byte[] original = Files.readAllBytes(out.resolve("test/Test.class")); byte[] actual = ClassWriter.writeClass(ClassReader.read(null, original)); diff --git a/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java b/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java index a0f0774..042bb80 100644 --- a/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java +++ b/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java @@ -102,5 +102,6 @@ public class SigRegressionTest { input = "LA<[-[Z>.I;"; sig = new SigParser(input).parseClassSig(); + assertThat(SigWriter.classSig(sig)).isEqualTo(input); } } diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java index 4cb8adf..c5b68ff 100644 --- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java +++ b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java @@ -36,7 +36,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -143,7 +142,7 @@ public abstract class AbstractTransitiveTest { // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations. Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar"); - List<String> sources = + ImmutableList<String> sources = new SourceBuilder() .addSourceLines( "c/C.java", @@ -152,18 +151,17 @@ public abstract class AbstractTransitiveTest { " @Anno(x = 2) static final Inner i; // a.A$Inner ", " static final int X = CONST; // a.A#CONST", "}") - .build().stream() + .build() + .stream() .map(Path::toString) .collect(toImmutableList()); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(sources) - .addClassPathEntries( - ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) - .setOutput(libc.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(sources) + .setClassPath( + ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) + .setOutput(libc.toString()) + .build()); assertThat(readJar(libc).keySet()) .containsExactly( diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java index b1da209..bc663cd 100644 --- a/javatests/com/google/turbine/deps/DependenciesTest.java +++ b/javatests/com/google/turbine/deps/DependenciesTest.java @@ -22,7 +22,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Streams; import com.google.turbine.binder.Binder; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPathBinder; @@ -40,7 +39,6 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -88,7 +86,7 @@ public class DependenciesTest { static class DepsBuilder { List<Path> classpath; - List<CompUnit> units = new ArrayList<>(); + ImmutableList.Builder<CompUnit> units = ImmutableList.builder(); DepsBuilder setClasspath(Path... classpath) { this.classpath = ImmutableList.copyOf(classpath); @@ -103,7 +101,7 @@ public class DependenciesTest { DepsProto.Dependencies run() throws IOException { BindingResult bound = Binder.bind( - units, + units.build(), ClassPathBinder.bindClasspath(classpath), TestClassPaths.TURBINE_BOOTCLASSPATH, /* moduleVersion=*/ Optional.empty()); @@ -116,7 +114,7 @@ public class DependenciesTest { } private Map<Path, DepsProto.Dependency.Kind> depsMap(DepsProto.Dependencies deps) { - return Streams.stream(deps.getDependencyList()) + return deps.getDependencyList().stream() .collect(Collectors.toMap(d -> Paths.get(d.getPath()), DepsProto.Dependency::getKind)); } @@ -333,6 +331,96 @@ public class DependenciesTest { } } + @Test + public void annotations_recursive() throws Exception { + Path libA = libA(); + Path libB = libB(); + + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "@A(B.class)", + "class Test {", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + @Test + public void annotations_field() throws Exception { + Path libA = libA(); + Path libB = libB(); + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "class Test {", + " @A(B.class)", + " int x;", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + @Test + public void annotations_method() throws Exception { + Path libA = libA(); + Path libB = libB(); + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "class Test {", + " @A(B.class)", + " void f() {}", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + private Path libB() throws Exception { + return new LibraryBuilder() + .addSourceLines( + "b/B.java", + "package b;", + "import java.lang.annotation.Retention;", + "import static java.lang.annotation.RetentionPolicy.RUNTIME;", + "@Retention(RUNTIME)", + "public @interface B {", + "}") + .compileToJar("libb.jar"); + } + + private Path libA() throws Exception { + return new LibraryBuilder() + .addSourceLines( + "a/A.java", + "package a;", + "import java.lang.annotation.Retention;", + "import static java.lang.annotation.RetentionPolicy.RUNTIME;", + "@Retention(RUNTIME)", + "public @interface A {", + " Class<?> value() default Object.class;", + "}") + .compileToJar("liba.jar"); + } + void writeDeps(Path path, ImmutableMap<String, DepsProto.Dependency.Kind> deps) throws IOException { DepsProto.Dependencies.Builder builder = diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java index f8c5b50..2c9f807 100644 --- a/javatests/com/google/turbine/deps/TransitiveTest.java +++ b/javatests/com/google/turbine/deps/TransitiveTest.java @@ -17,7 +17,6 @@ package com.google.turbine.deps; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import com.google.common.collect.ImmutableList; @@ -34,15 +33,12 @@ public class TransitiveTest extends AbstractTransitiveTest { protected Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath) throws IOException { Path out = temporaryFolder.newFolder().toPath().resolve("out.jar"); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(sources.stream().map(Path::toString).collect(toImmutableList())) - .addClassPathEntries( - classpath.stream().map(Path::toString).collect(toImmutableList())) - .setOutput(out.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(sources.stream().map(Path::toString).collect(toImmutableList())) + .setClassPath(classpath.stream().map(Path::toString).collect(toImmutableList())) + .setOutput(out.toString()) + .build()); return out; } } diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java index 680b073..a03473d 100644 --- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java +++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java @@ -16,11 +16,13 @@ package com.google.turbine.lower; -import static com.google.common.truth.Truth.assertThat; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; +import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Splitter; @@ -29,12 +31,13 @@ import com.google.common.io.MoreFiles; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPath; import com.google.turbine.binder.ClassPathBinder; import com.google.turbine.diag.SourceFile; import com.google.turbine.parse.Parser; import com.google.turbine.testing.AsmUtils; -import com.google.turbine.tree.Tree; +import com.google.turbine.tree.Tree.CompUnit; import com.sun.source.util.JavacTask; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.file.JavacFileManager; @@ -100,8 +103,11 @@ public class IntegrationTestSupport { public static Map<String, byte[]> canonicalize(Map<String, byte[]> in) { List<ClassNode> classes = toClassNodes(in); - // drop anonymous classes - classes = classes.stream().filter(n -> !isAnonymous(n)).collect(toCollection(ArrayList::new)); + // drop local and anonymous classes + classes = + classes.stream() + .filter(n -> !isAnonymous(n) && !isLocal(n)) + .collect(toCollection(ArrayList::new)); // collect all inner classes attributes Map<String, InnerClassNode> infos = new HashMap<>(); @@ -123,6 +129,10 @@ public class IntegrationTestSupport { return toByteCode(classes); } + private static boolean isLocal(ClassNode n) { + return n.outerMethod != null; + } + private static boolean isAnonymous(ClassNode n) { // JVMS 4.7.6: if C is anonymous, the value of the inner_name_index item must be zero return n.innerClasses.stream().anyMatch(i -> i.name.equals(n.name) && i.innerName == null); @@ -436,15 +446,41 @@ public class IntegrationTestSupport { ClassPath bootClassPath, Optional<String> moduleVersion) throws IOException { - List<Tree.CompUnit> units = + BindingResult bound = turbineAnalysis(input, classpath, bootClassPath, moduleVersion); + return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes(); + } + + public static BindingResult turbineAnalysis( + Map<String, String> input, + ImmutableList<Path> classpath, + ClassPath bootClassPath, + Optional<String> moduleVersion) + throws IOException { + ImmutableList<CompUnit> units = input.entrySet().stream() .map(e -> new SourceFile(e.getKey(), e.getValue())) .map(Parser::parse) - .collect(toList()); + .collect(toImmutableList()); - Binder.BindingResult bound = - Binder.bind(units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion); - return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes(); + return Binder.bind( + units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion); + } + + public static JavacTask runJavacAnalysis( + Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) + throws Exception { + return runJavacAnalysis(sources, classpath, options, new DiagnosticCollector<>()); + } + + public static JavacTask runJavacAnalysis( + Map<String, String> sources, + Collection<Path> classpath, + ImmutableList<String> options, + DiagnosticCollector<JavaFileObject> collector) + throws Exception { + FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + Path out = fs.getPath("out"); + return setupJavac(sources, classpath, options, collector, fs, out); } public static Map<String, byte[]> runJavac( @@ -457,10 +493,46 @@ public class IntegrationTestSupport { Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) throws Exception { + DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + Path out = fs.getPath("out"); + JavacTask task = setupJavac(sources, classpath, options, collector, fs, out); + + if (!task.call()) { + fail(collector.getDiagnostics().stream().map(d -> d.toString()).collect(joining("\n"))); + } + + List<Path> classes = new ArrayList<>(); + Files.walkFileTree( + out, + new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) + throws IOException { + if (path.getFileName().toString().endsWith(".class")) { + classes.add(path); + } + return FileVisitResult.CONTINUE; + } + }); + Map<String, byte[]> result = new LinkedHashMap<>(); + for (Path path : classes) { + String r = out.relativize(path).toString(); + result.put(r.substring(0, r.length() - ".class".length()), Files.readAllBytes(path)); + } + return result; + } + + private static JavacTask setupJavac( + Map<String, String> sources, + Collection<Path> classpath, + ImmutableList<String> options, + DiagnosticCollector<JavaFileObject> collector, + FileSystem fs, + Path out) + throws IOException { Path srcs = fs.getPath("srcs"); - Path out = fs.getPath("out"); Files.createDirectories(out); @@ -475,7 +547,6 @@ public class IntegrationTestSupport { } JavacTool compiler = JavacTool.create(); - DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); JavacFileManager fileManager = new JavacFileManager(new Context(), true, UTF_8); fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, ImmutableList.of(out)); fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); @@ -487,36 +558,13 @@ public class IntegrationTestSupport { StandardLocation.locationFor("MODULE_SOURCE_PATH"), ImmutableList.of(srcs)); } - JavacTask task = - compiler.getTask( - new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), - fileManager, - collector, - options, - ImmutableList.of(), - fileManager.getJavaFileObjectsFromPaths(inputs)); - - assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue(); - - List<Path> classes = new ArrayList<>(); - Files.walkFileTree( - out, - new SimpleFileVisitor<Path>() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) - throws IOException { - if (path.getFileName().toString().endsWith(".class")) { - classes.add(path); - } - return FileVisitResult.CONTINUE; - } - }); - Map<String, byte[]> result = new LinkedHashMap<>(); - for (Path path : classes) { - String r = out.relativize(path).toString(); - result.put(r.substring(0, r.length() - ".class".length()), Files.readAllBytes(path)); - } - return result; + return compiler.getTask( + new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), + fileManager, + collector, + options, + ImmutableList.of(), + fileManager.getJavaFileObjectsFromPaths(inputs)); } /** Normalizes and stringifies a collection of class files. */ @@ -535,17 +583,17 @@ public class IntegrationTestSupport { return sb.toString(); } - static class TestInput { + public static class TestInput { - final Map<String, String> sources; - final Map<String, String> classes; + public final Map<String, String> sources; + public final Map<String, String> classes; public TestInput(Map<String, String> sources, Map<String, String> classes) { this.sources = sources; this.classes = classes; } - static TestInput parse(String text) { + public static TestInput parse(String text) { Map<String, String> sources = new LinkedHashMap<>(); Map<String, String> classes = new LinkedHashMap<>(); String className = null; diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java index f7e9c18..85c3450 100644 --- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java +++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java @@ -308,6 +308,9 @@ public class LowerIntegrationTest { "shadow_inherited.test", "static_final_boxed.test", "anno_void.test", + "tyanno_varargs.test", + "tyanno_inner.test", + "local.test", }; List<Object[]> tests = 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 5ccbf01..08bc46d 100644 --- a/javatests/com/google/turbine/lower/LowerSignatureTest.java +++ b/javatests/com/google/turbine/lower/LowerSignatureTest.java @@ -83,7 +83,7 @@ public class LowerSignatureTest { assertThat(SigWriter.type(new LowerSignature().signature(type))) .isEqualTo("Ltest/Outer<Ljava/lang/Object;>.Inner<Ljava/lang/Object;>;"); // Type#toString is only for debugging - assertThat(type.toString()).isEqualTo("test/Outer<java/lang/Object>.Inner<java/lang/Object>"); + assertThat(type.toString()).isEqualTo("test.Outer<java.lang.Object>.Inner<java.lang.Object>"); } @Test diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java index 0de55c3..8151e81 100644 --- a/javatests/com/google/turbine/lower/LowerTest.java +++ b/javatests/com/google/turbine/lower/LowerTest.java @@ -33,6 +33,7 @@ import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.bytecode.ByteReader; import com.google.turbine.bytecode.ConstantPoolReader; @@ -104,12 +105,13 @@ public class LowerTest { new ClassSymbol("test/Test$Inner"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of())); int access = TurbineFlag.ACC_SUPER | TurbineFlag.ACC_PUBLIC; ImmutableList<SourceTypeBoundClass.MethodInfo> methods = ImmutableList.of( new SourceTypeBoundClass.MethodInfo( - new MethodSymbol(new ClassSymbol("test/Test"), "f"), + new MethodSymbol(-1, new ClassSymbol("test/Test"), "f"), ImmutableMap.of(), PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), ImmutableList.of(), @@ -120,9 +122,9 @@ public class LowerTest { ImmutableList.of(), null), new SourceTypeBoundClass.MethodInfo( - new MethodSymbol(new ClassSymbol("test/Test"), "g"), + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), ImmutableMap.of( - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "V"), + new TyVarSymbol(new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "V"), new SourceTypeBoundClass.TyVarInfo( IntersectionTy.create( ImmutableList.of( @@ -132,8 +134,9 @@ public class LowerTest { new ClassSymbol("java/lang/Runnable"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of()), - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), + new TyVarSymbol(new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "E"), new SourceTypeBoundClass.TyVarInfo( IntersectionTy.create( ImmutableList.of( @@ -143,17 +146,20 @@ public class LowerTest { new ClassSymbol("java/lang/Error"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of())), Type.VOID, ImmutableList.of( new SourceTypeBoundClass.ParamInfo( + new ParamSymbol( + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "foo"), PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), - "foo", ImmutableList.of(), 0)), ImmutableList.of( TyVar.create( - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), + new TyVarSymbol( + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "E"), ImmutableList.of())), TurbineFlag.ACC_PUBLIC, null, @@ -350,7 +356,7 @@ public class LowerTest { int typeRef, TypePath typePath, String desc, boolean visible) { path[0] = typePath; return null; - }; + } }; } }, @@ -604,9 +610,10 @@ public class LowerTest { assertThat(error) .hasMessageThat() .contains( - "Test.java:3: error: could not locate class file for A\n" - + " I i;\n" - + " ^"); + lines( + "Test.java:3: error: could not locate class file for A", + " I i;", + " ^")); } } @@ -640,6 +647,6 @@ public class LowerTest { } static String lines(String... lines) { - return Joiner.on("\n").join(lines); + return Joiner.on(System.lineSeparator()).join(lines); } } diff --git a/javatests/com/google/turbine/lower/testdata/local.test b/javatests/com/google/turbine/lower/testdata/local.test new file mode 100644 index 0000000..fd6d4c5 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/local.test @@ -0,0 +1,8 @@ +=== T.java === +class T { + Object f() { + class Local { + } + return new Local(); + } +}
\ No newline at end of file diff --git a/javatests/com/google/turbine/lower/testdata/tyanno_inner.test b/javatests/com/google/turbine/lower/testdata/tyanno_inner.test new file mode 100644 index 0000000..d42b3a3 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/tyanno_inner.test @@ -0,0 +1,16 @@ +%%% A.java %%% +import static java.lang.annotation.ElementType.TYPE_PARAMETER; + +import java.lang.annotation.Target; + +@interface A { + + @Target(TYPE_PARAMETER) + @interface I { + } +} + +=== T.java === +abstract class T<@A.I X> { + +} diff --git a/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test b/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test new file mode 100644 index 0000000..1fbc1e1 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test @@ -0,0 +1,12 @@ +=== T.java === +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + +import java.lang.annotation.Target; + +abstract class T { + @Target({PARAMETER, TYPE_USE}) + @interface A {} + + void f(boolean a, @A String b, Object @A... xs) {} +} diff --git a/javatests/com/google/turbine/main/MainTest.java b/javatests/com/google/turbine/main/MainTest.java index 65f3167..5d47632 100644 --- a/javatests/com/google/turbine/main/MainTest.java +++ b/javatests/com/google/turbine/main/MainTest.java @@ -17,17 +17,29 @@ package com.google.turbine.main; import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import com.google.common.io.MoreFiles; +import com.google.protobuf.ExtensionRegistry; import com.google.turbine.diag.TurbineError; import com.google.turbine.options.TurbineOptions; +import com.google.turbine.proto.ManifestProto; +import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; @@ -35,16 +47,27 @@ import java.time.ZoneId; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.stream.Stream; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; @RunWith(JUnit4.class) public class MainTest { @@ -84,13 +107,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(ImmutableList.of(src.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("test/package-info.class"); @@ -106,13 +127,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .setSourceJars(ImmutableList.of(srcjar.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSourceJars(ImmutableList.of(srcjar.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("test/package-info.class"); @@ -150,15 +169,13 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - TurbineOptions.builder() - .setRelease("9") - .addSources(ImmutableList.of(src.toString())) - .setSourceJars(ImmutableList.of(srcjar.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + TurbineOptions.builder() + .setRelease("9") + .setSources(ImmutableList.of(src.toString())) + .setSourceJars(ImmutableList.of(srcjar.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()) @@ -171,26 +188,48 @@ public class MainTest { MoreFiles.asCharSink(src, UTF_8).write("class Foo {}"); Path output = temporaryFolder.newFile("output.jar").toPath(); + Path gensrcOutput = temporaryFolder.newFile("gensrcOutput.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(ImmutableList.of(src.toString())) - .setTargetLabel("//foo:foo") - .setInjectingRuleKind("foo_library") - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setGensrcOutput(gensrcOutput.toString()) + .build()); try (JarFile jarFile = new JarFile(output.toFile())) { + try (Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsAtLeast("META-INF/", "META-INF/MANIFEST.MF"); + } Manifest manifest = jarFile.getManifest(); Attributes attributes = manifest.getMainAttributes(); - assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo"); - assertThat(attributes.getValue("Injecting-Rule-Kind")).isEqualTo("foo_library"); + ImmutableMap<String, ?> entries = + attributes.entrySet().stream() + .collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue)); + assertThat(entries) + .containsExactly( + "Created-By", "bazel", + "Manifest-Version", "1.0", + "Target-Label", "//foo:foo", + "Injecting-Rule-Kind", "foo_library"); assertThat(jarFile.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant()) .isEqualTo( LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()); } + try (JarFile jarFile = new JarFile(gensrcOutput.toFile())) { + Manifest manifest = jarFile.getManifest(); + Attributes attributes = manifest.getMainAttributes(); + ImmutableMap<String, ?> entries = + attributes.entrySet().stream() + .collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue)); + assertThat(entries) + .containsExactly( + "Created-By", "bazel", + "Manifest-Version", "1.0"); + } } @Test @@ -201,13 +240,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - TurbineOptions.builder() - .addSources(ImmutableList.of(src.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + TurbineOptions.builder() + .setSources(ImmutableList.of(src.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("java/lang/Object.class"); @@ -223,7 +260,7 @@ public class MainTest { try { Main.compile( TurbineOptions.builder() - .addSources(ImmutableList.of(src.toString())) + .setSources(ImmutableList.of(src.toString())) .setOutput(output.toString()) .build()); fail(); @@ -238,10 +275,200 @@ public class MainTest { MoreFiles.asCharSink(src, UTF_8).write("public class Test {}"); try { - Main.compile(optionsWithBootclasspath().addSources(ImmutableList.of(src.toString())).build()); + Main.compile(optionsWithBootclasspath().setSources(ImmutableList.of(src.toString())).build()); fail(); } catch (UsageException expected) { - assertThat(expected).hasMessageThat().contains("--output is required"); + assertThat(expected) + .hasMessageThat() + .contains("at least one of --output, --gensrc_output, or --resource_output is required"); + } + } + + @Test + public void noSources() throws IOException { + // Compilations with no sources (or source jars) are accepted, and create empty for requested + // outputs. This is helpful for the Bazel integration, which allows java_library rules to be + // declared without sources. + File gensrc = temporaryFolder.newFile("gensrc.jar"); + Main.compile(optionsWithBootclasspath().setGensrcOutput(gensrc.toString()).build()); + try (JarFile jarFile = new JarFile(gensrc); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsExactly("META-INF/", "META-INF/MANIFEST.MF"); + } + } + + @SupportedAnnotationTypes("*") + public static class SourceGeneratingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (Writer writer = processingEnv.getFiler().createSourceFile("g.Gen").openWriter()) { + writer.write("package g; class Gen {}"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + } + + @Test + public void testManifestProto() throws IOException { + Path src = temporaryFolder.newFile("Foo.java").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}"); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path gensrcOutput = temporaryFolder.newFile("gensrcOutput.jar").toPath(); + Path manifestProtoOutput = temporaryFolder.newFile("manifest.proto").toPath(); + + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setGensrcOutput(gensrcOutput.toString()) + .setOutputManifest(manifestProtoOutput.toString()) + .setProcessors(ImmutableList.of(SourceGeneratingProcessor.class.getName())) + .build()); + + assertThat(readManifestProto(manifestProtoOutput)) + .isEqualTo( + ManifestProto.Manifest.newBuilder() + .addCompilationUnit( + ManifestProto.CompilationUnit.newBuilder() + .setPkg("f") + .addTopLevel("Foo") + .setPath(src.toString()) + .setGeneratedByAnnotationProcessor(false) + .build()) + .addCompilationUnit( + ManifestProto.CompilationUnit.newBuilder() + .setPkg("g") + .addTopLevel("Gen") + .setPath("g/Gen.java") + .setGeneratedByAnnotationProcessor(true) + .build()) + .build()); + } + + private static ManifestProto.Manifest readManifestProto(Path manifestProtoOutput) + throws IOException { + ManifestProto.Manifest.Builder manifest = ManifestProto.Manifest.newBuilder(); + try (InputStream is = new BufferedInputStream(Files.newInputStream(manifestProtoOutput))) { + manifest.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); + } + return manifest.build(); + } + + @SupportedAnnotationTypes("*") + public static class CrashyProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + throw new AssertionError(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return false; + } + } + + @Test + public void noSourcesProcessing() throws IOException { + // Compilations with no sources shouldn't initialize annotation processors. + File gensrc = temporaryFolder.newFile("gensrc.jar"); + Main.compile( + optionsWithBootclasspath() + .setProcessors(ImmutableList.of(CrashyProcessor.class.getName())) + .setGensrcOutput(gensrc.toString()) + .build()); + try (JarFile jarFile = new JarFile(gensrc); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsExactly("META-INF/", "META-INF/MANIFEST.MF"); + } + } + + @SupportedAnnotationTypes("*") + public static class ClassGeneratingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (OutputStream outputStream = + processingEnv.getFiler().createClassFile("g.Gen").openOutputStream()) { + outputStream.write(dump()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + + public static byte[] dump() { + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit( + Opcodes.V1_8, + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + "g/Gen", + null, + "java/lang/Object", + null); + { + MethodVisitor methodVisitor = + classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + return classWriter.toByteArray(); + } + } + + @Test + public void classGeneration() throws IOException { + Path src = temporaryFolder.newFile("package-info.jar").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("@Deprecated package test;"); + File resources = temporaryFolder.newFile("resources.jar"); + Main.compile( + optionsWithBootclasspath() + .setProcessors(ImmutableList.of(ClassGeneratingProcessor.class.getName())) + .setSources(ImmutableList.of(src.toString())) + .setResourceOutput(resources.toString()) + .build()); + try (JarFile jarFile = new JarFile(resources); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)).containsExactly("g/Gen.class"); } } } diff --git a/javatests/com/google/turbine/main/ReducedClasspathTest.java b/javatests/com/google/turbine/main/ReducedClasspathTest.java new file mode 100644 index 0000000..d74c640 --- /dev/null +++ b/javatests/com/google/turbine/main/ReducedClasspathTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2019 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.main; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ExtensionRegistry; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.main.Main.Result; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; +import com.google.turbine.proto.DepsProto; +import com.google.turbine.proto.DepsProto.Dependency.Kind; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ReducedClasspathTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private Path liba; + private Path libb; + private Path libc; + private Path libcJdeps; + + @Before + public void setup() throws Exception { + Map<String, byte[]> compiled = + IntegrationTestSupport.runJavac( + TestInput.parse( + String.join( + "\n", + ImmutableList.of( + "=== a/A.java ===", + "package a;", + "public class A {", + " public static class I {}", + "}", + "=== b/B.java ===", + "package b;", + "import a.A;", + "public class B extends A {}", + "=== c/C.java ===", + "package c;", + "import b.B;", + "public class C extends B {}"))) + .sources, + /* classpath= */ ImmutableList.of()); + + liba = createLibrary(compiled, "liba.jar", "a/A", "a/A$I"); + libb = createLibrary(compiled, "libb.jar", "b/B"); + libc = createLibrary(compiled, "libc.jar", "c/C"); + + libcJdeps = temporaryFolder.newFile("libc.jdeps").toPath(); + try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(libcJdeps))) { + DepsProto.Dependencies.newBuilder() + .addDependency( + DepsProto.Dependency.newBuilder() + .setKind(Kind.EXPLICIT) + .setPath(libb.toString()) + .build()) + .build() + .writeTo(os); + } + } + + private Path createLibrary(Map<String, byte[]> compiled, String jarPath, String... classNames) + throws IOException { + Path lib = temporaryFolder.newFile(jarPath).toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { + for (String className : classNames) { + jos.putNextEntry(new JarEntry(className + ".class")); + jos.write(compiled.get(className)); + } + } + return lib; + } + + @Test + public void succeedsWithoutFallingBack() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath( + ImmutableList.of( + // ensure that the compilation succeeds without falling back by adding + // a jar to the transitive classpath that doesn't exist, which would cause + // the compilation to fail if it fell back + temporaryFolder.newFile("no.such.jar").toString(), + liba.toString(), + libb.toString(), + libc.toString())) + .setDirectJars(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + assertThat(result.transitiveClasspathFallback()).isFalse(); + } + + @Test + public void succeedsAfterFallingBack() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath(ImmutableList.of(liba.toString(), libb.toString(), libc.toString())) + .setDirectJars(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + assertThat(result.transitiveClasspathFallback()).isTrue(); + assertThat(result.reducedClasspathLength()).isEqualTo(2); + assertThat(result.transitiveClasspathLength()).isEqualTo(3); + } + + @Test + public void bazelFallback() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path jdeps = temporaryFolder.newFile("output.jdeps").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setTargetLabel("//java/com/google/foo") + .setOutputDeps(jdeps.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.BAZEL_REDUCED) + .setClassPath(ImmutableList.of(libc.toString())) + .setReducedClasspathLength(1) + .setFullClasspathLength(3) + .build()); + assertThat(result.transitiveClasspathFallback()).isTrue(); + assertThat(result.reducedClasspathLength()).isEqualTo(1); + assertThat(result.transitiveClasspathLength()).isEqualTo(3); + DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); + try (InputStream is = new BufferedInputStream(Files.newInputStream(jdeps))) { + deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); + } + assertThat(deps.build()) + .isEqualTo( + DepsProto.Dependencies.newBuilder() + .setRequiresReducedClasspathFallback(true) + .setRuleLabel("//java/com/google/foo") + .build()); + } + + @Test + public void noFallbackWithoutDirectJarsAndJdeps() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + try { + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + fail(); + } catch (TurbineError e) { + assertThat(e).hasMessageThat().contains("could not resolve I"); + } + } + + static String lines(String... lines) { + return Joiner.on(System.lineSeparator()).join(lines); + } +} diff --git a/javatests/com/google/turbine/model/ConstTest.java b/javatests/com/google/turbine/model/ConstTest.java index a64d0bf..984fd5a 100644 --- a/javatests/com/google/turbine/model/ConstTest.java +++ b/javatests/com/google/turbine/model/ConstTest.java @@ -16,12 +16,19 @@ package com.google.turbine.model; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.testing.EqualsTester; -import com.google.turbine.binder.bound.AnnotationValue; +import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.model.Const.ArrayInitValue; +import com.google.turbine.model.Const.IntValue; +import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type.ClassTy; import com.google.turbine.type.Type.PrimTy; import org.junit.Test; @@ -62,15 +69,31 @@ public class ConstTest { new Const.ArrayInitValue( ImmutableList.of(new Const.IntValue(3), new Const.IntValue(4)))) .addEqualityGroup( - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(3))), - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(3)))) + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(3)))), + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(3))))) .addEqualityGroup( - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(4))), - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(4)))) + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(4)))), + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(4))))) .addEqualityGroup( new TurbineClassValue(ClassTy.asNonParametricClassTy(new ClassSymbol("test/Clazz"))), new TurbineClassValue(ClassTy.asNonParametricClassTy(new ClassSymbol("test/Clazz")))) @@ -82,4 +105,37 @@ public class ConstTest { new TurbineClassValue(PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()))) .testEquals(); } + + @Test + public void toStringTest() { + assertThat(new Const.CharValue('\t').toString()).isEqualTo("\'\\t\'"); + assertThat(new EnumConstantValue(new FieldSymbol(new ClassSymbol("Foo"), "CONST")).toString()) + .isEqualTo("CONST"); + assertThat(makeAnno(ImmutableMap.of())).isEqualTo("@p.Anno"); + assertThat(makeAnno(ImmutableMap.of("value", new IntValue(1)))).isEqualTo("@p.Anno(1)"); + assertThat(makeAnno(ImmutableMap.of("x", new IntValue(1)))).isEqualTo("@p.Anno(x=1)"); + assertThat( + makeAnno( + ImmutableMap.of("value", new ArrayInitValue(ImmutableList.of(new IntValue(1)))))) + .isEqualTo("@p.Anno({1})"); + assertThat( + makeAnno( + ImmutableMap.of( + "value", + new ArrayInitValue(ImmutableList.of(new IntValue(1), new IntValue(2)))))) + .isEqualTo("@p.Anno({1, 2})"); + assertThat( + makeAnno(ImmutableMap.of("xs", new ArrayInitValue(ImmutableList.of(new IntValue(1)))))) + .isEqualTo("@p.Anno(xs={1})"); + assertThat(makeAnno(ImmutableMap.of("x", new IntValue(1), "y", new IntValue(2)))) + .isEqualTo("@p.Anno(x=1, y=2)"); + assertThat(new Const.StringValue("\"").toString()).isEqualTo("\"\\\"\""); + assertThat(new Const.ByteValue((byte) 42).toString()).isEqualTo("(byte)0x2a"); + assertThat(new Const.ShortValue((short) 42).toString()).isEqualTo("42"); + } + + private static String makeAnno(ImmutableMap<String, Const> value) { + return new TurbineAnnotationValue(new AnnoInfo(null, new ClassSymbol("p/Anno"), null, value)) + .toString(); + } } diff --git a/javatests/com/google/turbine/options/TurbineOptionsTest.java b/javatests/com/google/turbine/options/TurbineOptionsTest.java index a5872d9..d4b468b 100644 --- a/javatests/com/google/turbine/options/TurbineOptionsTest.java +++ b/javatests/com/google/turbine/options/TurbineOptionsTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -95,7 +96,7 @@ public class TurbineOptionsTest { assertThat(options.outputDeps()).hasValue("out.jdeps"); assertThat(options.targetLabel()).hasValue("//java/com/google/test"); assertThat(options.injectingRuleKind()).hasValue("foo_library"); - assertThat(options.shouldReduceClassPath()).isTrue(); + assertThat(options.reducedClasspathMode()).isEqualTo(ReducedClasspathMode.NONE); } @Test @@ -261,13 +262,22 @@ public class TurbineOptionsTest { @Test public void javacopts() throws Exception { String[] lines = { - "--javacopts", "--release", "9", "--", "--sources", "Test.java", + "--javacopts", + "--release", + "8", + "--", + "--sources", + "Test.java", + "--javacopts", + "--release", + "9", + "--", }; TurbineOptions options = TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); - assertThat(options.javacOpts()).containsExactly("--release", "9").inOrder(); + assertThat(options.javacOpts()).containsExactly("--release", "8", "--release", "9").inOrder(); assertThat(options.sources()).containsExactly("Test.java"); } @@ -315,20 +325,14 @@ public class TurbineOptionsTest { } @Test - public void shouldReduceClasspath() throws Exception { - { - TurbineOptions options = - TurbineOptionsParser.parse( - Iterables.concat(BASE_ARGS, ImmutableList.of("--reduce_classpath"))); - assertThat(options.shouldReduceClassPath()).isTrue(); - } - - { - TurbineOptions options = - TurbineOptionsParser.parse( - Iterables.concat(BASE_ARGS, ImmutableList.of("--noreduce_classpath"))); - assertThat(options.shouldReduceClassPath()).isFalse(); - } + public void miscOutputs() throws Exception { + TurbineOptions options = + TurbineOptionsParser.parse( + Iterables.concat( + BASE_ARGS, + ImmutableList.of("--gensrc_output", "gensrc.jar", "--profile", "turbine.prof"))); + assertThat(options.gensrcOutput()).hasValue("gensrc.jar"); + assertThat(options.profile()).hasValue("turbine.prof"); } @Test @@ -350,4 +354,23 @@ public class TurbineOptionsTest { } catch (IllegalArgumentException expected) { } } + + @Test + public void builtinProcessors() throws Exception { + String[] lines = {"--builtin_processors", "BuiltinProcessor"}; + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + assertThat(options.builtinProcessors()).containsExactly("BuiltinProcessor"); + } + + @Test + public void reducedClasspathMode() throws Exception { + for (ReducedClasspathMode mode : ReducedClasspathMode.values()) { + TurbineOptions options = + TurbineOptionsParser.parse( + Iterables.concat( + BASE_ARGS, ImmutableList.of("--reduce_classpath_mode", mode.name()))); + assertThat(options.reducedClasspathMode()).isEqualTo(mode); + } + } } diff --git a/javatests/com/google/turbine/parse/CommentParserTest.java b/javatests/com/google/turbine/parse/CommentParserTest.java new file mode 100644 index 0000000..a2f84d5 --- /dev/null +++ b/javatests/com/google/turbine/parse/CommentParserTest.java @@ -0,0 +1,78 @@ +/* + * 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.parse; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.turbine.tree.Tree; +import com.google.turbine.tree.Tree.MethDecl; +import com.google.turbine.tree.Tree.TyDecl; +import com.google.turbine.tree.Tree.VarDecl; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommentParserTest { + + @Test + public void comments() { + Tree.CompUnit unit = + Parser.parse( + Joiner.on('\n') + .join( + "package p;", + "/** hello world */", + "class Test {", + " /**", + " * This is", + " * class A", + " */", + " class A {", + " /** This is a method */", + " void f() {}", + " /** This is a field */", + " int g;", + " }", + " /* This is not javadoc */", + " class B {}", + " /**", + " * This is", + " * class C", + " */", + " class C {}", + "}\n")); + TyDecl decl = getOnlyElement(unit.decls()); + assertThat(decl.javadoc()).isEqualTo(" hello world "); + assertThat( + decl.members().stream() + .map(Tree.TyDecl.class::cast) + .filter(c -> c.javadoc() != null) + .collect(toImmutableMap(c -> c.name().value(), c -> c.javadoc()))) + .containsExactly( + "A", "\n * This is\n * class A\n ", + "C", "\n * This is\n * class C\n "); + TyDecl a = (TyDecl) decl.members().get(0); + MethDecl f = (MethDecl) a.members().get(0); + assertThat(f.javadoc()).isEqualTo(" This is a method "); + VarDecl g = (VarDecl) a.members().get(1); + assertThat(g.javadoc()).isEqualTo(" This is a field "); + } +} diff --git a/javatests/com/google/turbine/parse/JavacLexer.java b/javatests/com/google/turbine/parse/JavacLexer.java index af82dbe..d8939f1 100644 --- a/javatests/com/google/turbine/parse/JavacLexer.java +++ b/javatests/com/google/turbine/parse/JavacLexer.java @@ -280,8 +280,7 @@ public class JavacLexer { case CHARLITERAL: return String.format( "CHAR_LITERAL(%s)", SourceCodeEscapers.javaCharEscaper().escape(token.stringVal())); - default: - return token.kind.toString(); } + return token.kind.toString(); } } diff --git a/javatests/com/google/turbine/parse/LexerTest.java b/javatests/com/google/turbine/parse/LexerTest.java index 4867a22..8530d52 100644 --- a/javatests/com/google/turbine/parse/LexerTest.java +++ b/javatests/com/google/turbine/parse/LexerTest.java @@ -40,12 +40,6 @@ public class LexerTest { } @Test - public void unterminated() { - assertThat(lex("/* foo")).containsExactly("EOF"); - assertThat(lex("\" foo")).containsExactly("EOF"); - } - - @Test public void boolLiteral() { lexerComparisonTest("0b0101__01010"); assertThat(lex("1 + 0b1000100101")) diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java index 49fb273..6a9ad11 100644 --- a/javatests/com/google/turbine/parse/ParseErrorTest.java +++ b/javatests/com/google/turbine/parse/ParseErrorTest.java @@ -228,6 +228,57 @@ public class ParseErrorTest { } } + @Test + public void abruptMultivariableDeclaration() { + String input = "class T { int x,; }"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: expected token <identifier>", // + "class T { int x,; }", + " ^")); + } + } + + @Test + public void invalidAnnotation() { + String input = "@Foo(x = @E [] x) class T {}"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: invalid annotation argument", // + "@Foo(x = @E [] x) class T {}", + " ^")); + } + } + + @Test + public void unclosedComment() { + String input = "/** *\u001a/ class Test {}"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unclosed comment", // + "/** *\u001a/ class Test {}", + "^")); + } + } + private static String lines(String... lines) { return Joiner.on(System.lineSeparator()).join(lines); } diff --git a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java index 2637091..e3f7b63 100644 --- a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java +++ b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java @@ -16,10 +16,13 @@ package com.google.turbine.parse; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineError.ErrorKind; import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -57,15 +60,15 @@ public class UnicodeEscapePreprocessorTest { try { readAll("\\u00"); fail(); - } catch (AssertionError e) { - assertThat(e).hasMessage("unexpected end of input"); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); } try { readAll("\\u"); fail(); - } catch (AssertionError e) { - assertThat(e).hasMessage("unexpected end of input"); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); } } @@ -74,6 +77,16 @@ public class UnicodeEscapePreprocessorTest { assertThat(readAll("\\u005C\\\\u005C")).containsExactly('\\', '\\', '\\'); } + @Test + public void invalidEscape() { + try { + readAll("\\uUUUU"); + fail(); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE); + } + } + private List<Character> readAll(String input) { UnicodeEscapePreprocessor reader = new UnicodeEscapePreprocessor(new SourceFile(null, input)); List<Character> result = new ArrayList<>(); diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java new file mode 100644 index 0000000..6ea6e72 --- /dev/null +++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.Truth.assertWithMessage; + +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +/** + * A combo test for {@link TurbineTypes} that compares the behaviour of bipredicates like {@link + * Types#isSubtype(TypeMirror, TypeMirror)} with javac's implementation. + */ +abstract class AbstractTurbineTypesBiPredicateTest extends AbstractTurbineTypesTest { + + final String testDescription; + final TypesBiFunctionInput javacInput; + final TypesBiFunctionInput turbineInput; + + public AbstractTurbineTypesBiPredicateTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + this.testDescription = testDescription; + this.javacInput = javacInput; + this.turbineInput = turbineInput; + } + + protected void test(String symbol, TypeBiPredicate predicate) { + assertWithMessage("%s = %s", javacInput.format(symbol), turbineInput.format(symbol)) + .that(turbineInput.apply(predicate)) + .isEqualTo(javacInput.apply(predicate)); + } +} diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java new file mode 100644 index 0000000..e6a59bf --- /dev/null +++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java @@ -0,0 +1,527 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Streams; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.util.Context; +import java.net.URI; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.ElementScanner8; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; + +class AbstractTurbineTypesTest { + + protected static class TypeParameters { + protected final Types javacTypes; + protected final List<List<TypeMirror>> aGroups; + protected final Types turbineTypes; + protected final List<List<TypeMirror>> bGroups; + + private TypeParameters( + Types javacTypes, + List<List<TypeMirror>> aGroups, + Types turbineTypes, + List<List<TypeMirror>> bGroups) { + this.javacTypes = javacTypes; + this.aGroups = aGroups; + this.turbineTypes = turbineTypes; + this.bGroups = bGroups; + } + } + + protected interface TypeBiPredicate { + boolean apply(Types types, TypeMirror a, TypeMirror b); + } + + static class TypesBiFunctionInput { + final Types types; + final TypeMirror lhs; + final TypeMirror rhs; + + TypesBiFunctionInput(Types types, TypeMirror lhs, TypeMirror rhs) { + this.types = types; + this.lhs = lhs; + this.rhs = rhs; + } + + boolean apply(TypeBiPredicate predicate) { + return predicate.apply(types, lhs, rhs); + } + + String format(String symbol) { + return String.format("`%s` %s `%s`", lhs, symbol, rhs); + } + } + + protected static Iterable<Object[]> binaryParameters() throws Exception { + TypeParameters typeParameters = typeParameters(); + List<Object[]> params = new ArrayList<>(); + for (int i = 0; i < typeParameters.aGroups.size(); i++) { + List<TypeMirror> ax = typeParameters.aGroups.get(i); + List<TypeMirror> bx = typeParameters.bGroups.get(i); + Streams.zip( + Lists.cartesianProduct(ax, ax).stream(), + Lists.cartesianProduct(bx, bx).stream(), + (a, b) -> + new Object[] { + a.get(0) + " " + a.get(1), + new TypesBiFunctionInput(typeParameters.javacTypes, a.get(0), a.get(1)), + new TypesBiFunctionInput(typeParameters.turbineTypes, b.get(0), b.get(1)), + }) + .forEachOrdered(params::add); + } + return params; + } + + protected static Iterable<Object[]> unaryParameters() throws Exception { + TypeParameters typeParameters = typeParameters(); + List<Object[]> params = new ArrayList<>(); + for (int i = 0; i < typeParameters.aGroups.size(); i++) { + Streams.zip( + typeParameters.aGroups.get(i).stream(), + typeParameters.bGroups.get(i).stream(), + (a, b) -> + new Object[] { + a.toString(), typeParameters.javacTypes, a, typeParameters.turbineTypes, b, + }) + .forEachOrdered(params::add); + } + return params; + } + + protected static TypeParameters typeParameters() throws Exception { + String[][] types = { + // generics + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "Set", + "ArrayList", + "Collection", + "List<Object>", + "List<Number>", + "List<Integer>", + "ArrayList<Object>", + "ArrayList<Number>", + "ArrayList<Integer>", + }, + // wildcards + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "List<?>", + "List<Object>", + "List<Number>", + "List<Integer>", + "List<? extends Object>", + "List<? extends Number>", + "List<? extends Integer>", + "List<? super Object>", + "List<? super Number>", + "List<? super Integer>", + }, + // arrays + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "Object[]", + "Number[]", + "List<Integer>[]", + "List<? extends Integer>[]", + "long[]", + "int[]", + "int[][]", + "Long[]", + "Integer[]", + "Integer[][]", + }, + // primitives + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "int", + "char", + "byte", + "short", + "boolean", + "long", + "float", + "double", + "Integer", + "Character", + "Byte", + "Short", + "Boolean", + "Long", + "Float", + "Double", + }, + }; + List<String> files = new ArrayList<>(); + AtomicInteger idx = new AtomicInteger(); + for (String[] group : types) { + StringBuilder sb = new StringBuilder(); + Joiner.on('\n') + .appendTo( + sb, + "package p;", + "import java.util.*;", + "import java.io.*;", + String.format("abstract class Test%s {", idx.getAndIncrement()), + Streams.mapWithIndex( + Arrays.stream(group), (x, i) -> String.format(" %s f%d;\n", x, i)) + .collect(joining("\n")), + " abstract <T extends Serializable & List<T>> T f();", + " abstract <V extends List<V>> V g();", + " abstract <W extends ArrayList> W h();", + " abstract <X extends Serializable> X i();", + "}"); + String content = sb.toString(); + files.add(content); + } + // type hierarchies + files.add( + Joiner.on('\n') + .join( + "import java.util.*;", + "class Hierarchy {", // + " static class A<T> {", + " class I {}", + " }", + " static class D<T> extends A<T[]> {}", + " static class E<T> extends A<T> {", + " class J extends I {}", + " }", + " static class F<T> extends A {}", + " static class G<T> extends A<List<T>> {}", + " A rawA;", + " A<Object[]> a1;", + " A<Number[]> a2;", + " A<Integer[]> a3;", + " A<? super Object> a4;", + " A<? super Number> a5;", + " A<? super Integer> a6;", + " A<? extends Object> a7;", + " A<? extends Number> a8;", + " A<? extends Integer> a9;", + " A<List<Integer>> a10;", + " D<Object> d1;", + " D<Number> d2;", + " D<Integer> d3;", + " A<Object>.I i1;", + " A<Number>.I i2;", + " A<Integer>.I i3;", + " E<Object>.J j1;", + " E<Number>.J j2;", + " E<Integer>.J j3;", + " F<Integer> f1;", + " F<Number> f2;", + " F<Object> f3;", + " G<Integer> g1;", + " G<Number> g2;", + "}")); + // methods + files.add( + Joiner.on('\n') + .join( + "import java.io.*;", + "class Methods {", + " void f() {}", + " void g() {}", + " void f(int x) {}", + " void f(int x, int y) {}", + " abstract static class I {", + " abstract int f();", + " abstract void g() throws IOException;", + " abstract <T> void h();", + " abstract <T extends String> T i(T s);", + " }", + " abstract static class J {", + " abstract long f();", + " abstract void g();", + " abstract <T> void h();", + " abstract <T extends Number> T i(T s);", + " }", + " class K {", + " void f(K this, int x) {}", + " void g(K this) {}", + " <T extends Enum<T> & Serializable> void h(T t) {}", + " }", + " class L {", + " void f(int x) {}", + " void g() {}", + " <E extends Enum<E> & Serializable> void h(E t) {}", + " }", + "}", + "class M<T extends Enum<T> & Serializable> {", + " void h(T t) {}", + "}")); + + Context context = new Context(); + JavaFileManager fileManager = new JavacFileManager(context, true, UTF_8); + idx.set(0); + ImmutableList<SimpleJavaFileObject> compilationUnits = + files.stream() + .map( + x -> + new SimpleJavaFileObject( + URI.create("file://test" + idx.getAndIncrement() + ".java"), Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return x; + } + }) + .collect(toImmutableList()); + JavacTask task = + JavacTool.create() + .getTask( + /* out= */ null, + fileManager, + /* diagnosticListener= */ null, + /* options= */ ImmutableList.of(), + /* classes= */ ImmutableList.of(), + compilationUnits); + + Types javacTypes = task.getTypes(); + ImmutableMap<String, Element> javacElements = + Streams.stream(task.analyze()) + .collect(toImmutableMap(e -> e.getSimpleName().toString(), x -> x)); + + ImmutableList<CompUnit> units = files.stream().map(Parser::parse).collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + Types turbineTypes = new TurbineTypes(factory); + ImmutableMap<String, Element> turbineElements = + bound.units().keySet().stream() + .filter(x -> !x.binaryName().contains("$")) // only top level classes + .collect(toImmutableMap(x -> x.simpleName(), factory::element)); + + assertThat(javacElements.keySet()).containsExactlyElementsIn(turbineElements.keySet()); + + List<List<TypeMirror>> aGroups = new ArrayList<>(); + List<List<TypeMirror>> bGroups = new ArrayList<>(); + for (String name : javacElements.keySet()) { + + List<TypeMirror> aGroup = new ArrayList<>(); + List<TypeMirror> bGroup = new ArrayList<>(); + + ListMultimap<String, TypeMirror> javacInputs = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + javacElements + .get(name) + .getEnclosedElements() + .forEach(e -> getTypes(javacTypes, e, javacInputs)); + + ListMultimap<String, TypeMirror> turbineInputs = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + turbineElements + .get(name) + .getEnclosedElements() + .forEach(e -> getTypes(turbineTypes, e, turbineInputs)); + + assertThat(turbineInputs.keySet()).containsExactlyElementsIn(javacInputs.keySet()); + + for (String key : javacInputs.keySet()) { + List<TypeMirror> a = javacInputs.get(key); + List<TypeMirror> b = turbineInputs.get(key); + assertWithMessage(key) + .that(b.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList())) + .containsExactlyElementsIn( + a.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList())) + .inOrder(); + aGroup.addAll(a); + bGroup.addAll(b); + } + aGroups.add(aGroup); + bGroups.add(bGroup); + } + return new TypeParameters(javacTypes, aGroups, turbineTypes, bGroups); + } + + /** + * Discover all types contained in the given element, keyed by their immediate enclosing element. + */ + private static void getTypes( + Types typeUtils, Element element, Multimap<String, TypeMirror> types) { + element.accept( + new ElementScanner8<Void, Void>() { + + /** + * Returns an element name qualified by all enclosing elements, to allow comparison + * between javac and turbine's implementation to group related types. + */ + String key(Element e) { + Deque<String> flat = new ArrayDeque<>(); + while (e != null) { + flat.addFirst(e.getSimpleName().toString()); + if (e.getKind() == ElementKind.PACKAGE) { + break; + } + e = e.getEnclosingElement(); + } + return Joiner.on('.').join(flat); + } + + void addType(Element e, TypeMirror t) { + if (t != null) { + types.put(key(e), t); + t.accept( + new SimpleTypeVisitor8<Void, Void>() { + @Override + public Void visitDeclared(DeclaredType t, Void aVoid) { + for (TypeMirror a : t.getTypeArguments()) { + a.accept(this, null); + } + return null; + } + + @Override + public Void visitWildcard(WildcardType t, Void aVoid) { + types.put(key(e), t); + return null; + } + + @Override + public Void visitTypeVariable(TypeVariable t, Void aVoid) { + if (t.getUpperBound() != null) { + types.put(key(e), t.getUpperBound()); + } + return null; + } + }, + null); + } + } + + void addType(Element e, List<? extends TypeMirror> types) { + for (TypeMirror type : types) { + addType(e, type); + } + } + + @Override + public Void visitVariable(VariableElement e, Void unused) { + if (e.getSimpleName().toString().contains("this$")) { + // enclosing instance parameters + return null; + } + addType(e, e.asType()); + return super.visitVariable(e, null); + } + + @Override + public Void visitType(TypeElement e, Void unused) { + addType(e, e.asType()); + return super.visitType(e, null); + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + scan(e.getTypeParameters(), null); + scan(e.getParameters(), null); + addType(e, e.asType()); + addType(e, e.getReturnType()); + TypeMirror receiverType = e.getReceiverType(); + if (receiverType == null) { + // work around a javac bug in JDK < 14, see: + // https://bugs.openjdk.java.net/browse/JDK-8222369 + receiverType = typeUtils.getNoType(TypeKind.NONE); + } + addType(e, receiverType); + addType(e, e.getThrownTypes()); + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void unused) { + addType(e, e.asType()); + addType(e, e.getBounds()); + return null; + } + }, + null); + } +} diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java new file mode 100644 index 0000000..ed5af6a --- /dev/null +++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java @@ -0,0 +1,346 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.Processing; +import com.google.turbine.binder.Processing.ProcessorInfo; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProcessingIntegrationTest { + + @SupportedAnnotationTypes("*") + public static class CrashingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + throw new RuntimeException("crash!"); + } + } + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test extends NoSuch {", + "}")); + + @Test + public void crash() throws IOException { + ImmutableList<Tree.CompUnit> units = + SOURCES.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new CrashingProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + fail(); + } catch (TurbineError e) { + assertThat(e.diagnostics()).hasSize(2); + assertThat(e.diagnostics().get(0).message()).contains("could not resolve NoSuch"); + assertThat(e.diagnostics().get(1).message()).contains("crash!"); + } + } + + @SupportedAnnotationTypes("*") + public static class WarningProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "proc warning"); + try { + JavaFileObject file = processingEnv.getFiler().createSourceFile("Gen.java"); + try (Writer writer = file.openWriter()) { + writer.write("class Gen {}"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + if (roundEnv.processingOver()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "proc error"); + } + return false; + } + } + + @Test + public void warnings() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new WarningProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + fail(); + } catch (TurbineError e) { + ImmutableList<String> diags = + e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); + assertThat(diags).hasSize(2); + assertThat(diags.get(0)).contains("proc warning"); + assertThat(diags.get(1)).contains("proc error"); + } + } + + @SupportedAnnotationTypes("*") + public static class ResourceProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try { + try (Writer writer = processingEnv.getFiler().createSourceFile("Gen").openWriter()) { + writer.write("class Gen {}"); + } + try (Writer writer = + processingEnv + .getFiler() + .createResource(StandardLocation.SOURCE_OUTPUT, "", "source.txt") + .openWriter()) { + writer.write("hello source output"); + } + try (Writer writer = + processingEnv + .getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "class.txt") + .openWriter()) { + writer.write("hello class output"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + } + + @Test + public void resources() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new ResourceProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + assertThat(bound.generatedSources().keySet()).containsExactly("Gen.java", "source.txt"); + assertThat(bound.generatedClasses().keySet()).containsExactly("class.txt"); + + assertThat(bound.generatedSources().get("source.txt").source()) + .isEqualTo("hello source output"); + assertThat(new String(bound.generatedClasses().get("class.txt"), UTF_8)) + .isEqualTo("hello class output"); + } + + @Test + public void getAllAnnotations() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== A.java ===", // + "import java.lang.annotation.Inherited;", + "@Inherited", + "@interface A {}", + "=== B.java ===", // + "@interface B {}", + "=== One.java ===", // + "@A @B class One {}", + "=== Two.java ===", // + "class Two extends One {}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new ElementsAnnotatedWithProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + assertThat( + Splitter.on(System.lineSeparator()) + .omitEmptyStrings() + .split( + new String( + bound.generatedClasses().entrySet().stream() + .filter(s -> s.getKey().equals("output.txt")) + .collect(onlyElement()) + .getValue(), + UTF_8))) + .containsExactly("A: One, Two", "B: One"); + } + + @SupportedAnnotationTypes("*") + private static class ElementsAnnotatedWithProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (PrintWriter writer = + new PrintWriter( + processingEnv + .getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "output.txt") + .openWriter(), + /* autoFlush= */ true)) { + printAnnotatedElements(roundEnv, writer, "A"); + printAnnotatedElements(roundEnv, writer, "B"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + + private void printAnnotatedElements( + RoundEnvironment roundEnv, PrintWriter writer, String annotation) { + writer.println( + annotation + + ": " + + roundEnv + .getElementsAnnotatedWith( + processingEnv.getElementUtils().getTypeElement(annotation)) + .stream() + .map(e -> e.getSimpleName().toString()) + .collect(joining(", "))); + } + } +} diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java new file mode 100644 index 0000000..a049860 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java @@ -0,0 +1,249 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multimaps; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.testing.TestClassPaths; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.AbstractAnnotationValueVisitor8; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineAnnotationMirrorTest { + + private AnnotationMirror getAnnotation( + List<? extends AnnotationMirror> annotationMirrors, String name) { + return annotationMirrors.stream() + .filter(x -> x.getAnnotationType().asElement().getSimpleName().contentEquals(name)) + .findFirst() + .get(); + } + + private ImmutableMap<String, Object> values(AnnotationMirror a) { + return values(a.getElementValues()); + } + + /** + * Returns a map from the name of annotation elements to their values, see also {@link + * #getValue(AnnotationValue)}. + */ + private ImmutableMap<String, Object> values( + Map<? extends ExecutableElement, ? extends AnnotationValue> values) { + return values.entrySet().stream() + .collect( + toImmutableMap( + e -> e.getKey().getSimpleName().toString(), e -> getValue(e.getValue()))); + } + + /** + * Returns the given annotation value as an Object (for primitives), or a list (for arrays), or + * strings (for compound annotations, enums, and class literals). + */ + static Object getValue(AnnotationValue value) { + return value.accept( + new AbstractAnnotationValueVisitor8<Object, Void>() { + @Override + public Object visitBoolean(boolean b, Void unused) { + return b; + } + + @Override + public Object visitByte(byte b, Void unused) { + return b; + } + + @Override + public Object visitChar(char c, Void unused) { + return c; + } + + @Override + public Object visitDouble(double d, Void unused) { + return d; + } + + @Override + public Object visitFloat(float f, Void unused) { + return f; + } + + @Override + public Object visitInt(int i, Void unused) { + return i; + } + + @Override + public Object visitLong(long i, Void unused) { + return i; + } + + @Override + public Object visitShort(short s, Void unused) { + return s; + } + + @Override + public Object visitString(String s, Void unused) { + return s; + } + + @Override + public Object visitType(TypeMirror t, Void unused) { + return value.toString(); + } + + @Override + public Object visitEnumConstant(VariableElement c, Void unused) { + return value.toString(); + } + + @Override + public Object visitAnnotation(AnnotationMirror a, Void unused) { + return value.toString(); + } + + @Override + public Object visitArray(List<? extends AnnotationValue> vals, Void unused) { + return vals.stream().map(v -> v.accept(this, null)).collect(toImmutableList()); + } + }, + null); + } + + private static Stream<String> typeAnnotationNames(Element e) { + return e.asType().getAnnotationMirrors().stream() + .map(anno -> anno.getAnnotationType().asElement().getSimpleName().toString()); + } + + @Test + public void test() throws Exception { + TestInput input = + TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "import java.lang.annotation.ElementType;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "import java.lang.annotation.Target;", + "import java.util.Map;", + "import java.util.Map.Entry;", + "@Retention(RetentionPolicy.RUNTIME)", + "@interface A {", + " int x() default 0;", + " int y() default 1;", + " int[] z() default {};", + "}", + "@interface B {", + " Class<?> c() default String.class;", + " ElementType e() default ElementType.TYPE_USE;", + " A f() default @A;", + "}", + "@Retention(RetentionPolicy.RUNTIME)", + "@Target(ElementType.TYPE_USE)", + "@interface T {}", + "@Target(ElementType.TYPE_USE)", + "@interface V {}", + "", + "@A(y = 42, z = {43})", + "@B", + "class Test {", + " class I {}", + " @T Test. @V I f;", + " Map. @T Entry g;", + " @T Entry h;", + "}", + "")); + + Binder.BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + input.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + TurbineElements turbineElements = new TurbineElements(factory, turbineTypes); + + TurbineTypeElement te = factory.typeElement(new ClassSymbol("Test")); + + AnnotationMirror a = getAnnotation(te.getAnnotationMirrors(), "A"); + ((TypeElement) a.getAnnotationType().asElement()).getQualifiedName().contentEquals("A"); + assertThat(values(a)).containsExactly("y", 42, "z", ImmutableList.of(43)); + assertThat(values(turbineElements.getElementValuesWithDefaults(a))) + .containsExactly( + "x", 0, + "y", 42, + "z", ImmutableList.of(43)); + + AnnotationMirror b = getAnnotation(te.getAnnotationMirrors(), "B"); + assertThat(values(turbineElements.getElementValuesWithDefaults(b))) + .containsExactly( + "c", "java.lang.String.class", + "e", "java.lang.annotation.ElementType.TYPE_USE", + "f", "@A"); + + ListMultimap<String, String> fieldTypeAnnotations = + te.getEnclosedElements().stream() + .filter(e -> e.getKind().equals(ElementKind.FIELD)) + .collect( + Multimaps.flatteningToMultimap( + e -> e.getSimpleName().toString(), + e -> typeAnnotationNames(e), + MultimapBuilder.linkedHashKeys().arrayListValues()::build)); + assertThat(fieldTypeAnnotations) + .containsExactly( + "f", "V", + "g", "T", + "h", "T"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java new file mode 100644 index 0000000..d339700 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.parse.Parser; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineAnnotationProxyTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Retention(RetentionPolicy.RUNTIME) + public @interface A { + B b() default @B(-1); + + ElementType e() default ElementType.PACKAGE; + + int[] xs() default {}; + + Class<?> c() default String.class; + + Class<?>[] cx() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + public @interface B { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface C {} + + @Retention(RetentionPolicy.RUNTIME) + public @interface RS { + R[] value() default {}; + } + + @Repeatable(RS.class) + @Retention(RetentionPolicy.RUNTIME) + public @interface R { + int value() default 1; + } + + @A + static class I {} + + @Test + public void test() throws IOException { + + Path lib = temporaryFolder.newFile("lib.jar").toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { + addClass(jos, TurbineAnnotationProxyTest.class); + addClass(jos, A.class); + addClass(jos, B.class); + addClass(jos, C.class); + addClass(jos, R.class); + } + + TestInput input = + TestInput.parse( + Joiner.on('\n') + .join( + "=== Super.java ===", + "import " + B.class.getCanonicalName() + ";", + "import " + C.class.getCanonicalName() + ";", + "@B(42)", + "@C", + "class Super {}", + "=== Test.java ===", + "import " + A.class.getCanonicalName() + ";", + "import " + R.class.getCanonicalName() + ";", + "@A(xs = {1,2,3}, cx = {Integer.class, Long.class})", + "@R(1)", + "@R(2)", + "@R(3)", + "class Test extends Super {}", + "")); + + ImmutableList<CompUnit> units = + input.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + + Binder.BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of(lib)), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypeElement te = factory.typeElement(new ClassSymbol("Test")); + + A a = te.getAnnotation(A.class); + B b = te.getAnnotation(B.class); + assertThat(te.getAnnotation(C.class)).isNull(); + + assertThat(a.b().value()).isEqualTo(-1); + assertThat(a.e()).isEqualTo(ElementType.PACKAGE); + try { + a.c(); + fail(); + } catch (MirroredTypeException e) { + assertThat(e.getTypeMirror().getKind()).isEqualTo(TypeKind.DECLARED); + assertThat(getQualifiedName(e.getTypeMirror())).contains("java.lang.String"); + } + try { + a.cx(); + fail(); + } catch (MirroredTypesException e) { + assertThat( + e.getTypeMirrors().stream().map(m -> getQualifiedName(m)).collect(toImmutableList())) + .containsExactly("java.lang.Integer", "java.lang.Long"); + } + assertThat(Ints.asList(a.xs())).containsExactly(1, 2, 3).inOrder(); + assertThat(a.annotationType()).isEqualTo(A.class); + + assertThat(b.value()).isEqualTo(42); + + RS container = te.getAnnotation(RS.class); + assertThat(container.value()).hasLength(3); + R[] rs = te.getAnnotationsByType(R.class); + assertThat(rs).hasLength(3); + assertThat(Arrays.toString(rs)) + .isEqualTo( + String.format( + "[@%s(1), @%s(2), @%s(3)]", + R.class.getCanonicalName(), + R.class.getCanonicalName(), + R.class.getCanonicalName())); + + new EqualsTester() + .addEqualityGroup(a, te.getAnnotation(A.class)) + .addEqualityGroup(b, te.getAnnotation(B.class)) + .addEqualityGroup(rs[0]) + .addEqualityGroup(rs[1]) + .addEqualityGroup(rs[2]) + .addEqualityGroup(container) + .addEqualityGroup(I.class.getAnnotation(A.class)) + .addEqualityGroup("unrelated") + .testEquals(); + } + + private static void addClass(JarOutputStream jos, Class<?> clazz) throws IOException { + String entryPath = clazz.getName().replace('.', '/') + ".class"; + jos.putNextEntry(new JarEntry(entryPath)); + try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) { + ByteStreams.copy(is, jos); + } + } + + private static String getQualifiedName(TypeMirror typeMirror) { + return ((TypeElement) ((DeclaredType) typeMirror).asElement()).getQualifiedName().toString(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementTest.java b/javatests/com/google/turbine/processing/TurbineElementTest.java new file mode 100644 index 0000000..0b3448f --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.MoreCollectors; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type.ClassTy; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineElementTest { + + private final ModelFactory factory = + new ModelFactory( + TestClassPaths.TURBINE_BOOTCLASSPATH.env(), + ClassLoader.getSystemClassLoader(), + TestClassPaths.TURBINE_BOOTCLASSPATH.index()); + + @Test + public void typeElement() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/Map$Entry")); + TypeElement m = (TypeElement) e.getEnclosingElement(); + TypeMirror t = e.asType(); + + assertThat(e.getSimpleName().toString()).isEqualTo("Entry"); + assertThat(e.getQualifiedName().toString()).isEqualTo("java.util.Map.Entry"); + assertThat(e.toString()).isEqualTo("java.util.Map.Entry"); + assertThat(e.asType().toString()).isEqualTo("java.util.Map.Entry<K,V>"); + assertThat(e.getKind()).isEqualTo(ElementKind.INTERFACE); + assertThat(e.getNestingKind()).isEqualTo(NestingKind.MEMBER); + assertThat(e.getModifiers()) + .containsExactly(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.STATIC); + + assertThat(m.getSimpleName().toString()).isEqualTo("Map"); + assertThat(m.getSuperclass().getKind()).isEqualTo(TypeKind.NONE); + assertThat(m.getQualifiedName().toString()).isEqualTo("java.util.Map"); + assertThat(m.toString()).isEqualTo("java.util.Map"); + assertThat(m.asType().toString()).isEqualTo("java.util.Map<K,V>"); + assertThat(m.getNestingKind()).isEqualTo(NestingKind.TOP_LEVEL); + assertThat(m.getSuperclass().getKind()).isEqualTo(TypeKind.NONE); + assertThat(m.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + + assertThat(t.getKind()).isEqualTo(TypeKind.DECLARED); + } + + @Test + public void superClass() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat( + ((TypeElement) ((DeclaredType) e.getSuperclass()).asElement()) + .getQualifiedName() + .toString()) + .isEqualTo("java.util.AbstractMap"); + + e = factory.typeElement(new ClassSymbol("java/lang/annotation/ElementType")); + assertThat( + ((TypeElement) ((DeclaredType) e.getSuperclass()).asElement()) + .getQualifiedName() + .toString()) + .isEqualTo("java.lang.Enum"); + } + + @Test + public void interfaces() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat( + e.getInterfaces().stream() + .map( + i -> + ((TypeElement) ((DeclaredType) i).asElement()) + .getQualifiedName() + .toString()) + .collect(toImmutableList())) + .contains("java.util.Map"); + } + + @Test + public void typeParameters() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat(e.getTypeParameters().stream().map(Object::toString).collect(toImmutableList())) + .containsExactly("K", "V"); + for (TypeParameterElement t : e.getTypeParameters()) { + assertThat(t.getGenericElement()).isEqualTo(e); + assertThat(t.getEnclosingElement()).isEqualTo(e); + assertThat(t.getBounds()).containsExactly(factory.asTypeMirror(ClassTy.OBJECT)); + } + } + + @Test + public void enclosed() { + assertThat( + factory.typeElement(new ClassSymbol("java/lang/Integer")).getEnclosedElements().stream() + .map(e -> e.getKind() + " " + e) + .collect(toImmutableList())) + .containsAtLeast("METHOD parseInt(java.lang.String)", "FIELD MAX_VALUE"); + } + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/List")), + factory.typeElement(new ClassSymbol("java/util/List"))) + .addEqualityGroup(factory.typeElement(new ClassSymbol("java/util/ArrayList"))) + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/Map")).getTypeParameters().get(0), + factory.typeElement(new ClassSymbol("java/util/Map")).getTypeParameters().get(0)) + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/ArrayList")).getTypeParameters().get(0)) + .addEqualityGroup( + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "elementData")), + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "elementData"))) + .addEqualityGroup( + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "serialVersionUID"))) + .addEqualityGroup( + ((ExecutableElement) + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter( + e -> + e.getKind().equals(ElementKind.METHOD) + && e.getSimpleName().contentEquals("add")) + .skip(1) + .findFirst() + .get()) + .getParameters() + .get(0)) + .addEqualityGroup( + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .skip(1) + .findFirst() + .get()) + .addEqualityGroup( + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .findFirst() + .get(), + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .findFirst() + .get()) + .addEqualityGroup( + factory.packageElement(new PackageSymbol("java/util")), + factory.typeElement(new ClassSymbol("java/util/ArrayList")).getEnclosingElement()) + .addEqualityGroup(factory.packageElement(new PackageSymbol("java/lang"))) + .testEquals(); + } + + @Test + public void noElement() { + PackageElement p = factory.packageElement(new PackageSymbol("java/lang")); + assertThat(p.getEnclosingElement()).isNull(); + } + + @Test + public void objectSuper() { + assertThat(factory.typeElement(new ClassSymbol("java/lang/Object")).getSuperclass().getKind()) + .isEqualTo(TypeKind.NONE); + } + + @Test + public void typeKind() { + assertThat(factory.typeElement(new ClassSymbol("java/lang/annotation/Target")).getKind()) + .isEqualTo(ElementKind.ANNOTATION_TYPE); + assertThat(factory.typeElement(new ClassSymbol("java/lang/annotation/ElementType")).getKind()) + .isEqualTo(ElementKind.ENUM); + } + + @Test + public void parameter() { + ExecutableElement equals = + (ExecutableElement) + factory.typeElement(new ClassSymbol("java/lang/Object")).getEnclosedElements().stream() + .filter(e -> e.getSimpleName().contentEquals("equals")) + .collect(MoreCollectors.onlyElement()); + VariableElement parameter = getOnlyElement(equals.getParameters()); + assertThat(parameter.getKind()).isEqualTo(ElementKind.PARAMETER); + assertThat(parameter.asType().toString()).isEqualTo("java.lang.Object"); + assertThat(parameter.getModifiers()).isEmpty(); + assertThat(parameter.getEnclosedElements()).isEmpty(); + assertThat(parameter.getSimpleName().toString()).isNotEmpty(); + assertThat(parameter.getConstantValue()).isNull(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java b/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java new file mode 100644 index 0000000..11dedbf --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import com.sun.source.util.JavacTask; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.util.Elements; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineElementsGetAllMembersTest { + + @Parameters + public static Iterable<Object[]> parameters() { + // An array of test inputs. Each element is an array of lines of sources to compile. + String[][] inputs = { + { + "=== Test.java ===", // + "class Test {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Integer f();", + "}", + "=== Test.java ===", // + "class Test implements A, B {", + " Integer f() {", + " return 42;", + " }", + "}", + }, + { + "=== I.java ===", + "abstract class I {", + " abstract Integer f();", + "}", + "=== J.java ===", + "interface J extends I {", + " default Integer f() {", + " return 42;", + " }", + "}", + "=== Test.java ===", // + "class Test extends I implements J {", + "}", + }, + { + "=== I.java ===", + "interface I {", + " Integer f();", + "}", + "=== J.java ===", + "interface J extends I {", + " default Integer f() {", + " return 42;", + " }", + "}", + "=== Test.java ===", // + "class Test implements J, I {", + "}", + }, + { + "=== p/A.java ===", + "package p;", + "public class A {", + " public boolean f() {", + " return true;", + " }", + "}", + "=== p/B.java ===", + "package p;", + "public interface B {", + " public boolean f();", + "}", + "=== Test.java ===", // + "import p.*;", + "class Test extends A implements B {", + "}", + }, + { + "=== p/A.java ===", + "package p;", + "public class A {", + " public boolean f() {", + " return true;", + " }", + "}", + "=== p/B.java ===", + "package p;", + "public interface B {", + " public boolean f();", + "}", + "=== Middle.java ===", // + "import p.*;", + "public abstract class Middle extends A implements B {", + "}", + "=== Test.java ===", // + "class Test extends Middle {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Number f();", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Integer f();", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== I.java ===", + "interface I {", + " int x;", + "}", + "=== J.java ===", + "interface J {", + " int x;", + "}", + "=== B.java ===", + "class B {", + " int x;", + "}", + "=== C.java ===", + "class C extends B {", + " static int x;", + "}", + "=== Test.java ===", + "class Test extends C implements I, J {", + " int x;", + "}", + }, + { + "=== one/A.java ===", + "public class A {", + " int a;", + "}", + "=== two/B.java ===", + "public class B extends A {", + " int b;", + " private int c;", + " protected int d;", + "}", + "=== Test.java ===", + "public class Test extends B {", + " int x;", + "}", + }, + { + "=== A.java ===", + "interface A {", + " class I {}", + "}", + "=== B.java ===", + "interface B {", + " class J {}", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== A.java ===", + "import java.util.List;", + "interface A<T> {", + " List<? extends T> f();", + "}", + "=== Test.java ===", + "import java.util.List;", + "class Test<T extends Number> implements A<T> {", + " public List<? extends T> f() {", + " return null;", + " }", + "}", + }, + }; + return Arrays.stream(inputs) + .map(input -> TestInput.parse(Joiner.on('\n').join(input))) + .map(x -> new Object[] {x}) + .collect(toImmutableList()); + } + + private final TestInput input; + + public TurbineElementsGetAllMembersTest(TestInput input) { + this.input = input; + } + + // Compile the test inputs with javac and turbine, and assert that getAllMembers returns the + // same elements under each implementation. + @Test + public void test() throws Exception { + JavacTask javacTask = + IntegrationTestSupport.runJavacAnalysis( + input.sources, ImmutableList.of(), ImmutableList.of()); + Elements javacElements = javacTask.getElements(); + List<? extends Element> javacMembers = + javacElements.getAllMembers(requireNonNull(javacElements.getTypeElement("Test"))); + + ImmutableList<CompUnit> units = + input.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + + Binder.BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + TurbineElements turbineElements = new TurbineElements(factory, turbineTypes); + List<? extends Element> turbineMembers = + turbineElements.getAllMembers(factory.typeElement(new ClassSymbol("Test"))); + + assertThat(formatElements(turbineMembers)) + .containsExactlyElementsIn(formatElements(javacMembers)); + } + + private static ImmutableList<String> formatElements(Collection<? extends Element> elements) { + return elements.stream() + .map(e -> String.format("%s %s.%s %s", e.getKind(), e.getEnclosingElement(), e, e.asType())) + .collect(toImmutableList()); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementsTest.java b/javatests/com/google/turbine/processing/TurbineElementsTest.java new file mode 100644 index 0000000..770e6f6 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementsTest.java @@ -0,0 +1,353 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.testing.TestClassPaths; +import com.sun.source.util.JavacTask; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineElementsTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "@Deprecated", + "@A class Test extends One {}", + "=== One.java ===", + "/** javadoc", + " * for", + " * one", + " */", + "@B class One extends Two {", + " /** method javadoc */", + " void f() {}", + " /** field javadoc */", + " int x;", + "}", + "=== Two.java ===", + "/** javadoc", + " for", + " two with extra *", + " */", + "@C(1) class Two extends Three {}", + "=== Three.java ===", + "@C(2) class Three extends Four {}", + "=== Four.java ===", + "@D class Four {}", + "=== Annotations.java ===", + "import java.lang.annotation.Inherited;", + "@interface A {}", + "@interface B {}", + "@Inherited", + "@interface C {", + " int value() default 42;", + "}", + "@Inherited", + "@interface D {}", + "=== com/pkg/P.java ===", + "package com.pkg;", + "@interface P {}", + "=== com/pkg/package-info.java ===", + "@P", + "package com.pkg;", + "=== Const.java ===", + "class Const {", + " static final int X = 1867;", + "}", + "=== com/pkg/empty/package-info.java ===", + "@P", + "package com.pkg.empty;", + "import com.pkg.P;", + "=== com/pkg/A.java ===", + "package com.pkg;", + "class A {", + " class I {}", + "}", + "=== com/pkg/B.java ===", + "package com.pkg;", + "class B {}")); + + Elements javacElements; + ModelFactory factory; + TurbineElements turbineElements; + + @Before + public void setup() throws Exception { + JavacTask task = + IntegrationTestSupport.runJavacAnalysis( + SOURCES.sources, ImmutableList.of(), ImmutableList.of()); + task.analyze(); + javacElements = task.getElements(); + + BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + SOURCES.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + factory = new ModelFactory(env, TurbineElementsTest.class.getClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + turbineElements = new TurbineElements(factory, turbineTypes); + } + + @Test + public void constants() { + for (Object value : + Arrays.asList( + Short.valueOf((short) 1), + Short.MIN_VALUE, + Short.MAX_VALUE, + Byte.valueOf((byte) 1), + Byte.MIN_VALUE, + Byte.MAX_VALUE, + Integer.valueOf(1), + Integer.MIN_VALUE, + Integer.MAX_VALUE, + Long.valueOf(1), + Long.MIN_VALUE, + Long.MAX_VALUE, + Float.valueOf(1), + Float.NaN, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + Float.MAX_VALUE, + Float.MIN_VALUE, + Double.valueOf(1), + Double.NaN, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.MAX_VALUE, + Double.MIN_VALUE, + 'a', + '\n', + "hello", + "\"hello\n\"")) { + assertThat(turbineElements.getConstantExpression(value)) + .isEqualTo(javacElements.getConstantExpression(value)); + } + } + + @Test + public void getName() { + Name n = turbineElements.getName("hello"); + assertThat(n.contentEquals("hello")).isTrue(); + assertThat(n.contentEquals("goodbye")).isFalse(); + + assertThat(n.toString()).isEqualTo("hello"); + assertThat(n.toString()) + .isEqualTo(new String(new char[] {'h', 'e', 'l', 'l', 'o'})); // defeat interning + + assertThat(n.length()).isEqualTo(5); + + new EqualsTester() + .addEqualityGroup(turbineElements.getName("hello"), turbineElements.getName("hello")) + .addEqualityGroup(turbineElements.getName("goodbye")) + .testEquals(); + } + + @Test + public void getAllAnnotationMirrors() { + assertThat( + toStrings( + turbineElements.getAllAnnotationMirrors( + factory.typeElement(new ClassSymbol("Test"))))) + .containsExactly("@java.lang.Deprecated", "@A", "@C(1)", "@D"); + } + + @Test + public void getTypeElement() { + for (String name : Arrays.asList("java.util.Map", "java.util.Map.Entry")) { + assertThat(turbineElements.getTypeElement(name).getQualifiedName().toString()) + .isEqualTo(name); + } + assertThat(turbineElements.getTypeElement("NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.Object.NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.Integer.MAX_VALUE")).isNull(); + } + + private static ImmutableList<String> toStrings(List<?> inputs) { + return inputs.stream().map(String::valueOf).collect(toImmutableList()); + } + + @Test + public void isDeprecated() { + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("java.lang.Object"))) + .isFalse(); + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("One"))).isFalse(); + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("Test"))).isTrue(); + } + + @Test + public void getBinaryName() { + assertThat( + turbineElements + .getBinaryName(turbineElements.getTypeElement("java.util.Map.Entry")) + .toString()) + .isEqualTo("java.util.Map$Entry"); + } + + @Test + public void methodDefaultTest() { + assertThat( + ((ExecutableElement) + getOnlyElement(turbineElements.getTypeElement("C").getEnclosedElements())) + .getDefaultValue() + .getValue()) + .isEqualTo(42); + } + + @Test + public void constantFieldTest() { + assertThat( + ((VariableElement) + turbineElements.getTypeElement("Const").getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.FIELD)) + .collect(onlyElement())) + .getConstantValue()) + .isEqualTo(1867); + } + + @Test + public void packageElement() { + assertThat( + toStrings( + turbineElements.getAllAnnotationMirrors( + turbineElements.getPackageElement("com.pkg")))) + .containsExactly("@com.pkg.P"); + assertThat( + turbineElements.getAllAnnotationMirrors(turbineElements.getPackageElement("java.lang"))) + .isEmpty(); + assertThat(turbineElements.getPackageElement("com.google.no.such.pkg")).isNull(); + } + + @Test + public void packageMembers() { + assertThat( + turbineElements.getPackageElement("com.pkg").getEnclosedElements().stream() + .map(e -> ((TypeElement) e).getQualifiedName().toString()) + .collect(toImmutableList())) + .containsExactly("com.pkg.P", "com.pkg.A", "com.pkg.B"); + assertThat(turbineElements.getPackageElement("com.pkg.empty").getEnclosedElements()).isEmpty(); + } + + @Test + public void noElement() { + Element e = factory.noElement("com.google.Foo"); + assertThat(e.getKind()).isEqualTo(ElementKind.CLASS); + assertThat(e.getSimpleName().toString()).isEqualTo("Foo"); + assertThat(e.getEnclosingElement().toString()).isEqualTo("com.google"); + assertThat(e.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + + e = factory.noElement("Foo"); + assertThat(e.getSimpleName().toString()).isEqualTo("Foo"); + assertThat(e.getEnclosingElement().toString()).isEmpty(); + assertThat(e.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + } + + @Test + public void javadoc() { + TypeElement e = turbineElements.getTypeElement("One"); + assertThat(turbineElements.getDocComment(e)) + .isEqualTo( + " javadoc\n" // + + " for\n" + + " one\n" + + ""); + + assertThat( + turbineElements.getDocComment( + e.getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.FIELD)) + .collect(onlyElement()))) + .isEqualTo(" field javadoc "); + + assertThat( + turbineElements.getDocComment( + e.getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.METHOD)) + .collect(onlyElement()))) + .isEqualTo(" method javadoc "); + + e = turbineElements.getTypeElement("Two"); + assertThat(turbineElements.getDocComment(e)) + .isEqualTo( + " javadoc\n" // + + "for\n" + + "two with extra *\n" + + ""); + } + + @Test + public void syntheticParameters() { + assertThat( + ((ExecutableElement) + getOnlyElement( + turbineElements.getTypeElement("com.pkg.A.I").getEnclosedElements())) + .getParameters()) + .isEmpty(); + } + + @Test + public void printElements() { + StringWriter w = new StringWriter(); + turbineElements.printElements( + w, + turbineElements.getTypeElement("com.pkg.A"), + turbineElements.getTypeElement("com.pkg.A.I")); + assertThat(w.toString()).isEqualTo(lines("com.pkg.A", "com.pkg.A.I")); + } + + private String lines(String... lines) { + return Joiner.on(System.lineSeparator()).join(lines) + System.lineSeparator(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineFilerTest.java b/javatests/com/google/turbine/processing/TurbineFilerTest.java new file mode 100644 index 0000000..40b78ea --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineFilerTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.turbine.diag.SourceFile; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.FilerException; +import javax.lang.model.element.Element; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineFilerTest { + + private final Set<String> seen = new HashSet<>(); + private TurbineFiler filer; + + @Before + public void setup() { + Function<String, Supplier<byte[]>> classpath = + new Function<String, Supplier<byte[]>>() { + @Nullable + @Override + public Supplier<byte[]> apply(String input) { + return null; + } + }; + this.filer = new TurbineFiler(seen, classpath, TurbineFilerTest.class.getClassLoader()); + } + + @Test + public void hello() throws IOException { + JavaFileObject sourceFile = filer.createSourceFile("com.foo.Bar", (Element[]) null); + try (OutputStream os = sourceFile.openOutputStream()) { + os.write("hello".getBytes(UTF_8)); + } + assertThat(sourceFile.getLastModified()).isEqualTo(0); + + JavaFileObject classFile = filer.createClassFile("com.foo.Baz", (Element[]) null); + try (OutputStream os = classFile.openOutputStream()) { + os.write("goodbye".getBytes(UTF_8)); + } + assertThat(classFile.getLastModified()).isEqualTo(0); + + Collection<SourceFile> roundSources = filer.finishRound(); + assertThat(roundSources).hasSize(1); + assertThat(filer.generatedSources()).hasSize(1); + assertThat(filer.generatedClasses()).hasSize(1); + + SourceFile source = getOnlyElement(roundSources); + assertThat(source.path()).isEqualTo("com/foo/Bar.java"); + assertThat(source.source()).isEqualTo("hello"); + + Map.Entry<String, byte[]> clazz = getOnlyElement(filer.generatedClasses().entrySet()); + assertThat(clazz.getKey()).isEqualTo("com/foo/Baz.class"); + assertThat(new String(clazz.getValue(), UTF_8)).isEqualTo("goodbye"); + } + + @Test + public void existing() throws IOException { + seen.add("com/foo/Bar.java"); + seen.add("com/foo/Baz.class"); + + try { + filer.createSourceFile("com.foo.Bar", (Element[]) null); + fail(); + } catch (FilerException expected) { + } + filer.createSourceFile("com.foo.Baz", (Element[]) null); + + filer.createClassFile("com.foo.Bar", (Element[]) null); + try { + filer.createClassFile("com.foo.Baz", (Element[]) null); + fail(); + } catch (FilerException expected) { + } + } + + @Test + public void get() throws IOException { + for (StandardLocation location : + Arrays.asList( + StandardLocation.CLASS_OUTPUT, + StandardLocation.SOURCE_OUTPUT, + StandardLocation.ANNOTATION_PROCESSOR_PATH, + StandardLocation.CLASS_PATH)) { + try { + filer.getResource(location, "", "NoSuch"); + fail(); + } catch (FileNotFoundException expected) { + } + } + } + + @Test + public void sourceOutput() throws IOException { + JavaFileObject classFile = filer.createSourceFile("com.foo.Bar", (Element[]) null); + try (Writer writer = classFile.openWriter()) { + writer.write("hello"); + } + filer.finishRound(); + + FileObject output = filer.getResource(StandardLocation.SOURCE_OUTPUT, "com.foo", "Bar.java"); + assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8)) + .isEqualTo("hello"); + assertThat(output.getCharContent(false).toString()).isEqualTo("hello"); + assertThat(CharStreams.toString(output.openReader(true))).isEqualTo("hello"); + } + + @Test + public void classOutput() throws IOException { + JavaFileObject classFile = filer.createClassFile("com.foo.Baz", (Element[]) null); + try (OutputStream os = classFile.openOutputStream()) { + os.write("goodbye".getBytes(UTF_8)); + } + filer.finishRound(); + + FileObject output = filer.getResource(StandardLocation.CLASS_OUTPUT, "com.foo", "Baz.class"); + assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8)) + .isEqualTo("goodbye"); + assertThat(output.getCharContent(false).toString()).isEqualTo("goodbye"); + assertThat(CharStreams.toString(output.openReader(true))).isEqualTo("goodbye"); + } + + @Test + public void classpathResources() throws IOException { + FileObject resource = + filer.getResource(StandardLocation.ANNOTATION_PROCESSOR_PATH, "META-INF", "MANIFEST.MF"); + + assertThat(new String(ByteStreams.toByteArray(resource.openInputStream()), UTF_8)) + .contains("Manifest-Version:"); + assertThat(CharStreams.toString(resource.openReader(true))).contains("Manifest-Version:"); + assertThat(resource.getCharContent(false).toString()).contains("Manifest-Version:"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineMessagerTest.java b/javatests/com/google/turbine/processing/TurbineMessagerTest.java new file mode 100644 index 0000000..c1e6401 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineMessagerTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Comparator.comparing; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.Processing; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineDiagnostic; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree; +import com.sun.source.util.JavacTask; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementScanner8; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineMessagerTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "@A class Test {", + " @A @B void f() {}", + " @B int x;", + "}", + "=== One.java ===", + "class One<U, V> {", + " @A void f(@B int x, @C int y) {}", + " <X, Y> void g(X x) {}", + "}", + "=== Two.java ===", + "class Two {", + " @D(value = 1) int x1;", + " @D(1) int x2;", + " @E(1) int x3;", + " @E({1, 2}) int x4;", + " @E(value = {1, 2}, y = 3) int x5;", + " @E(y = 0, value = {1, 2}) int x6;", + "}", + "=== Annotations.java ===", + "@interface A {}", + "@interface B {}", + "@interface C {}", + "@interface D {", + " int value() default 0;", + "}", + "@interface E {", + " int[] value() default {};", + " int y() default 0;", + "}")); + + /** + * Tests {@link TurbineMessager} by logging a message at each {@link Element}, {@link + * AnnotationMirror}, and {@link AnnotationValue}. + */ + @SupportedAnnotationTypes("*") + public static class DiagnosticTesterProcessor extends AbstractProcessor { + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + for (Element e : roundEnv.getRootElements()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.valueOf(e)); + e.accept( + new ElementScanner8<Void, Void>() { + @Override + public Void scan(Element e, Void unused) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, String.valueOf(e), e); + for (AnnotationMirror a : e.getAnnotationMirrors()) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, String.format("%s %s", e, a), e, a); + for (AnnotationValue av : a.getElementValues().values()) { + processAnnotation(e, a, av); + } + } + return null; + } + + private void processAnnotation(Element e, AnnotationMirror a, AnnotationValue av) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, String.format("%s %s %s", e, a, av), e, a, av); + av.accept( + new SimpleAnnotationValueVisitor8<Void, Void>() { + @Override + public Void visitAnnotation(AnnotationMirror a, Void unused) { + visitAnnotationValues(a.getElementValues().values()); + return null; + } + + @Override + public Void visitArray(List<? extends AnnotationValue> vals, Void unused) { + visitAnnotationValues(vals); + return null; + } + + private void visitAnnotationValues( + Collection<? extends AnnotationValue> values) { + for (AnnotationValue av : values) { + processAnnotation(e, a, av); + } + } + }, + null); + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + scan(e.getTypeParameters(), null); + return super.visitExecutable(e, unused); + } + + @Override + public Void visitType(TypeElement e, Void unused) { + scan(e.getTypeParameters(), null); + return super.visitType(e, unused); + } + }, + null); + } + return false; + } + } + + @Test + public void test() throws Exception { + + // Processes the test sources with the DiagnosticTesterProcessor under both javac and turbine, + // and asserts that the diagnostics have the same source path, line, and column under each. + + DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); + JavacTask task = + IntegrationTestSupport.runJavacAnalysis( + SOURCES.sources, ImmutableList.of(), ImmutableList.of(), collector); + task.setProcessors(ImmutableList.of(new DiagnosticTesterProcessor())); + task.call(); + ImmutableList<String> javacDiagnostics = + collector.getDiagnostics().stream() + // sort the diagnostics for nicer test failure messages + .sorted( + comparing(TurbineMessagerTest::shortPath) + .thenComparing(Diagnostic::getLineNumber) + .thenComparing(Diagnostic::getColumnNumber)) + .map(TurbineMessagerTest::formatDiagnostic) + .collect(toImmutableList()); + + ImmutableList<String> turbineDiagnostics; + ImmutableList<Tree.CompUnit> units = + SOURCES.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new DiagnosticTesterProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + throw new AssertionError(); + } catch (TurbineError e) { + turbineDiagnostics = + e.diagnostics().stream() + .sorted( + comparing(TurbineDiagnostic::path) + .thenComparing(TurbineDiagnostic::line) + .thenComparing(TurbineDiagnostic::column)) + .map(TurbineMessagerTest::formatDiagnostic) + .collect(toImmutableList()); + } + + assertThat(turbineDiagnostics).containsExactlyElementsIn(javacDiagnostics).inOrder(); + } + + private static String formatDiagnostic(TurbineDiagnostic d) { + return String.format("%s:%s:%s %s", d.path(), d.line(), d.column(), d.message()); + } + + private static String formatDiagnostic(Diagnostic<? extends JavaFileObject> d) { + return String.format( + "%s:%s:%s %s", + shortPath(d), d.getLineNumber(), d.getColumnNumber(), d.getMessage(Locale.ENGLISH)); + } + + private static String shortPath(Diagnostic<? extends JavaFileObject> d) { + return d.getSource() != null + ? Paths.get(d.getSource().getName()).getFileName().toString() + : "<>"; + } +} diff --git a/javatests/com/google/turbine/processing/TurbineNameTest.java b/javatests/com/google/turbine/processing/TurbineNameTest.java new file mode 100644 index 0000000..f67fc82 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineNameTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineNameTest { + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + new TurbineName("hello"), new TurbineName("hello"), new TurbineName("hello")) + .addEqualityGroup(new TurbineName("is")) + .addEqualityGroup(new TurbineName("there")) + .addEqualityGroup(new TurbineName("anybody")) + .addEqualityGroup(new TurbineName("in")) + .testEquals(); + } + + @Test + public void asd() { + assertThat(new TurbineName("hello").contentEquals("hello")).isTrue(); + assertThat(new TurbineName("hello").contentEquals("goodbye")).isFalse(); + + assertThat(new TurbineName("hello").length()).isEqualTo(5); + + assertThat(new TurbineName("hello").charAt(0)).isEqualTo('h'); + + assertThat(new TurbineName("hello").subSequence(1, 4).toString()).isEqualTo("ell"); + + assertThat(new TurbineName("hello").toString()).isEqualTo("hello"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java b/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java new file mode 100644 index 0000000..bf08f89 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java @@ -0,0 +1,281 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.PrimTy; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineTypeMirrorTest { + + private final ModelFactory factory = + new ModelFactory( + TestClassPaths.TURBINE_BOOTCLASSPATH.env(), + ClassLoader.getSystemClassLoader(), + TestClassPaths.TURBINE_BOOTCLASSPATH.index()); + + @Test + public void primitiveTypes() { + for (TypeKind kind : TypeKind.values()) { + if (!kind.isPrimitive()) { + continue; + } + TurbineConstantTypeKind turbineKind = TurbineConstantTypeKind.valueOf(kind.name()); + TypeMirror type = factory.asTypeMirror(PrimTy.create(turbineKind, ImmutableList.of())); + assertThat(type.getKind()).isEqualTo(kind); + } + } + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of(), + ImmutableList.of()), + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.STRING), + ImmutableList.of()))))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.OBJECT), + ImmutableList.of()))))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry")))) + .addEqualityGroup( + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of())), + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.WildLowerBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.WildUpperBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of()))) + .addEqualityGroup(factory.asTypeMirror(Type.WildUnboundedTy.create(ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ArrayTy.create( + PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()), + ImmutableList.of()))) + .addEqualityGroup(factory.packageType(new PackageSymbol("java/lang"))) + .addEqualityGroup( + factory.asTypeMirror( + Type.TyVar.create( + new TyVarSymbol(new ClassSymbol("java/util/List"), "V"), ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.IntersectionTy.create( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/Cloneable")))))) + .addEqualityGroup(factory.noType()) + .testEquals(); + } + + @Test + public void roundTrip() { + DeclaredType te = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/List"), + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/String"))), + ImmutableList.of())))); + assertThat(te.asElement().asType()).isNotEqualTo(te); + assertThat(te.asElement().asType()) + .isEqualTo( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/List"), + ImmutableList.of( + Type.TyVar.create( + new TyVarSymbol(new ClassSymbol("java/util/List"), "E"), + ImmutableList.of())), + ImmutableList.of()))))); + } + + @Test + public void wildTy() { + WildcardType lower = + (WildcardType) + factory.asTypeMirror( + Type.WildLowerBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of())); + WildcardType upper = + (WildcardType) + factory.asTypeMirror( + Type.WildUpperBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Long")), + ImmutableList.of())); + WildcardType unbound = + (WildcardType) factory.asTypeMirror(Type.WildUnboundedTy.create(ImmutableList.of())); + + assertThat(lower.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(lower.getExtendsBound()).isNull(); + assertThat(lower.getSuperBound().getKind()).isEqualTo(TypeKind.DECLARED); + + assertThat(upper.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(upper.getExtendsBound().getKind()).isEqualTo(TypeKind.DECLARED); + assertThat(upper.getSuperBound()).isNull(); + + assertThat(unbound.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(unbound.getExtendsBound()).isNull(); + assertThat(unbound.getSuperBound()).isNull(); + } + + @Test + public void intersection() { + IntersectionType t = + (IntersectionType) + factory.asTypeMirror( + Type.IntersectionTy.create( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/Cloneable"))))); + + assertThat(t.getKind()).isEqualTo(TypeKind.INTERSECTION); + assertThat(t.getBounds()) + .containsExactlyElementsIn( + factory.asTypeMirrors( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Object")), + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Cloneable"))))); + } + + @Test + public void tyVar() { + TypeVariable t = + (TypeVariable) + Iterables.getOnlyElement( + factory + .typeElement(new ClassSymbol("java/util/Collections")) + .getEnclosedElements() + .stream() + .filter(e -> e.getSimpleName().contentEquals("sort")) + .filter(ExecutableElement.class::isInstance) + .map(ExecutableElement.class::cast) + .filter(e -> e.getParameters().size() == 1) + .findFirst() + .get() + .getTypeParameters()) + .asType(); + assertThat(t.getKind()).isEqualTo(TypeKind.TYPEVAR); + assertThat(t.getLowerBound().getKind()).isEqualTo(TypeKind.NONE); + assertThat(t.getUpperBound().toString()).isEqualTo("java.lang.Comparable<? super T>"); + } + + @Test + public void arrayType() { + ArrayType t = + (ArrayType) + factory.asTypeMirror( + Type.ArrayTy.create( + PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()), + ImmutableList.of())); + assertThat(t.getKind()).isEqualTo(TypeKind.ARRAY); + assertThat(t.getComponentType().getKind()).isEqualTo(TypeKind.LONG); + } + + @Test + public void declared() { + DeclaredType a = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of(), + ImmutableList.of()), + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.STRING), + ImmutableList.of())))); + DeclaredType b = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry"))); + + assertThat(a.getEnclosingType().getKind()).isEqualTo(TypeKind.NONE); + assertThat(b.getEnclosingType().getKind()).isEqualTo(TypeKind.NONE); + } + + @Test + public void method() { + ExecutableType type = + (ExecutableType) + ((TypeElement) factory.typeElement(new ClassSymbol("java/util/Collections"))) + .getEnclosedElements().stream() + .filter(e -> e.getSimpleName().contentEquals("replaceAll")) + .collect(onlyElement()) + .asType(); + assertThat(type.getTypeVariables()).hasSize(1); + assertThat(type.toString()).isEqualTo("<T>(java.util.List<T>,T,T)boolean"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java b/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java new file mode 100644 index 0000000..e9e411f --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesContainsTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesContainsTest( + String name, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(name, javacInput, turbineInput); + } + + @Test + public void contains() { + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("<=", Types::contains); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java new file mode 100644 index 0000000..0f9e6a6 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type.ArrayTy; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.WildLowerBoundedTy; +import com.google.turbine.type.Type.WildUnboundedTy; +import com.google.turbine.type.Type.WildUpperBoundedTy; +import java.util.Optional; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineTypesFactoryTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "class Test {", + " class I {}", + "}")); + + ModelFactory factory; + TurbineElements turbineElements; + TurbineTypes turbineTypes; + + @Before + public void setup() throws Exception { + + BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + SOURCES.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + factory = new ModelFactory(env, getClass().getClassLoader(), bound.tli()); + turbineTypes = new TurbineTypes(factory); + turbineElements = new TurbineElements(factory, turbineTypes); + } + + @Test + public void primitiveTypes() { + for (TypeKind kind : TypeKind.values()) { + if (kind.isPrimitive()) { + PrimitiveType type = turbineTypes.getPrimitiveType(kind); + assertThat(type.getKind()).isEqualTo(kind); + } else { + try { + turbineTypes.getPrimitiveType(kind); + fail(); + } catch (IllegalArgumentException expected) { + } + } + } + } + + @Test + public void arrayType() { + assertThat( + turbineTypes.isSameType( + turbineTypes.getArrayType( + turbineTypes.erasure(turbineElements.getTypeElement("java.util.Map").asType())), + factory.asTypeMirror( + ArrayTy.create( + ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map")), + ImmutableList.of())))) + .isTrue(); + } + + @Test + public void wildcardType() { + // wildcard types don't compare equal with isSameType, so compare their string representations + assertThat(turbineTypes.getWildcardType(null, null).toString()) + .isEqualTo(factory.asTypeMirror(WildUnboundedTy.create(ImmutableList.of())).toString()); + assertThat( + turbineTypes + .getWildcardType(turbineElements.getTypeElement("java.lang.String").asType(), null) + .toString()) + .isEqualTo( + factory + .asTypeMirror(WildUpperBoundedTy.create(ClassTy.STRING, ImmutableList.of())) + .toString()); + assertThat( + turbineTypes + .getWildcardType(null, turbineElements.getTypeElement("java.lang.String").asType()) + .toString()) + .isEqualTo( + factory + .asTypeMirror(WildLowerBoundedTy.create(ClassTy.STRING, ImmutableList.of())) + .toString()); + } + + @Test + public void declaredType() { + assertThat( + turbineTypes.isSameType( + turbineTypes.getDeclaredType( + turbineElements.getTypeElement("java.util.Map"), + turbineElements.getTypeElement("java.lang.String").asType(), + turbineElements.getTypeElement("java.lang.Integer").asType()), + factory.asTypeMirror( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of( + ClassTy.STRING, + ClassTy.asNonParametricClassTy(ClassSymbol.INTEGER)), + ImmutableList.of())))))) + .isTrue(); + assertThat( + turbineTypes.isSameType( + turbineTypes.getDeclaredType( + turbineTypes.getDeclaredType(turbineElements.getTypeElement("Test")), + turbineElements.getTypeElement("Test.I")), + factory.asTypeMirror( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("Test"), ImmutableList.of(), ImmutableList.of()), + SimpleClassTy.create( + new ClassSymbol("Test$I"), + ImmutableList.of(), + ImmutableList.of())))))) + .isTrue(); + } + + @Test + public void noType() { + assertThat(turbineTypes.getNoType(TypeKind.VOID).getKind()).isEqualTo(TypeKind.VOID); + assertThat(turbineTypes.getNoType(TypeKind.NONE).getKind()).isEqualTo(TypeKind.NONE); + try { + turbineTypes.getNoType(TypeKind.DECLARED); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void nullType() { + assertThat(turbineTypes.getNullType().getKind()).isEqualTo(TypeKind.NULL); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java new file mode 100644 index 0000000..75b93b0 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsAssignableTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsAssignableTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isAssignable() { + // see JDK-8039198 + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("isAssignable", Types::isAssignable); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java new file mode 100644 index 0000000..ef45991 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 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.processing; + +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSameTypeTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSameTypeTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSameType() { + test("isSameType", Types::isSameType); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java new file mode 100644 index 0000000..5f315da --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSubsignatureTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSubsignatureTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSubsignature() { + assume().that(javacInput.lhs.getKind()).isEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isEqualTo(TypeKind.EXECUTABLE); + + test( + "isSubsignature", + (types, x, y) -> types.isSubsignature((ExecutableType) x, (ExecutableType) y)); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java new file mode 100644 index 0000000..8370126 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSubtypeTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSubtypeTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSubtype() { + // see JDK-8039198 + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("<:", Types::isSubtype); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java new file mode 100644 index 0000000..eb5ee6c --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019 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.processing; + +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.truth.TruthJUnit.assume; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return unaryParameters(); + } + + final String testDescription; + final Types javacTypes; + final TypeMirror javacA; + final Types turbineTypes; + final TypeMirror turbineA; + + public TurbineTypesUnaryTest( + String testDescription, + Types javacTypes, + TypeMirror javacA, + Types turbineTypes, + TypeMirror turbineA) { + this.testDescription = testDescription; + this.javacTypes = javacTypes; + this.javacA = javacA; + this.turbineTypes = turbineTypes; + this.turbineA = turbineA; + } + + @Test + public void unboxedType() { + IllegalArgumentException thrown = null; + String expectedType = null; + try { + expectedType = javacTypes.unboxedType(javacA).toString(); + } catch (IllegalArgumentException e) { + thrown = e; + } + if (thrown != null) { + try { + turbineTypes.unboxedType(turbineA).toString(); + fail(String.format("expected unboxedType(`%s`) to throw", turbineA)); + } catch (IllegalArgumentException expected) { + // expected + } + } else { + String actual = turbineTypes.unboxedType(turbineA).toString(); + assertWithMessage("unboxedClass(`%s`) = unboxedClass(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expectedType); + } + } + + @Test + public void boxedClass() { + assume().that(javacA).isInstanceOf(PrimitiveType.class); + assume().that(turbineA).isInstanceOf(PrimitiveType.class); + + String expected = javacTypes.boxedClass((PrimitiveType) javacA).toString(); + String actual = turbineTypes.boxedClass((PrimitiveType) turbineA).toString(); + assertWithMessage("boxedClass(`%s`) = boxedClass(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + @Test + public void erasure() { + String expected = javacTypes.erasure(javacA).toString(); + String actual = turbineTypes.erasure(turbineA).toString(); + assertWithMessage("erasure(`%s`) = erasure(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + private static final ImmutableSet<TypeKind> UNSUPPORTED_BY_DIRECT_SUPERTYPES = + ImmutableSet.of(TypeKind.EXECUTABLE, TypeKind.PACKAGE); + + @Test + public void directSupertypes() { + assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).doesNotContain(javacA.getKind()); + + String expected = Joiner.on(", ").join(javacTypes.directSupertypes(javacA)); + String actual = Joiner.on(", ").join(turbineTypes.directSupertypes(turbineA)); + assertWithMessage("directSupertypes(`%s`) = directSupertypes(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + @Test + public void directSupertypesThrows() { + assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).contains(javacA.getKind()); + + try { + javacTypes.directSupertypes(turbineA); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + turbineTypes.directSupertypes(turbineA); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void asElement() { + // TODO(cushon): this looks like a javac bug + assume().that(javacA.getKind()).isNotEqualTo(TypeKind.INTERSECTION); + + String expected = String.valueOf(javacTypes.asElement(javacA)); + String actual = String.valueOf(turbineTypes.asElement(turbineA)); + assertWithMessage("asElement(`%s`) = asElement(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } +} diff --git a/javatests/com/google/turbine/testing/TestClassPaths.java b/javatests/com/google/turbine/testing/TestClassPaths.java index bf38913..93be916 100644 --- a/javatests/com/google/turbine/testing/TestClassPaths.java +++ b/javatests/com/google/turbine/testing/TestClassPaths.java @@ -67,7 +67,7 @@ public class TestClassPaths { public static TurbineOptions.Builder optionsWithBootclasspath() { TurbineOptions.Builder options = TurbineOptions.builder(); if (!BOOTCLASSPATH.isEmpty()) { - options.addBootClassPathEntries( + options.setBootClassPath( BOOTCLASSPATH.stream().map(Path::toString).collect(toImmutableList())); } else { options.setRelease("8"); diff --git a/javatests/com/google/turbine/type/TypeTest.java b/javatests/com/google/turbine/type/TypeTest.java new file mode 100644 index 0000000..be3eb9c --- /dev/null +++ b/javatests/com/google/turbine/type/TypeTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 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.type; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.tree.Tree.Ident; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.ErrorTy; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TypeTest { + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("java/util/Map"), ImmutableList.of(), ImmutableList.of()), + SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(ClassTy.STRING, ClassTy.STRING), + ImmutableList.of())))) + .addEqualityGroup( + SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(ClassTy.STRING, ClassTy.OBJECT), + ImmutableList.of())) + .addEqualityGroup(ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry"))) + .testEquals(); + } + + private static final int NO_POSITION = -1; + + @Test + public void error() { + assertThat( + ErrorTy.create( + ImmutableList.of( + new Ident(NO_POSITION, "com"), + new Ident(NO_POSITION, "foo"), + new Ident(NO_POSITION, "Bar"))) + .name()) + .isEqualTo("com.foo.Bar"); + } +} diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java index 67dcfe7..bfc9cdf 100644 --- a/javatests/com/google/turbine/zip/ZipTest.java +++ b/javatests/com/google/turbine/zip/ZipTest.java @@ -165,7 +165,7 @@ public class ZipTest { actual(path); fail(); } catch (ZipException e) { - assertThat(e).hasMessage("zip file comment length was 33, expected 17"); + assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17"); } } } @@ -74,13 +74,19 @@ <dependency> <groupId>com.google.truth</groupId> <artifactId>truth</artifactId> - <version>0.42</version> + <version>1.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.truth.extensions</groupId> + <artifactId>truth-proto-extension</artifactId> + <version>1.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.truth.extensions</groupId> <artifactId>truth-java8-extension</artifactId> - <version>0.42</version> + <version>1.0</version> <scope>test</scope> </dependency> <dependency> diff --git a/proto/deps.proto b/proto/deps.proto index 27013a2..e93cc95 100644 --- a/proto/deps.proto +++ b/proto/deps.proto @@ -20,7 +20,6 @@ option java_package = "com.google.turbine.proto"; option java_outer_classname = "DepsProto"; message Dependency { - enum Kind { // Dependency used explicitly in the source. EXPLICIT = 0; @@ -49,4 +48,9 @@ message Dependencies { // Whether the action was successful; even when compilation fails, partial // dependency information can be useful. optional bool success = 3; + + // If the Java action was started with a reduced classpath and an error + // occurred suggesting that it should be rerun with the full classpath, this + // will be true. + optional bool requires_reduced_classpath_fallback = 5; } diff --git a/proto/manifest.proto b/proto/manifest.proto new file mode 100644 index 0000000..20e85ef --- /dev/null +++ b/proto/manifest.proto @@ -0,0 +1,40 @@ +// Copyright 2020 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. + +// Definitions for dependency reports. + +syntax = "proto2"; + +option java_package = "com.google.turbine.proto"; +option java_outer_classname = "ManifestProto"; + +// Information about a single compilation unit (.java file) +message CompilationUnit { + // The path to the compilation unit + optional string path = 1; + + // The package of the source file + optional string pkg = 2; + + // Whether the source was generated by an annotation processor + optional bool generated_by_annotation_processor = 3; + + // The list of top-level types in the compilation unit + repeated string top_level = 4; +} + +// Top-level message found in .manifest artifacts +message Manifest { + repeated CompilationUnit compilation_unit = 1; +}
\ No newline at end of file |