From 74d04a6a489a4bfdddfa11e724e8a4bc34eaa794 Mon Sep 17 00:00:00 2001 From: Johnni Winther Date: Mon, 29 Jun 2015 12:56:06 +0200 Subject: [PATCH] Compute constant constructors in resolution. BUG= R=sigurdm@google.com Review URL: https://codereview.chromium.org//1218793002. --- .../lib/src/constants/expressions.dart | 52 +++ pkg/compiler/lib/src/elements/modelx.dart | 30 +- .../lib/src/resolution/constructors.dart | 214 ++++++++---- pkg/compiler/lib/src/resolution/members.dart | 307 ++++++++++++++---- .../lib/src/resolution/resolution_common.dart | 6 +- .../lib/src/resolution/resolution_result.dart | 35 +- tests/compiler/dart2js/resolver_test.dart | 2 +- 7 files changed, 522 insertions(+), 124 deletions(-) diff --git a/pkg/compiler/lib/src/constants/expressions.dart b/pkg/compiler/lib/src/constants/expressions.dart index 5644b0f5847..dc0a3d9c811 100644 --- a/pkg/compiler/lib/src/constants/expressions.dart +++ b/pkg/compiler/lib/src/constants/expressions.dart @@ -18,6 +18,7 @@ import '../elements/elements.dart' show import '../resolution/operators.dart'; import '../tree/tree.dart' show DartString; import '../universe/universe.dart' show CallStructure; +import '../util/util.dart'; import 'values.dart'; enum ConstantExpressionKind { @@ -141,6 +142,23 @@ class GenerativeConstantConstructor implements ConstantConstructor{ return appliedFieldMap; } + int get hashCode { + int hash = Hashing.objectHash(type); + hash = Hashing.mapHash(defaultValues, hash); + hash = Hashing.mapHash(fieldMap, hash); + return Hashing.objectHash(superConstructorInvocation, hash); + } + + bool operator ==(other) { + if (identical(this, other)) return true; + if (other is! GenerativeConstantConstructor) return false; + return + type == other.type && + superConstructorInvocation == other.superConstructorInvocation && + mapEquals(defaultValues, other.defaultValues) && + mapEquals(fieldMap, other.fieldMap); + } + String toString() { StringBuffer sb = new StringBuffer(); sb.write("{'type': $type"); @@ -157,6 +175,16 @@ class GenerativeConstantConstructor implements ConstantConstructor{ return sb.toString(); } + static bool mapEquals(Map map1, Map map2) { + if (map1.length != map1.length) return false; + for (var key in map1.keys) { + if (map1[key] != map2[key]) { + return false; + } + } + return true; + } + /// Creates the field-to-constant map from applying [args] to /// [constructorInvocation]. If [constructorInvocation] is `null`, an empty /// map is created. @@ -205,6 +233,20 @@ class RedirectingGenerativeConstantConstructor implements ConstantConstructor { return appliedFieldMap; } + int get hashCode { + int hash = Hashing.objectHash(thisConstructorInvocation); + return Hashing.mapHash(defaultValues, hash); + } + + bool operator ==(other) { + if (identical(this, other)) return true; + if (other is! RedirectingGenerativeConstantConstructor) return false; + return + thisConstructorInvocation == other.thisConstructorInvocation && + GenerativeConstantConstructor.mapEquals( + defaultValues, other.defaultValues); + } + String toString() { StringBuffer sb = new StringBuffer(); sb.write("{'type': ${thisConstructorInvocation.type}"); @@ -240,6 +282,16 @@ class RedirectingFactoryConstantConstructor implements ConstantConstructor { return constantConstructor.computeInstanceFields(arguments, callStructure); } + int get hashCode { + return Hashing.objectHash(targetConstructorInvocation); + } + + bool operator ==(other) { + if (identical(this, other)) return true; + if (other is! RedirectingFactoryConstantConstructor) return false; + return targetConstructorInvocation == other.targetConstructorInvocation; + } + String toString() { StringBuffer sb = new StringBuffer(); sb.write("{"); diff --git a/pkg/compiler/lib/src/elements/modelx.dart b/pkg/compiler/lib/src/elements/modelx.dart index cf96f4e3ebe..336216528a7 100644 --- a/pkg/compiler/lib/src/elements/modelx.dart +++ b/pkg/compiler/lib/src/elements/modelx.dart @@ -8,6 +8,7 @@ import 'common.dart'; import 'elements.dart'; import '../constants/expressions.dart'; import '../constants/constructors.dart'; +import '../helpers/helpers.dart'; import '../tree/tree.dart'; import '../util/util.dart'; import '../resolution/resolution.dart'; @@ -1248,16 +1249,16 @@ class VariableList implements DeclarationSite { } abstract class ConstantVariableMixin implements VariableElement { - ConstantExpression _constant; + ConstantExpression constantCache; ConstantExpression get constant { if (isPatch) { ConstantVariableMixin originVariable = origin; return originVariable.constant; } - assert(invariant(this, _constant != null, + assert(invariant(this, constantCache != null, message: "Constant has not been computed for $this.")); - return _constant; + return constantCache; } void set constant(ConstantExpression value) { @@ -1266,9 +1267,9 @@ abstract class ConstantVariableMixin implements VariableElement { originVariable.constant = value; return null; } - assert(invariant(this, _constant == null || _constant == value, + assert(invariant(this, constantCache == null || constantCache == value, message: "Constant has already been computed for $this.")); - _constant = value; + constantCache = value; } } @@ -1966,6 +1967,25 @@ abstract class ConstantConstructorMixin implements ConstructorElement { return _constantConstructor; } + void set constantConstructor(ConstantConstructor value) { + if (isPatch) { + ConstantConstructorMixin originConstructor = origin; + originConstructor.constantConstructor = value; + } else { + assert(invariant(this, isConst, + message: "Constant constructor set on non-constant " + "constructor $this.")); + assert(invariant(this, !isFromEnvironmentConstructor, + message: "Constant constructor set on fromEnvironment " + "constructor: $this.")); + assert(invariant(this, + _constantConstructor == null || _constantConstructor == value, + message: "Constant constructor already computed for $this:" + "Existing: $_constantConstructor, new: $value")); + _constantConstructor = value; + } + } + bool get isFromEnvironmentConstructor { return name == 'fromEnvironment' && library.isDartCore && diff --git a/pkg/compiler/lib/src/resolution/constructors.dart b/pkg/compiler/lib/src/resolution/constructors.dart index 9c75184413c..bcf61138159 100644 --- a/pkg/compiler/lib/src/resolution/constructors.dart +++ b/pkg/compiler/lib/src/resolution/constructors.dart @@ -6,12 +6,18 @@ part of resolution; class InitializerResolver { final ResolverVisitor visitor; - final Map initialized; + final ConstructorElementX constructor; + final FunctionExpression functionNode; + final Map initialized = {}; + final Map fieldInitializers = + {}; Link initializers; - bool hasSuper; + bool hasSuper = false; + bool isValidAsConstant = true; - InitializerResolver(this.visitor) - : initialized = new Map(), hasSuper = false; + bool get isConst => constructor.isConst; + + InitializerResolver(this.visitor, this.constructor, this.functionNode); ResolutionRegistry get registry => visitor.registry; @@ -37,6 +43,7 @@ class InitializerResolver { visitor.compiler.reportInfo( existing, MessageKind.ALREADY_INITIALIZED, {'fieldName': field.name}); + isValidAsConstant = false; } void checkForDuplicateInitializers(FieldElementX field, Node init) { @@ -54,12 +61,13 @@ class InitializerResolver { initialized[field] = init; } - void resolveFieldInitializer(FunctionElement constructor, SendSet init) { + void resolveFieldInitializer(SendSet init) { // init is of the form [this.]field = value. final Node selector = init.selector; final String name = selector.asIdentifier().source; // Lookup target field. Element target; + FieldElement field; if (isFieldInitializer(init)) { target = constructor.enclosingClass.lookupLocalMember(name); if (target == null) { @@ -72,6 +80,8 @@ class InitializerResolver { selector.asIdentifier(), constructor.enclosingClass); } else if (!target.isInstanceMember) { error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name}); + } else { + field = target; } } else { error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); @@ -80,38 +90,44 @@ class InitializerResolver { registry.registerStaticUse(target); checkForDuplicateInitializers(target, init); // Resolve initializing value. - visitor.visitInStaticContext(init.arguments.head); - } - - ClassElement getSuperOrThisLookupTarget(FunctionElement constructor, - bool isSuperCall, - Node diagnosticNode) { - ClassElement lookupTarget = constructor.enclosingClass; - if (isSuperCall) { - // Calculate correct lookup target and constructor name. - if (identical(lookupTarget, visitor.compiler.objectClass)) { - error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT); + ResolutionResult result = visitor.visitInStaticContext( + init.arguments.head, + inConstantInitializer: isConst); + if (isConst) { + if (result.isConstant && field != null) { + // TODO(johnniwinther): Report error if `result.constant` is `null`. + fieldInitializers[field] = result.constant; } else { - return lookupTarget.supertype.element; + isValidAsConstant = false; } } - return lookupTarget; } - Element resolveSuperOrThisForSend(FunctionElement constructor, - FunctionExpression functionNode, - Send call) { - // Resolve the selector and the arguments. - visitor.inStaticContext(() { - visitor.resolveSelector(call, null); - visitor.resolveArguments(call.argumentsNode); - }); - Selector selector = registry.getSelector(call); - bool isSuperCall = Initializers.isSuperConstructorCall(call); + InterfaceType getSuperOrThisLookupTarget(Node diagnosticNode, + {bool isSuperCall}) { + if (isSuperCall) { + // Calculate correct lookup target and constructor name. + if (identical(constructor.enclosingClass, visitor.compiler.objectClass)) { + error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT); + isValidAsConstant = false; + } else { + return constructor.enclosingClass.supertype; + } + } + return constructor.enclosingClass.thisType; + } - ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, - isSuperCall, - call); + ResolutionResult resolveSuperOrThisForSend(Send call) { + // Resolve the selector and the arguments. + ArgumentsResult argumentsResult = visitor.inStaticContext(() { + visitor.resolveSelector(call, null); + return visitor.resolveArguments(call.argumentsNode); + }, inConstantInitializer: isConst); + + bool isSuperCall = Initializers.isSuperConstructorCall(call); + InterfaceType targetType = + getSuperOrThisLookupTarget(call, isSuperCall: isSuperCall); + ClassElement lookupTarget = targetType.element; Selector constructorSelector = visitor.getRedirectingThisOrSuperConstructorSelector(call); FunctionElement calledConstructor = @@ -119,9 +135,8 @@ class InitializerResolver { final bool isImplicitSuperCall = false; final String className = lookupTarget.name; - verifyThatConstructorMatchesCall(constructor, - calledConstructor, - selector.callStructure, + verifyThatConstructorMatchesCall(calledConstructor, + argumentsResult.callStructure, isImplicitSuperCall, call, className, @@ -129,11 +144,28 @@ class InitializerResolver { registry.useElement(call, calledConstructor); registry.registerStaticUse(calledConstructor); - return calledConstructor; + if (isConst) { + if (isValidAsConstant && + calledConstructor.isConst && + argumentsResult.isValidAsConstant) { + CallStructure callStructure = argumentsResult.callStructure; + List arguments = argumentsResult.constantArguments; + return new ConstantResult( + call, + new ConstructedConstantExpression( + targetType, + calledConstructor, + callStructure, + arguments), + element: calledConstructor); + } else { + isValidAsConstant = false; + } + } + return new ResolutionResult.forElement(calledConstructor); } - void resolveImplicitSuperConstructorSend(FunctionElement constructor, - FunctionExpression functionNode) { + ConstructedConstantExpression resolveImplicitSuperConstructorSend() { // If the class has a super resolve the implicit super call. ClassElement classElement = constructor.enclosingClass; ClassElement superClass = classElement.superclass; @@ -141,18 +173,16 @@ class InitializerResolver { assert(superClass != null); assert(superClass.isResolved); - final bool isSuperCall = true; - ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, - isSuperCall, - functionNode); + InterfaceType targetType = + getSuperOrThisLookupTarget(functionNode, isSuperCall: true); + ClassElement lookupTarget = targetType.element; Selector constructorSelector = new Selector.callDefaultConstructor(); Element calledConstructor = lookupTarget.lookupConstructor( constructorSelector.name); final String className = lookupTarget.name; final bool isImplicitSuperCall = true; - verifyThatConstructorMatchesCall(constructor, - calledConstructor, + verifyThatConstructorMatchesCall(calledConstructor, CallStructure.NO_ARGS, isImplicitSuperCall, functionNode, @@ -160,11 +190,19 @@ class InitializerResolver { constructorSelector); registry.registerImplicitSuperCall(calledConstructor); registry.registerStaticUse(calledConstructor); + + if (isConst && isValidAsConstant) { + return new ConstructedConstantExpression( + targetType, + calledConstructor, + CallStructure.NO_ARGS, + const []); + } } + return null; } void verifyThatConstructorMatchesCall( - FunctionElement caller, ConstructorElementX lookedupConstructor, CallStructure call, bool isImplicitSuperCall, @@ -181,6 +219,7 @@ class InitializerResolver { : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR; visitor.compiler.reportError( diagnosticNode, kind, {'constructorName': fullConstructorName}); + isValidAsConstant = false; } else { lookedupConstructor.computeSignature(visitor.compiler); if (!call.signatureApplies(lookedupConstructor.functionSignature)) { @@ -188,12 +227,14 @@ class InitializerResolver { ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT : MessageKind.NO_MATCHING_CONSTRUCTOR; visitor.compiler.reportError(diagnosticNode, kind); - } else if (caller.isConst + isValidAsConstant = false; + } else if (constructor.isConst && !lookedupConstructor.isConst) { MessageKind kind = isImplicitSuperCall ? MessageKind.CONST_CALLS_NON_CONST_FOR_IMPLICIT : MessageKind.CONST_CALLS_NON_CONST; visitor.compiler.reportError(diagnosticNode, kind); + isValidAsConstant = false; } } } @@ -202,16 +243,50 @@ class InitializerResolver { * Resolve all initializers of this constructor. In the case of a redirecting * constructor, the resolved constructor's function element is returned. */ - ConstructorElement resolveInitializers(ConstructorElementX constructor, - FunctionExpression functionNode) { + ConstructorElement resolveInitializers() { + Map defaultValues = + {}; + ConstructedConstantExpression constructorInvocation; // Keep track of all "this.param" parameters specified for constructor so // that we can ensure that fields are initialized only once. FunctionSignature functionParameters = constructor.functionSignature; - functionParameters.forEachParameter((ParameterElement element) { + functionParameters.forEachParameter((ParameterElementX element) { + if (isConst) { + if (element.isOptional) { + if (element.constantCache == null) { + // TODO(johnniwinther): Remove this when all constant expressions + // can be computed during resolution. + isValidAsConstant = false; + } else { + ConstantExpression defaultValue = element.constant; + if (defaultValue != null) { + if (element.isNamed) { + defaultValues[element.name] = defaultValue; + } else { + int index = + element.functionDeclaration.parameters.indexOf(element); + defaultValues[index] = defaultValue; + } + } else { + isValidAsConstant = false; + } + } + } + } if (element.isInitializingFormal) { - InitializingFormalElement initializingFormal = element; - checkForDuplicateInitializers(initializingFormal.fieldElement, - element.initializer); + InitializingFormalElementX initializingFormal = element; + FieldElement field = initializingFormal.fieldElement; + checkForDuplicateInitializers(field, element.initializer); + if (isConst) { + if (element.isNamed) { + fieldInitializers[field] = new NamedArgumentReference(element.name); + } else { + int index = element.functionDeclaration.parameters.indexOf(element); + fieldInitializers[field] = new PositionalArgumentReference(index); + } + } else { + isValidAsConstant = false; + } } }); @@ -224,7 +299,7 @@ class InitializerResolver { for (Link link = initializers; !link.isEmpty; link = link.tail) { if (link.head.asSendSet() != null) { final SendSet init = link.head.asSendSet(); - resolveFieldInitializer(constructor, init); + resolveFieldInitializer(init); } else if (link.head.asSend() != null) { final Send call = link.head.asSend(); if (call.argumentsNode == null) { @@ -235,7 +310,14 @@ class InitializerResolver { if (resolvedSuper) { error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER); } - resolveSuperOrThisForSend(constructor, functionNode, call); + ResolutionResult result = resolveSuperOrThisForSend(call); + if (isConst) { + if (result.isConstant) { + constructorInvocation = result.constant; + } else { + isValidAsConstant = false; + } + } resolvedSuper = true; } else if (Initializers.isConstructorRedirect(call)) { // Check that there is no body (Language specification 7.5.1). If the @@ -256,9 +338,24 @@ class InitializerResolver { if (parameter.isInitializingFormal) { Node node = parameter.node; error(node, MessageKind.INITIALIZING_FORMAL_NOT_ALLOWED); + isValidAsConstant = false; } }); - return resolveSuperOrThisForSend(constructor, functionNode, call); + ResolutionResult result = resolveSuperOrThisForSend(call); + if (isConst) { + if (result.isConstant) { + constructorInvocation = result.constant; + } else { + isValidAsConstant = false; + } + if (isConst && isValidAsConstant) { + constructor.constantConstructor = + new RedirectingGenerativeConstantConstructor( + defaultValues, + constructorInvocation); + } + } + return result.element; } else { visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED); return null; @@ -268,7 +365,14 @@ class InitializerResolver { } } if (!resolvedSuper) { - resolveImplicitSuperConstructorSend(constructor, functionNode); + constructorInvocation = resolveImplicitSuperConstructorSend(); + } + if (isConst && isValidAsConstant) { + constructor.constantConstructor = new GenerativeConstantConstructor( + constructor.enclosingClass.thisType, + defaultValues, + fieldInitializers, + constructorInvocation); } return null; // If there was no redirection always return null. } diff --git a/pkg/compiler/lib/src/resolution/members.dart b/pkg/compiler/lib/src/resolution/members.dart index feb8df12e7c..7c1bdf96f2a 100644 --- a/pkg/compiler/lib/src/resolution/members.dart +++ b/pkg/compiler/lib/src/resolution/members.dart @@ -4,6 +4,24 @@ part of resolution; +/// The state of constants in resolutions. +enum ConstantState { + /// Expressions are not required to be constants. + NON_CONSTANT, + + /// Expressions are required to be constants. + /// + /// For instance the values of a constant list literal. + CONSTANT, + + /// Expressions are required to be constants and parameter references are + /// also considered constant. + /// + /// This is used for resolving constructor initializers of constant + /// constructors. + CONSTANT_INITIALIZER, +} + /** * Core implementation of resolution. * @@ -23,6 +41,7 @@ class ResolverVisitor extends MappingVisitor { bool inInstanceContext; bool inCheckContext; bool inCatchBlock; + ConstantState constantState; Scope scope; ClassElement currentClass; @@ -100,6 +119,8 @@ class ResolverVisitor extends MappingVisitor { !element.isTypedef && !element.enclosingElement.isTypedef, inCatchBlock = false, + constantState = element.isConst + ? ConstantState.CONSTANT : ConstantState.NON_CONSTANT, super(compiler, registry); CoreTypes get coreTypes => compiler.coreTypes; @@ -153,14 +174,6 @@ class ResolverVisitor extends MappingVisitor { return result; } - inStaticContext(action()) { - bool wasInstanceContext = inInstanceContext; - inInstanceContext = false; - var result = action(); - inInstanceContext = wasInstanceContext; - return result; - } - doInPromotionScope(Node node, action()) { promotionScope = promotionScope.prepend(node); var result = action(); @@ -168,8 +181,47 @@ class ResolverVisitor extends MappingVisitor { return result; } - ResolutionResult visitInStaticContext(Node node) { - return inStaticContext(() => visit(node)); + inStaticContext(action(), + {bool inConstantInitializer: false}) { + bool wasInstanceContext = inInstanceContext; + ConstantState oldConstantState = constantState; + constantState = inConstantInitializer + ? ConstantState.CONSTANT_INITIALIZER + : constantState; + inInstanceContext = false; + var result = action(); + inInstanceContext = wasInstanceContext; + constantState = oldConstantState; + return result; + } + + ResolutionResult visitInStaticContext(Node node, + {bool inConstantInitializer: false}) { + return inStaticContext( + () => visit(node), + inConstantInitializer: inConstantInitializer); + } + + /// Execute [action] where the constant state is `ConstantState.CONSTANT` if + /// not already `ConstantState.CONSTANT_INITIALIZER`. + inConstantContext(action()) { + ConstantState oldConstantState = constantState; + if (constantState != ConstantState.CONSTANT_INITIALIZER) { + constantState = ConstantState.CONSTANT; + } + var result = action(); + constantState = oldConstantState; + return result; + } + + /// Visit [node] where the constant state is `ConstantState.CONSTANT` if + /// not already `ConstantState.CONSTANT_INITIALIZER`. + ResolutionResult visitInConstantContext(Node node) { + ResolutionResult result = inConstantContext(() => visit(node)); + assert(invariant(node, result != null, + message: "No resolution result for $node.")); + + return result; } ErroneousElement reportAndCreateErroneousElement( @@ -324,7 +376,7 @@ class ResolverVisitor extends MappingVisitor { FunctionSignature functionParameters = function.functionSignature; Link parameterNodes = (node.parameters == null) ? const Link() : node.parameters.nodes; - functionParameters.forEachParameter((ParameterElement element) { + functionParameters.forEachParameter((ParameterElementX element) { // TODO(karlklose): should be a list of [FormalElement]s, but the actual // implementation uses [Element]. List optionals = functionParameters.optionalParameters; @@ -332,7 +384,16 @@ class ResolverVisitor extends MappingVisitor { NodeList nodes = parameterNodes.head; parameterNodes = nodes.nodes; } - visit(element.initializer); + if (element.isOptional) { + if (element.initializer != null) { + ResolutionResult result = visitInConstantContext(element.initializer); + if (result.isConstant) { + element.constant = result.constant; + } + } else { + element.constant = new NullConstantExpression(); + } + } VariableDefinitions variableDefinitions = parameterNodes.head; Node parameterNode = variableDefinitions.definitions.nodes.head; // Field parameters (this.x) are not visible inside the constructor. The @@ -340,7 +401,7 @@ class ResolverVisitor extends MappingVisitor { if (element.isInitializingFormal) { registry.useElement(parameterNode, element); } else { - LocalParameterElement parameterElement = element; + LocalParameterElementX parameterElement = element; defineLocalVariable(parameterNode, parameterElement); addToScope(parameterElement); } @@ -715,8 +776,10 @@ class ResolverVisitor extends MappingVisitor { return selector; } - CallStructure resolveArguments(NodeList list) { + ArgumentsResult resolveArguments(NodeList list) { if (list == null) return null; + bool isValidAsConstant = true; + List argumentResults = []; bool oldSendIsMemberAccess = sendIsMemberAccess; sendIsMemberAccess = false; Map seenNamedArguments = new Map(); @@ -724,7 +787,12 @@ class ResolverVisitor extends MappingVisitor { List namedArguments = []; for (Link link = list.nodes; !link.isEmpty; link = link.tail) { Expression argument = link.head; - visit(argument); + ResolutionResult result = visit(argument); + if (!result.isConstant) { + isValidAsConstant = false; + } + argumentResults.add(result); + NamedArgument namedArgument = argument.asNamedArgument(); if (namedArgument != null) { String source = namedArgument.name.source; @@ -734,16 +802,21 @@ class ResolverVisitor extends MappingVisitor { source, argument, seenNamedArguments[source]); + isValidAsConstant = false; } else { seenNamedArguments[source] = namedArgument; } } else if (!seenNamedArguments.isEmpty) { error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); + isValidAsConstant = false; } argumentCount++; } sendIsMemberAccess = oldSendIsMemberAccess; - return new CallStructure(argumentCount, namedArguments); + return new ArgumentsResult( + new CallStructure(argumentCount, namedArguments), + argumentResults, + isValidAsConstant: isValidAsConstant); } void registerTypeLiteralAccess(Send node, Element target) { @@ -1380,7 +1453,8 @@ class ResolverVisitor extends MappingVisitor { message: "Unexpected expression: $node")); Node expression = node.selector; visitExpression(expression); - CallStructure callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; Selector selector = callStructure.callSelector; // TODO(johnniwinther): Remove this when all information goes through the // [SendStructure]. @@ -1398,7 +1472,8 @@ class ResolverVisitor extends MappingVisitor { // If this send is of the form "assert(expr);", then // this is an assertion. - CallStructure callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; SendStructure sendStructure = const AssertStructure(); if (callStructure.argumentCount != 1) { compiler.reportError( @@ -1430,7 +1505,8 @@ class ResolverVisitor extends MappingVisitor { ResolutionResult handleThisAccess(Send node) { AccessSemantics accessSemantics = new AccessSemantics.thisAccess(); if (node.isCall) { - CallStructure callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; Selector selector = callStructure.callSelector; // TODO(johnniwinther): Handle invalid this access as an // [AccessSemantics]. @@ -1456,7 +1532,8 @@ class ResolverVisitor extends MappingVisitor { Selector selector; CallStructure callStructure = CallStructure.NO_ARGS; if (node.isCall) { - callStructure = resolveArguments(node.argumentsNode); + callStructure = + resolveArguments(node.argumentsNode).callStructure; selector = new Selector(SelectorKind.CALL, name, callStructure); } else { selector = new Selector(SelectorKind.GETTER, name, callStructure); @@ -1711,7 +1788,8 @@ class ResolverVisitor extends MappingVisitor { SendStructure sendStructure; Selector selector; if (node.isCall) { - CallStructure callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; selector = new Selector(SelectorKind.CALL, name, callStructure); registry.registerDynamicInvocation( new UniverseSelector(selector, null)); @@ -1803,7 +1881,8 @@ class ResolverVisitor extends MappingVisitor { SendStructure sendStructure; Selector selector; if (node.isCall) { - CallStructure callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; selector = new Selector(SelectorKind.CALL, name, callStructure); registry.registerDynamicInvocation( new UniverseSelector(selector, null)); @@ -1864,16 +1943,13 @@ class ResolverVisitor extends MappingVisitor { /// Handle access of a parameter, local variable or local function. ResolutionResult handleLocalAccess(Send node, Name name, Element element) { + ResolutionResult result = const NoneResult(); AccessSemantics semantics = computeLocalAccessSemantics(node, element); Selector selector; - CallStructure callStructure = CallStructure.NO_ARGS; if (node.isCall) { - callStructure = resolveArguments(node.argumentsNode); + CallStructure callStructure = + resolveArguments(node.argumentsNode).callStructure; selector = new Selector(SelectorKind.CALL, name, callStructure); - } else { - selector = new Selector(SelectorKind.GETTER, name, callStructure); - } - if (node.isCall) { bool isIncompatibleInvoke = false; switch (semantics.kind) { case AccessKind.LOCAL_FUNCTION: @@ -1904,6 +1980,48 @@ class ResolverVisitor extends MappingVisitor { ? new IncompatibleInvokeStructure(semantics, selector) : new InvokeStructure(semantics, selector)); } else { + switch (semantics.kind) { + case AccessKind.LOCAL_VARIABLE: + case AccessKind.LOCAL_FUNCTION: + result = new ElementResult(element); + break; + case AccessKind.PARAMETER: + case AccessKind.FINAL_PARAMETER: + if (constantState == ConstantState.CONSTANT_INITIALIZER) { + ParameterElement parameter = element; + if (parameter.isNamed) { + result = new ConstantResult( + node, + new NamedArgumentReference(parameter.name), + element: element); + } else { + result = new ConstantResult( + node, + new PositionalArgumentReference( + parameter.functionDeclaration.parameters.indexOf( + parameter)), + element: element); + } + } else { + result = new ElementResult(element); + } + break; + case AccessKind.FINAL_LOCAL_VARIABLE: + if (element.isConst) { + result = new ConstantResult( + node, + new VariableConstantExpression(element), + element: element); + } else { + result = new ElementResult(element); + } + break; + default: + internalError(node, + "Unexpected local access $semantics."); + break; + } + selector = new Selector(SelectorKind.GETTER, name, CallStructure.NO_ARGS); registry.registerSendStructure(node, new GetStructure(semantics, selector)); } @@ -1915,14 +2033,13 @@ class ResolverVisitor extends MappingVisitor { registerPotentialAccessInClosure(node, element); - return node.isPropertyAccess - ? new ElementResult(element) : const NoneResult(); + return result; } /// Handle access of a static or top level [element]. ResolutionResult handleStaticOrTopLevelAccess( Send node, Name name, Element element) { - + ResolutionResult result = const NoneResult(); MemberElement member; if (element.isAbstractField) { AbstractFieldElement abstractField = element; @@ -1939,16 +2056,14 @@ class ResolverVisitor extends MappingVisitor { member.computeType(compiler); Selector selector; - CallStructure callStructure = CallStructure.NO_ARGS; - if (node.isCall) { - callStructure = resolveArguments(node.argumentsNode); - selector = new Selector(SelectorKind.CALL, name, callStructure); - } else { - selector = new Selector(SelectorKind.GETTER, name, callStructure); - } AccessSemantics semantics = computeStaticOrTopLevelAccessSemantics(node, member); if (node.isCall) { + ArgumentsResult argumentsResult = + resolveArguments(node.argumentsNode); + CallStructure callStructure = argumentsResult.callStructure; + selector = new Selector(SelectorKind.CALL, name, callStructure); + bool isIncompatibleInvoke = false; switch (semantics.kind) { case AccessKind.STATIC_METHOD: @@ -1963,6 +2078,13 @@ class ResolverVisitor extends MappingVisitor { } else { registry.registerStaticUse(semantics.element); handleForeignCall(node, semantics.element, selector); + if (method == compiler.identicalFunction && + argumentsResult.isValidAsConstant) { + result = new ConstantResult(node, + new IdenticalConstantExpression( + argumentsResult.argumentResults[0].constant, + argumentsResult.argumentResults[1].constant)); + } } break; case AccessKind.STATIC_FIELD: @@ -1994,6 +2116,7 @@ class ResolverVisitor extends MappingVisitor { ? new IncompatibleInvokeStructure(semantics, selector) : new InvokeStructure(semantics, selector)); } else { + selector = new Selector(SelectorKind.GETTER, name, CallStructure.NO_ARGS); switch (semantics.kind) { case AccessKind.STATIC_METHOD: case AccessKind.TOPLEVEL_METHOD: @@ -2025,6 +2148,13 @@ class ResolverVisitor extends MappingVisitor { } registry.registerSendStructure(node, new GetStructure(semantics, selector)); + if (member.isConst) { + FieldElement field = member; + result = new ConstantResult( + node, new VariableConstantExpression(field), element: field); + } else { + result = new ElementResult(member); + } } // TODO(johnniwinther): Remove these when all information goes through @@ -2032,8 +2162,7 @@ class ResolverVisitor extends MappingVisitor { registry.useElement(node, member); registry.setSelector(node, selector); - return node.isPropertyAccess - ? new ElementResult(member) : const NoneResult(); + return result; } /// Handle access to resolved [element]. @@ -2535,6 +2664,7 @@ class ResolverVisitor extends MappingVisitor { } ConstructorElementX constructor = enclosingElement; bool isConstConstructor = constructor.isConst; + bool isValidAsConstant = isConstConstructor; ConstructorElement redirectionTarget = resolveRedirectingFactory( node, inConstContext: isConstConstructor); constructor.immediateRedirectionTarget = redirectionTarget; @@ -2554,9 +2684,13 @@ class ResolverVisitor extends MappingVisitor { if (isConstConstructor && !redirectionTarget.isConst) { compiler.reportError(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST); + isValidAsConstant = false; } if (redirectionTarget == constructor) { compiler.reportError(node, MessageKind.CYCLIC_REDIRECTING_FACTORY); + // TODO(johnniwinther): Create constant constructor for this case and + // let evaluation detect the cyclicity. + isValidAsConstant = false; } } @@ -2571,6 +2705,8 @@ class ResolverVisitor extends MappingVisitor { if (!isSubtype) { warning(node, MessageKind.NOT_ASSIGNABLE, {'fromType': targetType, 'toType': constructorType}); + // TODO(johnniwinther): Handle this (potentially) erroneous case. + isValidAsConstant = false; } redirectionTarget.computeType(compiler); @@ -2580,6 +2716,7 @@ class ResolverVisitor extends MappingVisitor { if (!targetSignature.isCompatibleWith(constructorSignature)) { assert(!isSubtype); registry.registerThrowNoSuchMethod(); + isValidAsConstant = false; } // Register a post process to check for cycles in the redirection chain and @@ -2595,6 +2732,30 @@ class ResolverVisitor extends MappingVisitor { if (isSymbolConstructor) { registry.registerSymbolConstructor(); } + if (isValidAsConstant) { + List names = []; + List arguments = []; + int index = 0; + constructorSignature.forEachParameter((ParameterElement parameter) { + if (parameter.isNamed) { + String name = parameter.name; + names.add(name); + arguments.add(new NamedArgumentReference(name)); + } else { + arguments.add(new PositionalArgumentReference(index)); + } + index++; + }); + CallStructure callStructure = + new CallStructure(constructorSignature.parameterCount, names); + constructor.constantConstructor = + new RedirectingFactoryConstantConstructor( + new ConstructedConstantExpression( + type, + redirectionTarget, + callStructure, + arguments)); + } return const NoneResult(); } @@ -2682,12 +2843,19 @@ class ResolverVisitor extends MappingVisitor { } ResolutionResult visitNewExpression(NewExpression node) { + bool isValidAsConstant = true; FunctionElement constructor = resolveConstructor(node); final bool isSymbolConstructor = constructor == compiler.symbolConstructor; final bool isMirrorsUsedConstant = node.isConst && (constructor == compiler.mirrorsUsedConstructor); Selector callSelector = resolveSelector(node.send, constructor); - resolveArguments(node.send.argumentsNode); + ArgumentsResult argumentsResult; + if (node.isConst) { + argumentsResult = + inConstantContext(() => resolveArguments(node.send.argumentsNode)); + } else { + argumentsResult = resolveArguments(node.send.argumentsNode); + } registry.useElement(node.send, constructor); if (Elements.isUnresolved(constructor)) { return new ResolutionResult.forElement(constructor); @@ -2705,12 +2873,14 @@ class ResolverVisitor extends MappingVisitor { compiler.reportError(node, MessageKind.CANNOT_INSTANTIATE_ENUM, {'enumName': cls.name}); + isValidAsConstant = false; } InterfaceType type = registry.getType(node); if (node.isConst && type.containsTypeVariables) { compiler.reportError(node.send.selector, MessageKind.TYPE_VARIABLE_IN_CONSTANT); + isValidAsConstant = false; } // TODO(johniwinther): Avoid registration of `type` in face of redirecting // factory constructors. @@ -2718,6 +2888,7 @@ class ResolverVisitor extends MappingVisitor { if (constructor.isGenerativeConstructor && cls.isAbstract) { warning(node, MessageKind.ABSTRACT_CLASS_INSTANTIATION); registry.registerAbstractClassInstantiation(); + isValidAsConstant = false; } if (isSymbolConstructor) { @@ -2752,6 +2923,19 @@ class ResolverVisitor extends MappingVisitor { } if (node.isConst) { analyzeConstantDeferred(node); + if (isValidAsConstant && + constructor.isConst && + argumentsResult.isValidAsConstant) { + CallStructure callStructure = argumentsResult.callStructure; + List arguments = argumentsResult.constantArguments; + ConstructedConstantExpression constant = + new ConstructedConstantExpression( + type, + constructor, + callStructure, + arguments); + return new ConstantResult(node, constant); + } } return const NoneResult(); @@ -2898,14 +3082,16 @@ class ResolverVisitor extends MappingVisitor { registry.registerRequiredType(listType, enclosingElement); if (node.isConst) { List constantExpressions = []; - for (Node element in node.elements) { - ResolutionResult elementResult = visit(element); - if (isValidAsConstant && elementResult.isConstant) { - constantExpressions.add(elementResult.constant); - } else { - isValidAsConstant = false; + inConstantContext(() { + for (Node element in node.elements) { + ResolutionResult elementResult = visit(element); + if (isValidAsConstant && elementResult.isConstant) { + constantExpressions.add(elementResult.constant); + } else { + isValidAsConstant = false; + } } - } + }); analyzeConstantDeferred(node); sendIsMemberAccess = false; if (isValidAsConstant) { @@ -3197,20 +3383,23 @@ class ResolverVisitor extends MappingVisitor { registry.registerMapLiteral(node, mapType, node.isConst); registry.registerRequiredType(mapType, enclosingElement); if (node.isConst) { + List keyExpressions = []; List valueExpressions = []; - for (LiteralMapEntry entry in node.entries) { - ResolutionResult keyResult = visit(entry.key); - ResolutionResult valueResult = visit(entry.value); - if (isValidAsConstant && - keyResult.isConstant && - valueResult.isConstant) { - keyExpressions.add(keyResult.constant); - valueExpressions.add(valueResult.constant); - } else { - isValidAsConstant = false; + inConstantContext(() { + for (LiteralMapEntry entry in node.entries) { + ResolutionResult keyResult = visit(entry.key); + ResolutionResult valueResult = visit(entry.value); + if (isValidAsConstant && + keyResult.isConstant && + valueResult.isConstant) { + keyExpressions.add(keyResult.constant); + valueExpressions.add(valueResult.constant); + } else { + isValidAsConstant = false; + } } - } + }); analyzeConstantDeferred(node); sendIsMemberAccess = false; if (isValidAsConstant) { diff --git a/pkg/compiler/lib/src/resolution/resolution_common.dart b/pkg/compiler/lib/src/resolution/resolution_common.dart index cb045cf1c5a..0f1a7784a4b 100644 --- a/pkg/compiler/lib/src/resolution/resolution_common.dart +++ b/pkg/compiler/lib/src/resolution/resolution_common.dart @@ -160,9 +160,9 @@ class ResolverTask extends CompilerTask { if (element.isGenerativeConstructor) { // Even if there is no initializer list we still have to do the // resolution in case there is an implicit super constructor call. - InitializerResolver resolver = new InitializerResolver(visitor); - FunctionElement redirection = - resolver.resolveInitializers(element, tree); + InitializerResolver resolver = + new InitializerResolver(visitor, element, tree); + FunctionElement redirection = resolver.resolveInitializers(); if (redirection != null) { resolveRedirectingConstructor(resolver, tree, element, redirection); } diff --git a/pkg/compiler/lib/src/resolution/resolution_result.dart b/pkg/compiler/lib/src/resolution/resolution_result.dart index 5426fe3f3de..ba2b74cdd58 100644 --- a/pkg/compiler/lib/src/resolution/resolution_result.dart +++ b/pkg/compiler/lib/src/resolution/resolution_result.dart @@ -23,6 +23,7 @@ abstract class ResolutionResult { } ResultKind get kind; + Node get node => null; Element get element => null; DartType get type => null; ConstantExpression get constant => null; @@ -64,11 +65,16 @@ class AssertResult extends ResolutionResult { String toString() => 'AssertResult()'; } +/// The result for resolving a constant expression. class ConstantResult extends ResolutionResult { final Node node; final ConstantExpression constant; + final Element element; - ConstantResult(this.node, this.constant); + /// Creates a result for the [constant] expression. [node] is provided for + /// error reporting on the constant and [element] is provided if the + /// expression additionally serves an [Element] like [ElementResult]. + ConstantResult(this.node, this.constant, {this.element}); bool get isConstant => true; @@ -83,4 +89,31 @@ class NoneResult extends ResolutionResult { ResultKind get kind => ResultKind.NONE; String toString() => 'NoneResult()'; +} + +/// The result of resolving a list of arguments. +class ArgumentsResult { + /// The call structure of the arguments. + final CallStructure callStructure; + + /// The resolutions results for each argument. + final List argumentResults; + + /// `true` if the arguments are valid as arguments to a constructed constant + /// expression. + final bool isValidAsConstant; + + ArgumentsResult( + this.callStructure, + this.argumentResults, + {this.isValidAsConstant}); + + /// Returns the list of [ConstantExpression]s for each of the arguments. If + /// [isValidAsConstant] is `false`, `null` is returned. + List get constantArguments { + if (!isValidAsConstant) return null; + return argumentResults.map((ResolutionResult result) { + return result.constant; + }).toList(); + } } \ No newline at end of file diff --git a/tests/compiler/dart2js/resolver_test.dart b/tests/compiler/dart2js/resolver_test.dart index 5084fe33b07..c25eb933877 100644 --- a/tests/compiler/dart2js/resolver_test.dart +++ b/tests/compiler/dart2js/resolver_test.dart @@ -686,7 +686,7 @@ Future resolveConstructor( new ResolverVisitor(compiler, element, new ResolutionRegistry.internal(compiler, new CollectingTreeElements(element))); - new InitializerResolver(visitor).resolveInitializers(element, tree); + new InitializerResolver(visitor, element, tree).resolveInitializers(); visitor.visit(tree.body); Expect.equals(expectedElementCount, map(visitor).length, "${map(visitor).values} for '$statement' in context of `$script`");