blob: b2c4e1c93712cae4aefbcb3f8bb7189297a689db [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 java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.turbine.binder.bound.AnnotationMetadata;
import com.google.turbine.binder.bound.EnumConstantValue;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TurbineClassValue;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.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.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.diag.TurbineLog.TurbineLogWithSource;
import com.google.turbine.model.Const;
import com.google.turbine.model.Const.ArrayInitValue;
import com.google.turbine.model.Const.Kind;
import com.google.turbine.model.Const.Value;
import com.google.turbine.model.TurbineElementType;
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.ClassTy.SimpleClassTy;
import com.google.turbine.type.Type.IntersectionTy;
import com.google.turbine.type.Type.TyKind;
import com.google.turbine.type.Type.TyVar;
import com.google.turbine.type.Type.WildLowerBoundedTy;
import com.google.turbine.type.Type.WildTy;
import com.google.turbine.type.Type.WildUnboundedTy;
import com.google.turbine.type.Type.WildUpperBoundedTy;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import org.jspecify.nullness.Nullable;
/** Binding pass to evaluate constant expressions. */
public class ConstBinder {
private final Env<FieldSymbol, Value> constantEnv;
private final ClassSymbol origin;
private final SourceTypeBoundClass base;
private final CompoundEnv<ClassSymbol, TypeBoundClass> env;
private final ConstEvaluator constEvaluator;
private final TurbineLogWithSource log;
public ConstBinder(
Env<FieldSymbol, Value> constantEnv,
ClassSymbol origin,
CompoundEnv<ClassSymbol, TypeBoundClass> env,
SourceTypeBoundClass base,
TurbineLogWithSource log) {
this.constantEnv = constantEnv;
this.origin = origin;
this.base = base;
this.env = env;
this.log = log;
this.constEvaluator =
new ConstEvaluator(
origin,
origin,
base.memberImports(),
base.source(),
base.scope(),
constantEnv,
env,
log);
}
public SourceTypeBoundClass bind() {
ImmutableList<AnnoInfo> annos =
new ConstEvaluator(
origin,
base.owner(),
base.memberImports(),
base.source(),
base.enclosingScope(),
constantEnv,
env,
log)
.evaluateAnnotations(base.annotations());
ImmutableList<TypeBoundClass.ParamInfo> components = bindParameters(base.components());
ImmutableList<TypeBoundClass.FieldInfo> fields = fields(base.fields());
ImmutableList<MethodInfo> methods = bindMethods(base.methods());
return new SourceTypeBoundClass(
bindTypes(base.interfaceTypes()),
base.superClassType() != null ? bindType(base.superClassType()) : null,
bindTypeParameters(base.typeParameterTypes()),
base.access(),
components,
methods,
fields,
base.owner(),
base.kind(),
base.children(),
base.typeParameters(),
base.enclosingScope(),
base.scope(),
base.memberImports(),
bindAnnotationMetadata(base.kind(), annos),
annos,
base.source(),
base.decl());
}
private ImmutableList<MethodInfo> bindMethods(ImmutableList<MethodInfo> methods) {
ImmutableList.Builder<MethodInfo> result = ImmutableList.builder();
for (MethodInfo f : methods) {
result.add(bindMethod(f));
}
return result.build();
}
private MethodInfo bindMethod(MethodInfo base) {
Const value = null;
if (base.decl() != null && base.decl().defaultValue().isPresent()) {
value =
constEvaluator.evalAnnotationValue(base.decl().defaultValue().get(), base.returnType());
}
return new MethodInfo(
base.sym(),
bindTypeParameters(base.tyParams()),
bindType(base.returnType()),
bindParameters(base.parameters()),
bindTypes(base.exceptions()),
base.access(),
value,
base.decl(),
constEvaluator.evaluateAnnotations(base.annotations()),
base.receiver() != null ? bindParameter(base.receiver()) : null);
}
private ImmutableList<ParamInfo> bindParameters(ImmutableList<ParamInfo> formals) {
ImmutableList.Builder<ParamInfo> result = ImmutableList.builder();
for (ParamInfo base : formals) {
result.add(bindParameter(base));
}
return result.build();
}
private ParamInfo bindParameter(ParamInfo base) {
ImmutableList<AnnoInfo> annos = constEvaluator.evaluateAnnotations(base.annotations());
return new ParamInfo(base.sym(), bindType(base.type()), annos, base.access());
}
static @Nullable AnnotationMetadata bindAnnotationMetadata(
TurbineTyKind kind, Iterable<AnnoInfo> annotations) {
if (kind != TurbineTyKind.ANNOTATION) {
return null;
}
RetentionPolicy retention = null;
ImmutableSet<TurbineElementType> target = null;
ClassSymbol repeatable = null;
for (AnnoInfo annotation : annotations) {
ClassSymbol sym = annotation.sym();
if (sym == null) {
continue;
}
switch (sym.binaryName()) {
case "java/lang/annotation/Retention":
retention = bindRetention(annotation);
break;
case "java/lang/annotation/Target":
target = bindTarget(annotation);
break;
case "java/lang/annotation/Repeatable":
repeatable = bindRepeatable(annotation);
break;
default:
break;
}
}
return new AnnotationMetadata(retention, target, repeatable);
}
private static @Nullable RetentionPolicy bindRetention(AnnoInfo annotation) {
Const value = annotation.values().get("value");
if (value == null) {
return null;
}
if (value.kind() != Kind.ENUM_CONSTANT) {
return null;
}
EnumConstantValue enumValue = (EnumConstantValue) value;
if (!enumValue.sym().owner().binaryName().equals("java/lang/annotation/RetentionPolicy")) {
return null;
}
return RetentionPolicy.valueOf(enumValue.sym().name());
}
private static ImmutableSet<TurbineElementType> bindTarget(AnnoInfo annotation) {
ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder();
// requireNonNull is safe because java.lang.annotation.Target declares `value`.
Const val = requireNonNull(annotation.values().get("value"));
switch (val.kind()) {
case ARRAY:
for (Const element : ((ArrayInitValue) val).elements()) {
if (element.kind() == Kind.ENUM_CONSTANT) {
bindTargetElement(result, (EnumConstantValue) element);
}
}
break;
case ENUM_CONSTANT:
bindTargetElement(result, (EnumConstantValue) val);
break;
default:
break;
}
return result.build();
}
private static @Nullable ClassSymbol bindRepeatable(AnnoInfo annotation) {
// requireNonNull is safe because java.lang.annotation.Repeatable declares `value`.
Const value = requireNonNull(annotation.values().get("value"));
if (value.kind() != Kind.CLASS_LITERAL) {
return null;
}
Type type = ((TurbineClassValue) value).type();
if (type.tyKind() != TyKind.CLASS_TY) {
return null;
}
return ((ClassTy) type).sym();
}
private static void bindTargetElement(
ImmutableSet.Builder<TurbineElementType> target, EnumConstantValue enumVal) {
if (enumVal.sym().owner().binaryName().equals("java/lang/annotation/ElementType")) {
target.add(TurbineElementType.valueOf(enumVal.sym().name()));
}
}
private ImmutableList<TypeBoundClass.FieldInfo> fields(ImmutableList<FieldInfo> fields) {
ImmutableList.Builder<TypeBoundClass.FieldInfo> result = ImmutableList.builder();
for (TypeBoundClass.FieldInfo base : fields) {
Value value = fieldValue(base);
result.add(
new TypeBoundClass.FieldInfo(
base.sym(),
bindType(base.type()),
base.access(),
constEvaluator.evaluateAnnotations(base.annotations()),
base.decl(),
value));
}
return result.build();
}
private @Nullable Value fieldValue(TypeBoundClass.FieldInfo base) {
if (base.decl() == null || !base.decl().init().isPresent()) {
return null;
}
if ((base.access() & TurbineFlag.ACC_FINAL) == 0) {
return null;
}
Type type = base.type();
switch (type.tyKind()) {
case PRIM_TY:
break;
case CLASS_TY:
if (((Type.ClassTy) type).sym().equals(ClassSymbol.STRING)) {
break;
}
// falls through
default:
return null;
}
Value value = constantEnv.get(base.sym());
if (value == null) {
return null;
}
if (type.tyKind().equals(TyKind.PRIM_TY)) {
value =
constEvaluator.coerce(
base.decl().init().get().position(), value, ((Type.PrimTy) type).primkind());
}
return value;
}
private ImmutableList<Type> bindTypes(ImmutableList<Type> types) {
ImmutableList.Builder<Type> result = ImmutableList.builder();
for (Type t : types) {
result.add(bindType(t));
}
return result.build();
}
private ImmutableMap<TyVarSymbol, TyVarInfo> bindTypeParameters(
ImmutableMap<TyVarSymbol, TyVarInfo> typarams) {
ImmutableMap.Builder<TyVarSymbol, TyVarInfo> result = ImmutableMap.builder();
for (Map.Entry<TyVarSymbol, TyVarInfo> entry : typarams.entrySet()) {
TyVarInfo info = entry.getValue();
result.put(
entry.getKey(),
new TyVarInfo(
(IntersectionTy) bindType(info.upperBound()),
/* lowerBound= */ null,
constEvaluator.evaluateAnnotations(info.annotations())));
}
return result.build();
}
private Type bindType(Type type) {
switch (type.tyKind()) {
case TY_VAR:
TyVar tyVar = (TyVar) type;
return TyVar.create(tyVar.sym(), constEvaluator.evaluateAnnotations(tyVar.annos()));
case CLASS_TY:
return bindClassType((ClassTy) type);
case ARRAY_TY:
ArrayTy arrayTy = (ArrayTy) type;
return ArrayTy.create(
bindType(arrayTy.elementType()), constEvaluator.evaluateAnnotations(arrayTy.annos()));
case WILD_TY:
{
WildTy wildTy = (WildTy) type;
switch (wildTy.boundKind()) {
case NONE:
return WildUnboundedTy.create(
constEvaluator.evaluateAnnotations(wildTy.annotations()));
case UPPER:
return WildUpperBoundedTy.create(
bindType(wildTy.bound()),
constEvaluator.evaluateAnnotations(wildTy.annotations()));
case LOWER:
return WildLowerBoundedTy.create(
bindType(wildTy.bound()),
constEvaluator.evaluateAnnotations(wildTy.annotations()));
}
throw new AssertionError(wildTy.boundKind());
}
case PRIM_TY:
case VOID_TY:
case ERROR_TY:
return type;
case INTERSECTION_TY:
return IntersectionTy.create(bindTypes(((IntersectionTy) type).bounds()));
default:
throw new AssertionError(type.tyKind());
}
}
private ClassTy bindClassType(ClassTy type) {
ClassTy classTy = type;
ImmutableList.Builder<SimpleClassTy> classes = ImmutableList.builder();
for (SimpleClassTy c : classTy.classes()) {
classes.add(
SimpleClassTy.create(
c.sym(), bindTypes(c.targs()), constEvaluator.evaluateAnnotations(c.annos())));
}
return ClassTy.create(classes.build());
}
}