blob: 26dce3276dbf548d9dbcbfa0d65f595aaaeab036 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.turbine.binder;
import static com.google.common.collect.Iterables.getOnlyElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.turbine.binder.bound.AnnotationValue;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
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.model.Const;
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.ClassTy.SimpleClassTy;
import com.google.turbine.type.Type.PrimTy;
import com.google.turbine.type.Type.TyVar;
import java.lang.annotation.ElementType;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* Disambiguate annotations on field, parameter, and method return types that could be declaration
* or type annotations.
*
* <p>Given a declaration like {@code private @A int x;} or {@code @A private int x;}, there are
* three possibilities:
*
* <ol>
* <li>{@code @A} is a declaration annotation on the field.
* <li>{@code @A} is a {@code TYPE_USE} annotation on the type.
* <li>{@code @A} sets {@code TYPE_USE} <em>and</em> {@code FIELD} targets, and appears in the
* bytecode as both a declaration annotation and as a type annotation.
* </ol>
*
* <p>This can't be disambiguated syntactically (note that the presence of other modifiers before or
* after the annotation has no bearing on whether it's a type annotation). So, we wait until
* constant binding is done, read the {@code @Target} meta-annotation for each ambiguous annotation,
* and move it to the appropriate location.
*/
public class DisambiguateTypeAnnotations {
public static SourceTypeBoundClass bind(
SourceTypeBoundClass base, Env<ClassSymbol, TypeBoundClass> env) {
return new SourceTypeBoundClass(
base.interfaceTypes(),
base.superClassType(),
base.typeParameterTypes(),
base.access(),
bindMethods(env, base.methods()),
bindFields(env, base.fields()),
base.owner(),
base.kind(),
base.children(),
base.typeParameters(),
base.enclosingScope(),
base.scope(),
base.memberImports(),
base.annotationMetadata(),
groupRepeated(env, base.annotations()),
base.source());
}
private static ImmutableList<MethodInfo> bindMethods(
Env<ClassSymbol, TypeBoundClass> env, ImmutableList<MethodInfo> fields) {
ImmutableList.Builder<MethodInfo> result = ImmutableList.builder();
for (MethodInfo field : fields) {
result.add(bindMethod(env, field));
}
return result.build();
}
private static MethodInfo bindMethod(Env<ClassSymbol, TypeBoundClass> env, MethodInfo base) {
ImmutableList.Builder<AnnoInfo> declarationAnnotations = ImmutableList.builder();
Type returnType =
disambiguate(
env,
base.name().equals("<init>") ? ElementType.CONSTRUCTOR : ElementType.METHOD,
base.returnType(),
base.annotations(),
declarationAnnotations);
return new MethodInfo(
base.sym(),
base.tyParams(),
returnType,
bindParameters(env, base.parameters()),
base.exceptions(),
base.access(),
base.defaultValue(),
base.decl(),
declarationAnnotations.build(),
base.receiver() != null ? bindParam(env, base.receiver()) : null);
}
private static ImmutableList<ParamInfo> bindParameters(
Env<ClassSymbol, TypeBoundClass> env, ImmutableList<ParamInfo> params) {
ImmutableList.Builder<ParamInfo> result = ImmutableList.builder();
for (ParamInfo param : params) {
result.add(bindParam(env, param));
}
return result.build();
}
private static ParamInfo bindParam(Env<ClassSymbol, TypeBoundClass> env, ParamInfo base) {
ImmutableList.Builder<AnnoInfo> declarationAnnotations = ImmutableList.builder();
Type type =
disambiguate(
env, ElementType.PARAMETER, base.type(), base.annotations(), declarationAnnotations);
return new ParamInfo(type, base.name(), declarationAnnotations.build(), base.access());
}
/**
* Moves type annotations in {@code annotations} to {@code type}, and adds any declaration
* annotations on {@code type} to {@code declarationAnnotations}.
*/
private static Type disambiguate(
Env<ClassSymbol, TypeBoundClass> env,
ElementType declarationTarget,
Type type,
ImmutableList<AnnoInfo> annotations,
Builder<AnnoInfo> declarationAnnotations) {
// desugar @Repeatable annotations before disambiguating: annotation containers may target
// a subset of the types targeted by their element annotation
annotations = groupRepeated(env, annotations);
ImmutableList.Builder<AnnoInfo> typeAnnotations = ImmutableList.builder();
for (AnnoInfo anno : annotations) {
Set<ElementType> target = env.get(anno.sym()).annotationMetadata().target();
if (target.contains(ElementType.TYPE_USE)) {
typeAnnotations.add(anno);
}
if (target.contains(declarationTarget)) {
declarationAnnotations.add(anno);
}
}
return addAnnotationsToType(type, typeAnnotations.build());
}
private static ImmutableList<FieldInfo> bindFields(
Env<ClassSymbol, TypeBoundClass> env, ImmutableList<FieldInfo> fields) {
ImmutableList.Builder<FieldInfo> result = ImmutableList.builder();
for (FieldInfo field : fields) {
result.add(bindField(env, field));
}
return result.build();
}
private static FieldInfo bindField(Env<ClassSymbol, TypeBoundClass> env, FieldInfo base) {
ImmutableList.Builder<AnnoInfo> declarationAnnotations = ImmutableList.builder();
Type type =
disambiguate(
env, ElementType.FIELD, base.type(), base.annotations(), declarationAnnotations);
return new FieldInfo(
base.sym(), type, base.access(), declarationAnnotations.build(), base.decl(), base.value());
}
/**
* Finds the left-most annotatable type in {@code type}, adds the {@code extra} type annotations
* to it, and removes any declaration annotations and saves them in {@code removed}.
*
* <p>The left-most type is e.g. the element type of an array, or the left-most type in a nested
* type declaration.
*
* <p>Note: the second case means that type annotation disambiguation has to occur on nested types
* before they are canonicalized.
*/
private static Type addAnnotationsToType(Type type, ImmutableList<AnnoInfo> extra) {
switch (type.tyKind()) {
case PRIM_TY:
PrimTy primTy = (PrimTy) type;
return new Type.PrimTy(primTy.primkind(), appendAnnotations(primTy.annos(), extra));
case CLASS_TY:
ClassTy classTy = (ClassTy) type;
SimpleClassTy base = classTy.classes.get(0);
SimpleClassTy simple =
new SimpleClassTy(base.sym(), base.targs(), appendAnnotations(base.annos(), extra));
return new Type.ClassTy(
ImmutableList.<SimpleClassTy>builder()
.add(simple)
.addAll(classTy.classes.subList(1, classTy.classes.size()))
.build());
case ARRAY_TY:
ArrayTy arrayTy = (ArrayTy) type;
return new ArrayTy(addAnnotationsToType(arrayTy.elementType(), extra), arrayTy.annos());
case TY_VAR:
TyVar tyVar = (TyVar) type;
return new Type.TyVar(tyVar.sym(), appendAnnotations(tyVar.annos(), extra));
case VOID_TY:
return type;
case WILD_TY:
throw new AssertionError("unexpected wildcard type outside type argument context");
default:
throw new AssertionError(type.tyKind());
}
}
private static ImmutableList<AnnoInfo> appendAnnotations(
ImmutableList<AnnoInfo> annos, ImmutableList<AnnoInfo> extra) {
return ImmutableList.<AnnoInfo>builder().addAll(annos).addAll(extra).build();
}
/**
* Group repeated annotations and wrap them in their container annotation.
*
* <p>For example, convert {@code @Foo @Foo} to {@code @Foos({@Foo, @Foo})}.
*
* <p>This method is used by {@link DisambiguateTypeAnnotations} for declaration annotations, and
* by {@link Lower} for type annotations. We could group type annotations here, but it would
* require another rewrite pass.
*/
public static ImmutableList<AnnoInfo> groupRepeated(
Env<ClassSymbol, TypeBoundClass> env, ImmutableList<AnnoInfo> annotations) {
Multimap<ClassSymbol, AnnoInfo> repeated = LinkedHashMultimap.create();
for (AnnoInfo anno : annotations) {
repeated.put(anno.sym(), anno);
}
Builder<AnnoInfo> result = ImmutableList.builder();
for (Map.Entry<ClassSymbol, Collection<AnnoInfo>> entry : repeated.asMap().entrySet()) {
ClassSymbol symbol = entry.getKey();
Collection<AnnoInfo> infos = entry.getValue();
if (infos.size() > 1) {
Builder<Const> elements = ImmutableList.builder();
for (AnnoInfo element : infos) {
elements.add(new AnnotationValue(element.sym(), element.values()));
}
ClassSymbol container = env.get(symbol).annotationMetadata().repeatable();
if (container == null) {
AnnoInfo anno = infos.iterator().next();
throw TurbineError.format(
anno.source(), anno.position(), ErrorKind.NONREPEATABLE_ANNOTATION, symbol);
}
result.add(
new AnnoInfo(
null,
container,
null,
ImmutableMap.of("value", new Const.ArrayInitValue(elements.build()))));
} else {
result.add(getOnlyElement(infos));
}
}
return result.build();
}
}