blob: 2bb02617a814c094926003bb8b5a2a3416e6a454 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:kernel/ast.dart' as ir;
import '../common.dart';
import '../common/names.dart';
import '../constants/constructors.dart';
import '../constants/expressions.dart';
import '../constants/values.dart';
import '../common_elements.dart';
import '../elements/entities.dart';
import '../elements/names.dart';
import '../elements/operators.dart';
import '../elements/types.dart';
import '../js_backend/backend.dart' show JavaScriptBackend;
import '../native/native.dart' as native;
import '../universe/call_structure.dart';
import '../universe/selector.dart';
import 'kernel_debug.dart';
/// Interface that translates between Kernel IR nodes and entities.
abstract class KernelToElementMap {
/// Access to the commonly used elements and types.
CommonElements get commonElements;
/// [ElementEnvironment] for library, class and member lookup.
ElementEnvironment get elementEnvironment;
/// Returns the [DartType] corresponding to [type].
DartType getDartType(ir.DartType type);
/// Returns the list of [DartType]s corresponding to [types].
List<DartType> getDartTypes(List<ir.DartType> types);
/// Returns the [InterfaceType] corresponding to [type].
InterfaceType getInterfaceType(ir.InterfaceType type);
/// Return the [InterfaceType] corresponding to the [cls] with the given
/// [typeArguments].
InterfaceType createInterfaceType(
ir.Class cls, List<ir.DartType> typeArguments);
/// Returns the [CallStructure] corresponding to the [arguments].
CallStructure getCallStructure(ir.Arguments arguments);
/// Returns the [Selector] corresponding to the invocation or getter/setter
/// access of [node].
Selector getSelector(ir.Expression node);
/// Returns the [ConstructorEntity] corresponding to the generative or factory
/// constructor [node].
ConstructorEntity getConstructor(ir.Member node);
/// Returns the [ConstructorEntity] corresponding to a super initializer in
/// [constructor].
///
/// The IR resolves super initializers to a [target] up in the type hierarchy.
/// Most of the time, the result of this function will be the entity
/// corresponding to that target. In the presence of unnamed mixins, this
/// function returns an entity for an intermediate synthetic constructor that
/// kernel doesn't explicitly represent.
///
/// For example:
/// class M {}
/// class C extends Object with M {}
///
/// Kernel will say that C()'s super initializer resolves to Object(), but
/// this function will return an entity representing the unnamed mixin
/// application "Object+M"'s constructor.
ConstructorEntity getSuperConstructor(
ir.Constructor constructor, ir.Member target);
/// Returns the [MemberEntity] corresponding to the member [node].
MemberEntity getMember(ir.Member node);
/// Returns the [FunctionEntity] corresponding to the procedure [node].
FunctionEntity getMethod(ir.Procedure node);
/// Returns the [FieldEntity] corresponding to the field [node].
FieldEntity getField(ir.Field node);
/// Returns the [ClassEntity] corresponding to the class [node].
ClassEntity getClass(ir.Class node);
/// Returns the [Local] corresponding to the [node]. The node must be either
/// a [ir.FunctionDeclaration] or [ir.FunctionExpression].
Local getLocalFunction(ir.TreeNode node);
/// Returns the [LibraryEntity] corresponding to the library [node].
LibraryEntity getLibrary(ir.Library node);
/// Returns the [Name] corresponding to [name].
Name getName(ir.Name name);
/// Returns `true` is [node] has a `@Native(...)` annotation.
bool isNativeClass(ir.Class node);
/// Return `true` if [node] is the `dart:_foreign_helper` library.
bool isForeignLibrary(ir.Library node);
/// Computes the native behavior for reading the native [field].
native.NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field,
{bool isJsInterop});
/// Computes the native behavior for writing to the native [field].
native.NativeBehavior getNativeBehaviorForFieldStore(ir.Field field);
/// Computes the native behavior for calling [procedure].
native.NativeBehavior getNativeBehaviorForMethod(ir.Procedure procedure,
{bool isJsInterop});
/// Computes the [native.NativeBehavior] for a call to the [JS] function.
native.NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node);
/// Computes the [native.NativeBehavior] for a call to the [JS_BUILTIN]
/// function.
native.NativeBehavior getNativeBehaviorForJsBuiltinCall(
ir.StaticInvocation node);
/// Computes the [native.NativeBehavior] for a call to the
/// [JS_EMBEDDED_GLOBAL] function.
native.NativeBehavior getNativeBehaviorForJsEmbeddedGlobalCall(
ir.StaticInvocation node);
/// Compute the kind of foreign helper function called by [node], if any.
ForeignKind getForeignKind(ir.StaticInvocation node);
/// Computes the [InterfaceType] referenced by a call to the
/// [JS_INTERCEPTOR_CONSTANT] function, if any.
InterfaceType getInterfaceTypeForJsInterceptorCall(ir.StaticInvocation node);
/// Computes the [ConstantValue] for the constant [expression].
ConstantValue getConstantValue(ir.Expression expression);
}
/// Kinds of foreign functions.
enum ForeignKind {
JS,
JS_BUILTIN,
JS_EMBEDDED_GLOBAL,
JS_INTERCEPTOR_CONSTANT,
NONE,
}
abstract class KernelToElementMapMixin implements KernelToElementMap {
DiagnosticReporter get reporter;
FunctionType getFunctionType(ir.FunctionNode node);
native.BehaviorBuilder get nativeBehaviorBuilder;
ConstantValue computeConstantValue(ConstantExpression constant);
@override
Name getName(ir.Name name) {
return new Name(
name.name, name.isPrivate ? getLibrary(name.library) : null);
}
@override
CallStructure getCallStructure(ir.Arguments arguments) {
int argumentCount = arguments.positional.length + arguments.named.length;
List<String> namedArguments = arguments.named.map((e) => e.name).toList();
return new CallStructure(argumentCount, namedArguments);
}
@override
Selector getSelector(ir.Expression node) {
// TODO(efortuna): This is screaming for a common interface between
// PropertyGet and SuperPropertyGet (and same for *Get). Talk to kernel
// folks.
if (node is ir.PropertyGet) {
return getGetterSelector(node.name);
}
if (node is ir.SuperPropertyGet) {
return getGetterSelector(node.name);
}
if (node is ir.PropertySet) {
return getSetterSelector(node.name);
}
if (node is ir.SuperPropertySet) {
return getSetterSelector(node.name);
}
if (node is ir.InvocationExpression) {
return getInvocationSelector(node);
}
throw new SpannableAssertionFailure(
CURRENT_ELEMENT_SPANNABLE,
"Can only get the selector for a property get or an invocation: "
"${node}");
}
Selector getInvocationSelector(ir.InvocationExpression invocation) {
Name name = getName(invocation.name);
SelectorKind kind;
if (Selector.isOperatorName(name.text)) {
if (name == Names.INDEX_NAME || name == Names.INDEX_SET_NAME) {
kind = SelectorKind.INDEX;
} else {
kind = SelectorKind.OPERATOR;
}
} else {
kind = SelectorKind.CALL;
}
CallStructure callStructure = getCallStructure(invocation.arguments);
return new Selector(kind, name, callStructure);
}
Selector getGetterSelector(ir.Name irName) {
Name name = new Name(
irName.name, irName.isPrivate ? getLibrary(irName.library) : null);
return new Selector.getter(name);
}
Selector getSetterSelector(ir.Name irName) {
Name name = new Name(
irName.name, irName.isPrivate ? getLibrary(irName.library) : null);
return new Selector.setter(name);
}
ConstantValue getConstantValue(ir.Expression node) {
ConstantExpression constant = new Constantifier(this).visit(node);
if (constant == null) {
throw new UnsupportedError(
'No constant for ${DebugPrinter.prettyPrint(node)}');
}
return computeConstantValue(constant);
}
/// Converts [annotations] into a list of [ConstantValue]s.
List<ConstantValue> getMetadata(List<ir.Expression> annotations) {
if (annotations.isEmpty) return const <ConstantValue>[];
List<ConstantValue> metadata = <ConstantValue>[];
annotations.forEach((ir.Expression node) {
metadata.add(getConstantValue(node));
});
return metadata;
}
/// Returns `true` is [node] has a `@Native(...)` annotation.
// TODO(johnniwinther): Cache this for later use.
bool isNativeClass(ir.Class node) {
for (ir.Expression annotation in node.annotations) {
if (annotation is ir.ConstructorInvocation) {
FunctionEntity target = getConstructor(annotation.target);
if (target.enclosingClass == commonElements.nativeAnnotationClass) {
return true;
}
}
}
return false;
}
/// Compute the kind of foreign helper function called by [node], if any.
ForeignKind getForeignKind(ir.StaticInvocation node) {
if (isForeignLibrary(node.target.enclosingLibrary)) {
switch (node.target.name.name) {
case JavaScriptBackend.JS:
return ForeignKind.JS;
case JavaScriptBackend.JS_BUILTIN:
return ForeignKind.JS_BUILTIN;
case JavaScriptBackend.JS_EMBEDDED_GLOBAL:
return ForeignKind.JS_EMBEDDED_GLOBAL;
case JavaScriptBackend.JS_INTERCEPTOR_CONSTANT:
return ForeignKind.JS_INTERCEPTOR_CONSTANT;
}
}
return ForeignKind.NONE;
}
/// Return `true` if [node] is the `dart:_foreign_helper` library.
bool isForeignLibrary(ir.Library node) {
return node.importUri == Uris.dart__foreign_helper;
}
/// Looks up [typeName] for use in the spec-string of a `JS` called.
// TODO(johnniwinther): Use this in [native.NativeBehavior] instead of calling
// the `ForeignResolver`.
// TODO(johnniwinther): Cache the result to avoid redundant lookups?
native.TypeLookup typeLookup({bool resolveAsRaw: true}) {
DartType lookup(String typeName, {bool required}) {
DartType findIn(Uri uri) {
LibraryEntity library = elementEnvironment.lookupLibrary(uri);
if (library != null) {
ClassEntity cls = elementEnvironment.lookupClass(library, typeName);
if (cls != null) {
// TODO(johnniwinther): Align semantics.
return resolveAsRaw
? elementEnvironment.getRawType(cls)
: elementEnvironment.getThisType(cls);
}
}
return null;
}
// TODO(johnniwinther): Narrow the set of lookups base on the depending
// library.
DartType type = findIn(Uris.dart_core);
type ??= findIn(Uris.dart__js_helper);
type ??= findIn(Uris.dart__interceptors);
type ??= findIn(Uris.dart__isolate_helper);
type ??= findIn(Uris.dart__native_typed_data);
type ??= findIn(Uris.dart_collection);
type ??= findIn(Uris.dart_math);
type ??= findIn(Uris.dart_html);
type ??= findIn(Uris.dart_html_common);
type ??= findIn(Uris.dart_svg);
type ??= findIn(Uris.dart_web_audio);
type ??= findIn(Uris.dart_web_gl);
type ??= findIn(Uris.dart_web_sql);
type ??= findIn(Uris.dart_indexed_db);
type ??= findIn(Uris.dart_typed_data);
if (type == null && required) {
reporter.reportErrorMessage(CURRENT_ELEMENT_SPANNABLE,
MessageKind.GENERIC, {'text': "Type '$typeName' not found."});
}
return type;
}
return lookup;
}
String _getStringArgument(ir.StaticInvocation node, int index) {
return node.arguments.positional[index].accept(new Stringifier());
}
/// Computes the [native.NativeBehavior] for a call to the [JS] function.
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node) {
if (node.arguments.positional.length < 2 ||
node.arguments.named.isNotEmpty) {
reporter.reportErrorMessage(
CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS);
return new native.NativeBehavior();
}
String specString = _getStringArgument(node, 0);
if (specString == null) {
reporter.reportErrorMessage(
CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS_FIRST);
return new native.NativeBehavior();
}
String codeString = _getStringArgument(node, 1);
if (codeString == null) {
reporter.reportErrorMessage(
CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS_SECOND);
return new native.NativeBehavior();
}
return native.NativeBehavior.ofJsCall(
specString,
codeString,
typeLookup(resolveAsRaw: true),
CURRENT_ELEMENT_SPANNABLE,
reporter,
commonElements);
}
/// Computes the [native.NativeBehavior] for a call to the [JS_BUILTIN]
/// function.
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForJsBuiltinCall(
ir.StaticInvocation node) {
if (node.arguments.positional.length < 1) {
reporter.internalError(
CURRENT_ELEMENT_SPANNABLE, "JS builtin expression has no type.");
return new native.NativeBehavior();
}
if (node.arguments.positional.length < 2) {
reporter.internalError(
CURRENT_ELEMENT_SPANNABLE, "JS builtin is missing name.");
return new native.NativeBehavior();
}
String specString = _getStringArgument(node, 0);
if (specString == null) {
reporter.internalError(
CURRENT_ELEMENT_SPANNABLE, "Unexpected first argument.");
return new native.NativeBehavior();
}
return native.NativeBehavior.ofJsBuiltinCall(
specString,
typeLookup(resolveAsRaw: true),
CURRENT_ELEMENT_SPANNABLE,
reporter,
commonElements);
}
/// Computes the [native.NativeBehavior] for a call to the
/// [JS_EMBEDDED_GLOBAL] function.
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForJsEmbeddedGlobalCall(
ir.StaticInvocation node) {
if (node.arguments.positional.length < 1) {
reporter.internalError(CURRENT_ELEMENT_SPANNABLE,
"JS embedded global expression has no type.");
return new native.NativeBehavior();
}
if (node.arguments.positional.length < 2) {
reporter.internalError(
CURRENT_ELEMENT_SPANNABLE, "JS embedded global is missing name.");
return new native.NativeBehavior();
}
if (node.arguments.positional.length > 2 ||
node.arguments.named.isNotEmpty) {
reporter.internalError(CURRENT_ELEMENT_SPANNABLE,
"JS embedded global has more than 2 arguments.");
return new native.NativeBehavior();
}
String specString = _getStringArgument(node, 0);
if (specString == null) {
reporter.internalError(
CURRENT_ELEMENT_SPANNABLE, "Unexpected first argument.");
return new native.NativeBehavior();
}
return native.NativeBehavior.ofJsEmbeddedGlobalCall(
specString,
typeLookup(resolveAsRaw: true),
CURRENT_ELEMENT_SPANNABLE,
reporter,
commonElements);
}
/// Computes the [InterfaceType] referenced by a call to the
/// [JS_INTERCEPTOR_CONSTANT] function, if any.
InterfaceType getInterfaceTypeForJsInterceptorCall(ir.StaticInvocation node) {
if (node.arguments.positional.length != 1 ||
node.arguments.named.isNotEmpty) {
reporter.reportErrorMessage(CURRENT_ELEMENT_SPANNABLE,
MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT);
}
ir.Node argument = node.arguments.positional.first;
if (argument is ir.TypeLiteral && argument.type is ir.InterfaceType) {
return getInterfaceType(argument.type);
}
return null;
}
/// Computes the native behavior for reading the native [field].
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field,
{bool isJsInterop}) {
DartType type = getDartType(field.type);
List<ConstantValue> metadata = getMetadata(field.annotations);
return nativeBehaviorBuilder.buildFieldLoadBehavior(
type, metadata, typeLookup(resolveAsRaw: false),
isJsInterop: isJsInterop);
}
/// Computes the native behavior for writing to the native [field].
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForFieldStore(ir.Field field) {
DartType type = getDartType(field.type);
return nativeBehaviorBuilder.buildFieldStoreBehavior(type);
}
/// Computes the native behavior for calling [procedure].
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForMethod(ir.Procedure procedure,
{bool isJsInterop}) {
DartType type = getFunctionType(procedure.function);
List<ConstantValue> metadata = getMetadata(procedure.annotations);
return nativeBehaviorBuilder.buildMethodBehavior(
type, metadata, typeLookup(resolveAsRaw: false),
isJsInterop: isJsInterop);
}
}
/// Visitor that converts string literals and concatenations of string literals
/// into the string value.
class Stringifier extends ir.ExpressionVisitor<String> {
@override
String visitStringLiteral(ir.StringLiteral node) => node.value;
@override
String visitStringConcatenation(ir.StringConcatenation node) {
StringBuffer sb = new StringBuffer();
for (ir.Expression expression in node.expressions) {
String value = expression.accept(this);
if (value == null) return null;
sb.write(value);
}
return sb.toString();
}
}
/// Visitor that converts a kernel constant expression into a
/// [ConstantExpression].
class Constantifier extends ir.ExpressionVisitor<ConstantExpression> {
final bool requireConstant;
final KernelToElementMapMixin elementAdapter;
Constantifier(this.elementAdapter, {this.requireConstant: true});
CommonElements get _commonElements => elementAdapter.commonElements;
ConstantExpression visit(ir.Expression node) {
ConstantExpression constant = node.accept(this);
if (constant == null && requireConstant) {
throw new UnsupportedError(
"No constant computed for $node (${node.runtimeType})");
}
return constant;
}
ConstantExpression defaultExpression(ir.Expression node) {
throw new UnimplementedError(
'Unimplemented constant expression $node (${node.runtimeType})');
}
List<ConstantExpression> _computeList(List<ir.Expression> expressions) {
List<ConstantExpression> list = <ConstantExpression>[];
for (ir.Expression expression in expressions) {
ConstantExpression constant = visit(expression);
if (constant == null) return null;
list.add(constant);
}
return list;
}
List<ConstantExpression> _computeArguments(ir.Arguments node) {
List<ConstantExpression> arguments = <ConstantExpression>[];
for (ir.Expression argument in node.positional) {
ConstantExpression constant = visit(argument);
if (constant == null) return null;
arguments.add(constant);
}
for (ir.NamedExpression argument in node.named) {
ConstantExpression constant = visit(argument.value);
if (constant == null) return null;
arguments.add(constant);
}
return arguments;
}
ConstructedConstantExpression _computeConstructorInvocation(
ir.Constructor target, ir.Arguments arguments) {
return new ConstructedConstantExpression(
elementAdapter.createInterfaceType(
target.enclosingClass, arguments.types),
elementAdapter.getConstructor(target),
elementAdapter.getCallStructure(arguments),
_computeArguments(arguments));
}
@override
ConstantExpression visitConstructorInvocation(ir.ConstructorInvocation node) {
return _computeConstructorInvocation(node.target, node.arguments);
}
@override
ConstantExpression visitVariableGet(ir.VariableGet node) {
if (node.variable.parent is ir.FunctionNode) {
ir.FunctionNode function = node.variable.parent;
int index = function.positionalParameters.indexOf(node.variable);
if (index != -1) {
return new PositionalArgumentReference(index);
} else {
assert(function.namedParameters.contains(node.variable));
return new NamedArgumentReference(node.variable.name);
}
}
throw new UnimplementedError(
'Unimplemented constant expression $node (${node.runtimeType})');
}
@override
ConstantExpression visitStaticGet(ir.StaticGet node) {
if (node.target is ir.Field) {
return new FieldConstantExpression(elementAdapter.getField(node.target));
} else if (node.target is ir.Procedure) {
FunctionEntity function = elementAdapter.getMethod(node.target);
DartType type = elementAdapter.getFunctionType(node.target.function);
return new FunctionConstantExpression(function, type);
}
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
@override
ConstantExpression visitNullLiteral(ir.NullLiteral node) {
return new NullConstantExpression();
}
@override
ConstantExpression visitBoolLiteral(ir.BoolLiteral node) {
return new BoolConstantExpression(node.value);
}
@override
ConstantExpression visitIntLiteral(ir.IntLiteral node) {
return new IntConstantExpression(node.value);
}
@override
ConstantExpression visitDoubleLiteral(ir.DoubleLiteral node) {
return new DoubleConstantExpression(node.value);
}
@override
ConstantExpression visitStringLiteral(ir.StringLiteral node) {
return new StringConstantExpression(node.value);
}
@override
ConstantExpression visitSymbolLiteral(ir.SymbolLiteral node) {
return new SymbolConstantExpression(node.value);
}
@override
ConstantExpression visitStringConcatenation(ir.StringConcatenation node) {
return new ConcatenateConstantExpression(_computeList(node.expressions));
}
@override
ConstantExpression visitMapLiteral(ir.MapLiteral node) {
if (!node.isConst) {
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
DartType keyType = elementAdapter.getDartType(node.keyType);
DartType valueType = elementAdapter.getDartType(node.valueType);
List<ConstantExpression> keys = <ConstantExpression>[];
List<ConstantExpression> values = <ConstantExpression>[];
for (ir.MapEntry entry in node.entries) {
keys.add(visit(entry.key));
values.add(visit(entry.value));
}
return new MapConstantExpression(
_commonElements.mapType(keyType, valueType), keys, values);
}
@override
ConstantExpression visitListLiteral(ir.ListLiteral node) {
if (!node.isConst) {
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
DartType elementType = elementAdapter.getDartType(node.typeArgument);
List<ConstantExpression> values = <ConstantExpression>[];
for (ir.Expression value in node.expressions) {
values.add(visit(value));
}
return new ListConstantExpression(
_commonElements.listType(elementType), values);
}
@override
ConstantExpression visitConditionalExpression(ir.ConditionalExpression node) {
ConstantExpression condition = visit(node.condition);
ConstantExpression trueExp = visit(node.then);
ConstantExpression falseExp = visit(node.otherwise);
return new ConditionalConstantExpression(condition, trueExp, falseExp);
}
@override
ConstantExpression visitPropertyGet(ir.PropertyGet node) {
if (node.name.name != 'length') {
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
ConstantExpression receiver = visit(node.receiver);
return new StringLengthConstantExpression(receiver);
}
@override
ConstantExpression visitMethodInvocation(ir.MethodInvocation node) {
// Method invocations are generally not constant expressions but unary
// and binary expressions are encoded as method invocations in kernel.
if (node.arguments.named.isNotEmpty) {
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
if (node.arguments.positional.length == 0) {
UnaryOperator operator;
if (node.name.name == UnaryOperator.NEGATE.selectorName) {
operator = UnaryOperator.NEGATE;
} else {
operator = UnaryOperator.parse(node.name.name);
}
if (operator != null) {
ConstantExpression expression = visit(node.receiver);
return new UnaryConstantExpression(operator, expression);
}
}
if (node.arguments.positional.length == 1) {
BinaryOperator operator = BinaryOperator.parse(node.name.name);
if (operator != null) {
ConstantExpression left = visit(node.receiver);
ConstantExpression right = visit(node.arguments.positional.single);
return new BinaryConstantExpression(left, operator, right);
}
}
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
@override
ConstantExpression visitStaticInvocation(ir.StaticInvocation node) {
MemberEntity member = elementAdapter.getMember(node.target);
if (member == _commonElements.identicalFunction) {
if (node.arguments.positional.length == 2 &&
node.arguments.named.isEmpty) {
ConstantExpression left = visit(node.arguments.positional[0]);
ConstantExpression right = visit(node.arguments.positional[1]);
return new IdenticalConstantExpression(left, right);
}
} else if (member.name == 'fromEnvironment' &&
node.arguments.positional.length == 1) {
ConstantExpression name = visit(node.arguments.positional.single);
ConstantExpression defaultValue;
if (node.arguments.named.length == 1) {
if (node.arguments.named.single.name != 'defaultValue') {
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
defaultValue = visit(node.arguments.named.single.value);
}
if (member.enclosingClass == _commonElements.boolClass) {
return new BoolFromEnvironmentConstantExpression(name, defaultValue);
} else if (member.enclosingClass == _commonElements.intClass) {
return new IntFromEnvironmentConstantExpression(name, defaultValue);
} else if (member.enclosingClass == _commonElements.stringClass) {
return new StringFromEnvironmentConstantExpression(name, defaultValue);
}
}
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
@override
ConstantExpression visitLogicalExpression(ir.LogicalExpression node) {
BinaryOperator operator = BinaryOperator.parse(node.operator);
if (operator != null) {
ConstantExpression left = visit(node.left);
ConstantExpression right = visit(node.right);
return new BinaryConstantExpression(left, operator, right);
}
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
@override
ConstantExpression visitLet(ir.Let node) {
ir.Expression body = node.body;
if (body is ir.ConditionalExpression) {
ir.Expression condition = body.condition;
if (condition is ir.MethodInvocation) {
ir.Expression receiver = condition.receiver;
ir.Expression otherwise = body.otherwise;
if (condition.name.name == BinaryOperator.EQ.name &&
receiver is ir.VariableGet &&
condition.arguments.positional.single is ir.NullLiteral &&
otherwise is ir.VariableGet) {
if (receiver.variable == node.variable &&
otherwise.variable == node.variable) {
// We have <left> ?? <right> encoded as:
// let #1 = <left> in #1 == null ? <right> : #1
ConstantExpression left = visit(node.variable.initializer);
ConstantExpression right = visit(body.then);
// TODO(johnniwinther): Remove [IF_NULL] binary constant expression
// when the resolver is removed; then we no longer need the
// expressions to be structurally equivalence for equivalence
// testing.
return new BinaryConstantExpression(
left, BinaryOperator.IF_NULL, right);
}
}
}
}
throw new UnimplementedError(
'Unexpected constant expression $node (${node.runtimeType})');
}
/// Compute the [ConstantConstructor] corresponding to the const constructor
/// [node].
ConstantConstructor computeConstantConstructor(ir.Constructor node) {
assert(node.isConst);
ir.Class cls = node.enclosingClass;
InterfaceType type = elementAdapter.elementEnvironment
.getThisType(elementAdapter.getClass(cls));
Map<dynamic, ConstantExpression> defaultValues =
<dynamic, ConstantExpression>{};
int parameterIndex = 0;
node.function.positionalParameters
.forEach((ir.VariableDeclaration parameter) {
if (parameterIndex >= node.function.requiredParameterCount) {
if (parameter.initializer != null) {
defaultValues[parameterIndex] = parameter.initializer.accept(this);
} else {
defaultValues[parameterIndex] = new NullConstantExpression();
}
}
parameterIndex++;
});
node.function.namedParameters.forEach((ir.VariableDeclaration parameter) {
defaultValues[parameter.name] = parameter.initializer.accept(this);
});
bool isRedirecting = node.initializers.length == 1 &&
node.initializers.single is ir.RedirectingInitializer;
Map<FieldEntity, ConstantExpression> fieldMap =
<FieldEntity, ConstantExpression>{};
void registerField(ir.Field field, ConstantExpression constant) {
fieldMap[elementAdapter.getField(field)] = constant;
}
if (!isRedirecting) {
for (ir.Field field in cls.fields) {
if (field.isStatic) continue;
if (field.initializer != null) {
registerField(field, field.initializer.accept(this));
}
}
}
ConstructedConstantExpression superConstructorInvocation;
for (ir.Initializer initializer in node.initializers) {
if (initializer is ir.FieldInitializer) {
registerField(initializer.field, initializer.value.accept(this));
} else if (initializer is ir.SuperInitializer) {
superConstructorInvocation = _computeConstructorInvocation(
initializer.target, initializer.arguments);
} else if (initializer is ir.RedirectingInitializer) {
superConstructorInvocation = _computeConstructorInvocation(
initializer.target, initializer.arguments);
} else {
throw new UnsupportedError(
'Unexpected initializer $node (${node.runtimeType})');
}
}
if (isRedirecting) {
return new RedirectingGenerativeConstantConstructor(
defaultValues, superConstructorInvocation);
} else {
return new GenerativeConstantConstructor(
type, defaultValues, fieldMap, superConstructorInvocation);
}
}
}