| // Copyright (c) 2012, 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. |
| |
| part of resolution; |
| |
| abstract class TreeElements { |
| AnalyzableElement get analyzedElement; |
| Iterable<Node> get superUses; |
| |
| /// Iterables of the dependencies that this [TreeElement] records of |
| /// [analyzedElement]. |
| Iterable<Element> get allElements; |
| void forEachConstantNode(f(Node n, ConstantExpression c)); |
| |
| /// A set of additional dependencies. See [registerDependency] below. |
| Iterable<Element> get otherDependencies; |
| |
| Element operator[](Node node); |
| |
| // TODO(johnniwinther): Investigate whether [Node] could be a [Send]. |
| Selector getSelector(Node node); |
| Selector getGetterSelectorInComplexSendSet(SendSet node); |
| Selector getOperatorSelectorInComplexSendSet(SendSet node); |
| DartType getType(Node node); |
| void setSelector(Node node, Selector selector); |
| void setGetterSelectorInComplexSendSet(SendSet node, Selector selector); |
| void setOperatorSelectorInComplexSendSet(SendSet node, Selector selector); |
| |
| /// Returns the for-in loop variable for [node]. |
| Element getForInVariable(ForIn node); |
| Selector getIteratorSelector(ForIn node); |
| Selector getMoveNextSelector(ForIn node); |
| Selector getCurrentSelector(ForIn node); |
| void setIteratorSelector(ForIn node, Selector selector); |
| void setMoveNextSelector(ForIn node, Selector selector); |
| void setCurrentSelector(ForIn node, Selector selector); |
| void setConstant(Node node, ConstantExpression constant); |
| ConstantExpression getConstant(Node node); |
| bool isAssert(Send send); |
| |
| /// Returns the [FunctionElement] defined by [node]. |
| FunctionElement getFunctionDefinition(FunctionExpression node); |
| |
| /// Returns target constructor for the redirecting factory body [node]. |
| ConstructorElement getRedirectingTargetConstructor( |
| RedirectingFactoryBody node); |
| |
| /** |
| * Returns [:true:] if [node] is a type literal. |
| * |
| * Resolution marks this by setting the type on the node to be the |
| * type that the literal refers to. |
| */ |
| bool isTypeLiteral(Send node); |
| |
| /// Returns the type that the type literal [node] refers to. |
| DartType getTypeLiteralType(Send node); |
| |
| /// Register additional dependencies required by [analyzedElement]. |
| /// For example, elements that are used by a backend. |
| void registerDependency(Element element); |
| |
| /// Returns a list of nodes that potentially mutate [element] anywhere in its |
| /// scope. |
| List<Node> getPotentialMutations(VariableElement element); |
| |
| /// Returns a list of nodes that potentially mutate [element] in [node]. |
| List<Node> getPotentialMutationsIn(Node node, VariableElement element); |
| |
| /// Returns a list of nodes that potentially mutate [element] in a closure. |
| List<Node> getPotentialMutationsInClosure(VariableElement element); |
| |
| /// Returns a list of nodes that access [element] within a closure in [node]. |
| List<Node> getAccessesByClosureIn(Node node, VariableElement element); |
| |
| /// Returns the jump target defined by [node]. |
| JumpTarget getTargetDefinition(Node node); |
| |
| /// Returns the jump target of the [node]. |
| JumpTarget getTargetOf(GotoStatement node); |
| |
| /// Returns the label defined by [node]. |
| LabelDefinition getLabelDefinition(Label node); |
| |
| /// Returns the label that [node] targets. |
| LabelDefinition getTargetLabel(GotoStatement node); |
| } |
| |
| class TreeElementMapping implements TreeElements { |
| final AnalyzableElement analyzedElement; |
| Map<Spannable, Selector> _selectors; |
| Map<Node, DartType> _types; |
| Setlet<Node> _superUses; |
| Setlet<Element> _otherDependencies; |
| Map<Node, ConstantExpression> _constants; |
| Map<VariableElement, List<Node>> _potentiallyMutated; |
| Map<Node, Map<VariableElement, List<Node>>> _potentiallyMutatedIn; |
| Map<VariableElement, List<Node>> _potentiallyMutatedInClosure; |
| Map<Node, Map<VariableElement, List<Node>>> _accessedByClosureIn; |
| Setlet<Element> _elements; |
| Setlet<Send> _asserts; |
| |
| /// Map from nodes to the targets they define. |
| Map<Node, JumpTarget> _definedTargets; |
| |
| /// Map from goto statements to their targets. |
| Map<GotoStatement, JumpTarget> _usedTargets; |
| |
| /// Map from labels to their label definition. |
| Map<Label, LabelDefinition> _definedLabels; |
| |
| /// Map from labeled goto statements to the labels they target. |
| Map<GotoStatement, LabelDefinition> _targetLabels; |
| |
| final int hashCode = ++_hashCodeCounter; |
| static int _hashCodeCounter = 0; |
| |
| TreeElementMapping(this.analyzedElement); |
| |
| operator []=(Node node, Element element) { |
| assert(invariant(node, () { |
| FunctionExpression functionExpression = node.asFunctionExpression(); |
| if (functionExpression != null) { |
| return !functionExpression.modifiers.isExternal; |
| } |
| return true; |
| })); |
| // TODO(johnniwinther): Simplify this invariant to use only declarations in |
| // [TreeElements]. |
| assert(invariant(node, () { |
| if (!element.isErroneous && analyzedElement != null && element.isPatch) { |
| return analyzedElement.implementationLibrary.isPatch; |
| } |
| return true; |
| })); |
| // TODO(ahe): Investigate why the invariant below doesn't hold. |
| // assert(invariant(node, |
| // getTreeElement(node) == element || |
| // getTreeElement(node) == null, |
| // message: '${getTreeElement(node)}; $element')); |
| |
| if (_elements == null) { |
| _elements = new Setlet<Element>(); |
| } |
| _elements.add(element); |
| setTreeElement(node, element); |
| } |
| |
| operator [](Node node) => getTreeElement(node); |
| |
| void setType(Node node, DartType type) { |
| if (_types == null) { |
| _types = new Maplet<Node, DartType>(); |
| } |
| _types[node] = type; |
| } |
| |
| DartType getType(Node node) => _types != null ? _types[node] : null; |
| |
| Iterable<Node> get superUses { |
| return _superUses != null ? _superUses : const <Node>[]; |
| } |
| |
| void addSuperUse(Node node) { |
| if (_superUses == null) { |
| _superUses = new Setlet<Node>(); |
| } |
| _superUses.add(node); |
| } |
| |
| Selector _getSelector(Spannable node) { |
| return _selectors != null ? _selectors[node] : null; |
| } |
| |
| void _setSelector(Spannable node, Selector selector) { |
| if (_selectors == null) { |
| _selectors = new Maplet<Spannable, Selector>(); |
| } |
| _selectors[node] = selector; |
| } |
| |
| void setSelector(Node node, Selector selector) { |
| _setSelector(node, selector); |
| } |
| |
| Selector getSelector(Node node) => _getSelector(node); |
| |
| int getSelectorCount() => _selectors == null ? 0 : _selectors.length; |
| |
| void setGetterSelectorInComplexSendSet(SendSet node, Selector selector) { |
| _setSelector(node.selector, selector); |
| } |
| |
| Selector getGetterSelectorInComplexSendSet(SendSet node) { |
| return _getSelector(node.selector); |
| } |
| |
| void setOperatorSelectorInComplexSendSet(SendSet node, Selector selector) { |
| _setSelector(node.assignmentOperator, selector); |
| } |
| |
| Selector getOperatorSelectorInComplexSendSet(SendSet node) { |
| return _getSelector(node.assignmentOperator); |
| } |
| |
| // The following methods set selectors on the "for in" node. Since |
| // we're using three selectors, we need to use children of the node, |
| // and we arbitrarily choose which ones. |
| |
| void setIteratorSelector(ForIn node, Selector selector) { |
| _setSelector(node, selector); |
| } |
| |
| Selector getIteratorSelector(ForIn node) { |
| return _getSelector(node); |
| } |
| |
| void setMoveNextSelector(ForIn node, Selector selector) { |
| _setSelector(node.forToken, selector); |
| } |
| |
| Selector getMoveNextSelector(ForIn node) { |
| return _getSelector(node.forToken); |
| } |
| |
| void setCurrentSelector(ForIn node, Selector selector) { |
| _setSelector(node.inToken, selector); |
| } |
| |
| Selector getCurrentSelector(ForIn node) { |
| return _getSelector(node.inToken); |
| } |
| |
| Element getForInVariable(ForIn node) { |
| return this[node]; |
| } |
| |
| void setConstant(Node node, ConstantExpression constant) { |
| if (_constants == null) { |
| _constants = new Maplet<Node, ConstantExpression>(); |
| } |
| _constants[node] = constant; |
| } |
| |
| ConstantExpression getConstant(Node node) { |
| return _constants != null ? _constants[node] : null; |
| } |
| |
| bool isTypeLiteral(Send node) { |
| return getType(node) != null; |
| } |
| |
| DartType getTypeLiteralType(Send node) { |
| return getType(node); |
| } |
| |
| void registerDependency(Element element) { |
| if (element == null) return; |
| if (_otherDependencies == null) { |
| _otherDependencies = new Setlet<Element>(); |
| } |
| _otherDependencies.add(element.implementation); |
| } |
| |
| Iterable<Element> get otherDependencies { |
| return _otherDependencies != null ? _otherDependencies : const <Element>[]; |
| } |
| |
| List<Node> getPotentialMutations(VariableElement element) { |
| if (_potentiallyMutated == null) return const <Node>[]; |
| List<Node> mutations = _potentiallyMutated[element]; |
| if (mutations == null) return const <Node>[]; |
| return mutations; |
| } |
| |
| void registerPotentialMutation(VariableElement element, Node mutationNode) { |
| if (_potentiallyMutated == null) { |
| _potentiallyMutated = new Maplet<VariableElement, List<Node>>(); |
| } |
| _potentiallyMutated.putIfAbsent(element, () => <Node>[]).add(mutationNode); |
| } |
| |
| List<Node> getPotentialMutationsIn(Node node, VariableElement element) { |
| if (_potentiallyMutatedIn == null) return const <Node>[]; |
| Map<VariableElement, List<Node>> mutationsIn = _potentiallyMutatedIn[node]; |
| if (mutationsIn == null) return const <Node>[]; |
| List<Node> mutations = mutationsIn[element]; |
| if (mutations == null) return const <Node>[]; |
| return mutations; |
| } |
| |
| void registerPotentialMutationIn(Node contextNode, VariableElement element, |
| Node mutationNode) { |
| if (_potentiallyMutatedIn == null) { |
| _potentiallyMutatedIn = |
| new Maplet<Node, Map<VariableElement, List<Node>>>(); |
| } |
| Map<VariableElement, List<Node>> mutationMap = |
| _potentiallyMutatedIn.putIfAbsent(contextNode, |
| () => new Maplet<VariableElement, List<Node>>()); |
| mutationMap.putIfAbsent(element, () => <Node>[]).add(mutationNode); |
| } |
| |
| List<Node> getPotentialMutationsInClosure(VariableElement element) { |
| if (_potentiallyMutatedInClosure == null) return const <Node>[]; |
| List<Node> mutations = _potentiallyMutatedInClosure[element]; |
| if (mutations == null) return const <Node>[]; |
| return mutations; |
| } |
| |
| void registerPotentialMutationInClosure(VariableElement element, |
| Node mutationNode) { |
| if (_potentiallyMutatedInClosure == null) { |
| _potentiallyMutatedInClosure = new Maplet<VariableElement, List<Node>>(); |
| } |
| _potentiallyMutatedInClosure.putIfAbsent( |
| element, () => <Node>[]).add(mutationNode); |
| } |
| |
| List<Node> getAccessesByClosureIn(Node node, VariableElement element) { |
| if (_accessedByClosureIn == null) return const <Node>[]; |
| Map<VariableElement, List<Node>> accessesIn = _accessedByClosureIn[node]; |
| if (accessesIn == null) return const <Node>[]; |
| List<Node> accesses = accessesIn[element]; |
| if (accesses == null) return const <Node>[]; |
| return accesses; |
| } |
| |
| void setAccessedByClosureIn(Node contextNode, VariableElement element, |
| Node accessNode) { |
| if (_accessedByClosureIn == null) { |
| _accessedByClosureIn = new Map<Node, Map<VariableElement, List<Node>>>(); |
| } |
| Map<VariableElement, List<Node>> accessMap = |
| _accessedByClosureIn.putIfAbsent(contextNode, |
| () => new Maplet<VariableElement, List<Node>>()); |
| accessMap.putIfAbsent(element, () => <Node>[]).add(accessNode); |
| } |
| |
| String toString() => 'TreeElementMapping($analyzedElement)'; |
| |
| Iterable<Element> get allElements { |
| return _elements != null ? _elements : const <Element>[]; |
| } |
| |
| void forEachConstantNode(f(Node n, ConstantExpression c)) { |
| if (_constants != null) { |
| _constants.forEach(f); |
| } |
| } |
| |
| void setAssert(Send node) { |
| if (_asserts == null) { |
| _asserts = new Setlet<Send>(); |
| } |
| _asserts.add(node); |
| } |
| |
| bool isAssert(Send node) { |
| return _asserts != null && _asserts.contains(node); |
| } |
| |
| FunctionElement getFunctionDefinition(FunctionExpression node) { |
| return this[node]; |
| } |
| |
| ConstructorElement getRedirectingTargetConstructor( |
| RedirectingFactoryBody node) { |
| return this[node]; |
| } |
| |
| void defineTarget(Node node, JumpTarget target) { |
| if (_definedTargets == null) { |
| _definedTargets = new Maplet<Node, JumpTarget>(); |
| } |
| _definedTargets[node] = target; |
| } |
| |
| void undefineTarget(Node node) { |
| if (_definedTargets != null) { |
| _definedTargets.remove(node); |
| if (_definedTargets.isEmpty) { |
| _definedTargets = null; |
| } |
| } |
| } |
| |
| JumpTarget getTargetDefinition(Node node) { |
| return _definedTargets != null ? _definedTargets[node] : null; |
| } |
| |
| void registerTargetOf(GotoStatement node, JumpTarget target) { |
| if (_usedTargets == null) { |
| _usedTargets = new Maplet<GotoStatement, JumpTarget>(); |
| } |
| _usedTargets[node] = target; |
| } |
| |
| JumpTarget getTargetOf(GotoStatement node) { |
| return _usedTargets != null ? _usedTargets[node] : null; |
| } |
| |
| void defineLabel(Label label, LabelDefinition target) { |
| if (_definedLabels == null) { |
| _definedLabels = new Maplet<Label, LabelDefinition>(); |
| } |
| _definedLabels[label] = target; |
| } |
| |
| void undefineLabel(Label label) { |
| if (_definedLabels != null) { |
| _definedLabels.remove(label); |
| if (_definedLabels.isEmpty) { |
| _definedLabels = null; |
| } |
| } |
| } |
| |
| LabelDefinition getLabelDefinition(Label label) { |
| return _definedLabels != null ? _definedLabels[label] : null; |
| } |
| |
| void registerTargetLabel(GotoStatement node, LabelDefinition label) { |
| assert(node.target != null); |
| if (_targetLabels == null) { |
| _targetLabels = new Maplet<GotoStatement, LabelDefinition>(); |
| } |
| _targetLabels[node] = label; |
| } |
| |
| LabelDefinition getTargetLabel(GotoStatement node) { |
| assert(node.target != null); |
| return _targetLabels != null ? _targetLabels[node] : null; |
| } |
| } |
| |
| class ResolverTask extends CompilerTask { |
| final ConstantCompiler constantCompiler; |
| |
| ResolverTask(Compiler compiler, this.constantCompiler) : super(compiler); |
| |
| String get name => 'Resolver'; |
| |
| TreeElements resolve(Element element) { |
| return measure(() { |
| if (Elements.isErroneousElement(element)) return null; |
| |
| processMetadata([result]) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| metadata.ensureResolved(compiler); |
| } |
| return result; |
| } |
| |
| ElementKind kind = element.kind; |
| if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR) || |
| identical(kind, ElementKind.FUNCTION) || |
| identical(kind, ElementKind.GETTER) || |
| identical(kind, ElementKind.SETTER)) { |
| return processMetadata(resolveMethodElement(element)); |
| } |
| |
| if (identical(kind, ElementKind.FIELD)) { |
| return processMetadata(resolveField(element)); |
| } |
| if (element.isClass) { |
| ClassElement cls = element; |
| cls.ensureResolved(compiler); |
| return processMetadata(); |
| } else if (element.isTypedef) { |
| TypedefElement typdef = element; |
| return processMetadata(resolveTypedef(typdef)); |
| } |
| |
| compiler.unimplemented(element, "resolve($element)"); |
| }); |
| } |
| |
| void resolveRedirectingConstructor(InitializerResolver resolver, |
| Node node, |
| FunctionElement constructor, |
| FunctionElement redirection) { |
| assert(invariant(node, constructor.isImplementation, |
| message: 'Redirecting constructors must be resolved on implementation ' |
| 'elements.')); |
| Setlet<FunctionElement> seen = new Setlet<FunctionElement>(); |
| seen.add(constructor); |
| while (redirection != null) { |
| // Ensure that we follow redirections through implementation elements. |
| redirection = redirection.implementation; |
| if (seen.contains(redirection)) { |
| resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE); |
| return; |
| } |
| seen.add(redirection); |
| redirection = resolver.visitor.resolveConstructorRedirection(redirection); |
| } |
| } |
| |
| void checkMatchingPatchParameters(FunctionElement origin, |
| Link<Element> originParameters, |
| Link<Element> patchParameters) { |
| while (!originParameters.isEmpty) { |
| ParameterElementX originParameter = originParameters.head; |
| ParameterElementX patchParameter = patchParameters.head; |
| // TODO(johnniwinther): Remove the conditional patching when we never |
| // resolve the same method twice. |
| if (!originParameter.isPatched) { |
| originParameter.applyPatch(patchParameter); |
| } else { |
| assert(invariant(origin, originParameter.patch == patchParameter, |
| message: "Inconsistent repatch of $originParameter.")); |
| } |
| DartType originParameterType = originParameter.computeType(compiler); |
| DartType patchParameterType = patchParameter.computeType(compiler); |
| if (originParameterType != patchParameterType) { |
| compiler.reportError( |
| originParameter.parseNode(compiler), |
| MessageKind.PATCH_PARAMETER_TYPE_MISMATCH, |
| {'methodName': origin.name, |
| 'parameterName': originParameter.name, |
| 'originParameterType': originParameterType, |
| 'patchParameterType': patchParameterType}); |
| compiler.reportInfo(patchParameter, |
| MessageKind.PATCH_POINT_TO_PARAMETER, |
| {'parameterName': patchParameter.name}); |
| } else { |
| // Hack: Use unparser to test parameter equality. This only works |
| // because we are restricting patch uses and the approach cannot be used |
| // elsewhere. |
| |
| // The node contains the type, so there is a potential overlap. |
| // Therefore we only check the text if the types are identical. |
| String originParameterText = |
| originParameter.parseNode(compiler).toString(); |
| String patchParameterText = |
| patchParameter.parseNode(compiler).toString(); |
| if (originParameterText != patchParameterText |
| // We special case the list constructor because of the |
| // optional parameter. |
| && origin != compiler.unnamedListConstructor) { |
| compiler.reportError( |
| originParameter.parseNode(compiler), |
| MessageKind.PATCH_PARAMETER_MISMATCH, |
| {'methodName': origin.name, |
| 'originParameter': originParameterText, |
| 'patchParameter': patchParameterText}); |
| compiler.reportInfo(patchParameter, |
| MessageKind.PATCH_POINT_TO_PARAMETER, |
| {'parameterName': patchParameter.name}); |
| } |
| } |
| |
| originParameters = originParameters.tail; |
| patchParameters = patchParameters.tail; |
| } |
| } |
| |
| void checkMatchingPatchSignatures(FunctionElement origin, |
| FunctionElement patch) { |
| // TODO(johnniwinther): Show both origin and patch locations on errors. |
| FunctionExpression originTree = origin.node; |
| FunctionSignature originSignature = origin.functionSignature; |
| FunctionExpression patchTree = patch.node; |
| FunctionSignature patchSignature = patch.functionSignature; |
| |
| if (originSignature.type.returnType != patchSignature.type.returnType) { |
| compiler.withCurrentElement(patch, () { |
| Node errorNode = |
| patchTree.returnType != null ? patchTree.returnType : patchTree; |
| error(errorNode, MessageKind.PATCH_RETURN_TYPE_MISMATCH, |
| {'methodName': origin.name, |
| 'originReturnType': originSignature.type.returnType, |
| 'patchReturnType': patchSignature.type.returnType}); |
| }); |
| } |
| if (originSignature.requiredParameterCount != |
| patchSignature.requiredParameterCount) { |
| compiler.withCurrentElement(patch, () { |
| error(patchTree, |
| MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH, |
| {'methodName': origin.name, |
| 'originParameterCount': originSignature.requiredParameterCount, |
| 'patchParameterCount': patchSignature.requiredParameterCount}); |
| }); |
| } else { |
| checkMatchingPatchParameters(origin, |
| originSignature.requiredParameters, |
| patchSignature.requiredParameters); |
| } |
| if (originSignature.optionalParameterCount != 0 && |
| patchSignature.optionalParameterCount != 0) { |
| if (originSignature.optionalParametersAreNamed != |
| patchSignature.optionalParametersAreNamed) { |
| compiler.withCurrentElement(patch, () { |
| error(patchTree, |
| MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH, |
| {'methodName': origin.name}); |
| }); |
| } |
| } |
| if (originSignature.optionalParameterCount != |
| patchSignature.optionalParameterCount) { |
| compiler.withCurrentElement(patch, () { |
| error(patchTree, |
| MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH, |
| {'methodName': origin.name, |
| 'originParameterCount': originSignature.optionalParameterCount, |
| 'patchParameterCount': patchSignature.optionalParameterCount}); |
| }); |
| } else { |
| checkMatchingPatchParameters(origin, |
| originSignature.optionalParameters, |
| patchSignature.optionalParameters); |
| } |
| } |
| |
| static void processAsyncMarker(Compiler compiler, |
| BaseFunctionElementX element) { |
| FunctionExpression functionExpression = element.node; |
| AsyncModifier asyncModifier = functionExpression.asyncModifier; |
| if (asyncModifier != null) { |
| if (!compiler.enableAsyncAwait) { |
| compiler.reportError(asyncModifier, |
| MessageKind.EXPERIMENTAL_ASYNC_AWAIT, |
| {'modifier': element.asyncMarker}); |
| } else if (!compiler.analyzeOnly) { |
| compiler.reportError(asyncModifier, |
| MessageKind.EXPERIMENTAL_ASYNC_AWAIT, |
| {'modifier': element.asyncMarker}); |
| } |
| |
| if (asyncModifier.isAsynchronous) { |
| element.asyncMarker = asyncModifier.isYielding |
| ? AsyncMarker.ASYNC_STAR : AsyncMarker.ASYNC; |
| } else { |
| element.asyncMarker = AsyncMarker.SYNC_STAR; |
| } |
| if (element.isAbstract) { |
| compiler.reportError(asyncModifier, |
| MessageKind.ASYNC_MODIFIER_ON_ABSTRACT_METHOD, |
| {'modifier': element.asyncMarker}); |
| } else if (element.isConstructor) { |
| compiler.reportError(asyncModifier, |
| MessageKind.ASYNC_MODIFIER_ON_CONSTRUCTOR, |
| {'modifier': element.asyncMarker}); |
| } else if (functionExpression.body.asReturn() != null && |
| element.asyncMarker.isYielding) { |
| compiler.reportError(asyncModifier, |
| MessageKind.YIELDING_MODIFIER_ON_ARROW_BODY, |
| {'modifier': element.asyncMarker}); |
| } |
| } |
| } |
| |
| TreeElements resolveMethodElement(FunctionElementX element) { |
| assert(invariant(element, element.isDeclaration)); |
| return compiler.withCurrentElement(element, () { |
| bool isConstructor = |
| identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR); |
| if (compiler.enqueuer.resolution.hasBeenResolved(element)) { |
| // TODO(karlklose): Remove the check for [isConstructor]. [elememts] |
| // should never be non-null, not even for constructors. |
| assert(invariant(element, element.isConstructor, |
| message: 'Non-constructor element $element ' |
| 'has already been analyzed.')); |
| return element.resolvedAst.elements; |
| } |
| if (element.isSynthesized) { |
| if (isConstructor) { |
| ResolutionRegistry registry = |
| new ResolutionRegistry(compiler, element); |
| ConstructorElement constructor = element.asFunctionElement(); |
| ConstructorElement target = constructor.definingConstructor; |
| // Ensure the signature of the synthesized element is |
| // resolved. This is the only place where the resolver is |
| // seeing this element. |
| element.computeSignature(compiler); |
| if (!target.isErroneous) { |
| registry.registerStaticUse(target); |
| registry.registerImplicitSuperCall(target); |
| } |
| return registry.mapping; |
| } else { |
| assert(element.isDeferredLoaderGetter); |
| return _ensureTreeElements(element); |
| } |
| } |
| element.parseNode(compiler); |
| element.computeType(compiler); |
| processAsyncMarker(compiler, element); |
| if (element.isPatched) { |
| FunctionElementX patch = element.patch; |
| compiler.withCurrentElement(patch, () { |
| patch.parseNode(compiler); |
| patch.computeType(compiler); |
| }); |
| checkMatchingPatchSignatures(element, patch); |
| element = patch; |
| processAsyncMarker(compiler, element); |
| } |
| return compiler.withCurrentElement(element, () { |
| FunctionExpression tree = element.node; |
| if (tree.modifiers.isExternal) { |
| error(tree, MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION); |
| return null; |
| } |
| if (isConstructor || element.isFactoryConstructor) { |
| if (tree.returnType != null) { |
| error(tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE); |
| } |
| if (element.modifiers.isConst && |
| tree.hasBody() && |
| !tree.isRedirectingFactory) { |
| compiler.reportError(tree, MessageKind.CONST_CONSTRUCTOR_HAS_BODY); |
| } |
| } |
| |
| ResolverVisitor visitor = visitorFor(element); |
| ResolutionRegistry registry = visitor.registry; |
| registry.defineFunction(tree, element); |
| visitor.setupFunction(tree, element); |
| |
| if (isConstructor && !element.isForwardingConstructor) { |
| // 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); |
| if (redirection != null) { |
| resolveRedirectingConstructor(resolver, tree, element, redirection); |
| } |
| } else if (element.isForwardingConstructor) { |
| // Initializers will be checked on the original constructor. |
| } else if (tree.initializers != null) { |
| error(tree, MessageKind.FUNCTION_WITH_INITIALIZER); |
| } |
| |
| if (!compiler.analyzeSignaturesOnly || tree.isRedirectingFactory) { |
| // We need to analyze the redirecting factory bodies to ensure that |
| // we can analyze compile-time constants. |
| visitor.visit(tree.body); |
| } |
| |
| // Get the resolution tree and check that the resolved |
| // function doesn't use 'super' if it is mixed into another |
| // class. This is the part of the 'super' mixin check that |
| // happens when a function is resolved after the mixin |
| // application has been performed. |
| TreeElements resolutionTree = registry.mapping; |
| ClassElement enclosingClass = element.enclosingClass; |
| if (enclosingClass != null) { |
| // TODO(johnniwinther): Find another way to obtain mixin uses. |
| Iterable<MixinApplicationElement> mixinUses = |
| compiler.world.allMixinUsesOf(enclosingClass); |
| ClassElement mixin = enclosingClass; |
| for (MixinApplicationElement mixinApplication in mixinUses) { |
| checkMixinSuperUses(resolutionTree, mixinApplication, mixin); |
| } |
| } |
| return resolutionTree; |
| }); |
| }); |
| } |
| |
| /// Creates a [ResolverVisitor] for resolving an AST in context of [element]. |
| /// If [useEnclosingScope] is `true` then the initial scope of the visitor |
| /// does not include inner scope of [element]. |
| /// |
| /// This method should only be used by this library (or tests of |
| /// this library). |
| ResolverVisitor visitorFor(Element element, {bool useEnclosingScope: false}) { |
| return new ResolverVisitor(compiler, element, |
| new ResolutionRegistry(compiler, element), |
| useEnclosingScope: useEnclosingScope); |
| } |
| |
| TreeElements resolveField(FieldElementX element) { |
| VariableDefinitions tree = element.parseNode(compiler); |
| if(element.modifiers.isStatic && element.isTopLevel) { |
| error(element.modifiers.getStatic(), |
| MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC); |
| } |
| ResolverVisitor visitor = visitorFor(element); |
| ResolutionRegistry registry = visitor.registry; |
| // TODO(johnniwinther): Maybe remove this when placeholderCollector migrates |
| // to the backend ast. |
| registry.defineElement(tree.definitions.nodes.head, element); |
| // TODO(johnniwinther): Share the resolved type between all variables |
| // declared in the same declaration. |
| if (tree.type != null) { |
| element.variables.type = visitor.resolveTypeAnnotation(tree.type); |
| } else { |
| element.variables.type = const DynamicType(); |
| } |
| |
| Expression initializer = element.initializer; |
| Modifiers modifiers = element.modifiers; |
| if (initializer != null) { |
| // TODO(johnniwinther): Avoid analyzing initializers if |
| // [Compiler.analyzeSignaturesOnly] is set. |
| visitor.visit(initializer); |
| } else if (modifiers.isConst) { |
| compiler.reportError(element, MessageKind.CONST_WITHOUT_INITIALIZER); |
| } else if (modifiers.isFinal && !element.isInstanceMember) { |
| compiler.reportError(element, MessageKind.FINAL_WITHOUT_INITIALIZER); |
| } else { |
| registry.registerInstantiatedClass(compiler.nullClass); |
| } |
| |
| if (Elements.isStaticOrTopLevelField(element)) { |
| visitor.addDeferredAction(element, () { |
| if (element.modifiers.isConst) { |
| constantCompiler.compileConstant(element); |
| } else { |
| constantCompiler.compileVariable(element); |
| } |
| }); |
| if (initializer != null) { |
| if (!element.modifiers.isConst) { |
| // TODO(johnniwinther): Determine the const-ness eagerly to avoid |
| // unnecessary registrations. |
| registry.registerLazyField(); |
| } |
| } |
| } |
| |
| // Perform various checks as side effect of "computing" the type. |
| element.computeType(compiler); |
| |
| return registry.mapping; |
| } |
| |
| DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) { |
| DartType type = resolveReturnType(element, annotation); |
| if (type.isVoid) { |
| error(annotation, MessageKind.VOID_NOT_ALLOWED); |
| } |
| return type; |
| } |
| |
| DartType resolveReturnType(Element element, TypeAnnotation annotation) { |
| if (annotation == null) return const DynamicType(); |
| DartType result = visitorFor(element).resolveTypeAnnotation(annotation); |
| if (result == null) { |
| // TODO(karklose): warning. |
| return const DynamicType(); |
| } |
| return result; |
| } |
| |
| void resolveRedirectionChain(ConstructorElementX constructor, |
| Spannable node) { |
| ConstructorElementX target = constructor; |
| InterfaceType targetType; |
| List<Element> seen = new List<Element>(); |
| // Follow the chain of redirections and check for cycles. |
| while (target.isRedirectingFactory) { |
| if (target.internalEffectiveTarget != null) { |
| // We found a constructor that already has been processed. |
| targetType = target.effectiveTargetType; |
| assert(invariant(target, targetType != null, |
| message: 'Redirection target type has not been computed for ' |
| '$target')); |
| target = target.internalEffectiveTarget; |
| break; |
| } |
| |
| Element nextTarget = target.immediateRedirectionTarget; |
| if (seen.contains(nextTarget)) { |
| error(node, MessageKind.CYCLIC_REDIRECTING_FACTORY); |
| break; |
| } |
| seen.add(target); |
| target = nextTarget; |
| } |
| |
| if (targetType == null) { |
| assert(!target.isRedirectingFactory); |
| targetType = target.enclosingClass.thisType; |
| } |
| |
| // [target] is now the actual target of the redirections. Run through |
| // the constructors again and set their [redirectionTarget], so that we |
| // do not have to run the loop for these constructors again. Furthermore, |
| // compute [redirectionTargetType] for each factory by computing the |
| // substitution of the target type with respect to the factory type. |
| while (!seen.isEmpty) { |
| ConstructorElementX factory = seen.removeLast(); |
| |
| // [factory] must already be analyzed but the [TreeElements] might not |
| // have been stored in the enqueuer cache yet. |
| // TODO(johnniwinther): Store [TreeElements] in the cache before |
| // resolution of the element. |
| TreeElements treeElements = factory.treeElements; |
| assert(invariant(node, treeElements != null, |
| message: 'No TreeElements cached for $factory.')); |
| FunctionExpression functionNode = factory.parseNode(compiler); |
| RedirectingFactoryBody redirectionNode = functionNode.body; |
| InterfaceType factoryType = treeElements.getType(redirectionNode); |
| |
| targetType = targetType.substByContext(factoryType); |
| factory.effectiveTarget = target; |
| factory.effectiveTargetType = targetType; |
| } |
| } |
| |
| /** |
| * Load and resolve the supertypes of [cls]. |
| * |
| * Warning: do not call this method directly. It should only be |
| * called by [resolveClass] and [ClassSupertypeResolver]. |
| */ |
| void loadSupertypes(BaseClassElementX cls, Spannable from) { |
| compiler.withCurrentElement(cls, () => measure(() { |
| if (cls.supertypeLoadState == STATE_DONE) return; |
| if (cls.supertypeLoadState == STATE_STARTED) { |
| compiler.reportError(from, MessageKind.CYCLIC_CLASS_HIERARCHY, |
| {'className': cls.name}); |
| cls.supertypeLoadState = STATE_DONE; |
| cls.hasIncompleteHierarchy = true; |
| cls.allSupertypesAndSelf = |
| compiler.objectClass.allSupertypesAndSelf.extendClass( |
| cls.computeType(compiler)); |
| cls.supertype = cls.allSupertypes.head; |
| assert(invariant(from, cls.supertype != null, |
| message: 'Missing supertype on cyclic class $cls.')); |
| cls.interfaces = const Link<DartType>(); |
| return; |
| } |
| cls.supertypeLoadState = STATE_STARTED; |
| compiler.withCurrentElement(cls, () { |
| // TODO(ahe): Cache the node in cls. |
| cls.parseNode(compiler).accept( |
| new ClassSupertypeResolver(compiler, cls)); |
| if (cls.supertypeLoadState != STATE_DONE) { |
| cls.supertypeLoadState = STATE_DONE; |
| } |
| }); |
| })); |
| } |
| |
| // TODO(johnniwinther): Remove this queue when resolution has been split into |
| // syntax and semantic resolution. |
| TypeDeclarationElement currentlyResolvedTypeDeclaration; |
| Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>(); |
| Queue<ClassElement> pendingClassesToBePostProcessed = |
| new Queue<ClassElement>(); |
| |
| /// Resolve [element] using [resolveTypeDeclaration]. |
| /// |
| /// This methods ensure that class declarations encountered through type |
| /// annotations during the resolution of [element] are resolved after |
| /// [element] has been resolved. |
| // TODO(johnniwinther): Encapsulate this functionality in a |
| // 'TypeDeclarationResolver'. |
| _resolveTypeDeclaration(TypeDeclarationElement element, |
| resolveTypeDeclaration()) { |
| return compiler.withCurrentElement(element, () { |
| return measure(() { |
| TypeDeclarationElement previousResolvedTypeDeclaration = |
| currentlyResolvedTypeDeclaration; |
| currentlyResolvedTypeDeclaration = element; |
| var result = resolveTypeDeclaration(); |
| if (previousResolvedTypeDeclaration == null) { |
| do { |
| while (!pendingClassesToBeResolved.isEmpty) { |
| pendingClassesToBeResolved.removeFirst().ensureResolved(compiler); |
| } |
| while (!pendingClassesToBePostProcessed.isEmpty) { |
| _postProcessClassElement( |
| pendingClassesToBePostProcessed.removeFirst()); |
| } |
| } while (!pendingClassesToBeResolved.isEmpty); |
| assert(pendingClassesToBeResolved.isEmpty); |
| assert(pendingClassesToBePostProcessed.isEmpty); |
| } |
| currentlyResolvedTypeDeclaration = previousResolvedTypeDeclaration; |
| return result; |
| }); |
| }); |
| } |
| |
| /** |
| * Resolve the class [element]. |
| * |
| * Before calling this method, [element] was constructed by the |
| * scanner and most fields are null or empty. This method fills in |
| * these fields and also ensure that the supertypes of [element] are |
| * resolved. |
| * |
| * Warning: Do not call this method directly. Instead use |
| * [:element.ensureResolved(compiler):]. |
| */ |
| TreeElements resolveClass(BaseClassElementX element) { |
| return _resolveTypeDeclaration(element, () { |
| // TODO(johnniwinther): Store the mapping in the resolution enqueuer. |
| ResolutionRegistry registry = new ResolutionRegistry(compiler, element); |
| resolveClassInternal(element, registry); |
| return element.treeElements; |
| }); |
| } |
| |
| void _ensureClassWillBeResolved(ClassElement element) { |
| if (currentlyResolvedTypeDeclaration == null) { |
| element.ensureResolved(compiler); |
| } else { |
| pendingClassesToBeResolved.add(element); |
| } |
| } |
| |
| void resolveClassInternal(BaseClassElementX element, |
| ResolutionRegistry registry) { |
| if (!element.isPatch) { |
| compiler.withCurrentElement(element, () => measure(() { |
| assert(element.resolutionState == STATE_NOT_STARTED); |
| element.resolutionState = STATE_STARTED; |
| Node tree = element.parseNode(compiler); |
| loadSupertypes(element, tree); |
| |
| ClassResolverVisitor visitor = |
| new ClassResolverVisitor(compiler, element, registry); |
| visitor.visit(tree); |
| element.resolutionState = STATE_DONE; |
| compiler.onClassResolved(element); |
| pendingClassesToBePostProcessed.add(element); |
| })); |
| if (element.isPatched) { |
| // Ensure handling patch after origin. |
| element.patch.ensureResolved(compiler); |
| } |
| } else { // Handle patch classes: |
| element.resolutionState = STATE_STARTED; |
| // Ensure handling origin before patch. |
| element.origin.ensureResolved(compiler); |
| // Ensure that the type is computed. |
| element.computeType(compiler); |
| // Copy class hierarchy from origin. |
| element.supertype = element.origin.supertype; |
| element.interfaces = element.origin.interfaces; |
| element.allSupertypesAndSelf = element.origin.allSupertypesAndSelf; |
| // Stepwise assignment to ensure invariant. |
| element.supertypeLoadState = STATE_STARTED; |
| element.supertypeLoadState = STATE_DONE; |
| element.resolutionState = STATE_DONE; |
| // TODO(johnniwinther): Check matching type variables and |
| // empty extends/implements clauses. |
| } |
| } |
| |
| void _postProcessClassElement(BaseClassElementX element) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| metadata.ensureResolved(compiler); |
| if (!element.isProxy && |
| metadata.constant.value == compiler.proxyConstant) { |
| element.isProxy = true; |
| } |
| } |
| |
| // Force resolution of metadata on non-instance members since they may be |
| // inspected by the backend while emitting. Metadata on instance members is |
| // handled as a result of processing instantiated class members in the |
| // enqueuer. |
| // TODO(ahe): Avoid this eager resolution. |
| element.forEachMember((_, Element member) { |
| if (!member.isInstanceMember) { |
| compiler.withCurrentElement(member, () { |
| for (MetadataAnnotation metadata in member.metadata) { |
| metadata.ensureResolved(compiler); |
| } |
| }); |
| } |
| }); |
| |
| computeClassMember(element, Compiler.CALL_OPERATOR_NAME); |
| } |
| |
| void computeClassMembers(ClassElement element) { |
| MembersCreator.computeAllClassMembers(compiler, element); |
| } |
| |
| void computeClassMember(ClassElement element, String name) { |
| MembersCreator.computeClassMembersByName(compiler, element, name); |
| } |
| |
| void checkClass(ClassElement element) { |
| computeClassMembers(element); |
| if (element.isMixinApplication) { |
| checkMixinApplication(element); |
| } else { |
| checkClassMembers(element); |
| } |
| } |
| |
| void checkMixinApplication(MixinApplicationElementX mixinApplication) { |
| Modifiers modifiers = mixinApplication.modifiers; |
| int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT; |
| if (illegalFlags != 0) { |
| Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags); |
| compiler.reportError( |
| modifiers, |
| MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS, |
| {'modifiers': illegalModifiers}); |
| } |
| |
| // In case of cyclic mixin applications, the mixin chain will have |
| // been cut. If so, we have already reported the error to the |
| // user so we just return from here. |
| ClassElement mixin = mixinApplication.mixin; |
| if (mixin == null) return; |
| |
| // Check that we're not trying to use Object as a mixin. |
| if (mixin.superclass == null) { |
| compiler.reportError(mixinApplication, |
| MessageKind.ILLEGAL_MIXIN_OBJECT); |
| // Avoid reporting additional errors for the Object class. |
| return; |
| } |
| |
| if (mixin.isEnumClass) { |
| // Mixing in an enum has already caused a compile-time error. |
| return; |
| } |
| |
| // Check that the mixed in class has Object as its superclass. |
| if (!mixin.superclass.isObject) { |
| compiler.reportError(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS); |
| } |
| |
| // Check that the mixed in class doesn't have any constructors and |
| // make sure we aren't mixing in methods that use 'super'. |
| mixin.forEachLocalMember((AstElement member) { |
| if (member.isGenerativeConstructor && !member.isSynthesized) { |
| compiler.reportError(member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR); |
| } else { |
| // Get the resolution tree and check that the resolved member |
| // doesn't use 'super'. This is the part of the 'super' mixin |
| // check that happens when a function is resolved before the |
| // mixin application has been performed. |
| // TODO(johnniwinther): Obtain the [TreeElements] for [member] |
| // differently. |
| if (compiler.enqueuer.resolution.hasBeenResolved(member)) { |
| checkMixinSuperUses( |
| member.resolvedAst.elements, |
| mixinApplication, |
| mixin); |
| } |
| } |
| }); |
| } |
| |
| void checkMixinSuperUses(TreeElements resolutionTree, |
| MixinApplicationElement mixinApplication, |
| ClassElement mixin) { |
| // TODO(johnniwinther): Avoid the use of [TreeElements] here. |
| if (resolutionTree == null) return; |
| Iterable<Node> superUses = resolutionTree.superUses; |
| if (superUses.isEmpty) return; |
| compiler.reportError(mixinApplication, |
| MessageKind.ILLEGAL_MIXIN_WITH_SUPER, |
| {'className': mixin.name}); |
| // Show the user the problematic uses of 'super' in the mixin. |
| for (Node use in superUses) { |
| compiler.reportInfo( |
| use, |
| MessageKind.ILLEGAL_MIXIN_SUPER_USE); |
| } |
| } |
| |
| void checkClassMembers(ClassElement cls) { |
| assert(invariant(cls, cls.isDeclaration)); |
| if (cls.isObject) return; |
| // TODO(johnniwinther): Should this be done on the implementation element as |
| // well? |
| List<Element> constConstructors = <Element>[]; |
| List<Element> nonFinalInstanceFields = <Element>[]; |
| cls.forEachMember((holder, member) { |
| compiler.withCurrentElement(member, () { |
| // Perform various checks as side effect of "computing" the type. |
| member.computeType(compiler); |
| |
| // Check modifiers. |
| if (member.isFunction && member.modifiers.isFinal) { |
| compiler.reportError( |
| member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER); |
| } |
| if (member.isConstructor) { |
| final mismatchedFlagsBits = |
| member.modifiers.flags & |
| (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT); |
| if (mismatchedFlagsBits != 0) { |
| final mismatchedFlags = |
| new Modifiers.withFlags(null, mismatchedFlagsBits); |
| compiler.reportError( |
| member, |
| MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS, |
| {'modifiers': mismatchedFlags}); |
| } |
| if (member.modifiers.isConst) { |
| constConstructors.add(member); |
| } |
| } |
| if (member.isField) { |
| if (member.modifiers.isConst && !member.modifiers.isStatic) { |
| compiler.reportError( |
| member, MessageKind.ILLEGAL_CONST_FIELD_MODIFIER); |
| } |
| if (!member.modifiers.isStatic && !member.modifiers.isFinal) { |
| nonFinalInstanceFields.add(member); |
| } |
| } |
| checkAbstractField(member); |
| checkUserDefinableOperator(member); |
| }); |
| }); |
| if (!constConstructors.isEmpty && !nonFinalInstanceFields.isEmpty) { |
| Spannable span = constConstructors.length > 1 |
| ? cls : constConstructors[0]; |
| compiler.reportError(span, |
| MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS, |
| {'className': cls.name}); |
| if (constConstructors.length > 1) { |
| for (Element constructor in constConstructors) { |
| compiler.reportInfo(constructor, |
| MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_CONSTRUCTOR); |
| } |
| } |
| for (Element field in nonFinalInstanceFields) { |
| compiler.reportInfo(field, |
| MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_FIELD); |
| } |
| } |
| } |
| |
| void checkAbstractField(Element member) { |
| // Only check for getters. The test can only fail if there is both a setter |
| // and a getter with the same name, and we only need to check each abstract |
| // field once, so we just ignore setters. |
| if (!member.isGetter) return; |
| |
| // Find the associated abstract field. |
| ClassElement classElement = member.enclosingClass; |
| Element lookupElement = classElement.lookupLocalMember(member.name); |
| if (lookupElement == null) { |
| compiler.internalError(member, |
| "No abstract field for accessor"); |
| } else if (!identical(lookupElement.kind, ElementKind.ABSTRACT_FIELD)) { |
| compiler.internalError(member, |
| "Inaccessible abstract field for accessor"); |
| } |
| AbstractFieldElement field = lookupElement; |
| |
| FunctionElementX getter = field.getter; |
| if (getter == null) return; |
| FunctionElementX setter = field.setter; |
| if (setter == null) return; |
| int getterFlags = getter.modifiers.flags | Modifiers.FLAG_ABSTRACT; |
| int setterFlags = setter.modifiers.flags | Modifiers.FLAG_ABSTRACT; |
| if (!identical(getterFlags, setterFlags)) { |
| final mismatchedFlags = |
| new Modifiers.withFlags(null, getterFlags ^ setterFlags); |
| compiler.reportError( |
| field.getter, |
| MessageKind.GETTER_MISMATCH, |
| {'modifiers': mismatchedFlags}); |
| compiler.reportError( |
| field.setter, |
| MessageKind.SETTER_MISMATCH, |
| {'modifiers': mismatchedFlags}); |
| } |
| } |
| |
| void checkUserDefinableOperator(Element member) { |
| FunctionElement function = member.asFunctionElement(); |
| if (function == null) return; |
| String value = member.name; |
| if (value == null) return; |
| if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return; |
| |
| bool isMinus = false; |
| int requiredParameterCount; |
| MessageKind messageKind; |
| if (identical(value, 'unary-')) { |
| isMinus = true; |
| messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 0; |
| } else if (isMinusOperator(value)) { |
| isMinus = true; |
| messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 1; |
| } else if (isUnaryOperator(value)) { |
| messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 0; |
| } else if (isBinaryOperator(value)) { |
| messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 1; |
| if (identical(value, '==')) checkOverrideHashCode(member); |
| } else if (isTernaryOperator(value)) { |
| messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 2; |
| } else { |
| compiler.internalError(function, |
| 'Unexpected user defined operator $value'); |
| } |
| checkArity(function, requiredParameterCount, messageKind, isMinus); |
| } |
| |
| void checkOverrideHashCode(FunctionElement operatorEquals) { |
| if (operatorEquals.isAbstract) return; |
| ClassElement cls = operatorEquals.enclosingClass; |
| Element hashCodeImplementation = |
| cls.lookupLocalMember('hashCode'); |
| if (hashCodeImplementation != null) return; |
| compiler.reportHint( |
| operatorEquals, MessageKind.OVERRIDE_EQUALS_NOT_HASH_CODE, |
| {'class': cls.name}); |
| } |
| |
| void checkArity(FunctionElement function, |
| int requiredParameterCount, MessageKind messageKind, |
| bool isMinus) { |
| FunctionExpression node = function.node; |
| FunctionSignature signature = function.functionSignature; |
| if (signature.requiredParameterCount != requiredParameterCount) { |
| Node errorNode = node; |
| if (node.parameters != null) { |
| if (isMinus || |
| signature.requiredParameterCount < requiredParameterCount) { |
| // If there are too few parameters, point to the whole parameter list. |
| // For instance |
| // |
| // int operator +() {} |
| // ^^ |
| // |
| // int operator []=(value) {} |
| // ^^^^^^^ |
| // |
| // For operator -, always point the whole parameter list, like |
| // |
| // int operator -(a, b) {} |
| // ^^^^^^ |
| // |
| // instead of |
| // |
| // int operator -(a, b) {} |
| // ^ |
| // |
| // since the correction might not be to remove 'b' but instead to |
| // remove 'a, b'. |
| errorNode = node.parameters; |
| } else { |
| errorNode = node.parameters.nodes.skip(requiredParameterCount).head; |
| } |
| } |
| compiler.reportError( |
| errorNode, messageKind, {'operatorName': function.name}); |
| } |
| if (signature.optionalParameterCount != 0) { |
| Node errorNode = |
| node.parameters.nodes.skip(signature.requiredParameterCount).head; |
| if (signature.optionalParametersAreNamed) { |
| compiler.reportError( |
| errorNode, |
| MessageKind.OPERATOR_NAMED_PARAMETERS, |
| {'operatorName': function.name}); |
| } else { |
| compiler.reportError( |
| errorNode, |
| MessageKind.OPERATOR_OPTIONAL_PARAMETERS, |
| {'operatorName': function.name}); |
| } |
| } |
| } |
| |
| reportErrorWithContext(Element errorneousElement, |
| MessageKind errorMessage, |
| Element contextElement, |
| MessageKind contextMessage) { |
| compiler.reportError( |
| errorneousElement, |
| errorMessage, |
| {'memberName': contextElement.name, |
| 'className': contextElement.enclosingClass.name}); |
| compiler.reportInfo(contextElement, contextMessage); |
| } |
| |
| |
| FunctionSignature resolveSignature(FunctionElementX element) { |
| MessageKind defaultValuesError = null; |
| if (element.isFactoryConstructor) { |
| FunctionExpression body = element.parseNode(compiler); |
| if (body.isRedirectingFactory) { |
| defaultValuesError = MessageKind.REDIRECTING_FACTORY_WITH_DEFAULT; |
| } |
| } |
| return compiler.withCurrentElement(element, () { |
| FunctionExpression node = |
| compiler.parser.measure(() => element.parseNode(compiler)); |
| return measure(() => SignatureResolver.analyze( |
| compiler, node.parameters, node.returnType, element, |
| new ResolutionRegistry(compiler, element), |
| defaultValuesError: defaultValuesError, |
| createRealParameters: true)); |
| }); |
| } |
| |
| TreeElements resolveTypedef(TypedefElementX element) { |
| if (element.isResolved) return element.treeElements; |
| compiler.world.allTypedefs.add(element); |
| return _resolveTypeDeclaration(element, () { |
| ResolutionRegistry registry = new ResolutionRegistry(compiler, element); |
| return compiler.withCurrentElement(element, () { |
| return measure(() { |
| assert(element.resolutionState == STATE_NOT_STARTED); |
| element.resolutionState = STATE_STARTED; |
| Typedef node = |
| compiler.parser.measure(() => element.parseNode(compiler)); |
| TypedefResolverVisitor visitor = |
| new TypedefResolverVisitor(compiler, element, registry); |
| visitor.visit(node); |
| element.resolutionState = STATE_DONE; |
| return registry.mapping; |
| }); |
| }); |
| }); |
| } |
| |
| void resolveMetadataAnnotation(MetadataAnnotationX annotation) { |
| compiler.withCurrentElement(annotation.annotatedElement, () => measure(() { |
| assert(annotation.resolutionState == STATE_NOT_STARTED); |
| annotation.resolutionState = STATE_STARTED; |
| |
| Node node = annotation.parseNode(compiler); |
| Element annotatedElement = annotation.annotatedElement; |
| AnalyzableElement context = annotatedElement.analyzableElement; |
| ClassElement classElement = annotatedElement.enclosingClass; |
| if (classElement != null) { |
| // The annotation is resolved in the scope of [classElement]. |
| classElement.ensureResolved(compiler); |
| } |
| assert(invariant(node, context != null, |
| message: "No context found for metadata annotation " |
| "on $annotatedElement.")); |
| ResolverVisitor visitor = visitorFor(context, useEnclosingScope: true); |
| ResolutionRegistry registry = visitor.registry; |
| node.accept(visitor); |
| // TODO(johnniwinther): Avoid passing the [TreeElements] to |
| // [compileMetadata]. |
| annotation.constant = |
| constantCompiler.compileMetadata(annotation, node, registry.mapping); |
| // TODO(johnniwinther): Register the relation between the annotation |
| // and the annotated element instead. This will allow the backend to |
| // retrieve the backend constant and only register metadata on the |
| // elements for which it is needed. (Issue 17732). |
| registry.registerMetadataConstant(annotation, annotatedElement); |
| annotation.resolutionState = STATE_DONE; |
| })); |
| } |
| |
| error(Spannable node, MessageKind kind, [arguments = const {}]) { |
| // TODO(ahe): Make non-fatal. |
| compiler.reportFatalError(node, kind, arguments); |
| } |
| |
| Link<MetadataAnnotation> resolveMetadata(Element element, |
| VariableDefinitions node) { |
| LinkBuilder<MetadataAnnotation> metadata = |
| new LinkBuilder<MetadataAnnotation>(); |
| for (Metadata annotation in node.metadata.nodes) { |
| ParameterMetadataAnnotation metadataAnnotation = |
| new ParameterMetadataAnnotation(annotation); |
| metadataAnnotation.annotatedElement = element; |
| metadata.addLast(metadataAnnotation.ensureResolved(compiler)); |
| } |
| return metadata.toLink(); |
| } |
| } |
| |
| class InitializerResolver { |
| final ResolverVisitor visitor; |
| final Map<Element, Node> initialized; |
| Link<Node> initializers; |
| bool hasSuper; |
| |
| InitializerResolver(this.visitor) |
| : initialized = new Map<Element, Node>(), hasSuper = false; |
| |
| ResolutionRegistry get registry => visitor.registry; |
| |
| error(Node node, MessageKind kind, [arguments = const {}]) { |
| visitor.error(node, kind, arguments); |
| } |
| |
| warning(Node node, MessageKind kind, [arguments = const {}]) { |
| visitor.warning(node, kind, arguments); |
| } |
| |
| bool isFieldInitializer(SendSet node) { |
| if (node.selector.asIdentifier() == null) return false; |
| if (node.receiver == null) return true; |
| if (node.receiver.asIdentifier() == null) return false; |
| return node.receiver.asIdentifier().isThis(); |
| } |
| |
| reportDuplicateInitializerError(Element field, Node init, Node existing) { |
| visitor.compiler.reportError( |
| init, |
| MessageKind.DUPLICATE_INITIALIZER, {'fieldName': field.name}); |
| visitor.compiler.reportInfo( |
| existing, |
| MessageKind.ALREADY_INITIALIZED, {'fieldName': field.name}); |
| } |
| |
| void checkForDuplicateInitializers(FieldElementX field, Node init) { |
| // [field] can be null if it could not be resolved. |
| if (field == null) return; |
| String name = field.name; |
| if (initialized.containsKey(field)) { |
| reportDuplicateInitializerError(field, init, initialized[field]); |
| } else if (field.isFinal) { |
| field.parseNode(visitor.compiler); |
| Expression initializer = field.initializer; |
| if (initializer != null) { |
| reportDuplicateInitializerError(field, init, initializer); |
| } |
| } |
| initialized[field] = init; |
| } |
| |
| void resolveFieldInitializer(FunctionElement constructor, 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; |
| if (isFieldInitializer(init)) { |
| target = constructor.enclosingClass.lookupLocalMember(name); |
| if (target == null) { |
| error(selector, MessageKind.CANNOT_RESOLVE, {'name': name}); |
| } else if (target.kind != ElementKind.FIELD) { |
| error(selector, MessageKind.NOT_A_FIELD, {'fieldName': name}); |
| } else if (!target.isInstanceMember) { |
| error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name}); |
| } |
| } else { |
| error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); |
| } |
| registry.useElement(init, target); |
| 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); |
| } else { |
| return lookupTarget.supertype.element; |
| } |
| } |
| return lookupTarget; |
| } |
| |
| Element resolveSuperOrThisForSend(FunctionElement constructor, |
| FunctionExpression functionNode, |
| Send call) { |
| // Resolve the selector and the arguments. |
| ResolverTask resolver = visitor.compiler.resolver; |
| visitor.inStaticContext(() { |
| visitor.resolveSelector(call, null); |
| visitor.resolveArguments(call.argumentsNode); |
| }); |
| Selector selector = registry.getSelector(call); |
| bool isSuperCall = Initializers.isSuperConstructorCall(call); |
| |
| ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, |
| isSuperCall, |
| call); |
| Selector constructorSelector = |
| visitor.getRedirectingThisOrSuperConstructorSelector(call); |
| FunctionElement calledConstructor = |
| lookupTarget.lookupConstructor(constructorSelector); |
| |
| final bool isImplicitSuperCall = false; |
| final String className = lookupTarget.name; |
| verifyThatConstructorMatchesCall(constructor, |
| calledConstructor, |
| selector, |
| isImplicitSuperCall, |
| call, |
| className, |
| constructorSelector); |
| |
| registry.useElement(call, calledConstructor); |
| registry.registerStaticUse(calledConstructor); |
| return calledConstructor; |
| } |
| |
| void resolveImplicitSuperConstructorSend(FunctionElement constructor, |
| FunctionExpression functionNode) { |
| // If the class has a super resolve the implicit super call. |
| ClassElement classElement = constructor.enclosingClass; |
| ClassElement superClass = classElement.superclass; |
| if (classElement != visitor.compiler.objectClass) { |
| assert(superClass != null); |
| assert(superClass.resolutionState == STATE_DONE); |
| String constructorName = ''; |
| Selector callToMatch = new Selector.call( |
| constructorName, |
| classElement.library, |
| 0); |
| |
| final bool isSuperCall = true; |
| ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, |
| isSuperCall, |
| functionNode); |
| Selector constructorSelector = new Selector.callDefaultConstructor( |
| visitor.enclosingElement.library); |
| Element calledConstructor = lookupTarget.lookupConstructor( |
| constructorSelector); |
| |
| final String className = lookupTarget.name; |
| final bool isImplicitSuperCall = true; |
| verifyThatConstructorMatchesCall(constructor, |
| calledConstructor, |
| callToMatch, |
| isImplicitSuperCall, |
| functionNode, |
| className, |
| constructorSelector); |
| registry.registerImplicitSuperCall(calledConstructor); |
| registry.registerStaticUse(calledConstructor); |
| } |
| } |
| |
| void verifyThatConstructorMatchesCall( |
| FunctionElement caller, |
| FunctionElement lookedupConstructor, |
| Selector call, |
| bool isImplicitSuperCall, |
| Node diagnosticNode, |
| String className, |
| Selector constructorSelector) { |
| if (lookedupConstructor == null |
| || !lookedupConstructor.isGenerativeConstructor) { |
| String fullConstructorName = Elements.constructorNameForDiagnostics( |
| className, |
| constructorSelector.name); |
| MessageKind kind = isImplicitSuperCall |
| ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT |
| : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR; |
| visitor.compiler.reportError( |
| diagnosticNode, kind, {'constructorName': fullConstructorName}); |
| } else { |
| lookedupConstructor.computeSignature(visitor.compiler); |
| if (!call.applies(lookedupConstructor, visitor.compiler.world)) { |
| MessageKind kind = isImplicitSuperCall |
| ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT |
| : MessageKind.NO_MATCHING_CONSTRUCTOR; |
| visitor.compiler.reportError(diagnosticNode, kind); |
| } else if (caller.isConst |
| && !lookedupConstructor.isConst) { |
| visitor.compiler.reportError( |
| diagnosticNode, MessageKind.CONST_CALLS_NON_CONST); |
| } |
| } |
| } |
| |
| /** |
| * Resolve all initializers of this constructor. In the case of a redirecting |
| * constructor, the resolved constructor's function element is returned. |
| */ |
| FunctionElement resolveInitializers(FunctionElement constructor, |
| FunctionExpression functionNode) { |
| // 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) { |
| if (element.isInitializingFormal) { |
| InitializingFormalElement initializingFormal = element; |
| checkForDuplicateInitializers(initializingFormal.fieldElement, |
| element.initializer); |
| } |
| }); |
| |
| if (functionNode.initializers == null) { |
| initializers = const Link<Node>(); |
| } else { |
| initializers = functionNode.initializers.nodes; |
| } |
| FunctionElement result; |
| bool resolvedSuper = false; |
| for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) { |
| if (link.head.asSendSet() != null) { |
| final SendSet init = link.head.asSendSet(); |
| resolveFieldInitializer(constructor, init); |
| } else if (link.head.asSend() != null) { |
| final Send call = link.head.asSend(); |
| if (call.argumentsNode == null) { |
| error(link.head, MessageKind.INVALID_INITIALIZER); |
| continue; |
| } |
| if (Initializers.isSuperConstructorCall(call)) { |
| if (resolvedSuper) { |
| error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER); |
| } |
| resolveSuperOrThisForSend(constructor, functionNode, call); |
| resolvedSuper = true; |
| } else if (Initializers.isConstructorRedirect(call)) { |
| // Check that there is no body (Language specification 7.5.1). If the |
| // constructor is also const, we already reported an error in |
| // [resolveMethodElement]. |
| if (functionNode.hasBody() && !constructor.isConst) { |
| error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY); |
| } |
| // Check that there are no other initializers. |
| if (!initializers.tail.isEmpty) { |
| error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER); |
| } |
| // Check that there are no field initializing parameters. |
| Compiler compiler = visitor.compiler; |
| FunctionSignature signature = constructor.functionSignature; |
| signature.forEachParameter((ParameterElement parameter) { |
| if (parameter.isInitializingFormal) { |
| Node node = parameter.node; |
| error(node, MessageKind.INITIALIZING_FORMAL_NOT_ALLOWED); |
| } |
| }); |
| return resolveSuperOrThisForSend(constructor, functionNode, call); |
| } else { |
| visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED); |
| return null; |
| } |
| } else { |
| error(link.head, MessageKind.INVALID_INITIALIZER); |
| } |
| } |
| if (!resolvedSuper) { |
| resolveImplicitSuperConstructorSend(constructor, functionNode); |
| } |
| return null; // If there was no redirection always return null. |
| } |
| } |
| |
| class CommonResolverVisitor<R> extends Visitor<R> { |
| final Compiler compiler; |
| |
| CommonResolverVisitor(Compiler this.compiler); |
| |
| R visitNode(Node node) { |
| internalError(node, |
| 'internal error: Unhandled node: ${node.getObjectDescription()}'); |
| return null; |
| } |
| |
| R visitEmptyStatement(Node node) => null; |
| |
| /** Convenience method for visiting nodes that may be null. */ |
| R visit(Node node) => (node == null) ? null : node.accept(this); |
| |
| void error(Spannable node, MessageKind kind, [Map arguments = const {}]) { |
| compiler.reportFatalError(node, kind, arguments); |
| } |
| |
| void warning(Spannable node, MessageKind kind, [Map arguments = const {}]) { |
| compiler.reportWarning(node, kind, arguments); |
| } |
| |
| void internalError(Spannable node, message) { |
| compiler.internalError(node, message); |
| } |
| |
| void addDeferredAction(Element element, DeferredAction action) { |
| compiler.enqueuer.resolution.addDeferredAction(element, action); |
| } |
| } |
| |
| abstract class LabelScope { |
| LabelScope get outer; |
| LabelDefinition lookup(String label); |
| } |
| |
| class LabeledStatementLabelScope implements LabelScope { |
| final LabelScope outer; |
| final Map<String, LabelDefinition> labels; |
| LabeledStatementLabelScope(this.outer, this.labels); |
| LabelDefinition lookup(String labelName) { |
| LabelDefinition label = labels[labelName]; |
| if (label != null) return label; |
| return outer.lookup(labelName); |
| } |
| } |
| |
| class SwitchLabelScope implements LabelScope { |
| final LabelScope outer; |
| final Map<String, LabelDefinition> caseLabels; |
| |
| SwitchLabelScope(this.outer, this.caseLabels); |
| |
| LabelDefinition lookup(String labelName) { |
| LabelDefinition result = caseLabels[labelName]; |
| if (result != null) return result; |
| return outer.lookup(labelName); |
| } |
| } |
| |
| class EmptyLabelScope implements LabelScope { |
| const EmptyLabelScope(); |
| LabelDefinition lookup(String label) => null; |
| LabelScope get outer { |
| throw 'internal error: empty label scope has no outer'; |
| } |
| } |
| |
| class StatementScope { |
| LabelScope labels; |
| Link<JumpTarget> breakTargetStack; |
| Link<JumpTarget> continueTargetStack; |
| // Used to provide different numbers to statements if one is inside the other. |
| // Can be used to make otherwise duplicate labels unique. |
| int nestingLevel = 0; |
| |
| StatementScope() |
| : labels = const EmptyLabelScope(), |
| breakTargetStack = const Link<JumpTarget>(), |
| continueTargetStack = const Link<JumpTarget>(); |
| |
| LabelDefinition lookupLabel(String label) { |
| return labels.lookup(label); |
| } |
| |
| JumpTarget currentBreakTarget() => |
| breakTargetStack.isEmpty ? null : breakTargetStack.head; |
| |
| JumpTarget currentContinueTarget() => |
| continueTargetStack.isEmpty ? null : continueTargetStack.head; |
| |
| void enterLabelScope(Map<String, LabelDefinition> elements) { |
| labels = new LabeledStatementLabelScope(labels, elements); |
| nestingLevel++; |
| } |
| |
| void exitLabelScope() { |
| nestingLevel--; |
| labels = labels.outer; |
| } |
| |
| void enterLoop(JumpTarget element) { |
| breakTargetStack = breakTargetStack.prepend(element); |
| continueTargetStack = continueTargetStack.prepend(element); |
| nestingLevel++; |
| } |
| |
| void exitLoop() { |
| nestingLevel--; |
| breakTargetStack = breakTargetStack.tail; |
| continueTargetStack = continueTargetStack.tail; |
| } |
| |
| void enterSwitch(JumpTarget breakElement, |
| Map<String, LabelDefinition> continueElements) { |
| breakTargetStack = breakTargetStack.prepend(breakElement); |
| labels = new SwitchLabelScope(labels, continueElements); |
| nestingLevel++; |
| } |
| |
| void exitSwitch() { |
| nestingLevel--; |
| breakTargetStack = breakTargetStack.tail; |
| labels = labels.outer; |
| } |
| } |
| |
| class TypeResolver { |
| final Compiler compiler; |
| |
| TypeResolver(this.compiler); |
| |
| /// Tries to resolve the type name as an element. |
| Element resolveTypeName(Identifier prefixName, |
| Identifier typeName, |
| Scope scope, |
| {bool deferredIsMalformed: true}) { |
| Element element; |
| bool deferredTypeAnnotation = false; |
| if (prefixName != null) { |
| Element prefixElement = |
| lookupInScope(compiler, prefixName, scope, prefixName.source); |
| if (prefixElement != null && prefixElement.isPrefix) { |
| // The receiver is a prefix. Lookup in the imported members. |
| PrefixElement prefix = prefixElement; |
| element = prefix.lookupLocalMember(typeName.source); |
| // TODO(17260, sigurdm): The test for DartBackend is there because |
| // dart2dart outputs malformed types with prefix. |
| if (element != null && |
| prefix.isDeferred && |
| deferredIsMalformed && |
| compiler.backend is! DartBackend) { |
| element = new ErroneousElementX(MessageKind.DEFERRED_TYPE_ANNOTATION, |
| {'node': typeName}, |
| element.name, |
| element); |
| } |
| } else { |
| // The caller of this method will create the ErroneousElement for |
| // the MalformedType. |
| element = null; |
| } |
| } else { |
| String stringValue = typeName.source; |
| element = lookupInScope(compiler, typeName, scope, typeName.source); |
| } |
| return element; |
| } |
| |
| DartType resolveTypeAnnotation(MappingVisitor visitor, TypeAnnotation node, |
| {bool malformedIsError: false, |
| bool deferredIsMalformed: true}) { |
| ResolutionRegistry registry = visitor.registry; |
| |
| Identifier typeName; |
| DartType type; |
| |
| DartType checkNoTypeArguments(DartType type) { |
| List<DartType> arguments = new List<DartType>(); |
| bool hasTypeArgumentMismatch = resolveTypeArguments( |
| visitor, node, const <DartType>[], arguments); |
| if (hasTypeArgumentMismatch) { |
| return new MalformedType( |
| new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, |
| {'type': node}, typeName.source, visitor.enclosingElement), |
| type, arguments); |
| } |
| return type; |
| } |
| |
| Identifier prefixName; |
| Send send = node.typeName.asSend(); |
| if (send != null) { |
| // The type name is of the form [: prefix . identifier :]. |
| prefixName = send.receiver.asIdentifier(); |
| typeName = send.selector.asIdentifier(); |
| } else { |
| typeName = node.typeName.asIdentifier(); |
| if (identical(typeName.source, 'void')) { |
| type = const VoidType(); |
| checkNoTypeArguments(type); |
| registry.useType(node, type); |
| return type; |
| } else if (identical(typeName.source, 'dynamic')) { |
| type = const DynamicType(); |
| checkNoTypeArguments(type); |
| registry.useType(node, type); |
| return type; |
| } |
| } |
| |
| Element element = resolveTypeName(prefixName, typeName, visitor.scope, |
| deferredIsMalformed: deferredIsMalformed); |
| |
| DartType reportFailureAndCreateType(MessageKind messageKind, |
| Map messageArguments, |
| {DartType userProvidedBadType, |
| Element erroneousElement}) { |
| if (malformedIsError) { |
| visitor.error(node, messageKind, messageArguments); |
| } else { |
| registry.registerThrowRuntimeError(); |
| visitor.warning(node, messageKind, messageArguments); |
| } |
| if (erroneousElement == null) { |
| erroneousElement = new ErroneousElementX( |
| messageKind, messageArguments, typeName.source, |
| visitor.enclosingElement); |
| } |
| List<DartType> arguments = <DartType>[]; |
| resolveTypeArguments(visitor, node, const <DartType>[], arguments); |
| return new MalformedType(erroneousElement, |
| userProvidedBadType, arguments); |
| } |
| |
| // Try to construct the type from the element. |
| if (element == null) { |
| type = reportFailureAndCreateType( |
| MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node.typeName}); |
| } else if (element.isAmbiguous) { |
| AmbiguousElement ambiguous = element; |
| type = reportFailureAndCreateType( |
| ambiguous.messageKind, ambiguous.messageArguments); |
| ambiguous.diagnose(registry.mapping.analyzedElement, compiler); |
| } else if (element.isErroneous) { |
| ErroneousElement erroneousElement = element; |
| type = reportFailureAndCreateType( |
| erroneousElement.messageKind, erroneousElement.messageArguments, |
| erroneousElement: erroneousElement); |
| } else if (!element.impliesType) { |
| type = reportFailureAndCreateType( |
| MessageKind.NOT_A_TYPE, {'node': node.typeName}); |
| } else { |
| bool addTypeVariableBoundsCheck = false; |
| if (element.isClass) { |
| ClassElement cls = element; |
| // TODO(johnniwinther): [_ensureClassWillBeResolved] should imply |
| // [computeType]. |
| compiler.resolver._ensureClassWillBeResolved(cls); |
| element.computeType(compiler); |
| List<DartType> arguments = <DartType>[]; |
| bool hasTypeArgumentMismatch = resolveTypeArguments( |
| visitor, node, cls.typeVariables, arguments); |
| if (hasTypeArgumentMismatch) { |
| type = new BadInterfaceType(cls.declaration, |
| new InterfaceType.forUserProvidedBadType(cls.declaration, |
| arguments)); |
| } else { |
| if (arguments.isEmpty) { |
| type = cls.rawType; |
| } else { |
| type = new InterfaceType(cls.declaration, arguments.toList(growable: false)); |
| addTypeVariableBoundsCheck = true; |
| } |
| } |
| } else if (element.isTypedef) { |
| TypedefElement typdef = element; |
| // TODO(johnniwinther): [ensureResolved] should imply [computeType]. |
| typdef.ensureResolved(compiler); |
| element.computeType(compiler); |
| List<DartType> arguments = <DartType>[]; |
| bool hasTypeArgumentMismatch = resolveTypeArguments( |
| visitor, node, typdef.typeVariables, arguments); |
| if (hasTypeArgumentMismatch) { |
| type = new BadTypedefType(typdef, |
| new TypedefType.forUserProvidedBadType(typdef, arguments)); |
| } else { |
| if (arguments.isEmpty) { |
| type = typdef.rawType; |
| } else { |
| type = new TypedefType(typdef, arguments.toList(growable: false)); |
| addTypeVariableBoundsCheck = true; |
| } |
| } |
| } else if (element.isTypeVariable) { |
| Element outer = |
| visitor.enclosingElement.outermostEnclosingMemberOrTopLevel; |
| bool isInFactoryConstructor = |
| outer != null && outer.isFactoryConstructor; |
| if (!outer.isClass && |
| !outer.isTypedef && |
| !isInFactoryConstructor && |
| Elements.isInStaticContext(visitor.enclosingElement)) { |
| registry.registerThrowRuntimeError(); |
| type = reportFailureAndCreateType( |
| MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, |
| {'typeVariableName': node}, |
| userProvidedBadType: element.computeType(compiler)); |
| } else { |
| type = element.computeType(compiler); |
| } |
| type = checkNoTypeArguments(type); |
| } else { |
| compiler.internalError(node, |
| "Unexpected element kind ${element.kind}."); |
| } |
| if (addTypeVariableBoundsCheck) { |
| registry.registerTypeVariableBoundCheck(); |
| visitor.addDeferredAction( |
| visitor.enclosingElement, |
| () => checkTypeVariableBounds(node, type)); |
| } |
| } |
| registry.useType(node, type); |
| return type; |
| } |
| |
| /// Checks the type arguments of [type] against the type variable bounds. |
| void checkTypeVariableBounds(TypeAnnotation node, GenericType type) { |
| void checkTypeVariableBound(_, DartType typeArgument, |
| TypeVariableType typeVariable, |
| DartType bound) { |
| if (!compiler.types.isSubtype(typeArgument, bound)) { |
| compiler.reportWarning(node, |
| MessageKind.INVALID_TYPE_VARIABLE_BOUND, |
| {'typeVariable': typeVariable, |
| 'bound': bound, |
| 'typeArgument': typeArgument, |
| 'thisType': type.element.thisType}); |
| } |
| }; |
| |
| compiler.types.checkTypeVariableBounds(type, checkTypeVariableBound); |
| } |
| |
| /** |
| * Resolves the type arguments of [node] and adds these to [arguments]. |
| * |
| * Returns [: true :] if the number of type arguments did not match the |
| * number of type variables. |
| */ |
| bool resolveTypeArguments(MappingVisitor visitor, |
| TypeAnnotation node, |
| List<DartType> typeVariables, |
| List<DartType> arguments) { |
| if (node.typeArguments == null) { |
| return false; |
| } |
| int expectedVariables = typeVariables.length; |
| int index = 0; |
| bool typeArgumentCountMismatch = false; |
| for (Link<Node> typeArguments = node.typeArguments.nodes; |
| !typeArguments.isEmpty; |
| typeArguments = typeArguments.tail, index++) { |
| if (index > expectedVariables - 1) { |
| visitor.warning( |
| typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); |
| typeArgumentCountMismatch = true; |
| } |
| DartType argType = resolveTypeAnnotation(visitor, typeArguments.head); |
| // TODO(karlklose): rewrite to not modify [arguments]. |
| arguments.add(argType); |
| } |
| if (index < expectedVariables) { |
| visitor.warning(node.typeArguments, |
| MessageKind.MISSING_TYPE_ARGUMENT); |
| typeArgumentCountMismatch = true; |
| } |
| return typeArgumentCountMismatch; |
| } |
| } |
| |
| /** |
| * Common supertype for resolver visitors that record resolutions in a |
| * [ResolutionRegistry]. |
| */ |
| abstract class MappingVisitor<T> extends CommonResolverVisitor<T> { |
| final ResolutionRegistry registry; |
| final TypeResolver typeResolver; |
| /// The current enclosing element for the visited AST nodes. |
| Element get enclosingElement; |
| /// The current scope of the visitor. |
| Scope get scope; |
| |
| MappingVisitor(Compiler compiler, ResolutionRegistry this.registry) |
| : typeResolver = new TypeResolver(compiler), |
| super(compiler); |
| |
| AsyncMarker get currentAsyncMarker => AsyncMarker.SYNC; |
| |
| /// Add [element] to the current scope and check for duplicate definitions. |
| void addToScope(Element element) { |
| Element existing = scope.add(element); |
| if (existing != element) { |
| reportDuplicateDefinition(element.name, element, existing); |
| } |
| } |
| |
| void checkLocalDefinitionName(Node node, Element element) { |
| if (currentAsyncMarker != AsyncMarker.SYNC) { |
| if (element.name == 'yield' || |
| element.name == 'async' || |
| element.name == 'await') { |
| compiler.reportError( |
| node, MessageKind.ASYNC_KEYWORD_AS_IDENTIFIER, |
| {'keyword': element.name, |
| 'modifier': currentAsyncMarker}); |
| } |
| } |
| } |
| |
| /// Register [node] as the definition of [element]. |
| void defineLocalVariable(Node node, LocalVariableElement element) { |
| invariant(node, element != null); |
| checkLocalDefinitionName(node, element); |
| registry.defineElement(node, element); |
| } |
| |
| void reportDuplicateDefinition(String name, |
| Spannable definition, |
| Spannable existing) { |
| compiler.reportError(definition, |
| MessageKind.DUPLICATE_DEFINITION, {'name': name}); |
| compiler.reportInfo(existing, |
| MessageKind.EXISTING_DEFINITION, {'name': name}); |
| } |
| } |
| |
| /** |
| * Core implementation of resolution. |
| * |
| * Do not subclass or instantiate this class outside this library |
| * except for testing. |
| */ |
| class ResolverVisitor extends MappingVisitor<ResolutionResult> { |
| /** |
| * The current enclosing element for the visited AST nodes. |
| * |
| * This field is updated when nested closures are visited. |
| */ |
| Element enclosingElement; |
| bool inInstanceContext; |
| bool inCheckContext; |
| bool inCatchBlock; |
| |
| Scope scope; |
| ClassElement currentClass; |
| ExpressionStatement currentExpressionStatement; |
| bool sendIsMemberAccess = false; |
| StatementScope statementScope; |
| int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION |
| | ElementCategory.IMPLIES_TYPE; |
| |
| /** |
| * Record of argument nodes to JS_INTERCEPTOR_CONSTANT for deferred |
| * processing. |
| */ |
| Set<Node> argumentsToJsInterceptorConstant = null; |
| |
| /// When visiting the type declaration of the variable in a [ForIn] loop, |
| /// the initializer of the variable is implicit and we should not emit an |
| /// error when verifying that all final variables are initialized. |
| bool allowFinalWithoutInitializer = false; |
| |
| /// The nodes for which variable access and mutation must be registered in |
| /// order to determine when the static type of variables types is promoted. |
| Link<Node> promotionScope = const Link<Node>(); |
| |
| bool isPotentiallyMutableTarget(Element target) { |
| if (target == null) return false; |
| return (target.isVariable || target.isParameter) && |
| !(target.isFinal || target.isConst); |
| } |
| |
| // TODO(ahe): Find a way to share this with runtime implementation. |
| static final RegExp symbolValidationPattern = |
| new RegExp(r'^(?:[a-zA-Z$][a-zA-Z$0-9_]*\.)*(?:[a-zA-Z$][a-zA-Z$0-9_]*=?|' |
| r'-|' |
| r'unary-|' |
| r'\[\]=|' |
| r'~|' |
| r'==|' |
| r'\[\]|' |
| r'\*|' |
| r'/|' |
| r'%|' |
| r'~/|' |
| r'\+|' |
| r'<<|' |
| r'>>|' |
| r'>=|' |
| r'>|' |
| r'<=|' |
| r'<|' |
| r'&|' |
| r'\^|' |
| r'\|' |
| r')$'); |
| |
| ResolverVisitor(Compiler compiler, |
| Element element, |
| ResolutionRegistry registry, |
| {bool useEnclosingScope: false}) |
| : this.enclosingElement = element, |
| // When the element is a field, we are actually resolving its |
| // initial value, which should not have access to instance |
| // fields. |
| inInstanceContext = (element.isInstanceMember && !element.isField) |
| || element.isGenerativeConstructor, |
| this.currentClass = element.isClassMember ? element.enclosingClass |
| : null, |
| this.statementScope = new StatementScope(), |
| scope = useEnclosingScope |
| ? Scope.buildEnclosingScope(element) : element.buildScope(), |
| // The type annotations on a typedef do not imply type checks. |
| // TODO(karlklose): clean this up (dartbug.com/8870). |
| inCheckContext = compiler.enableTypeAssertions && |
| !element.isLibrary && |
| !element.isTypedef && |
| !element.enclosingElement.isTypedef, |
| inCatchBlock = false, |
| super(compiler, registry); |
| |
| AsyncMarker get currentAsyncMarker { |
| if (enclosingElement is FunctionElement) { |
| FunctionElement function = enclosingElement; |
| return function.asyncMarker; |
| } |
| return AsyncMarker.SYNC; |
| } |
| |
| Element reportLookupErrorIfAny(Element result, Node node, String name) { |
| if (!Elements.isUnresolved(result)) { |
| if (!inInstanceContext && result.isInstanceMember) { |
| compiler.reportError( |
| node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); |
| return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE, |
| {'name': name}, |
| name, enclosingElement); |
| } else if (result.isAmbiguous) { |
| AmbiguousElement ambiguous = result; |
| compiler.reportError( |
| node, ambiguous.messageKind, ambiguous.messageArguments); |
| ambiguous.diagnose(enclosingElement, compiler); |
| return new ErroneousElementX(ambiguous.messageKind, |
| ambiguous.messageArguments, |
| name, enclosingElement); |
| } |
| } |
| return result; |
| } |
| |
| // Create, or reuse an already created, target element for a statement. |
| JumpTarget getOrDefineTarget(Node statement) { |
| JumpTarget element = registry.getTargetDefinition(statement); |
| if (element == null) { |
| element = new JumpTargetX(statement, |
| statementScope.nestingLevel, |
| enclosingElement); |
| registry.defineTarget(statement, element); |
| } |
| return element; |
| } |
| |
| doInCheckContext(action()) { |
| bool wasInCheckContext = inCheckContext; |
| inCheckContext = true; |
| var result = action(); |
| inCheckContext = wasInCheckContext; |
| 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(); |
| promotionScope = promotionScope.tail; |
| return result; |
| } |
| |
| visitInStaticContext(Node node) { |
| inStaticContext(() => visit(node)); |
| } |
| |
| ErroneousElement warnAndCreateErroneousElement(Node node, |
| String name, |
| MessageKind kind, |
| [Map arguments = const {}]) { |
| compiler.reportWarning(node, kind, arguments); |
| return new ErroneousElementX(kind, arguments, name, enclosingElement); |
| } |
| |
| ResolutionResult visitIdentifier(Identifier node) { |
| if (node.isThis()) { |
| if (!inInstanceContext) { |
| error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node}); |
| } |
| return null; |
| } else if (node.isSuper()) { |
| if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC); |
| if ((ElementCategory.SUPER & allowedCategory) == 0) { |
| error(node, MessageKind.INVALID_USE_OF_SUPER); |
| } |
| return null; |
| } else { |
| String name = node.source; |
| Element element = lookupInScope(compiler, node, scope, name); |
| if (Elements.isUnresolved(element) && name == 'dynamic') { |
| // TODO(johnniwinther): Remove this hack when we can return more complex |
| // objects than [Element] from this method. |
| element = compiler.typeClass; |
| // Set the type to be `dynamic` to mark that this is a type literal. |
| registry.setType(node, const DynamicType()); |
| } |
| element = reportLookupErrorIfAny(element, node, node.source); |
| if (element == null) { |
| if (!inInstanceContext) { |
| element = warnAndCreateErroneousElement( |
| node, node.source, MessageKind.CANNOT_RESOLVE, |
| {'name': node}); |
| registry.registerThrowNoSuchMethod(); |
| } |
| } else if (element.isErroneous) { |
| // Use the erroneous element. |
| } else { |
| if ((element.kind.category & allowedCategory) == 0) { |
| // TODO(ahe): Improve error message. Need UX input. |
| error(node, MessageKind.GENERIC, |
| {'text': "is not an expression $element"}); |
| } |
| } |
| if (!Elements.isUnresolved(element) && element.isClass) { |
| ClassElement classElement = element; |
| classElement.ensureResolved(compiler); |
| } |
| return new ElementResult(registry.useElement(node, element)); |
| } |
| } |
| |
| ResolutionResult visitTypeAnnotation(TypeAnnotation node) { |
| DartType type = resolveTypeAnnotation(node); |
| if (inCheckContext) { |
| registry.registerIsCheck(type); |
| } |
| return new TypeResult(type); |
| } |
| |
| bool isNamedConstructor(Send node) => node.receiver != null; |
| |
| Selector getRedirectingThisOrSuperConstructorSelector(Send node) { |
| if (isNamedConstructor(node)) { |
| String constructorName = node.selector.asIdentifier().source; |
| return new Selector.callConstructor( |
| constructorName, |
| enclosingElement.library); |
| } else { |
| return new Selector.callDefaultConstructor( |
| enclosingElement.library); |
| } |
| } |
| |
| FunctionElement resolveConstructorRedirection(FunctionElementX constructor) { |
| FunctionExpression node = constructor.parseNode(compiler); |
| |
| // A synthetic constructor does not have a node. |
| if (node == null) return null; |
| if (node.initializers == null) return null; |
| Link<Node> initializers = node.initializers.nodes; |
| if (!initializers.isEmpty && |
| Initializers.isConstructorRedirect(initializers.head)) { |
| Selector selector = |
| getRedirectingThisOrSuperConstructorSelector(initializers.head); |
| final ClassElement classElement = constructor.enclosingClass; |
| return classElement.lookupConstructor(selector); |
| } |
| return null; |
| } |
| |
| void setupFunction(FunctionExpression node, FunctionElement function) { |
| Element enclosingElement = function.enclosingElement; |
| if (node.modifiers.isStatic && |
| enclosingElement.kind != ElementKind.CLASS) { |
| compiler.reportError(node, MessageKind.ILLEGAL_STATIC); |
| } |
| |
| scope = new MethodScope(scope, function); |
| // Put the parameters in scope. |
| FunctionSignature functionParameters = function.functionSignature; |
| Link<Node> parameterNodes = (node.parameters == null) |
| ? const Link<Node>() : node.parameters.nodes; |
| functionParameters.forEachParameter((ParameterElement element) { |
| // TODO(karlklose): should be a list of [FormalElement]s, but the actual |
| // implementation uses [Element]. |
| Link<Element> optionals = functionParameters.optionalParameters; |
| if (!optionals.isEmpty && element == optionals.head) { |
| NodeList nodes = parameterNodes.head; |
| parameterNodes = nodes.nodes; |
| } |
| visit(element.initializer); |
| VariableDefinitions variableDefinitions = parameterNodes.head; |
| Node parameterNode = variableDefinitions.definitions.nodes.head; |
| // Field parameters (this.x) are not visible inside the constructor. The |
| // fields they reference are visible, but must be resolved independently. |
| if (element.isInitializingFormal) { |
| registry.useElement(parameterNode, element); |
| } else { |
| LocalParameterElement parameterElement = element; |
| defineLocalVariable(parameterNode, parameterElement); |
| addToScope(parameterElement); |
| } |
| parameterNodes = parameterNodes.tail; |
| }); |
| addDeferredAction(enclosingElement, () { |
| functionParameters.forEachOptionalParameter((Element parameter) { |
| compiler.resolver.constantCompiler.compileConstant(parameter); |
| }); |
| }); |
| if (inCheckContext) { |
| functionParameters.forEachParameter((ParameterElement element) { |
| registry.registerIsCheck(element.type); |
| }); |
| } |
| } |
| |
| visitCascade(Cascade node) { |
| visit(node.expression); |
| } |
| |
| visitCascadeReceiver(CascadeReceiver node) { |
| visit(node.expression); |
| } |
| |
| visitClassNode(ClassNode node) { |
| internalError(node, "shouldn't be called"); |
| } |
| |
| visitIn(Node node, Scope nestedScope) { |
| Scope oldScope = scope; |
| scope = nestedScope; |
| ResolutionResult result = visit(node); |
| scope = oldScope; |
| return result; |
| } |
| |
| /** |
| * Introduces new default targets for break and continue |
| * before visiting the body of the loop |
| */ |
| visitLoopBodyIn(Loop loop, Node body, Scope bodyScope) { |
| JumpTarget element = getOrDefineTarget(loop); |
| statementScope.enterLoop(element); |
| visitIn(body, bodyScope); |
| statementScope.exitLoop(); |
| if (!element.isTarget) { |
| registry.undefineTarget(loop); |
| } |
| } |
| |
| visitBlock(Block node) { |
| visitIn(node.statements, new BlockScope(scope)); |
| } |
| |
| visitDoWhile(DoWhile node) { |
| visitLoopBodyIn(node, node.body, new BlockScope(scope)); |
| visit(node.condition); |
| } |
| |
| visitEmptyStatement(EmptyStatement node) { } |
| |
| visitExpressionStatement(ExpressionStatement node) { |
| ExpressionStatement oldExpressionStatement = currentExpressionStatement; |
| currentExpressionStatement = node; |
| visit(node.expression); |
| currentExpressionStatement = oldExpressionStatement; |
| } |
| |
| visitFor(For node) { |
| Scope blockScope = new BlockScope(scope); |
| visitIn(node.initializer, blockScope); |
| visitIn(node.condition, blockScope); |
| visitIn(node.update, blockScope); |
| visitLoopBodyIn(node, node.body, blockScope); |
| } |
| |
| visitFunctionDeclaration(FunctionDeclaration node) { |
| assert(node.function.name != null); |
| visitFunctionExpression(node.function, inFunctionDeclaration: true); |
| } |
| |
| |
| /// Process a local function declaration or an anonymous function expression. |
| /// |
| /// [inFunctionDeclaration] is `true` when the current node is the immediate |
| /// child of a function declaration. |
| /// |
| /// This is used to distinguish local function declarations from anonymous |
| /// function expressions. |
| visitFunctionExpression(FunctionExpression node, |
| {bool inFunctionDeclaration: false}) { |
| bool doAddToScope = inFunctionDeclaration; |
| if (!inFunctionDeclaration && node.name != null) { |
| compiler.reportError( |
| node.name, |
| MessageKind.NAMED_FUNCTION_EXPRESSION, |
| {'name': node.name}); |
| } |
| visit(node.returnType); |
| String name; |
| if (node.name == null) { |
| name = ""; |
| } else { |
| name = node.name.asIdentifier().source; |
| } |
| LocalFunctionElementX function = new LocalFunctionElementX( |
| name, node, ElementKind.FUNCTION, Modifiers.EMPTY, |
| enclosingElement); |
| function.functionSignatureCache = |
| SignatureResolver.analyze(compiler, node.parameters, node.returnType, |
| function, registry, createRealParameters: true); |
| ResolverTask.processAsyncMarker(compiler, function); |
| checkLocalDefinitionName(node, function); |
| registry.defineFunction(node, function); |
| if (doAddToScope) { |
| addToScope(function); |
| } |
| Scope oldScope = scope; // The scope is modified by [setupFunction]. |
| setupFunction(node, function); |
| |
| Element previousEnclosingElement = enclosingElement; |
| enclosingElement = function; |
| // Run the body in a fresh statement scope. |
| StatementScope oldStatementScope = statementScope; |
| statementScope = new StatementScope(); |
| visit(node.body); |
| statementScope = oldStatementScope; |
| |
| scope = oldScope; |
| enclosingElement = previousEnclosingElement; |
| |
| registry.registerClosure(function); |
| registry.registerInstantiatedClass(compiler.functionClass); |
| } |
| |
| visitIf(If node) { |
| doInPromotionScope(node.condition.expression, () => visit(node.condition)); |
| doInPromotionScope(node.thenPart, |
| () => visitIn(node.thenPart, new BlockScope(scope))); |
| visitIn(node.elsePart, new BlockScope(scope)); |
| } |
| |
| ResolutionResult resolveSend(Send node) { |
| Selector selector = resolveSelector(node, null); |
| if (node.isSuperCall) registry.registerSuperUse(node); |
| |
| if (node.receiver == null) { |
| // If this send is of the form "assert(expr);", then |
| // this is an assertion. |
| if (selector.isAssert) { |
| if (selector.argumentCount != 1) { |
| error(node.selector, |
| MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT, |
| {'argumentCount': selector.argumentCount}); |
| } else if (selector.namedArgumentCount != 0) { |
| error(node.selector, |
| MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS, |
| {'argumentCount': selector.namedArgumentCount}); |
| } |
| registry.registerAssert(node); |
| return const AssertResult(); |
| } |
| |
| return node.selector.accept(this); |
| } |
| |
| var oldCategory = allowedCategory; |
| allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER; |
| ResolutionResult resolvedReceiver = visit(node.receiver); |
| allowedCategory = oldCategory; |
| |
| Element target; |
| String name = node.selector.asIdentifier().source; |
| if (identical(name, 'this')) { |
| // TODO(ahe): Why is this using GENERIC? |
| error(node.selector, MessageKind.GENERIC, |
| {'text': "expected an identifier"}); |
| } else if (node.isSuperCall) { |
| if (node.isOperator) { |
| if (isUserDefinableOperator(name)) { |
| name = selector.name; |
| } else { |
| error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name}); |
| } |
| } |
| if (!inInstanceContext) { |
| error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); |
| return null; |
| } |
| if (currentClass.supertype == null) { |
| // This is just to guard against internal errors, so no need |
| // for a real error message. |
| error(node.receiver, MessageKind.GENERIC, |
| {'text': "Object has no superclass"}); |
| } |
| // TODO(johnniwinther): Ensure correct behavior if currentClass is a |
| // patch. |
| target = currentClass.lookupSuperSelector(selector); |
| // [target] may be null which means invoking noSuchMethod on |
| // super. |
| if (target == null) { |
| target = warnAndCreateErroneousElement( |
| node, name, MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass, 'memberName': name}); |
| // We still need to register the invocation, because we might |
| // call [:super.noSuchMethod:] which calls |
| // [JSInvocationMirror._invokeOn]. |
| registry.registerDynamicInvocation(selector); |
| registry.registerSuperNoSuchMethod(); |
| } |
| } else if (resolvedReceiver == null || |
| Elements.isUnresolved(resolvedReceiver.element)) { |
| return null; |
| } else if (resolvedReceiver.element.isClass) { |
| ClassElement receiverClass = resolvedReceiver.element; |
| receiverClass.ensureResolved(compiler); |
| if (node.isOperator) { |
| // When the resolved receiver is a class, we can have two cases: |
| // 1) a static send: C.foo, or |
| // 2) an operator send, where the receiver is a class literal: 'C + 1'. |
| // The following code that looks up the selector on the resolved |
| // receiver will treat the second as the invocation of a static operator |
| // if the resolved receiver is not null. |
| return null; |
| } |
| MembersCreator.computeClassMembersByName( |
| compiler, receiverClass.declaration, name); |
| target = receiverClass.lookupLocalMember(name); |
| if (target == null || target.isInstanceMember) { |
| registry.registerThrowNoSuchMethod(); |
| // TODO(johnniwinther): With the simplified [TreeElements] invariant, |
| // try to resolve injected elements if [currentClass] is in the patch |
| // library of [receiverClass]. |
| |
| // TODO(karlklose): this should be reported by the caller of |
| // [resolveSend] to select better warning messages for getters and |
| // setters. |
| MessageKind kind = (target == null) |
| ? MessageKind.MEMBER_NOT_FOUND |
| : MessageKind.MEMBER_NOT_STATIC; |
| return new ElementResult(warnAndCreateErroneousElement( |
| node, name, kind, |
| {'className': receiverClass.name, 'memberName': name})); |
| } else if (isPrivateName(name) && |
| target.library != enclosingElement.library) { |
| registry.registerThrowNoSuchMethod(); |
| return new ElementResult(warnAndCreateErroneousElement( |
| node, name, MessageKind.PRIVATE_ACCESS, |
| {'libraryName': target.library.getLibraryOrScriptName(), |
| 'name': name})); |
| } |
| } else if (resolvedReceiver.element.isPrefix) { |
| PrefixElement prefix = resolvedReceiver.element; |
| target = prefix.lookupLocalMember(name); |
| if (Elements.isUnresolved(target)) { |
| registry.registerThrowNoSuchMethod(); |
| return new ElementResult(warnAndCreateErroneousElement( |
| node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER, |
| {'libraryName': prefix.name, 'memberName': name})); |
| } else if (target.isAmbiguous) { |
| registry.registerThrowNoSuchMethod(); |
| AmbiguousElement ambiguous = target; |
| target = warnAndCreateErroneousElement(node, name, |
| ambiguous.messageKind, |
| ambiguous.messageArguments); |
| ambiguous.diagnose(enclosingElement, compiler); |
| return new ElementResult(target); |
| } else if (target.kind == ElementKind.CLASS) { |
| ClassElement classElement = target; |
| classElement.ensureResolved(compiler); |
| } |
| } |
| return new ElementResult(target); |
| } |
| |
| static Selector computeSendSelector(Send node, |
| LibraryElement library, |
| Element element) { |
| // First determine if this is part of an assignment. |
| bool isSet = node.asSendSet() != null; |
| |
| if (node.isIndex) { |
| return isSet ? new Selector.indexSet() : new Selector.index(); |
| } |
| |
| if (node.isOperator) { |
| String source = node.selector.asOperator().source; |
| String string = source; |
| if (identical(string, '!') || |
| identical(string, '&&') || identical(string, '||') || |
| identical(string, 'is') || identical(string, 'as') || |
| identical(string, '?') || |
| identical(string, '>>>')) { |
| return null; |
| } |
| String op = source; |
| if (!isUserDefinableOperator(source)) { |
| op = Elements.mapToUserOperatorOrNull(source); |
| } |
| if (op == null) { |
| // Unsupported operator. An error has been reported during parsing. |
| return new Selector.call( |
| source, library, node.argumentsNode.slowLength(), []); |
| } |
| return node.arguments.isEmpty |
| ? new Selector.unaryOperator(op) |
| : new Selector.binaryOperator(op); |
| } |
| |
| Identifier identifier = node.selector.asIdentifier(); |
| if (node.isPropertyAccess) { |
| assert(!isSet); |
| return new Selector.getter(identifier.source, library); |
| } else if (isSet) { |
| return new Selector.setter(identifier.source, library); |
| } |
| |
| // Compute the arity and the list of named arguments. |
| int arity = 0; |
| List<String> named = <String>[]; |
| for (Link<Node> link = node.argumentsNode.nodes; |
| !link.isEmpty; |
| link = link.tail) { |
| Expression argument = link.head; |
| NamedArgument namedArgument = argument.asNamedArgument(); |
| if (namedArgument != null) { |
| named.add(namedArgument.name.source); |
| } |
| arity++; |
| } |
| |
| if (element != null && element.isConstructor) { |
| return new Selector.callConstructor( |
| element.name, library, arity, named); |
| } |
| |
| // If we're invoking a closure, we do not have an identifier. |
| return (identifier == null) |
| ? new Selector.callClosure(arity, named) |
| : new Selector.call(identifier.source, library, arity, named); |
| } |
| |
| Selector resolveSelector(Send node, Element element) { |
| LibraryElement library = enclosingElement.library; |
| Selector selector = computeSendSelector(node, library, element); |
| if (selector != null) registry.setSelector(node, selector); |
| return selector; |
| } |
| |
| void resolveArguments(NodeList list) { |
| if (list == null) return; |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| Map<String, Node> seenNamedArguments = new Map<String, Node>(); |
| for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) { |
| Expression argument = link.head; |
| visit(argument); |
| NamedArgument namedArgument = argument.asNamedArgument(); |
| if (namedArgument != null) { |
| String source = namedArgument.name.source; |
| if (seenNamedArguments.containsKey(source)) { |
| reportDuplicateDefinition( |
| source, |
| argument, |
| seenNamedArguments[source]); |
| } else { |
| seenNamedArguments[source] = namedArgument; |
| } |
| } else if (!seenNamedArguments.isEmpty) { |
| error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); |
| } |
| } |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| } |
| |
| ResolutionResult visitSend(Send node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = node.isPropertyAccess || node.isCall; |
| ResolutionResult result; |
| if (node.isLogicalAnd) { |
| result = doInPromotionScope(node.receiver, () => resolveSend(node)); |
| } else { |
| result = resolveSend(node); |
| } |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| |
| Element target = result != null ? result.element : null; |
| |
| if (target != null |
| && target == compiler.mirrorSystemGetNameFunction |
| &&
|