blob: f3607356fb11f1eebf1e8e3a9bfe18ec43ee5787 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart2js.constants.evaluation;
import 'package:front_end/src/api_unstable/dart2js.dart' show Link;
import '../common.dart';
import '../common_elements.dart' show CommonElements;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../universe/call_structure.dart' show CallStructure;
import 'constructors.dart';
import 'expressions.dart';
import 'values.dart';
/// Environment used for evaluating constant expressions.
abstract class EvaluationEnvironment {
CommonElements get commonElements;
DartTypes get types;
/// Type in the enclosing constructed
InterfaceType get enclosingConstructedType;
/// Read environments string passed in using the '-Dname=value' option.
String readFromEnvironment(String name);
/// Returns the [ConstantExpression] for the value of the constant [local].
ConstantExpression getLocalConstant(covariant Local local);
/// Returns the [ConstantExpression] for the value of the constant [field].
ConstantExpression getFieldConstant(covariant FieldEntity field);
/// Returns the [ConstantConstructor] corresponding to the constant
/// [constructor].
ConstantConstructor getConstructorConstant(
covariant ConstructorEntity constructor);
/// Performs the substitution of the type arguments of [target] for their
/// corresponding type variables in [type].
DartType substByContext(
covariant DartType base, covariant InterfaceType target);
/// Returns [type] in the context of the [enclosingConstructedType].
DartType getTypeInContext(DartType type);
void reportWarning(
ConstantExpression expression, MessageKind kind, Map arguments);
void reportError(
ConstantExpression expression, MessageKind kind, Map arguments);
ConstantValue evaluateConstructor(ConstructorEntity constructor,
InterfaceType type, ConstantValue evaluate());
ConstantValue evaluateMapBody(ConstantValue evaluate());
ConstantValue evaluateField(FieldEntity field, ConstantValue evaluate());
/// `true` if assertions are enabled.
bool get enableAssertions;
/// If `true`, implicit casts should be checked.
///
/// This is used to avoid circular dependencies between js-interop classes
/// and their metadata. For non-metadata constants we always check the casts.
bool get checkCasts;
}
abstract class EvaluationEnvironmentBase implements EvaluationEnvironment {
Link<Spannable> _spannableStack = const Link<Spannable>();
@override
InterfaceType enclosingConstructedType;
final Set<FieldEntity> _currentlyEvaluatedFields = new Set<FieldEntity>();
final bool constantRequired;
EvaluationEnvironmentBase(Spannable spannable, {this.constantRequired}) {
_spannableStack = _spannableStack.prepend(spannable);
}
@override
bool get checkCasts => true;
DiagnosticReporter get reporter;
/// Returns the [Spannable] used for reporting errors and warnings.
///
/// Returns the second-to-last in the spannable stack, if available, to point
/// to the use, rather than the declaration, of a constructor or field.
///
/// For instance
///
/// const foo = const bool.fromEnvironment("foo", default: 0);
///
/// will point to `foo` instead of the declaration of `bool.fromEnvironment`.
Spannable get _spannable => _spannableStack.tail.isEmpty
? _spannableStack.head
: _spannableStack.tail.head;
@override
ConstantValue evaluateField(FieldEntity field, ConstantValue evaluate()) {
if (_currentlyEvaluatedFields.add(field)) {
_spannableStack = _spannableStack.prepend(field);
ConstantValue result = evaluate();
_currentlyEvaluatedFields.remove(field);
_spannableStack = _spannableStack.tail;
return result;
}
if (constantRequired) {
reporter.reportErrorMessage(
field, MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS);
}
return new NonConstantValue();
}
@override
ConstantValue evaluateConstructor(ConstructorEntity constructor,
InterfaceType type, ConstantValue evaluate()) {
_spannableStack = _spannableStack.prepend(constructor);
var old = enclosingConstructedType;
enclosingConstructedType = type;
ConstantValue result = evaluate();
// All const set literals have as an immediate child a const map. The map
// evaluate method calls evaluateMapBody and reset this flag immediately.
// Because there are no other children, the flag is kept false.
enclosingConstructedType = old;
_spannableStack = _spannableStack.tail;
return result;
}
@override
ConstantValue evaluateMapBody(ConstantValue evaluate()) {
return evaluate();
}
@override
void reportError(
ConstantExpression expression, MessageKind kind, Map arguments) {
if (constantRequired) {
// TODO(johnniwinther): Should [ConstantExpression] have a location?
reporter.reportErrorMessage(_spannable, kind, arguments);
}
}
@override
void reportWarning(
ConstantExpression expression, MessageKind kind, Map arguments) {
if (constantRequired) {
reporter.reportWarningMessage(_spannable, kind, arguments);
}
}
@override
DartType getTypeInContext(DartType type) {
// Running example for comments:
//
// class A<T> {
// final T t;
// const A(dynamic t) : this.t = t; // implicitly `t as A.T`
// }
// class B<S> extends A<S> {
// const B(dynamic s) : super(s);
// }
// main() => const B<num>(0);
//
// We visit `t as A.T` while evaluating `const B<num>(0)`.
// The `as` type is `A.T`.
DartType typeInContext = type;
// The enclosing type is `B<num>`.
DartType enclosingType = enclosingConstructedType;
if (enclosingType != null) {
ClassEntity contextClass;
type.forEachTypeVariable((TypeVariableType type) {
if (type.element.typeDeclaration is ClassEntity) {
// We find `A` from `A.T`. Since we don't have nested classes, class
// based type variables can only come from the same class.
contextClass = type.element.typeDeclaration;
}
});
if (contextClass != null) {
// The enclosing type `B<num>` as an instance of `A` is `A<num>`.
enclosingType = types.asInstanceOf(enclosingType, contextClass);
}
// `A.T` in the context of the enclosing type `A<num>` is `num`.
typeInContext = enclosingType != null
? substByContext(typeInContext, enclosingType)
: typeInContext;
}
return typeInContext;
}
}
/// The normalized arguments passed to a const constructor computed from the
/// actual [arguments] and the [defaultValues] of the called construrctor.
class NormalizedArguments {
final Map<dynamic /*int|String*/, ConstantExpression> defaultValues;
final CallStructure callStructure;
final List<ConstantExpression> arguments;
NormalizedArguments(this.defaultValues, this.callStructure, this.arguments);
/// Returns the normalized named argument [name].
ConstantExpression getNamedArgument(String name) {
int index = callStructure.namedArguments.indexOf(name);
if (index == -1) {
// The named argument is not provided.
assert(
defaultValues[name] != null,
failedAt(CURRENT_ELEMENT_SPANNABLE,
"No default value for named argument '$name' in $this."));
return defaultValues[name];
}
ConstantExpression value =
arguments[index + callStructure.positionalArgumentCount];
assert(
value != null,
failedAt(CURRENT_ELEMENT_SPANNABLE,
"No value for named argument '$name' in $this."));
return value;
}
/// Returns the normalized [index]th positional argument.
ConstantExpression getPositionalArgument(int index) {
if (index >= callStructure.positionalArgumentCount) {
// The positional argument is not provided.
assert(
defaultValues[index] != null,
failedAt(CURRENT_ELEMENT_SPANNABLE,
"No default value for positional argument $index in $this."));
return defaultValues[index];
}
ConstantExpression value = arguments[index];
assert(
value != null,
failedAt(CURRENT_ELEMENT_SPANNABLE,
"No value for positional argument $index in $this."));
return value;
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('NormalizedArguments[');
sb.write('defaultValues={');
bool needsComma = false;
defaultValues.forEach((var key, ConstantExpression value) {
if (needsComma) {
sb.write(',');
}
if (key is String) {
sb.write('"');
sb.write(key);
sb.write('"');
} else {
sb.write(key);
}
sb.write(':');
sb.write(value.toStructuredText());
needsComma = true;
});
sb.write('},callStructure=');
sb.write(callStructure);
sb.write(',arguments=[');
arguments.forEach((ConstantExpression value) {
if (needsComma) {
sb.write(',');
}
sb.write(value.toStructuredText());
needsComma = true;
});
sb.write(']]');
return sb.toString();
}
}