| /* |
| * 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.base.Preconditions.checkNotNull; |
| import static java.util.Objects.requireNonNull; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Iterables; |
| import com.google.turbine.binder.bound.EnumConstantValue; |
| import com.google.turbine.binder.bound.TurbineAnnotationValue; |
| import com.google.turbine.binder.bound.TurbineClassValue; |
| import com.google.turbine.binder.bound.TypeBoundClass; |
| import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; |
| import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; |
| import com.google.turbine.binder.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.MemberImportIndex; |
| import com.google.turbine.binder.lookup.Scope; |
| import com.google.turbine.binder.sym.ClassSymbol; |
| import com.google.turbine.binder.sym.FieldSymbol; |
| import com.google.turbine.binder.sym.Symbol; |
| import com.google.turbine.diag.SourceFile; |
| import com.google.turbine.diag.TurbineDiagnostic; |
| import com.google.turbine.diag.TurbineError; |
| import com.google.turbine.diag.TurbineError.ErrorKind; |
| import com.google.turbine.diag.TurbineLog.TurbineLogWithSource; |
| import com.google.turbine.model.Const; |
| import com.google.turbine.model.Const.ArrayInitValue; |
| import com.google.turbine.model.Const.CharValue; |
| import com.google.turbine.model.Const.ConstCastError; |
| import com.google.turbine.model.Const.DoubleValue; |
| import com.google.turbine.model.Const.FloatValue; |
| import com.google.turbine.model.Const.StringValue; |
| import com.google.turbine.model.Const.Value; |
| import com.google.turbine.model.TurbineConstantTypeKind; |
| import com.google.turbine.model.TurbineFlag; |
| import com.google.turbine.model.TurbineTyKind; |
| import com.google.turbine.tree.Tree; |
| import com.google.turbine.tree.Tree.ArrayInit; |
| import com.google.turbine.tree.Tree.Binary; |
| import com.google.turbine.tree.Tree.ClassLiteral; |
| import com.google.turbine.tree.Tree.ClassTy; |
| import com.google.turbine.tree.Tree.Conditional; |
| import com.google.turbine.tree.Tree.ConstVarName; |
| import com.google.turbine.tree.Tree.Expression; |
| import com.google.turbine.tree.Tree.Ident; |
| import com.google.turbine.tree.Tree.Paren; |
| import com.google.turbine.tree.Tree.PrimTy; |
| import com.google.turbine.tree.Tree.TypeCast; |
| import com.google.turbine.tree.Tree.Unary; |
| import com.google.turbine.tree.TurbineOperatorKind; |
| import com.google.turbine.type.AnnoInfo; |
| import com.google.turbine.type.Type; |
| import java.util.ArrayDeque; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import org.jspecify.nullness.Nullable; |
| |
| /** |
| * Constant expression evaluation. |
| * |
| * <p>JLS ยง15.4 requires this class to be strictfp. |
| */ |
| public strictfp class ConstEvaluator { |
| |
| /** The symbol of the originating class, for visibility checks. */ |
| private final @Nullable ClassSymbol origin; |
| |
| /** The symbol of the enclosing class, for lexical field lookups. */ |
| private final @Nullable ClassSymbol owner; |
| |
| /** Member imports of the enclosing compilation unit. */ |
| private final MemberImportIndex memberImports; |
| |
| /** The current source file. */ |
| private final SourceFile source; |
| |
| /** The constant variable environment. */ |
| private final Env<FieldSymbol, Value> values; |
| |
| /** The class environment. */ |
| private final CompoundEnv<ClassSymbol, TypeBoundClass> env; |
| |
| private final Scope scope; |
| |
| private final TurbineLogWithSource log; |
| |
| public ConstEvaluator( |
| @Nullable ClassSymbol origin, |
| @Nullable ClassSymbol owner, |
| MemberImportIndex memberImports, |
| SourceFile source, |
| Scope scope, |
| Env<FieldSymbol, Value> values, |
| CompoundEnv<ClassSymbol, TypeBoundClass> env, |
| TurbineLogWithSource log) { |
| |
| this.origin = origin; |
| this.owner = owner; |
| this.memberImports = memberImports; |
| this.source = source; |
| this.values = values; |
| this.env = env; |
| this.scope = scope; |
| this.log = log; |
| } |
| |
| /** Evaluates the given expression's value. */ |
| public @Nullable Const eval(Tree t) { |
| switch (t.kind()) { |
| case LITERAL: |
| { |
| Value a = (Value) ((Tree.Literal) t).value(); |
| if (a == null) { |
| return null; |
| } |
| switch (a.constantTypeKind()) { |
| case CHAR: |
| case INT: |
| case LONG: |
| case FLOAT: |
| case DOUBLE: |
| case BOOLEAN: |
| case STRING: |
| return a; |
| case SHORT: |
| case BYTE: |
| case NULL: |
| } |
| throw new AssertionError(a.constantTypeKind()); |
| } |
| case VOID_TY: |
| throw new AssertionError(t.kind()); |
| case CONST_VAR_NAME: |
| return evalConstVar((ConstVarName) t); |
| case CLASS_LITERAL: |
| return evalClassLiteral((ClassLiteral) t); |
| case BINARY: |
| return evalBinary((Binary) t); |
| case PAREN: |
| return eval(((Paren) t).expr()); |
| case TYPE_CAST: |
| return evalCast((TypeCast) t); |
| case UNARY: |
| return evalUnary((Unary) t); |
| case CONDITIONAL: |
| return evalConditional((Conditional) t); |
| case ARRAY_INIT: |
| return evalArrayInit((ArrayInit) t); |
| case ANNO_EXPR: |
| return evalAnno(((Tree.AnnoExpr) t).value()); |
| default: |
| throw error(t.position(), ErrorKind.EXPRESSION_ERROR); |
| } |
| } |
| |
| /** Evaluates a class literal. */ |
| Const evalClassLiteral(ClassLiteral t) { |
| return new TurbineClassValue(evalClassLiteralType(t.type())); |
| } |
| |
| private Type evalClassLiteralType(Tree.Type type) { |
| switch (type.kind()) { |
| case PRIM_TY: |
| return Type.PrimTy.create(((PrimTy) type).tykind(), ImmutableList.of()); |
| case VOID_TY: |
| return Type.VOID; |
| case CLASS_TY: |
| return resolveClass((ClassTy) type); |
| case ARR_TY: |
| return Type.ArrayTy.create( |
| evalClassLiteralType(((Tree.ArrTy) type).elem()), ImmutableList.of()); |
| default: |
| throw new AssertionError(type.kind()); |
| } |
| } |
| |
| /** |
| * Resolves the {@link ClassSymbol} for the given {@link Tree.ClassTy}, with handling for |
| * non-canonical qualified type names. |
| * |
| * <p>Similar to {@code HierarchyBinder#resolveClass}, except we can't unconditionally consider |
| * members of the current class (e.g. when binding constants inside annotations on that class), |
| * and when we do want to consider members we can rely on them being in the current scope (it |
| * isn't completed during the hierarchy phase). |
| */ |
| private Type resolveClass(ClassTy classTy) { |
| ArrayDeque<Ident> flat = new ArrayDeque<>(); |
| for (ClassTy curr = classTy; curr != null; curr = curr.base().orElse(null)) { |
| flat.addFirst(curr.name()); |
| } |
| LookupResult result = scope.lookup(new LookupKey(ImmutableList.copyOf(flat))); |
| if (result == null) { |
| log.error(classTy.position(), ErrorKind.CANNOT_RESOLVE, flat.getFirst()); |
| return Type.ErrorTy.create(flat); |
| } |
| if (result.sym().symKind() != Symbol.Kind.CLASS) { |
| throw error(classTy.position(), ErrorKind.UNEXPECTED_TYPE_PARAMETER, flat.getFirst()); |
| } |
| ClassSymbol classSym = (ClassSymbol) result.sym(); |
| for (Ident bit : result.remaining()) { |
| classSym = resolveNext(classTy.position(), classSym, bit); |
| } |
| return Type.ClassTy.asNonParametricClassTy(classSym); |
| } |
| |
| private ClassSymbol resolveNext(int position, ClassSymbol sym, Ident bit) { |
| ClassSymbol next = Resolve.resolve(env, origin, sym, bit); |
| if (next == null) { |
| throw error( |
| position, ErrorKind.SYMBOL_NOT_FOUND, new ClassSymbol(sym.binaryName() + '$' + bit)); |
| } |
| return next; |
| } |
| |
| /** Evaluates a reference to another constant variable. */ |
| @Nullable |
| Const evalConstVar(ConstVarName t) { |
| FieldInfo field = resolveField(t); |
| if (field == null) { |
| return null; |
| } |
| if ((field.access() & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM) { |
| return new EnumConstantValue(field.sym()); |
| } |
| if (field.value() != null) { |
| return field.value(); |
| } |
| return values.get(field.sym()); |
| } |
| |
| FieldInfo resolveField(ConstVarName t) { |
| Ident simpleName = t.name().get(0); |
| FieldInfo field = lexicalField(env, owner, simpleName); |
| if (field != null) { |
| return field; |
| } |
| field = resolveQualifiedField(t); |
| if (field != null) { |
| return field; |
| } |
| ClassSymbol classSymbol = memberImports.singleMemberImport(simpleName.value()); |
| if (classSymbol != null) { |
| field = Resolve.resolveField(env, origin, classSymbol, simpleName); |
| if (field != null) { |
| return field; |
| } |
| } |
| Iterator<ClassSymbol> it = memberImports.onDemandImports(); |
| while (it.hasNext()) { |
| field = Resolve.resolveField(env, origin, it.next(), simpleName); |
| if (field == null) { |
| continue; |
| } |
| // resolve handles visibility of inherited members; on-demand imports of private members are |
| // a special case |
| if ((field.access() & TurbineFlag.ACC_PRIVATE) == TurbineFlag.ACC_PRIVATE) { |
| continue; |
| } |
| return field; |
| } |
| throw error( |
| t.position(), |
| ErrorKind.CANNOT_RESOLVE, |
| String.format("field %s", Iterables.getLast(t.name()))); |
| } |
| |
| private @Nullable FieldInfo resolveQualifiedField(ConstVarName t) { |
| if (t.name().size() <= 1) { |
| return null; |
| } |
| LookupResult result = scope.lookup(new LookupKey(t.name())); |
| if (result == null) { |
| return null; |
| } |
| if (result.remaining().isEmpty()) { |
| // unexpectedly resolved qualified name to a type |
| return null; |
| } |
| ClassSymbol sym = (ClassSymbol) result.sym(); |
| for (int i = 0; i < result.remaining().size() - 1; i++) { |
| sym = Resolve.resolve(env, sym, sym, result.remaining().get(i)); |
| if (sym == null) { |
| return null; |
| } |
| } |
| return Resolve.resolveField(env, origin, sym, Iterables.getLast(result.remaining())); |
| } |
| |
| /** Search for constant variables in lexically enclosing scopes. */ |
| private @Nullable FieldInfo lexicalField( |
| Env<ClassSymbol, TypeBoundClass> env, @Nullable ClassSymbol sym, Ident name) { |
| while (sym != null) { |
| TypeBoundClass info = env.getNonNull(sym); |
| FieldInfo field = Resolve.resolveField(env, origin, sym, name); |
| if (field != null) { |
| return field; |
| } |
| sym = info.owner(); |
| } |
| return null; |
| } |
| |
| /** Casts the value to the given type. */ |
| private Const cast(int position, Type ty, Const value) { |
| checkNotNull(value); |
| switch (ty.tyKind()) { |
| case CLASS_TY: |
| case TY_VAR: |
| return value; |
| case PRIM_TY: |
| if (!value.kind().equals(Const.Kind.PRIMITIVE)) { |
| throw error(position, ErrorKind.EXPRESSION_ERROR); |
| } |
| return coerce(position, (Value) value, ((Type.PrimTy) ty).primkind()); |
| default: |
| throw new AssertionError(ty.tyKind()); |
| } |
| } |
| |
| /** Casts the constant value to the given type. */ |
| Value coerce(int position, Value value, TurbineConstantTypeKind kind) { |
| switch (kind) { |
| case BYTE: |
| return asByte(position, value); |
| case SHORT: |
| return asShort(position, value); |
| case INT: |
| return asInt(position, value); |
| case LONG: |
| return asLong(position, value); |
| case FLOAT: |
| return asFloat(position, value); |
| case DOUBLE: |
| return asDouble(position, value); |
| case CHAR: |
| return asChar(position, value); |
| case BOOLEAN: |
| case STRING: |
| case NULL: |
| if (!value.constantTypeKind().equals(kind)) { |
| throw typeError(position, value, kind); |
| } |
| return value; |
| } |
| throw new AssertionError(kind); |
| } |
| |
| private Const.BooleanValue asBoolean(int position, Value value) { |
| if (!value.constantTypeKind().equals(TurbineConstantTypeKind.BOOLEAN)) { |
| throw typeError(position, value, TurbineConstantTypeKind.BOOLEAN); |
| } |
| return (Const.BooleanValue) value; |
| } |
| |
| private Const.StringValue asString(int position, Value value) { |
| if (!value.constantTypeKind().equals(TurbineConstantTypeKind.STRING)) { |
| throw typeError(position, value, TurbineConstantTypeKind.STRING); |
| } |
| return (Const.StringValue) value; |
| } |
| |
| private Const.StringValue toString(int position, Value value) { |
| String result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = String.valueOf(((Const.CharValue) value).value()); |
| break; |
| case SHORT: |
| result = String.valueOf(((Const.ShortValue) value).value()); |
| break; |
| case INT: |
| result = String.valueOf(((Const.IntValue) value).value()); |
| break; |
| case LONG: |
| result = String.valueOf(((Const.LongValue) value).value()); |
| break; |
| case FLOAT: |
| result = String.valueOf(((Const.FloatValue) value).value()); |
| break; |
| case DOUBLE: |
| result = String.valueOf(((Const.DoubleValue) value).value()); |
| break; |
| case BOOLEAN: |
| result = String.valueOf(((Const.BooleanValue) value).value()); |
| break; |
| case BYTE: |
| result = String.valueOf(((Const.ByteValue) value).value()); |
| break; |
| case STRING: |
| return (StringValue) value; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.STRING); |
| } |
| return new Const.StringValue(result); |
| } |
| |
| private Const.CharValue asChar(int position, Value value) { |
| char result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| return (Const.CharValue) value; |
| case BYTE: |
| result = (char) ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| result = (char) ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| result = (char) ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| result = (char) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| result = (char) ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| result = (char) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.CHAR); |
| } |
| return new Const.CharValue(result); |
| } |
| |
| private Const.ByteValue asByte(int position, Value value) { |
| byte result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = (byte) ((Const.CharValue) value).value(); |
| break; |
| case BYTE: |
| return (Const.ByteValue) value; |
| case SHORT: |
| result = (byte) ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| result = (byte) ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| result = (byte) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| result = (byte) ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| result = (byte) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.BYTE); |
| } |
| return new Const.ByteValue(result); |
| } |
| |
| private Const.ShortValue asShort(int position, Value value) { |
| short result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = (short) ((Const.CharValue) value).value(); |
| break; |
| case BYTE: |
| result = ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| return (Const.ShortValue) value; |
| case INT: |
| result = (short) ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| result = (short) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| result = (short) ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| result = (short) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.SHORT); |
| } |
| return new Const.ShortValue(result); |
| } |
| |
| private Const.IntValue asInt(int position, Value value) { |
| int result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = ((CharValue) value).value(); |
| break; |
| case BYTE: |
| result = ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| result = ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| return (Const.IntValue) value; |
| case LONG: |
| result = (int) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| result = (int) ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| result = (int) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.INT); |
| } |
| return new Const.IntValue(result); |
| } |
| |
| private Const.LongValue asLong(int position, Value value) { |
| long result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = ((CharValue) value).value(); |
| break; |
| case BYTE: |
| result = ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| result = ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| result = ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| return (Const.LongValue) value; |
| case FLOAT: |
| result = (long) ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| result = (long) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.LONG); |
| } |
| return new Const.LongValue(result); |
| } |
| |
| private Const.FloatValue asFloat(int position, Value value) { |
| float result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = ((CharValue) value).value(); |
| break; |
| case BYTE: |
| result = ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| result = ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| result = (float) ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| result = (float) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| return (FloatValue) value; |
| case DOUBLE: |
| result = (float) ((Const.DoubleValue) value).value(); |
| break; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.FLOAT); |
| } |
| return new Const.FloatValue(result); |
| } |
| |
| private Const.DoubleValue asDouble(int position, Value value) { |
| double result; |
| switch (value.constantTypeKind()) { |
| case CHAR: |
| result = ((CharValue) value).value(); |
| break; |
| case BYTE: |
| result = ((Const.ByteValue) value).value(); |
| break; |
| case SHORT: |
| result = ((Const.ShortValue) value).value(); |
| break; |
| case INT: |
| result = ((Const.IntValue) value).value(); |
| break; |
| case LONG: |
| result = (double) ((Const.LongValue) value).value(); |
| break; |
| case FLOAT: |
| result = ((Const.FloatValue) value).value(); |
| break; |
| case DOUBLE: |
| return (DoubleValue) value; |
| default: |
| throw typeError(position, value, TurbineConstantTypeKind.DOUBLE); |
| } |
| return new Const.DoubleValue(result); |
| } |
| |
| private @Nullable Value evalValue(Expression tree) { |
| Const result = eval(tree); |
| // TODO(cushon): consider distinguishing between constant field and annotation values, |
| // and only allowing class literals / enum constants in the latter |
| return (result instanceof Value) ? (Value) result : null; |
| } |
| |
| private @Nullable Value evalConditional(Conditional t) { |
| Value condition = evalValue(t.cond()); |
| if (condition == null) { |
| return null; |
| } |
| return asBoolean(t.position(), condition).value() |
| ? evalValue(t.iftrue()) |
| : evalValue(t.iffalse()); |
| } |
| |
| private @Nullable Value evalUnary(Unary t) { |
| Value expr = evalValue(t.expr()); |
| if (expr == null) { |
| return null; |
| } |
| switch (t.op()) { |
| case NOT: |
| return unaryNegate(t.position(), expr); |
| case BITWISE_COMP: |
| return bitwiseComp(t.position(), expr); |
| case UNARY_PLUS: |
| return unaryPlus(t.position(), expr); |
| case NEG: |
| return unaryMinus(t.position(), expr); |
| default: |
| throw new AssertionError(t.op()); |
| } |
| } |
| |
| private @Nullable Value unaryNegate(int position, Value expr) { |
| switch (expr.constantTypeKind()) { |
| case BOOLEAN: |
| return new Const.BooleanValue(!asBoolean(position, expr).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value bitwiseComp(int position, Value expr) { |
| expr = promoteUnary(position, expr); |
| switch (expr.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue(~asInt(position, expr).value()); |
| case LONG: |
| return new Const.LongValue(~asLong(position, expr).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value unaryPlus(int position, Value expr) { |
| expr = promoteUnary(position, expr); |
| switch (expr.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue(+asInt(position, expr).value()); |
| case LONG: |
| return new Const.LongValue(+asLong(position, expr).value()); |
| case FLOAT: |
| return new Const.FloatValue(+asFloat(position, expr).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(+asDouble(position, expr).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value unaryMinus(int position, Value expr) { |
| expr = promoteUnary(position, expr); |
| switch (expr.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue(-asInt(position, expr).value()); |
| case LONG: |
| return new Const.LongValue(-asLong(position, expr).value()); |
| case FLOAT: |
| return new Const.FloatValue(-asFloat(position, expr).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(-asDouble(position, expr).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value evalCast(TypeCast t) { |
| Value expr = evalValue(t.expr()); |
| if (expr == null) { |
| return null; |
| } |
| switch (t.ty().kind()) { |
| case PRIM_TY: |
| return coerce(t.expr().position(), expr, ((Tree.PrimTy) t.ty()).tykind()); |
| case CLASS_TY: |
| { |
| ClassTy classTy = (ClassTy) t.ty(); |
| // TODO(cushon): check package? |
| if (!classTy.name().value().equals("String")) { |
| // Explicit boxing cases (e.g. `(Boolean) false`) are legal, but not const exprs. |
| return null; |
| } |
| return toString(t.expr().position(), expr); |
| } |
| default: |
| throw new AssertionError(t.ty().kind()); |
| } |
| } |
| |
| private @Nullable Value add(int position, Value a, Value b) { |
| if (a.constantTypeKind() == TurbineConstantTypeKind.STRING |
| || b.constantTypeKind() == TurbineConstantTypeKind.STRING) { |
| return new Const.StringValue(toString(position, a).value() + toString(position, b).value()); |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() + asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() + asLong(position, b).value()); |
| case FLOAT: |
| return new Const.FloatValue(asFloat(position, a).value() + asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(asDouble(position, a).value() + asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value subtract(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() - asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() - asLong(position, b).value()); |
| case FLOAT: |
| return new Const.FloatValue(asFloat(position, a).value() - asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(asDouble(position, a).value() - asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value mult(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() * asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() * asLong(position, b).value()); |
| case FLOAT: |
| return new Const.FloatValue(asFloat(position, a).value() * asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(asDouble(position, a).value() * asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value divide(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() / asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() / asLong(position, b).value()); |
| case FLOAT: |
| return new Const.FloatValue(asFloat(position, a).value() / asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(asDouble(position, a).value() / asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value mod(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() % asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() % asLong(position, b).value()); |
| case FLOAT: |
| return new Const.FloatValue(asFloat(position, a).value() % asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.DoubleValue(asDouble(position, a).value() % asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private static final int INT_SHIFT_MASK = 0b11111; |
| |
| private static final int LONG_SHIFT_MASK = 0b111111; |
| |
| private @Nullable Value shiftLeft(int position, Value a, Value b) { |
| a = promoteUnary(position, a); |
| b = promoteUnary(position, b); |
| switch (a.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue( |
| asInt(position, a).value() << (asInt(position, b).value() & INT_SHIFT_MASK)); |
| case LONG: |
| return new Const.LongValue( |
| asLong(position, a).value() << (asInt(position, b).value() & LONG_SHIFT_MASK)); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value shiftRight(int position, Value a, Value b) { |
| a = promoteUnary(position, a); |
| b = promoteUnary(position, b); |
| switch (a.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue( |
| asInt(position, a).value() >> (asInt(position, b).value() & INT_SHIFT_MASK)); |
| case LONG: |
| return new Const.LongValue( |
| asLong(position, a).value() >> (asInt(position, b).value() & LONG_SHIFT_MASK)); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value unsignedShiftRight(int position, Value a, Value b) { |
| a = promoteUnary(position, a); |
| b = promoteUnary(position, b); |
| switch (a.constantTypeKind()) { |
| case INT: |
| return new Const.IntValue( |
| asInt(position, a).value() >>> (asInt(position, b).value() & INT_SHIFT_MASK)); |
| case LONG: |
| return new Const.LongValue( |
| asLong(position, a).value() >>> (asInt(position, b).value() & LONG_SHIFT_MASK)); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); |
| } |
| } |
| |
| private @Nullable Value lessThan(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() < asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() < asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() < asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() < asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value lessThanEqual(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() <= asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() <= asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() <= asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() <= asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value greaterThan(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() > asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() > asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() > asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() > asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value greaterThanEqual(int position, Value a, Value b) { |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() >= asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() >= asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() >= asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() >= asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value equal(int position, Value a, Value b) { |
| switch (a.constantTypeKind()) { |
| case STRING: |
| return new Const.BooleanValue( |
| asString(position, a).value().equals(asString(position, b).value())); |
| case BOOLEAN: |
| return new Const.BooleanValue( |
| asBoolean(position, a).value() == asBoolean(position, b).value()); |
| default: |
| break; |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() == asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() == asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() == asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() == asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value notEqual(int position, Value a, Value b) { |
| switch (a.constantTypeKind()) { |
| case STRING: |
| return new Const.BooleanValue( |
| !asString(position, a).value().equals(asString(position, b).value())); |
| case BOOLEAN: |
| return new Const.BooleanValue( |
| asBoolean(position, a).value() != asBoolean(position, b).value()); |
| default: |
| break; |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.BooleanValue(asInt(position, a).value() != asInt(position, b).value()); |
| case LONG: |
| return new Const.BooleanValue(asLong(position, a).value() != asLong(position, b).value()); |
| case FLOAT: |
| return new Const.BooleanValue(asFloat(position, a).value() != asFloat(position, b).value()); |
| case DOUBLE: |
| return new Const.BooleanValue( |
| asDouble(position, a).value() != asDouble(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private Value bitwiseAnd(int position, Value a, Value b) { |
| switch (a.constantTypeKind()) { |
| case BOOLEAN: |
| return new Const.BooleanValue( |
| asBoolean(position, a).value() & asBoolean(position, b).value()); |
| default: |
| break; |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() & asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() & asLong(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private Value bitwiseOr(int position, Value a, Value b) { |
| switch (a.constantTypeKind()) { |
| case BOOLEAN: |
| return new Const.BooleanValue( |
| asBoolean(position, a).value() | asBoolean(position, b).value()); |
| default: |
| break; |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() | asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() | asLong(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value bitwiseXor(int position, Value a, Value b) { |
| switch (a.constantTypeKind()) { |
| case BOOLEAN: |
| return new Const.BooleanValue( |
| asBoolean(position, a).value() ^ asBoolean(position, b).value()); |
| default: |
| break; |
| } |
| TurbineConstantTypeKind type = promoteBinary(position, a, b); |
| a = coerce(position, a, type); |
| b = coerce(position, b, type); |
| switch (type) { |
| case INT: |
| return new Const.IntValue(asInt(position, a).value() ^ asInt(position, b).value()); |
| case LONG: |
| return new Const.LongValue(asLong(position, a).value() ^ asLong(position, b).value()); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, type); |
| } |
| } |
| |
| private @Nullable Value evalBinary(Binary t) { |
| Value result = null; |
| boolean first = true; |
| for (Expression child : t.children()) { |
| Value value = evalValue(child); |
| if (value == null) { |
| return null; |
| } |
| if (first) { |
| result = value; |
| } else { |
| result = evalBinary(child.position(), t.op(), requireNonNull(result), value); |
| } |
| first = false; |
| } |
| return result; |
| } |
| |
| private @Nullable Value evalBinary(int position, TurbineOperatorKind op, Value lhs, Value rhs) { |
| switch (op) { |
| case PLUS: |
| return add(position, lhs, rhs); |
| case MINUS: |
| return subtract(position, lhs, rhs); |
| case MULT: |
| return mult(position, lhs, rhs); |
| case DIVIDE: |
| return divide(position, lhs, rhs); |
| case MODULO: |
| return mod(position, lhs, rhs); |
| case SHIFT_LEFT: |
| return shiftLeft(position, lhs, rhs); |
| case SHIFT_RIGHT: |
| return shiftRight(position, lhs, rhs); |
| case UNSIGNED_SHIFT_RIGHT: |
| return unsignedShiftRight(position, lhs, rhs); |
| case LESS_THAN: |
| return lessThan(position, lhs, rhs); |
| case GREATER_THAN: |
| return greaterThan(position, lhs, rhs); |
| case LESS_THAN_EQ: |
| return lessThanEqual(position, lhs, rhs); |
| case GREATER_THAN_EQ: |
| return greaterThanEqual(position, lhs, rhs); |
| case EQUAL: |
| return equal(position, lhs, rhs); |
| case NOT_EQUAL: |
| return notEqual(position, lhs, rhs); |
| case AND: |
| return new Const.BooleanValue( |
| asBoolean(position, lhs).value() && asBoolean(position, rhs).value()); |
| case OR: |
| return new Const.BooleanValue( |
| asBoolean(position, lhs).value() || asBoolean(position, rhs).value()); |
| case BITWISE_AND: |
| return bitwiseAnd(position, lhs, rhs); |
| case BITWISE_XOR: |
| return bitwiseXor(position, lhs, rhs); |
| case BITWISE_OR: |
| return bitwiseOr(position, lhs, rhs); |
| default: |
| throw new AssertionError(op); |
| } |
| } |
| |
| private Value promoteUnary(int position, Value v) { |
| switch (v.constantTypeKind()) { |
| case CHAR: |
| case SHORT: |
| case BYTE: |
| return asInt(position, v); |
| case INT: |
| case LONG: |
| case FLOAT: |
| case DOUBLE: |
| return v; |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, v.constantTypeKind()); |
| } |
| } |
| |
| private TurbineConstantTypeKind promoteBinary(int position, Value a, Value b) { |
| a = promoteUnary(position, a); |
| b = promoteUnary(position, b); |
| switch (a.constantTypeKind()) { |
| case INT: |
| switch (b.constantTypeKind()) { |
| case INT: |
| case LONG: |
| case DOUBLE: |
| case FLOAT: |
| return b.constantTypeKind(); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); |
| } |
| case LONG: |
| switch (b.constantTypeKind()) { |
| case INT: |
| return TurbineConstantTypeKind.LONG; |
| case LONG: |
| case DOUBLE: |
| case FLOAT: |
| return b.constantTypeKind(); |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); |
| } |
| case FLOAT: |
| switch (b.constantTypeKind()) { |
| case INT: |
| case LONG: |
| case FLOAT: |
| return TurbineConstantTypeKind.FLOAT; |
| case DOUBLE: |
| return TurbineConstantTypeKind.DOUBLE; |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); |
| } |
| case DOUBLE: |
| switch (b.constantTypeKind()) { |
| case INT: |
| case LONG: |
| case FLOAT: |
| case DOUBLE: |
| return TurbineConstantTypeKind.DOUBLE; |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, b.constantTypeKind()); |
| } |
| default: |
| throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind()); |
| } |
| } |
| |
| ImmutableList<AnnoInfo> evaluateAnnotations(ImmutableList<AnnoInfo> annotations) { |
| ImmutableList.Builder<AnnoInfo> result = ImmutableList.builder(); |
| for (AnnoInfo annotation : annotations) { |
| result.add(evaluateAnnotation(annotation)); |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Evaluates annotation arguments given the symbol of the annotation declaration and a list of |
| * expression trees. |
| */ |
| AnnoInfo evaluateAnnotation(AnnoInfo info) { |
| // bail if annotation has not been resolved |
| if (info.sym() == null) { |
| return info; |
| } |
| TypeBoundClass annoClass = env.getNonNull(info.sym()); |
| if (annoClass.kind() != TurbineTyKind.ANNOTATION) { |
| // we've already reported an error for non-annotation symbols used as annotations, |
| // skip error handling for annotation arguments |
| return info; |
| } |
| Map<String, MethodInfo> template = new LinkedHashMap<>(); |
| if (annoClass != null) { |
| for (MethodInfo method : annoClass.methods()) { |
| template.put(method.name(), method); |
| } |
| } |
| |
| Map<String, Const> values = new LinkedHashMap<>(); |
| for (Expression arg : info.args()) { |
| Expression expr; |
| String key; |
| if (arg.kind() == Tree.Kind.ASSIGN) { |
| Tree.Assign assign = (Tree.Assign) arg; |
| key = assign.name().value(); |
| expr = assign.expr(); |
| } else { |
| if (info.args().size() != 1) { |
| throw error(arg.position(), ErrorKind.ANNOTATION_VALUE_NAME); |
| } |
| // expand the implicit 'value' name; `@Foo(42)` is sugar for `@Foo(value=42)` |
| key = "value"; |
| expr = arg; |
| } |
| MethodInfo methodInfo = template.remove(key); |
| if (methodInfo == null) { |
| log.error( |
| arg.position(), |
| ErrorKind.CANNOT_RESOLVE, |
| String.format("element %s() in %s", key, info.sym())); |
| continue; |
| } |
| Const value = evalAnnotationValue(expr, methodInfo.returnType()); |
| if (value == null) { |
| log.error(expr.position(), ErrorKind.EXPRESSION_ERROR); |
| continue; |
| } |
| Const existing = values.put(key, value); |
| if (existing != null) { |
| log.error(arg.position(), ErrorKind.INVALID_ANNOTATION_ARGUMENT); |
| continue; |
| } |
| } |
| for (MethodInfo methodInfo : template.values()) { |
| if (!methodInfo.hasDefaultValue()) { |
| throw error( |
| info.tree().position(), ErrorKind.MISSING_ANNOTATION_ARGUMENT, methodInfo.name()); |
| } |
| } |
| return info.withValues(ImmutableMap.copyOf(values)); |
| } |
| |
| private @Nullable TurbineAnnotationValue evalAnno(Tree.Anno t) { |
| LookupResult result = scope.lookup(new LookupKey(t.name())); |
| if (result == null) { |
| log.error( |
| t.name().get(0).position(), ErrorKind.CANNOT_RESOLVE, Joiner.on(".").join(t.name())); |
| return null; |
| } |
| ClassSymbol sym = (ClassSymbol) result.sym(); |
| for (Ident name : result.remaining()) { |
| sym = Resolve.resolve(env, sym, sym, name); |
| if (sym == null) { |
| throw error(name.position(), ErrorKind.CANNOT_RESOLVE, name.value()); |
| } |
| } |
| if (sym == null) { |
| return null; |
| } |
| if (env.getNonNull(sym).kind() != TurbineTyKind.ANNOTATION) { |
| log.error(t.position(), ErrorKind.NOT_AN_ANNOTATION, sym); |
| } |
| AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, ImmutableMap.of())); |
| return new TurbineAnnotationValue(annoInfo); |
| } |
| |
| private @Nullable ArrayInitValue evalArrayInit(ArrayInit t) { |
| ImmutableList.Builder<Const> elements = ImmutableList.builder(); |
| for (Expression e : t.exprs()) { |
| Const arg = eval(e); |
| if (arg == null) { |
| return null; |
| } |
| elements.add(arg); |
| } |
| return new Const.ArrayInitValue(elements.build()); |
| } |
| |
| @Nullable |
| Const evalAnnotationValue(Tree tree, Type ty) { |
| if (ty == null) { |
| throw error(tree.position(), ErrorKind.EXPRESSION_ERROR); |
| } |
| Const value = eval(tree); |
| if (value == null) { |
| log.error(tree.position(), ErrorKind.EXPRESSION_ERROR); |
| return null; |
| } |
| switch (ty.tyKind()) { |
| case PRIM_TY: |
| if (!(value instanceof Value)) { |
| throw error(tree.position(), ErrorKind.EXPRESSION_ERROR); |
| } |
| return coerce(tree.position(), (Value) value, ((Type.PrimTy) ty).primkind()); |
| case CLASS_TY: |
| case TY_VAR: |
| return value; |
| case ARRAY_TY: |
| { |
| Type elementType = ((Type.ArrayTy) ty).elementType(); |
| ImmutableList<Const> elements = |
| value.kind() == Const.Kind.ARRAY |
| ? ((Const.ArrayInitValue) value).elements() |
| : ImmutableList.of(value); |
| ImmutableList.Builder<Const> coerced = ImmutableList.builder(); |
| for (Const element : elements) { |
| coerced.add(cast(tree.position(), elementType, element)); |
| } |
| return new Const.ArrayInitValue(coerced.build()); |
| } |
| default: |
| throw new AssertionError(ty.tyKind()); |
| } |
| } |
| |
| private TurbineError error(int position, ErrorKind kind, Object... args) { |
| return TurbineError.format(source, position, kind, args); |
| } |
| |
| private TurbineError typeError(int position, Value value, TurbineConstantTypeKind kind) { |
| return error(position, ErrorKind.TYPE_CONVERSION, value, value.constantTypeKind(), kind); |
| } |
| |
| public @Nullable Value evalFieldInitializer(Expression expression, Type type) { |
| try { |
| Const value = eval(expression); |
| if (value == null || value.kind() != Const.Kind.PRIMITIVE) { |
| return null; |
| } |
| return (Value) cast(expression.position(), type, value); |
| } catch (TurbineError error) { |
| for (TurbineDiagnostic diagnostic : error.diagnostics()) { |
| switch (diagnostic.kind()) { |
| case CANNOT_RESOLVE: |
| // assume this wasn't a constant |
| return null; |
| default: // fall out |
| } |
| } |
| throw error; |
| } catch (ConstCastError error) { |
| return null; |
| } |
| } |
| } |