diff options
Diffstat (limited to 'java/com/google/turbine/processing')
14 files changed, 5629 insertions, 0 deletions
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); + } +} |