blob: 6a44dc64dfcbac5dad03b80adfcdf16ba4f6b103 [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:js_runtime/shared/embedded_names.dart';
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/js.dart' as js;
import '../js_backend/backend.dart' show JavaScriptBackend;
import '../js_backend/namer.dart';
import '../js_emitter/code_emitter_task.dart';
import '../native/native.dart' as native;
import '../universe/call_structure.dart';
import '../universe/selector.dart';
import 'element_map.dart';
import 'kernel_debug.dart';
abstract class KernelToElementMapBaseMixin implements KernelToElementMap {
DiagnosticReporter get reporter;
ElementEnvironment get elementEnvironment;
LibraryEntity getLibrary(ir.Library node);
ConstantValue computeConstantValue(
Spannable spannable, ConstantExpression constant,
{bool requireConstant: true});
@override
Name getName(ir.Name name) {
return new Name(
name.name, name.isPrivate ? getLibrary(name.library) : null);
}
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 failedAt(
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);
}
/// 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` call.
// TODO(johnniwinther): Use this in [native.NativeBehavior] instead of calling
// the `ForeignResolver`.
native.TypeLookup typeLookup({bool resolveAsRaw: true}) {
return resolveAsRaw
? (_cachedTypeLookupRaw ??= _typeLookup(resolveAsRaw: true))
: (_cachedTypeLookupFull ??= _typeLookup(resolveAsRaw: false));
}
native.TypeLookup _cachedTypeLookupRaw;
native.TypeLookup _cachedTypeLookupFull;
native.TypeLookup _typeLookup({bool resolveAsRaw: true}) {
bool cachedMayLookupInMain;
bool mayLookupInMain() {
var mainUri = elementEnvironment.mainLibrary.canonicalUri;
// Tests permit lookup outside of dart: libraries.
return mainUri.path.contains('sdk/tests/compiler/dart2js_native') ||
mainUri.path.contains('sdk/tests/compiler/dart2js_extra');
}
DartType lookup(String typeName, {bool required}) {
DartType findInLibrary(LibraryEntity library) {
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;
}
DartType findIn(Uri uri) {
return findInLibrary(elementEnvironment.lookupLibrary(uri));
}
// TODO(johnniwinther): Narrow the set of lookups based on the depending
// library.
// TODO(johnniwinther): Cache more results to avoid redundant lookups?
DartType type;
if (cachedMayLookupInMain ??= mayLookupInMain()) {
type ??= findInLibrary(elementEnvironment.mainLibrary);
}
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);
type ??= findIn(Uris.dart_mirrors);
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);
}
js.Name getNameForJsGetName(ConstantValue constant, Namer namer) {
int index = _extractEnumIndexFromConstantValue(
constant, commonElements.jsGetNameEnum);
if (index == null) return null;
return namer.getNameForJsGetName(
CURRENT_ELEMENT_SPANNABLE, JsGetName.values[index]);
}
int _extractEnumIndexFromConstantValue(
ConstantValue constant, ClassEntity classElement) {
if (constant is ConstructedConstantValue) {
if (constant.type.element == classElement) {
assert(constant.fields.length == 1 || constant.fields.length == 2);
ConstantValue indexConstant = constant.fields.values.first;
if (indexConstant is IntConstantValue) {
return indexConstant.primitiveValue;
}
}
}
return null;
}
ConstantValue getConstantValue(ir.Expression node,
{bool requireConstant: true, bool implicitNull: false}) {
ConstantExpression constant;
if (node == null) {
if (!implicitNull) {
throw failedAt(
CURRENT_ELEMENT_SPANNABLE, 'No expression for constant.');
}
constant = new NullConstantExpression();
} else {
constant =
new Constantifier(this, requireConstant: requireConstant).visit(node);
}
if (constant == null) {
if (requireConstant) {
throw new UnsupportedError(
'No constant for ${DebugPrinter.prettyPrint(node)}');
}
return null;
}
ConstantValue value = computeConstantValue(
computeSourceSpanFromTreeNode(node), constant,
requireConstant: requireConstant);
if (!value.isConstant && !requireConstant) {
return null;
}
return value;
}
/// 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;
}
FunctionEntity getSuperNoSuchMethod(ClassEntity cls) {
while (cls != null) {
cls = elementEnvironment.getSuperClass(cls);
MemberEntity member =
elementEnvironment.lookupClassMember(cls, Identifiers.noSuchMethod_);
if (member != null) {
if (member.isFunction) {
FunctionEntity function = member;
if (function.parameterStructure.positionalParameters >= 1) {
return function;
}
}
// If [member] is not a valid `noSuchMethod` the target is
// `Object.superNoSuchMethod`.
break;
}
}
FunctionEntity function = elementEnvironment.lookupClassMember(
commonElements.objectClass, Identifiers.noSuchMethod_);
assert(function != null,
failedAt(cls, "No super noSuchMethod found for class $cls."));
return function;
}
}
abstract class KernelToElementMapForImpactMixin
implements KernelToElementMapForImpact, KernelToElementMapBaseMixin {
DiagnosticReporter get reporter;
native.BehaviorBuilder get nativeBehaviorBuilder;
/// 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;
}
/// 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 [member].
// TODO(johnniwinther): Cache this for later use.
native.NativeBehavior getNativeBehaviorForMethod(ir.Member member,
{bool isJsInterop}) {
DartType type;
if (member is ir.Procedure) {
type = getFunctionType(member.function);
} else if (member is ir.Constructor) {
type = getFunctionType(member.function);
} else {
failedAt(CURRENT_ELEMENT_SPANNABLE, "Unexpected method node $member.");
}
List<ConstantValue> metadata = getMetadata(member.annotations);
return nativeBehaviorBuilder.buildMethodBehavior(
type, metadata, typeLookup(resolveAsRaw: false),
isJsInterop: isJsInterop);
}
}
abstract class KernelToElementMapForBuildingMixin
implements KernelToElementMapForBuilding, KernelToElementMapBaseMixin {
js.Template getJsBuiltinTemplate(
ConstantValue constant, CodeEmitterTask emitter) {
int index = _extractEnumIndexFromConstantValue(
constant, commonElements.jsBuiltinEnum);
if (index == null) return null;
return emitter.builtinTemplateFor(JsBuiltin.values[index]);
}
}
/// 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 KernelToElementMapBaseMixin elementMap;
ir.TreeNode failNode;
Constantifier(this.elementMap, {this.requireConstant: true});
CommonElements get _commonElements => elementMap.commonElements;
ConstantExpression visit(ir.Expression node) {
ConstantExpression constant = node.accept(this);
if (constant == null && requireConstant) {
elementMap.reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(failNode ?? node),
MessageKind.NOT_A_COMPILE_TIME_CONSTANT);
return new ErroneousConstantExpression();
}
return constant;
}
ConstantExpression defaultExpression(ir.Expression node) {
if (requireConstant) {
failNode ??= node;
}
return null;
}
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) {
List<ConstantExpression> expressions = _computeArguments(arguments);
if (expressions == null) return null;
return new ConstructedConstantExpression(
elementMap.createInterfaceType(target.enclosingClass, arguments.types),
elementMap.getConstructor(target),
elementMap.getCallStructure(arguments),
expressions);
}
@override
ConstantExpression visitConstructorInvocation(ir.ConstructorInvocation node) {
if (!node.isConst) return null;
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);
}
} else if (node.variable.isConst) {
return visit(node.variable.initializer);
}
return defaultExpression(node);
}
@override
ConstantExpression visitStaticGet(ir.StaticGet node) {
ir.Member target = node.target;
if (target is ir.Field && target.isConst) {
return new FieldConstantExpression(elementMap.getField(node.target));
} else if (node.target is ir.Procedure) {
FunctionEntity function = elementMap.getMethod(node.target);
DartType type = elementMap.getFunctionType(node.target.function);
return new FunctionConstantExpression(function, type);
}
return defaultExpression(node);
}
@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) {
List<ConstantExpression> expressions = _computeList(node.expressions);
if (expressions == null) return null;
return new ConcatenateConstantExpression(expressions);
}
@override
ConstantExpression visitMapLiteral(ir.MapLiteral node) {
if (!node.isConst) {
return defaultExpression(node);
}
DartType keyType = elementMap.getDartType(node.keyType);
DartType valueType = elementMap.getDartType(node.valueType);
List<ConstantExpression> keys = <ConstantExpression>[];
List<ConstantExpression> values = <ConstantExpression>[];
for (ir.MapEntry entry in node.entries) {
ConstantExpression key = visit(entry.key);
if (key == null) return null;
keys.add(key);
ConstantExpression value = visit(entry.value);
if (value == null) return null;
values.add(value);
}
return new MapConstantExpression(
_commonElements.mapType(keyType, valueType), keys, values);
}
@override
ConstantExpression visitListLiteral(ir.ListLiteral node) {
if (!node.isConst) {
return defaultExpression(node);
}
DartType elementType = elementMap.getDartType(node.typeArgument);
List<ConstantExpression> values = <ConstantExpression>[];
for (ir.Expression expression in node.expressions) {
ConstantExpression value = visit(expression);
if (value == null) return null;
values.add(value);
}
return new ListConstantExpression(
_commonElements.listType(elementType), values);
}
@override
ConstantExpression visitTypeLiteral(ir.TypeLiteral node) {
String name;
DartType type = elementMap.getDartType(node.type);
if (type.isDynamic) {
name = 'dynamic';
} else if (type is InterfaceType) {
name = type.element.name;
} else if (type.isTypedef) {
// TODO(johnniwinther): Compute a name for the type literal? It is only
// used in error messages in the old SSA builder.
name = '?';
} else if (node.type is ir.FunctionType) {
ir.FunctionType functionType = node.type;
if (functionType.typedef != null) {
type = elementMap.getTypedefType(functionType.typedef);
name = functionType.typedef.name;
} else {
// TODO(johnniwinther): Remove branch when [KernelAstAdapter] is
// removed.
name = '?';
}
} else {
return defaultExpression(node);
}
return new TypeConstantExpression(type, name);
}
@override
ConstantExpression visitNot(ir.Not node) {
ConstantExpression expression = visit(node.operand);
if (expression == null) return null;
return new UnaryConstantExpression(UnaryOperator.NOT, expression);
}
@override
ConstantExpression visitConditionalExpression(ir.ConditionalExpression node) {
ConstantExpression condition = visit(node.condition);
if (condition == null) return null;
ConstantExpression trueExp = visit(node.then);
if (trueExp == null) return null;
ConstantExpression falseExp = visit(node.otherwise);
if (falseExp == null) return null;
return new ConditionalConstantExpression(condition, trueExp, falseExp);
}
@override
ConstantExpression visitPropertyGet(ir.PropertyGet node) {
if (node.name.name != 'length') {
failNode ??= node;
return null;
}
ConstantExpression receiver = visit(node.receiver);
if (receiver == null) return null;
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) {
return defaultExpression(node);
}
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);
if (expression == null) return null;
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);
if (left == null) return null;
ConstantExpression right = visit(node.arguments.positional.single);
if (right == null) return null;
return new BinaryConstantExpression(left, operator, right);
}
}
return defaultExpression(node);
}
@override
ConstantExpression visitStaticInvocation(ir.StaticInvocation node) {
MemberEntity member = elementMap.getMember(node.target);
if (member == _commonElements.identicalFunction) {
if (node.arguments.positional.length == 2 &&
node.arguments.named.isEmpty) {
ConstantExpression left = visit(node.arguments.positional[0]);
if (left == null) return null;
ConstantExpression right = visit(node.arguments.positional[1]);
if (right == null) return null;
return new IdenticalConstantExpression(left, right);
}
} else if (member.name == 'fromEnvironment' &&
node.arguments.positional.length == 1) {
ConstantExpression name = visit(node.arguments.positional.single);
if (name == null) return null;
ConstantExpression defaultValue;
if (node.arguments.named.length == 1) {
if (node.arguments.named.single.name != 'defaultValue') {
return defaultExpression(node);
}
defaultValue = visit(node.arguments.named.single.value);
if (defaultValue == null) return null;
}
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);
}
}
return defaultExpression(node);
}
@override
ConstantExpression visitLogicalExpression(ir.LogicalExpression node) {
BinaryOperator operator = BinaryOperator.parse(node.operator);
if (operator != null &&
BinaryConstantExpression.potentialOperator(operator)) {
ConstantExpression left = visit(node.left);
if (left == null) return null;
ConstantExpression right = visit(node.right);
if (right == null) return null;
return new BinaryConstantExpression(left, operator, right);
}
return defaultExpression(node);
}
@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);
if (left == null) return null;
ConstantExpression right = visit(body.then);
if (right == null) return null;
// 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);
}
}
}
}
return defaultExpression(node);
}
/// Compute the [ConstantConstructor] corresponding to the const constructor
/// [node].
ConstantConstructor computeConstantConstructor(ir.Constructor node) {
assert(node.isConst);
ir.Class cls = node.enclosingClass;
InterfaceType type =
elementMap.elementEnvironment.getThisType(elementMap.getClass(cls));
Map<dynamic, ConstantExpression> defaultValues =
<dynamic, ConstantExpression>{};
int parameterIndex = 0;
for (ir.VariableDeclaration parameter
in node.function.positionalParameters) {
if (parameterIndex >= node.function.requiredParameterCount) {
ConstantExpression defaultValue;
if (parameter.initializer != null) {
defaultValue = parameter.initializer.accept(this);
} else {
defaultValue = new NullConstantExpression();
}
if (defaultValue == null) return null;
defaultValues[parameterIndex] = defaultValue;
}
parameterIndex++;
}
for (ir.VariableDeclaration parameter in node.function.namedParameters) {
ConstantExpression defaultValue = parameter.initializer.accept(this);
if (defaultValue == null) return null;
defaultValues[parameter.name] = defaultValue;
}
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[elementMap.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);
}
}
}