| // 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; |
| |
| /// The state of constants in resolutions. |
| enum ConstantState { |
| /// Expressions are not required to be constants. |
| NON_CONSTANT, |
| |
| /// Expressions are required to be constants. |
| /// |
| /// For instance the values of a constant list literal. |
| CONSTANT, |
| |
| /// Expressions are required to be constants and parameter references are |
| /// also considered constant. |
| /// |
| /// This is used for resolving constructor initializers of constant |
| /// constructors. |
| CONSTANT_INITIALIZER, |
| } |
| |
| /** |
| * Core implementation of resolution. |
| * |
| * 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; |
| |
| /// Whether we are in a context where `this` is accessible (this will be false |
| /// in static contexts, factory methods, and field initializers). |
| bool inInstanceContext; |
| bool inCheckContext; |
| bool inCatchBlock; |
| ConstantState constantState; |
| |
| 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, |
| constantState = element.isConst |
| ? ConstantState.CONSTANT : ConstantState.NON_CONSTANT, |
| super(compiler, registry); |
| |
| CoreTypes get coreTypes => compiler.coreTypes; |
| |
| 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; |
| } |
| |
| doInPromotionScope(Node node, action()) { |
| promotionScope = promotionScope.prepend(node); |
| var result = action(); |
| promotionScope = promotionScope.tail; |
| return result; |
| } |
| |
| inStaticContext(action(), |
| {bool inConstantInitializer: false}) { |
| bool wasInstanceContext = inInstanceContext; |
| ConstantState oldConstantState = constantState; |
| constantState = inConstantInitializer |
| ? ConstantState.CONSTANT_INITIALIZER |
| : constantState; |
| inInstanceContext = false; |
| var result = action(); |
| inInstanceContext = wasInstanceContext; |
| constantState = oldConstantState; |
| return result; |
| } |
| |
| ResolutionResult visitInStaticContext(Node node, |
| {bool inConstantInitializer: false}) { |
| return inStaticContext( |
| () => visit(node), |
| inConstantInitializer: inConstantInitializer); |
| } |
| |
| /// Execute [action] where the constant state is `ConstantState.CONSTANT` if |
| /// not already `ConstantState.CONSTANT_INITIALIZER`. |
| inConstantContext(action()) { |
| ConstantState oldConstantState = constantState; |
| if (constantState != ConstantState.CONSTANT_INITIALIZER) { |
| constantState = ConstantState.CONSTANT; |
| } |
| var result = action(); |
| constantState = oldConstantState; |
| return result; |
| } |
| |
| /// Visit [node] where the constant state is `ConstantState.CONSTANT` if |
| /// not already `ConstantState.CONSTANT_INITIALIZER`. |
| ResolutionResult visitInConstantContext(Node node) { |
| ResolutionResult result = inConstantContext(() => visit(node)); |
| assert(invariant(node, result != null, |
| message: "No resolution result for $node.")); |
| |
| return result; |
| } |
| |
| ErroneousElement reportAndCreateErroneousElement( |
| Node node, |
| String name, |
| MessageKind kind, |
| Map arguments, |
| {bool isError: false}) { |
| if (isError) { |
| compiler.reportError(node, kind, arguments); |
| } else { |
| compiler.reportWarning(node, kind, arguments); |
| } |
| // TODO(ahe): Use [allowedCategory] to synthesize a more precise subclass |
| // of [ErroneousElementX]. For example, [ErroneousFieldElementX], |
| // [ErroneousConstructorElementX], etc. |
| return new ErroneousElementX(kind, arguments, name, enclosingElement); |
| } |
| |
| /// Report a warning or error on an unresolved access in non-instance context. |
| /// |
| /// The [ErroneousElement] corresponding to the message is returned. |
| ErroneousElement reportCannotResolve(Node node, String name) { |
| assert(invariant(node, !inInstanceContext, |
| message: "ResolverVisitor.reportCannotResolve must not be called in " |
| "instance context.")); |
| |
| // We report an error within initializers because `this` is implicitly |
| // accessed when unqualified identifiers are not resolved. For |
| // details, see section 16.14.3 of the spec (2nd edition): |
| // An unqualified invocation `i` of the form `id(a1, ...)` |
| // ... |
| // If `i` does not occur inside a top level or static function, `i` |
| // is equivalent to `this.id(a1 , ...)`. |
| bool inInitializer = |
| enclosingElement.isGenerativeConstructor || |
| (enclosingElement.isInstanceMember && enclosingElement.isField); |
| MessageKind kind; |
| Map arguments = {'name': name}; |
| if (inInitializer) { |
| kind = MessageKind.CANNOT_RESOLVE_IN_INITIALIZER; |
| } else if (name == 'await') { |
| var functionName = enclosingElement.name; |
| if (functionName == '') { |
| kind = MessageKind.CANNOT_RESOLVE_AWAIT_IN_CLOSURE; |
| } else { |
| kind = MessageKind.CANNOT_RESOLVE_AWAIT; |
| arguments['functionName'] = functionName; |
| } |
| } else { |
| kind = MessageKind.CANNOT_RESOLVE; |
| } |
| registry.registerThrowNoSuchMethod(); |
| return reportAndCreateErroneousElement( |
| node, name, kind, arguments, isError: inInitializer); |
| } |
| |
| ResolutionResult visitIdentifier(Identifier node) { |
| if (node.isThis()) { |
| if (!inInstanceContext) { |
| error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node}); |
| } |
| return const NoneResult(); |
| } 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 const NoneResult(); |
| } 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, name); |
| if (element == null) { |
| if (!inInstanceContext) { |
| element = reportCannotResolve(node, name); |
| } |
| } else if (element.isErroneous) { |
| // Use the erroneous element. |
| } else { |
| if ((element.kind.category & allowedCategory) == 0) { |
| element = reportAndCreateErroneousElement( |
| node, name, MessageKind.GENERIC, |
| // TODO(ahe): Improve error message. Need UX input. |
| {'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)); |
| } |
| } |
| |
| TypeResult 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(); |
| } |
| } |
| |
| 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.name); |
| } |
| 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((ParameterElementX element) { |
| // TODO(karlklose): should be a list of [FormalElement]s, but the actual |
| // implementation uses [Element]. |
| List<Element> optionals = functionParameters.optionalParameters; |
| if (!optionals.isEmpty && element == optionals.first) { |
| NodeList nodes = parameterNodes.head; |
| parameterNodes = nodes.nodes; |
| } |
| if (element.isOptional) { |
| if (element.initializer != null) { |
| ResolutionResult result = visitInConstantContext(element.initializer); |
| if (result.isConstant) { |
| element.constant = result.constant; |
| } |
| } else { |
| element.constant = new NullConstantExpression(); |
| } |
| } |
| VariableDefinitions variableDefinitions = parameterNodes.head; |
| Node parameterNode = variableDefinitions.definitions.nodes.head; |
| // Field parameters (this.x) are not visible inside the constructor. The |
| // fields they reference are visible, but must be resolved independently. |
| if (element.isInitializingFormal) { |
| registry.useElement(parameterNode, element); |
| } else { |
| LocalParameterElementX parameterElement = element; |
| defineLocalVariable(parameterNode, parameterElement); |
| addToScope(parameterElement); |
| } |
| parameterNodes = parameterNodes.tail; |
| }); |
| addDeferredAction(enclosingElement, () { |
| functionParameters.forEachOptionalParameter( |
| (ParameterElementX parameter) { |
| parameter.constant = |
| compiler.resolver.constantCompiler.compileConstant(parameter); |
| }); |
| }); |
| if (inCheckContext) { |
| functionParameters.forEachParameter((ParameterElement element) { |
| registry.registerIsCheck(element.type); |
| }); |
| } |
| } |
| |
| ResolutionResult visitCascade(Cascade node) { |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitCascadeReceiver(CascadeReceiver node) { |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult 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 |
| */ |
| void 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); |
| } |
| } |
| |
| ResolutionResult visitBlock(Block node) { |
| visitIn(node.statements, new BlockScope(scope)); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitDoWhile(DoWhile node) { |
| visitLoopBodyIn(node, node.body, new BlockScope(scope)); |
| visit(node.condition); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitEmptyStatement(EmptyStatement node) { |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitExpressionStatement(ExpressionStatement node) { |
| ExpressionStatement oldExpressionStatement = currentExpressionStatement; |
| currentExpressionStatement = node; |
| visit(node.expression); |
| currentExpressionStatement = oldExpressionStatement; |
| return const NoneResult(); |
| } |
| |
| ResolutionResult 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); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitFunctionDeclaration(FunctionDeclaration node) { |
| assert(node.function.name != null); |
| visitFunctionExpression(node.function, inFunctionDeclaration: true); |
| return const NoneResult(); |
| } |
| |
| |
| /// 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. |
| ResolutionResult 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); |
| ResolverTask.processAsyncMarker(compiler, function, registry); |
| function.functionSignatureCache = SignatureResolver.analyze( |
| compiler, |
| node.parameters, |
| node.returnType, |
| function, |
| registry, |
| createRealParameters: true, |
| isFunctionExpression: !inFunctionDeclaration); |
| 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); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult 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)); |
| return const NoneResult(); |
| } |
| |
| 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) { |
| internalError(node, "Unexpected assert: $node"); |
| } |
| |
| return node.selector.accept(this); |
| } |
| |
| var oldCategory = allowedCategory; |
| allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER; |
| |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| int oldAllowedCategory = allowedCategory; |
| |
| // Conditional sends like `e?.foo` treat the receiver as an expression. So |
| // `C?.foo` needs to be treated like `(C).foo`, not like C.foo. Prefixes and |
| // super are not allowed on their own in that context. |
| if (node.isConditional) { |
| sendIsMemberAccess = false; |
| allowedCategory = |
| ElementCategory.VARIABLE | |
| ElementCategory.FUNCTION | |
| ElementCategory.IMPLIES_TYPE; |
| } |
| ResolutionResult resolvedReceiver = visit(node.receiver); |
| if (node.isConditional) { |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| allowedCategory = oldAllowedCategory; |
| } |
| |
| allowedCategory = oldCategory; |
| |
| Element target; |
| String name = node.selector.asIdentifier().source; |
| if (identical(name, 'this')) { |
| error(node.selector, MessageKind.THIS_PROPERTY); |
| return const NoneResult(); |
| } else if (node.isSuperCall) { |
| if (node.isOperator) { |
| if (isUserDefinableOperator(name)) { |
| name = selector.name; |
| } else { |
| error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name}); |
| return const NoneResult(); |
| } |
| } |
| if (!inInstanceContext) { |
| error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); |
| return const NoneResult(); |
| } |
| 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"}); |
| return const NoneResult(); |
| } |
| // TODO(johnniwinther): Ensure correct behavior if currentClass is a |
| // patch. |
| target = currentClass.lookupSuperByName(selector.memberName); |
| // [target] may be null which means invoking noSuchMethod on |
| // super. |
| if (target == null) { |
| target = reportAndCreateErroneousElement( |
| node, name, MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': name}); |
| // We still need to register the invocation, because we might |
| // call [:super.noSuchMethod:] which calls |
| // [JSInvocationMirror._invokeOn]. |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| registry.registerSuperNoSuchMethod(); |
| } |
| } else if (Elements.isUnresolved(resolvedReceiver.element)) { |
| return const NoneResult(); |
| } 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 const NoneResult(); |
| } |
| 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(reportAndCreateErroneousElement( |
| node, name, kind, |
| {'className': receiverClass.name, 'memberName': name})); |
| } else if (isPrivateName(name) && |
| target.library != enclosingElement.library) { |
| registry.registerThrowNoSuchMethod(); |
| return new ElementResult(reportAndCreateErroneousElement( |
| 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(reportAndCreateErroneousElement( |
| node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER, |
| {'libraryName': prefix.name, 'memberName': name})); |
| } else if (target.isAmbiguous) { |
| registry.registerThrowNoSuchMethod(); |
| AmbiguousElement ambiguous = target; |
| target = reportAndCreateErroneousElement( |
| 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 ResolutionResult.forElement(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, '??') || |
| 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; |
| } |
| |
| ArgumentsResult resolveArguments(NodeList list) { |
| if (list == null) return null; |
| bool isValidAsConstant = true; |
| List<ResolutionResult> argumentResults = <ResolutionResult>[]; |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| Map<String, Node> seenNamedArguments = new Map<String, Node>(); |
| int argumentCount = 0; |
| List<String> namedArguments = <String>[]; |
| for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) { |
| Expression argument = link.head; |
| ResolutionResult result = visit(argument); |
| if (!result.isConstant) { |
| isValidAsConstant = false; |
| } |
| argumentResults.add(result); |
| |
| NamedArgument namedArgument = argument.asNamedArgument(); |
| if (namedArgument != null) { |
| String source = namedArgument.name.source; |
| namedArguments.add(source); |
| if (seenNamedArguments.containsKey(source)) { |
| reportDuplicateDefinition( |
| source, |
| argument, |
| seenNamedArguments[source]); |
| isValidAsConstant = false; |
| } else { |
| seenNamedArguments[source] = namedArgument; |
| } |
| } else if (!seenNamedArguments.isEmpty) { |
| error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); |
| isValidAsConstant = false; |
| } |
| argumentCount++; |
| } |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| return new ArgumentsResult( |
| new CallStructure(argumentCount, namedArguments), |
| argumentResults, |
| isValidAsConstant: isValidAsConstant); |
| } |
| |
| void registerTypeLiteralAccess(Send node, Element target) { |
| // Set the type of the node to [Type] to mark this send as a |
| // type literal. |
| DartType type; |
| |
| // TODO(johnniwinther): Remove this hack when we can pass more complex |
| // information between methods than resolved elements. |
| if (target == compiler.typeClass && node.receiver == null) { |
| // Potentially a 'dynamic' type literal. |
| type = registry.getType(node.selector); |
| } |
| if (type == null) { |
| if (target.isTypedef || target.isClass) { |
| TypeDeclarationElement typeDeclaration = target; |
| typeDeclaration.computeType(compiler); |
| type = typeDeclaration.rawType; |
| } else { |
| TypeVariableElement typeVariable = target; |
| type = typeVariable.type; |
| } |
| } |
| registry.registerTypeLiteral(node, type); |
| |
| if (!target.isTypeVariable) { |
| // Don't try to make constants of calls and assignments to type literals. |
| if (!node.isCall && node.asSendSet() == null) { |
| analyzeConstantDeferred(node, enforceConst: false); |
| } else { |
| // The node itself is not a constant but we register the selector (the |
| // identifier that refers to the class/typedef) as a constant. |
| if (node.receiver != null) { |
| // This is a hack for the case of prefix.Type, we need to store |
| // the element on the selector, so [analyzeConstant] can build |
| // the type literal from the selector. |
| registry.useElement(node.selector, target); |
| } |
| analyzeConstantDeferred(node.selector, enforceConst: false); |
| } |
| } |
| } |
| |
| /// Check that access to `super` is currently allowed. |
| bool checkSuperAccess(Send node) { |
| if (!inInstanceContext) { |
| compiler.reportError(node, MessageKind.NO_SUPER_IN_STATIC); |
| return false; |
| } |
| if (node.isConditional) { |
| // `super?.foo` is not allowed. |
| compiler.reportError(node, MessageKind.INVALID_USE_OF_SUPER); |
| return false; |
| } |
| if (currentClass.supertype == null) { |
| // This is just to guard against internal errors, so no need |
| // for a real error message. |
| compiler.reportError(node, MessageKind.GENERIC, |
| {'text': "Object has no superclass"}); |
| return false; |
| } |
| registry.registerSuperUse(node); |
| return true; |
| } |
| |
| /// Check that access to `this` is currently allowed. |
| bool checkThisAccess(Send node) { |
| if (!inInstanceContext) { |
| compiler.reportError(node, MessageKind.NO_THIS_AVAILABLE); |
| return false; |
| } |
| return true; |
| } |
| |
| /// Compute the [AccessSemantics] corresponding to a super access of [target]. |
| AccessSemantics computeSuperAccessSemantics(Spannable node, Element target) { |
| if (target.isErroneous) { |
| return new StaticAccess.unresolvedSuper(target); |
| } else if (target.isGetter) { |
| return new StaticAccess.superGetter(target); |
| } else if (target.isSetter) { |
| return new StaticAccess.superSetter(target); |
| } else if (target.isField) { |
| if (target.isFinal) { |
| return new StaticAccess.superFinalField(target); |
| } else { |
| return new StaticAccess.superField(target); |
| } |
| } else { |
| assert(invariant(node, target.isFunction, |
| message: "Unexpected super target '$target'.")); |
| return new StaticAccess.superMethod(target); |
| } |
| } |
| |
| /// Compute the [AccessSemantics] corresponding to a local access of [target]. |
| AccessSemantics computeLocalAccessSemantics(Spannable node, |
| LocalElement target) { |
| if (target.isParameter) { |
| if (target.isFinal || target.isConst) { |
| return new StaticAccess.finalParameter(target); |
| } else { |
| return new StaticAccess.parameter(target); |
| } |
| } else if (target.isVariable) { |
| if (target.isFinal || target.isConst) { |
| return new StaticAccess.finalLocalVariable(target); |
| } else { |
| return new StaticAccess.localVariable(target); |
| } |
| } else { |
| assert(invariant(node, target.isFunction, |
| message: "Unexpected local target '$target'.")); |
| return new StaticAccess.localFunction(target); |
| } |
| } |
| |
| /// Compute the [AccessSemantics] corresponding to a static or toplevel access |
| /// of [target]. |
| AccessSemantics computeStaticOrTopLevelAccessSemantics( |
| Spannable node, |
| Element target) { |
| |
| target = target.declaration; |
| if (target.isErroneous) { |
| // This handles elements with parser errors. |
| // TODO(johnniwinther): Elements with parse error should not set |
| // [isErroneous] to `true`. |
| return new StaticAccess.unresolved(target); |
| } |
| if (target.isStatic) { |
| if (target.isGetter) { |
| return new StaticAccess.staticGetter(target); |
| } else if (target.isSetter) { |
| return new StaticAccess.staticSetter(target); |
| } else if (target.isField) { |
| if (target.isFinal || target.isConst) { |
| return new StaticAccess.finalStaticField(target); |
| } else { |
| return new StaticAccess.staticField(target); |
| } |
| } else { |
| assert(invariant(node, target.isFunction, |
| message: "Unexpected static target '$target'.")); |
| return new StaticAccess.staticMethod(target); |
| } |
| } else { |
| assert(invariant(node, target.isTopLevel, |
| message: "Unexpected statically resolved target '$target'.")); |
| if (target.isGetter) { |
| return new StaticAccess.topLevelGetter(target); |
| } else if (target.isSetter) { |
| return new StaticAccess.topLevelSetter(target); |
| } else if (target.isField) { |
| if (target.isFinal) { |
| return new StaticAccess.finalTopLevelField(target); |
| } else { |
| return new StaticAccess.topLevelField(target); |
| } |
| } else { |
| assert(invariant(node, target.isFunction, |
| message: "Unexpected top level target '$target'.")); |
| return new StaticAccess.topLevelMethod(target); |
| } |
| } |
| } |
| |
| /// Compute the [AccessSemantics] for accessing the name of [selector] on the |
| /// super class. |
| /// |
| /// If no matching super member is found and error is reported and |
| /// `noSuchMethod` on `super` is registered. Furthermore, if [alternateName] |
| /// is provided, the [AccessSemantics] corresponding to the alternate name is |
| /// returned. For instance, the access of a super setter for an unresolved |
| /// getter: |
| /// |
| /// class Super { |
| /// set name(_) {} |
| /// } |
| /// class Sub extends Super { |
| /// foo => super.name; // Access to the setter. |
| /// } |
| /// |
| AccessSemantics computeSuperAccessSemanticsForSelector( |
| Spannable node, |
| Selector selector, |
| {Name alternateName}) { |
| |
| Name name = selector.memberName; |
| // TODO(johnniwinther): Ensure correct behavior if currentClass is a |
| // patch. |
| Element target = currentClass.lookupSuperByName(name); |
| // [target] may be null which means invoking noSuchMethod on super. |
| if (target == null) { |
| Element error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': name}); |
| if (alternateName != null) { |
| target = currentClass.lookupSuperByName(alternateName); |
| } |
| if (target == null) { |
| // If a setter wasn't resolved, use the [ErroneousElement]. |
| target = error; |
| } |
| // We still need to register the invocation, because we might |
| // call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn]. |
| registry.registerDynamicInvocation(new UniverseSelector(selector, null)); |
| registry.registerSuperNoSuchMethod(); |
| } |
| return computeSuperAccessSemantics(node, target); |
| } |
| |
| /// Resolve [node] as a subexpression that is _not_ the prefix of a member |
| /// access. For instance `a` in `a + b`, as opposed to `a` in `a.b`. |
| ResolutionResult visitExpression(Node node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| ResolutionResult result = visit(node); |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| return result; |
| } |
| |
| /// Resolve [node] as a subexpression that _is_ the prefix of a member access. |
| /// For instance `a` in `a.b`, as opposed to `a` in `a + b`. |
| ResolutionResult visitExpressionPrefix(Node node) { |
| int oldAllowedCategory = allowedCategory; |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER; |
| sendIsMemberAccess = true; |
| ResolutionResult result = visit(node); |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| allowedCategory = oldAllowedCategory; |
| return result; |
| } |
| |
| /// Resolved [node] as a subexpression that is the prefix of a conditional |
| /// access. For instance `a` in `a?.b`. |
| // TODO(johnniwinther): Is this equivalent to [visitExpression]? |
| ResolutionResult visitConditionalPrefix(Node node) { |
| // Conditional sends like `e?.foo` treat the receiver as an expression. So |
| // `C?.foo` needs to be treated like `(C).foo`, not like C.foo. Prefixes and |
| // super are not allowed on their own in that context. |
| int oldAllowedCategory = allowedCategory; |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| allowedCategory = |
| ElementCategory.VARIABLE | |
| ElementCategory.FUNCTION | |
| ElementCategory.IMPLIES_TYPE; |
| ResolutionResult result = visit(node); |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| allowedCategory = oldAllowedCategory; |
| return result; |
| } |
| |
| /// Handle a type test expression, like `a is T` and `a is! T`. |
| ResolutionResult handleIs(Send node) { |
| Node expression = node.receiver; |
| visitExpression(expression); |
| |
| // TODO(johnniwinther): Use seen type tests to avoid registration of |
| // mutation/access to unpromoted variables. |
| |
| Send notTypeNode = node.arguments.head.asSend(); |
| DartType type; |
| SendStructure sendStructure; |
| if (notTypeNode != null) { |
| // `e is! T`. |
| Node typeNode = notTypeNode.receiver; |
| type = resolveTypeAnnotation(typeNode); |
| sendStructure = new IsNotStructure(type); |
| } else { |
| // `e is T`. |
| Node typeNode = node.arguments.head; |
| type = resolveTypeAnnotation(typeNode); |
| sendStructure = new IsStructure(type); |
| } |
| registry.registerIsCheck(type); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } |
| |
| /// Handle a type cast expression, like `a as T`. |
| ResolutionResult handleAs(Send node) { |
| Node expression = node.receiver; |
| visitExpression(expression); |
| |
| Node typeNode = node.arguments.head; |
| DartType type = resolveTypeAnnotation(typeNode); |
| registry.registerAsCheck(type); |
| registry.registerSendStructure(node, new AsStructure(type)); |
| return const NoneResult(); |
| } |
| |
| /// Handle the unary expression of an unresolved unary operator [text], like |
| /// the no longer supported `+a`. |
| ResolutionResult handleUnresolvedUnary(Send node, String text) { |
| Node expression = node.receiver; |
| if (node.isSuperCall) { |
| checkSuperAccess(node); |
| } else { |
| visitExpression(expression); |
| } |
| |
| registry.registerSendStructure(node, const InvalidUnaryStructure()); |
| return const NoneResult(); |
| } |
| |
| /// Handle the unary expression of a user definable unary [operator], like |
| /// `-a`, and `-super`. |
| ResolutionResult handleUserDefinableUnary(Send node, UnaryOperator operator) { |
| ResolutionResult result = const NoneResult(); |
| Node expression = node.receiver; |
| Selector selector = operator.selector; |
| // TODO(johnniwinther): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| AccessSemantics semantics; |
| if (node.isSuperCall) { |
| if (checkSuperAccess(node)) { |
| semantics = computeSuperAccessSemanticsForSelector(node, selector); |
| // TODO(johnniwinther): Add information to [AccessSemantics] about |
| // whether it is erroneous. |
| if (semantics.kind == AccessKind.SUPER_METHOD) { |
| registry.registerStaticUse(semantics.element.declaration); |
| } |
| // TODO(johnniwinther): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, semantics.element); |
| } |
| } else { |
| ResolutionResult expressionResult = visitExpression(expression); |
| semantics = new DynamicAccess.dynamicProperty(expression); |
| registry.registerDynamicInvocation(new UniverseSelector(selector, null)); |
| |
| if (expressionResult.isConstant) { |
| bool isValidConstant; |
| ConstantExpression expressionConstant = expressionResult.constant; |
| DartType knownExpressionType = |
| expressionConstant.getKnownType(coreTypes); |
| switch (operator.kind) { |
| case UnaryOperatorKind.COMPLEMENT: |
| isValidConstant = |
| knownExpressionType == coreTypes.intType; |
| break; |
| case UnaryOperatorKind.NEGATE: |
| isValidConstant = |
| knownExpressionType == coreTypes.intType || |
| knownExpressionType == coreTypes.doubleType; |
| break; |
| case UnaryOperatorKind.NOT: |
| internalError(node, |
| "Unexpected user definable unary operator: $operator"); |
| } |
| if (isValidConstant) { |
| // TODO(johnniwinther): Handle potentially invalid constant |
| // expressions. |
| ConstantExpression constant = |
| new UnaryConstantExpression(operator, expressionConstant); |
| registry.setConstant(node, constant); |
| result = new ConstantResult(node, constant); |
| } |
| } |
| } |
| if (semantics != null) { |
| // TODO(johnniwinther): Support invalid super access as an |
| // [AccessSemantics]. |
| registry.registerSendStructure(node, |
| new UnaryStructure(semantics, operator)); |
| } |
| return result; |
| } |
| |
| /// Handle a not expression, like `!a`. |
| ResolutionResult handleNot(Send node, UnaryOperator operator) { |
| assert(invariant(node, operator.kind == UnaryOperatorKind.NOT)); |
| |
| Node expression = node.receiver; |
| ResolutionResult result = visitExpression(expression); |
| registry.registerSendStructure(node, |
| new NotStructure(new DynamicAccess.dynamicProperty(expression))); |
| |
| if (result.isConstant) { |
| ConstantExpression expressionConstant = result.constant; |
| if (expressionConstant.getKnownType(coreTypes) == coreTypes.boolType) { |
| // TODO(johnniwinther): Handle potentially invalid constant expressions. |
| ConstantExpression constant = |
| new UnaryConstantExpression(operator, expressionConstant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } |
| |
| return const NoneResult(); |
| } |
| |
| /// Handle a logical and expression, like `a && b`. |
| ResolutionResult handleLogicalAnd(Send node) { |
| Node left = node.receiver; |
| Node right = node.arguments.head; |
| ResolutionResult leftResult = |
| doInPromotionScope(left, () => visitExpression(left)); |
| ResolutionResult rightResult = |
| doInPromotionScope(right, () => visitExpression(right)); |
| registry.registerSendStructure(node, const LogicalAndStructure()); |
| |
| if (leftResult.isConstant && rightResult.isConstant) { |
| ConstantExpression leftConstant = leftResult.constant; |
| ConstantExpression rightConstant = rightResult.constant; |
| if (leftConstant.getKnownType(coreTypes) == coreTypes.boolType && |
| rightConstant.getKnownType(coreTypes) == coreTypes.boolType) { |
| // TODO(johnniwinther): Handle potentially invalid constant expressions. |
| ConstantExpression constant = new BinaryConstantExpression( |
| leftConstant, |
| BinaryOperator.LOGICAL_AND, |
| rightConstant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } |
| |
| return const NoneResult(); |
| } |
| |
| /// Handle a logical or expression, like `a || b`. |
| ResolutionResult handleLogicalOr(Send node) { |
| Node left = node.receiver; |
| Node right = node.arguments.head; |
| ResolutionResult leftResult = visitExpression(left); |
| ResolutionResult rightResult = visitExpression(right); |
| registry.registerSendStructure(node, const LogicalOrStructure()); |
| |
| if (leftResult.isConstant && rightResult.isConstant) { |
| ConstantExpression leftConstant = leftResult.constant; |
| ConstantExpression rightConstant = rightResult.constant; |
| if (leftConstant.getKnownType(coreTypes) == coreTypes.boolType && |
| rightConstant.getKnownType(coreTypes) == coreTypes.boolType) { |
| // TODO(johnniwinther): Handle potentially invalid constant expressions. |
| ConstantExpression constant = new BinaryConstantExpression( |
| leftConstant, |
| BinaryOperator.LOGICAL_OR, |
| rightConstant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } |
| return const NoneResult(); |
| } |
| |
| /// Handle an if-null expression, like `a ?? b`. |
| ResolutionResult handleIfNull(Send node) { |
| Node left = node.receiver; |
| Node right = node.arguments.head; |
| visitExpression(left); |
| visitExpression(right); |
| registry.registerSendStructure(node, const IfNullStructure()); |
| return const NoneResult(); |
| } |
| |
| /// Handle the binary expression of an unresolved binary operator [text], like |
| /// the no longer supported `a === b`. |
| ResolutionResult handleUnresolvedBinary(Send node, String text) { |
| Node left = node.receiver; |
| Node right = node.arguments.head; |
| if (node.isSuperCall) { |
| checkSuperAccess(node); |
| } else { |
| visitExpression(left); |
| } |
| visitExpression(right); |
| registry.registerSendStructure(node, const InvalidBinaryStructure()); |
| return const NoneResult(); |
| } |
| |
| /// Handle the binary expression of a user definable binary [operator], like |
| /// `a + b`, `super + b`, `a == b` and `a != b`. |
| ResolutionResult handleUserDefinableBinary(Send node, |
| BinaryOperator operator) { |
| ResolutionResult result = const NoneResult(); |
| Node left = node.receiver; |
| Node right = node.arguments.head; |
| AccessSemantics semantics; |
| Selector selector; |
| if (operator.kind == BinaryOperatorKind.INDEX) { |
| selector = new Selector.index(); |
| } else { |
| selector = new Selector.binaryOperator(operator.selectorName); |
| } |
| // TODO(johnniwinther): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| if (node.isSuperCall) { |
| if (checkSuperAccess(node)) { |
| semantics = computeSuperAccessSemanticsForSelector(node, selector); |
| // TODO(johnniwinther): Add information to [AccessSemantics] about |
| // whether it is erroneous. |
| if (semantics.kind == AccessKind.SUPER_METHOD) { |
| registry.registerStaticUse(semantics.element.declaration); |
| } |
| // TODO(johnniwinther): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, semantics.element); |
| } |
| visitExpression(right); |
| } else { |
| ResolutionResult leftResult = visitExpression(left); |
| ResolutionResult rightResult = visitExpression(right); |
| registry.registerDynamicInvocation(new UniverseSelector(selector, null)); |
| semantics = new DynamicAccess.dynamicProperty(left); |
| |
| if (leftResult.isConstant && rightResult.isConstant) { |
| bool isValidConstant; |
| ConstantExpression leftConstant = leftResult.constant; |
| ConstantExpression rightConstant = leftResult.constant; |
| DartType knownLeftType = leftConstant.getKnownType(coreTypes); |
| DartType knownRightType = rightConstant.getKnownType(coreTypes); |
| switch (operator.kind) { |
| case BinaryOperatorKind.EQ: |
| case BinaryOperatorKind.NOT_EQ: |
| isValidConstant = |
| (knownLeftType == coreTypes.intType || |
| knownLeftType == coreTypes.doubleType || |
| knownLeftType == coreTypes.stringType || |
| knownLeftType == coreTypes.boolType || |
| knownLeftType == coreTypes.nullType) && |
| (knownRightType == coreTypes.intType || |
| knownRightType == coreTypes.doubleType || |
| knownRightType == coreTypes.stringType || |
| knownRightType == coreTypes.boolType || |
| knownRightType == coreTypes.nullType); |
| break; |
| case BinaryOperatorKind.ADD: |
| isValidConstant = |
| (knownLeftType == coreTypes.intType || |
| knownLeftType == coreTypes.doubleType || |
| knownLeftType == coreTypes.stringType) && |
| (knownRightType == coreTypes.intType || |
| knownRightType == coreTypes.doubleType || |
| knownRightType == coreTypes.stringType); |
| break; |
| case BinaryOperatorKind.SUB: |
| case BinaryOperatorKind.MUL: |
| case BinaryOperatorKind.DIV: |
| case BinaryOperatorKind.IDIV: |
| case BinaryOperatorKind.MOD: |
| case BinaryOperatorKind.GTEQ: |
| case BinaryOperatorKind.GT: |
| case BinaryOperatorKind.LTEQ: |
| case BinaryOperatorKind.LT: |
| isValidConstant = |
| (knownLeftType == coreTypes.intType || |
| knownLeftType == coreTypes.doubleType) && |
| (knownRightType == coreTypes.intType || |
| knownRightType == coreTypes.doubleType); |
| break; |
| case BinaryOperatorKind.SHL: |
| case BinaryOperatorKind.SHR: |
| case BinaryOperatorKind.AND: |
| case BinaryOperatorKind.OR: |
| case BinaryOperatorKind.XOR: |
| isValidConstant = |
| knownLeftType == coreTypes.intType && |
| knownRightType == coreTypes.intType; |
| break; |
| case BinaryOperatorKind.INDEX: |
| isValidConstant = false; |
| break; |
| case BinaryOperatorKind.LOGICAL_AND: |
| case BinaryOperatorKind.LOGICAL_OR: |
| case BinaryOperatorKind.IF_NULL: |
| internalError(node, "Unexpected binary operator '${operator}'."); |
| break; |
| } |
| if (isValidConstant) { |
| // TODO(johnniwinther): Handle potentially invalid constant |
| // expressions. |
| ConstantExpression constant = new BinaryConstantExpression( |
| leftResult.constant, |
| operator, |
| rightResult.constant); |
| registry.setConstant(node, constant); |
| result = new ConstantResult(node, constant); |
| } |
| } |
| } |
| |
| if (semantics != null) { |
| // TODO(johnniwinther): Support invalid super access as an |
| // [AccessSemantics]. |
| SendStructure sendStructure; |
| switch (operator.kind) { |
| case BinaryOperatorKind.EQ: |
| sendStructure = new EqualsStructure(semantics); |
| break; |
| case BinaryOperatorKind.NOT_EQ: |
| sendStructure = new NotEqualsStructure(semantics); |
| break; |
| case BinaryOperatorKind.INDEX: |
| sendStructure = new IndexStructure(semantics); |
| break; |
| case BinaryOperatorKind.ADD: |
| case BinaryOperatorKind.SUB: |
| case BinaryOperatorKind.MUL: |
| case BinaryOperatorKind.DIV: |
| case BinaryOperatorKind.IDIV: |
| case BinaryOperatorKind.MOD: |
| case BinaryOperatorKind.SHL: |
| case BinaryOperatorKind.SHR: |
| case BinaryOperatorKind.GTEQ: |
| case BinaryOperatorKind.GT: |
| case BinaryOperatorKind.LTEQ: |
| case BinaryOperatorKind.LT: |
| case BinaryOperatorKind.AND: |
| case BinaryOperatorKind.OR: |
| case BinaryOperatorKind.XOR: |
| sendStructure = new BinaryStructure(semantics, operator); |
| break; |
| case BinaryOperatorKind.LOGICAL_AND: |
| case BinaryOperatorKind.LOGICAL_OR: |
| case BinaryOperatorKind.IF_NULL: |
| internalError(node, "Unexpected binary operator '${operator}'."); |
| break; |
| } |
| registry.registerSendStructure(node, sendStructure); |
| } |
| return result; |
| } |
| |
| /// Handle an invocation of an expression, like `(){}()` or `(foo)()`. |
| ResolutionResult handleExpressionInvoke(Send node) { |
| assert(invariant(node, node.isCall, |
| message: "Unexpected expression: $node")); |
| Node expression = node.selector; |
| visitExpression(expression); |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| Selector selector = callStructure.callSelector; |
| // TODO(johnniwinther): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| registry.registerDynamicInvocation(new UniverseSelector(selector, null)); |
| registry.registerSendStructure(node, |
| new InvokeStructure(new AccessSemantics.expression(), selector)); |
| return const NoneResult(); |
| } |
| |
| /// Handle a, possibly invalid, assertion, like `assert(cond)` or `assert()`. |
| ResolutionResult handleAssert(Send node) { |
| assert(invariant(node, node.isCall, |
| message: "Unexpected assert: $node")); |
| // If this send is of the form "assert(expr);", then |
| // this is an assertion. |
| |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| SendStructure sendStructure = const AssertStructure(); |
| if (callStructure.argumentCount != 1) { |
| compiler.reportError( |
| node.selector, |
| MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT, |
| {'argumentCount': callStructure.argumentCount}); |
| sendStructure = const InvalidAssertStructure(); |
| } else if (callStructure.namedArgumentCount != 0) { |
| compiler.reportError( |
| node.selector, |
| MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS, |
| {'argumentCount': callStructure.namedArgumentCount}); |
| sendStructure = const InvalidAssertStructure(); |
| } |
| registry.registerAssert(node); |
| registry.registerSendStructure(node, sendStructure); |
| return const AssertResult(); |
| } |
| |
| /// Handle access of a property of [name] on `this`, like `this.name` and |
| /// `this.name()`, or `name` and `name()` in instance context. |
| ResolutionResult handleThisPropertyAccess(Send node, Name name) { |
| AccessSemantics semantics = new AccessSemantics.thisProperty(); |
| return handleDynamicAccessSemantics(node, name, semantics); |
| } |
| |
| /// Handle access on `this`, like `this()` and `this` when it is parsed as a |
| /// [Send] node. |
| ResolutionResult handleThisAccess(Send node) { |
| AccessSemantics accessSemantics = new AccessSemantics.thisAccess(); |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| Selector selector = callStructure.callSelector; |
| // TODO(johnniwinther): Handle invalid this access as an |
| // [AccessSemantics]. |
| if (checkThisAccess(node)) { |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| registry.registerSendStructure(node, |
| new InvokeStructure(accessSemantics, selector)); |
| } |
| // TODO(johnniwinther): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| } else { |
| // TODO(johnniwinther): Handle get of `this` when it is a [Send] node. |
| internalError(node, "Unexpected node '$node'."); |
| } |
| return const NoneResult(); |
| } |
| |
| /// Handle access of a super property, like `super.foo` and `super.foo()`. |
| ResolutionResult handleSuperPropertyAccess(Send node, Name name) { |
| Element target; |
| Selector selector; |
| CallStructure callStructure = CallStructure.NO_ARGS; |
| if (node.isCall) { |
| callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector(SelectorKind.CALL, name, callStructure); |
| } else { |
| selector = new Selector(SelectorKind.GETTER, name, callStructure); |
| } |
| if (checkSuperAccess(node)) { |
| AccessSemantics semantics = computeSuperAccessSemanticsForSelector( |
| node, selector, alternateName: name.setter); |
| if (node.isCall) { |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.SUPER_METHOD: |
| MethodElementX superMethod = semantics.element; |
| superMethod.computeSignature(compiler); |
| if (!callStructure.signatureApplies( |
| superMethod.functionSignature)) { |
| registry.registerThrowNoSuchMethod(); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| registry.registerSuperNoSuchMethod(); |
| isIncompatibleInvoke = true; |
| } else { |
| registry.registerStaticInvocation(semantics.element); |
| } |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| registry.registerStaticUse(semantics.element); |
| selector = callStructure.callSelector; |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| break; |
| case AccessKind.SUPER_SETTER: |
| case AccessKind.UNRESOLVED_SUPER: |
| // NoSuchMethod registered in [computeSuperSemantics]. |
| break; |
| default: |
| internalError(node, "Unexpected super property access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, |
| isIncompatibleInvoke |
| ? new IncompatibleInvokeStructure(semantics, selector) |
| : new InvokeStructure(semantics, selector)); |
| } else { |
| switch (semantics.kind) { |
| case AccessKind.SUPER_METHOD: |
| // TODO(johnniwinther): Method this should be registered as a |
| // closurization. |
| registry.registerStaticUse(semantics.element); |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| registry.registerStaticUse(semantics.element); |
| break; |
| case AccessKind.SUPER_SETTER: |
| case AccessKind.UNRESOLVED_SUPER: |
| // NoSuchMethod registered in [computeSuperSemantics]. |
| break; |
| default: |
| internalError(node, "Unexpected super property access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, |
| new GetStructure(semantics, selector)); |
| } |
| target = semantics.element; |
| } |
| |
| // TODO(johnniwinther): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, target); |
| registry.setSelector(node, selector); |
| return const NoneResult(); |
| } |
| |
| /// Handle a [Send] whose selector is an [Operator], like `a && b`, `a is T`, |
| /// `a + b`, and `~a`. |
| ResolutionResult handleOperatorSend(Send node) { |
| String operatorText = node.selector.asOperator().source; |
| if (operatorText == 'is') { |
| return handleIs(node); |
| } else if (operatorText == 'as') { |
| return handleAs(node); |
| } else if (node.arguments.isEmpty) { |
| UnaryOperator operator = UnaryOperator.parse(operatorText); |
| if (operator == null) { |
| return handleUnresolvedUnary(node, operatorText); |
| } else { |
| switch (operator.kind) { |
| case UnaryOperatorKind.NOT: |
| return handleNot(node, operator); |
| case UnaryOperatorKind.COMPLEMENT: |
| case UnaryOperatorKind.NEGATE: |
| assert(invariant(node, operator.isUserDefinable, |
| message: "Unexpected unary operator '${operator}'.")); |
| return handleUserDefinableUnary(node, operator); |
| } |
| return handleUserDefinableUnary(node, operator); |
| } |
| } else { |
| BinaryOperator operator = BinaryOperator.parse(operatorText); |
| if (operator == null) { |
| return handleUnresolvedBinary(node, operatorText); |
| } else { |
| switch (operator.kind) { |
| case BinaryOperatorKind.LOGICAL_AND: |
| return handleLogicalAnd(node); |
| case BinaryOperatorKind.LOGICAL_OR: |
| return handleLogicalOr(node); |
| case BinaryOperatorKind.IF_NULL: |
| return handleIfNull(node); |
| case BinaryOperatorKind.EQ: |
| case BinaryOperatorKind.NOT_EQ: |
| case BinaryOperatorKind.INDEX: |
| case BinaryOperatorKind.ADD: |
| case BinaryOperatorKind.SUB: |
| case BinaryOperatorKind.MUL: |
| case BinaryOperatorKind.DIV: |
| case BinaryOperatorKind.IDIV: |
| case BinaryOperatorKind.MOD: |
| case BinaryOperatorKind.SHL: |
| case BinaryOperatorKind.SHR: |
| case BinaryOperatorKind.GTEQ: |
| case BinaryOperatorKind.GT: |
| case BinaryOperatorKind.LTEQ: |
| case BinaryOperatorKind.LT: |
| case BinaryOperatorKind.AND: |
| case BinaryOperatorKind.OR: |
| case BinaryOperatorKind.XOR: |
| return handleUserDefinableBinary(node, operator); |
| } |
| } |
| } |
| } |
| |
| /// Handle qualified access to an unresolved static class member, like `a.b` |
| /// or `a.b()` where `a` is a class and `b` is unresolved. |
| ResolutionResult handleUnresolvedStaticMemberAccess( |
| Send node, Name name, ClassElement receiverClass) { |
| // TODO(johnniwinther): Share code with [handleStaticInstanceMemberAccess] |
| // and [handlePrivateStaticMemberAccess]. |
| registry.registerThrowNoSuchMethod(); |
| // TODO(johnniwinther): Produce a different error if [name] is resolves to |
| // a constructor. |
| |
| // 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. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.MEMBER_NOT_FOUND, |
| {'className': receiverClass.name, 'memberName': name.text}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleErroneousAccess( |
| node, name, error, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified access of an instance member, like `a.b` or `a.b()` where |
| /// `a` is a class and `b` is a non-static member. |
| ResolutionResult handleStaticInstanceMemberAccess( |
| Send node, Name name, ClassElement receiverClass, Element member) { |
| |
| 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. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.MEMBER_NOT_STATIC, |
| {'className': receiverClass.name, 'memberName': name}); |
| |
| // TODO(johnniwinther): Add an [AccessSemantics] for statically accessed |
| // instance members. |
| return handleErroneousAccess( |
| node, name, error, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified access of an inaccessible private static class member, |
| /// like `a._b` or `a.b()` where `a` is class, `_b` is static member of `a` |
| /// but `a` is not defined in the current library. |
| ResolutionResult handlePrivateStaticMemberAccess( |
| Send node, Name name, ClassElement receiverClass, Element member) { |
| registry.registerThrowNoSuchMethod(); |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.PRIVATE_ACCESS, |
| {'libraryName': member.library.getLibraryOrScriptName(), |
| 'name': name}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleErroneousAccess( |
| node, name, error, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified access to a static member, like `a.b` or `a.b()` where |
| /// `a` is a class and `b` is a static member of `a`. |
| ResolutionResult handleStaticMemberAccess( |
| Send node, Name memberName, ClassElement receiverClass) { |
| String name = memberName.text; |
| 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 const NoneResult(); |
| } |
| MembersCreator.computeClassMembersByName( |
| compiler, receiverClass.declaration, name); |
| Element member = receiverClass.lookupLocalMember(name); |
| if (member == null) { |
| return handleUnresolvedStaticMemberAccess( |
| node, memberName, receiverClass); |
| } else if (member.isAmbiguous) { |
| return handleAmbiguousSend(node, memberName, member); |
| } else if (member.isInstanceMember) { |
| return handleStaticInstanceMemberAccess( |
| node, memberName, receiverClass, member); |
| } else if (memberName.isPrivate && memberName.library != member.library) { |
| return handlePrivateStaticMemberAccess( |
| node, memberName, receiverClass, member); |
| } else { |
| return handleStaticOrTopLevelAccess(node, memberName, member); |
| } |
| } |
| |
| /// Handle qualified [Send] where the receiver resolves to an [Element], like |
| /// `a.b` where `a` is a local, field, class, or prefix, etc. |
| ResolutionResult handleResolvedQualifiedSend( |
| Send node, Name name, Element element) { |
| if (element.isPrefix) { |
| return oldVisitSend(node); |
| } else if (element.isClass) { |
| return handleStaticMemberAccess(node, name, element); |
| } |
| return oldVisitSend(node); |
| } |
| |
| /// Handle dynamic access of [semantics]. |
| ResolutionResult handleDynamicAccessSemantics( |
| Send node, Name name, AccessSemantics semantics) { |
| SendStructure sendStructure; |
| Selector selector; |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector(SelectorKind.CALL, name, callStructure); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| sendStructure = new InvokeStructure(semantics, selector); |
| } else { |
| assert(invariant(node, node.isPropertyAccess)); |
| selector = new Selector( |
| SelectorKind.GETTER, name, CallStructure.NO_ARGS); |
| registry.registerDynamicGetter( |
| new UniverseSelector(selector, null)); |
| sendStructure = new GetStructure(semantics, selector); |
| } |
| registry.registerSendStructure(node, sendStructure); |
| // TODO(johnniwinther): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| return const NoneResult(); |
| } |
| |
| /// Handle dynamic property access, like `a.b` or `a.b()` where `a` is not a |
| /// prefix or class. |
| ResolutionResult handleDynamicPropertyAccess(Send node, Name name) { |
| AccessSemantics semantics = |
| new DynamicAccess.dynamicProperty(node.receiver); |
| return handleDynamicAccessSemantics(node, name, semantics); |
| } |
| |
| /// Handle conditional access, like `a?.b` or `a?.b()`. |
| ResolutionResult handleConditionalAccess(Send node, Name name) { |
| Node receiver = node.receiver; |
| visitConditionalPrefix(receiver); |
| AccessSemantics semantics = |
| new DynamicAccess.ifNotNullProperty(receiver); |
| return handleDynamicAccessSemantics(node, name, semantics); |
| } |
| |
| /// Handle `this` as a qualified property, like `a.this`. |
| ResolutionResult handleQualifiedThisAccess(Send node, Name name) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node.selector, |
| name.text, |
| MessageKind.THIS_PROPERTY, {}, |
| isError: true); |
| // TODO(johnniwinther): Support `this` as property as an [AccessSemantics]. |
| AccessSemantics accessSemantics = new StaticAccess.unresolved(error); |
| return handleErroneousAccess(node, name, error, accessSemantics); |
| } |
| |
| /// Handle a qualified [Send], that is where the receiver is non-null, like |
| /// `a.b`, `a.b()`, `this.a()` and `super.a()`. |
| ResolutionResult handleQualifiedSend(Send node) { |
| Identifier selector = node.selector.asIdentifier(); |
| String text = selector.source; |
| Name name = new Name(text, enclosingElement.library); |
| if (text == 'this') { |
| return handleQualifiedThisAccess(node, name); |
| } else if (node.isSuperCall) { |
| return handleSuperPropertyAccess(node, name); |
| } else if (node.receiver.isThis()) { |
| if (checkThisAccess(node)) { |
| return handleThisPropertyAccess(node, name); |
| } |
| // TODO(johnniwinther): Handle invalid this access as an |
| // [AccessSemantics]. |
| return const NoneResult(); |
| } else if (node.isConditional) { |
| return handleConditionalAccess(node, name); |
| } |
| ResolutionResult result = visitExpressionPrefix(node.receiver); |
| if (result.element != null) { |
| return handleResolvedQualifiedSend(node, name, result.element); |
| } else { |
| return handleDynamicPropertyAccess(node, name); |
| } |
| } |
| |
| /// Handle access unresolved access to [name] in a non-instance context. |
| ResolutionResult handleUnresolvedAccess( |
| Send node, Name name, Element element) { |
| // TODO(johnniwinther): Support unresolved top level access as an |
| // [AccessSemantics]. |
| AccessSemantics accessSemantics = new StaticAccess.unresolved(element); |
| return handleErroneousAccess(node, name, element, accessSemantics); |
| } |
| |
| /// Handle erroneous access of [element] of the given [accessSemantics]. |
| ResolutionResult handleErroneousAccess( |
| Send node, Name name, Element element, AccessSemantics accessSemantics) { |
| SendStructure sendStructure; |
| Selector selector; |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector(SelectorKind.CALL, name, callStructure); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| sendStructure = new InvokeStructure(accessSemantics, selector); |
| } else { |
| assert(invariant(node, node.isPropertyAccess)); |
| selector = new Selector( |
| SelectorKind.GETTER, name, CallStructure.NO_ARGS); |
| registry.registerDynamicGetter( |
| new UniverseSelector(selector, null)); |
| sendStructure = new GetStructure(accessSemantics, selector); |
| } |
| // TODO(johnniwinther): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| registry.useElement(node, element); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } |
| |
| /// Handle access to an ambiguous element, that is, a name imported twice. |
| ResolutionResult handleAmbiguousSend( |
| Send node, |
| Name name, |
| AmbiguousElement element) { |
| |
| compiler.reportError( |
| node, element.messageKind, element.messageArguments); |
| element.diagnose(enclosingElement, compiler); |
| |
| ErroneousElement error = new ErroneousElementX( |
| element.messageKind, |
| element.messageArguments, |
| name.text, |
| enclosingElement); |
| |
| // TODO(johnniwinther): Support ambiguous access as an [AccessSemantics]. |
| AccessSemantics accessSemantics = new StaticAccess.unresolved(error); |
| return handleErroneousAccess(node, name, error, accessSemantics); |
| } |
| |
| /// Handle access of an instance [member] from a non-instance context. |
| ResolutionResult handleStaticInstanceSend( |
| Send node, Name name, MemberElement member) { |
| compiler.reportError( |
| node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': member.name}); |
| ErroneousElement error = new ErroneousElementX( |
| MessageKind.NO_INSTANCE_AVAILABLE, |
| {'name': name}, |
| name.text, |
| enclosingElement); |
| |
| // TODO(johnniwinther): Support static instance access as an |
| // [AccessSemantics]. |
| AccessSemantics accessSemantics = new StaticAccess.unresolved(error); |
| return handleErroneousAccess(node, name, error, accessSemantics); |
| } |
| |
| /// Handle access of a parameter, local variable or local function. |
| ResolutionResult handleLocalAccess(Send node, Name name, Element element) { |
| ResolutionResult result = const NoneResult(); |
| AccessSemantics semantics = computeLocalAccessSemantics(node, element); |
| Selector selector; |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector(SelectorKind.CALL, name, callStructure); |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.LOCAL_FUNCTION: |
| LocalFunctionElementX function = semantics.element; |
| function.computeSignature(compiler); |
| if (!callStructure.signatureApplies(function.functionSignature)) { |
| registry.registerThrowNoSuchMethod(); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| isIncompatibleInvoke = true; |
| } |
| break; |
| case AccessKind.PARAMETER: |
| case AccessKind.FINAL_PARAMETER: |
| case AccessKind.LOCAL_VARIABLE: |
| case AccessKind.FINAL_LOCAL_VARIABLE: |
| selector = callStructure.callSelector; |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| break; |
| default: |
| internalError(node, |
| "Unexpected local access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, |
| isIncompatibleInvoke |
| ? new IncompatibleInvokeStructure(semantics, selector) |
| : new InvokeStructure(semantics, selector)); |
| } else { |
| switch (semantics.kind) { |
| case AccessKind.LOCAL_VARIABLE: |
| case AccessKind.LOCAL_FUNCTION: |
| result = new ElementResult(element); |
| break; |
| case AccessKind.PARAMETER: |
| case AccessKind.FINAL_PARAMETER: |
| if (constantState == ConstantState.CONSTANT_INITIALIZER) { |
| ParameterElement parameter = element; |
| if (parameter.isNamed) { |
| result = new ConstantResult( |
| node, |
| new NamedArgumentReference(parameter.name), |
| element: element); |
| } else { |
| result = new ConstantResult( |
| node, |
| new PositionalArgumentReference( |
| parameter.functionDeclaration.parameters.indexOf( |
| parameter)), |
| element: element); |
| } |
| } else { |
| result = new ElementResult(element); |
| } |
| break; |
| case AccessKind.FINAL_LOCAL_VARIABLE: |
| if (element.isConst) { |
| result = new ConstantResult( |
| node, |
| new VariableConstantExpression(element), |
| element: element); |
| } else { |
| result = new ElementResult(element); |
| } |
| break; |
| default: |
| internalError(node, |
| "Unexpected local access $semantics."); |
| break; |
| } |
| selector = new Selector(SelectorKind.GETTER, name, CallStructure.NO_ARGS); |
| registry.registerSendStructure(node, |
| new GetStructure(semantics, selector)); |
| } |
| |
| // TODO(johnniwinther): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, element); |
| registry.setSelector(node, selector); |
| |
| registerPotentialAccessInClosure(node, element); |
| |
| return result; |
| } |
| |
| /// Handle access of a static or top level [element]. |
| ResolutionResult handleStaticOrTopLevelAccess( |
| Send node, Name name, Element element) { |
| ResolutionResult result = const NoneResult(); |
| MemberElement member; |
| if (element.isAbstractField) { |
| AbstractFieldElement abstractField = element; |
| if (abstractField.getter != null) { |
| member = abstractField.getter; |
| } else { |
| member = abstractField.setter; |
| } |
| } else { |
| member = element; |
| } |
| // TODO(johnniwinther): Needed to provoke a parsing and with it discovery |
| // of parse errors to make [element] erroneous. Fix this! |
| member.computeType(compiler); |
| |
| Selector selector; |
| AccessSemantics semantics = |
| computeStaticOrTopLevelAccessSemantics(node, member); |
| if (node.isCall) { |
| ArgumentsResult argumentsResult = |
| resolveArguments(node.argumentsNode); |
| CallStructure callStructure = argumentsResult.callStructure; |
| selector = new Selector(SelectorKind.CALL, name, callStructure); |
| |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.STATIC_METHOD: |
| case AccessKind.TOPLEVEL_METHOD: |
| MethodElementX method = semantics.element; |
| method.computeSignature(compiler); |
| if (!callStructure.signatureApplies(method.functionSignature)) { |
| registry.registerThrowNoSuchMethod(); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| isIncompatibleInvoke = true; |
| } else { |
| registry.registerStaticUse(semantics.element); |
| handleForeignCall(node, semantics.element, selector); |
| if (method == compiler.identicalFunction && |
| argumentsResult.isValidAsConstant) { |
| result = new ConstantResult(node, |
| new IdenticalConstantExpression( |
| argumentsResult.argumentResults[0].constant, |
| argumentsResult.argumentResults[1].constant)); |
| } |
| } |
| break; |
| case AccessKind.STATIC_FIELD: |
| case AccessKind.FINAL_STATIC_FIELD: |
| case AccessKind.STATIC_GETTER: |
| case AccessKind.TOPLEVEL_FIELD: |
| case AccessKind.FINAL_TOPLEVEL_FIELD: |
| case AccessKind.TOPLEVEL_GETTER: |
| registry.registerStaticUse(semantics.element); |
| selector = callStructure.callSelector; |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| break; |
| case AccessKind.STATIC_SETTER: |
| case AccessKind.TOPLEVEL_SETTER: |
| case AccessKind.UNRESOLVED: |
| registry.registerThrowNoSuchMethod(); |
| member = reportAndCreateErroneousElement( |
| node.selector, name.text, |
| MessageKind.CANNOT_RESOLVE_GETTER, const {}); |
| break; |
| default: |
| internalError(node, |
| "Unexpected statically resolved access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, |
| isIncompatibleInvoke |
| ? new IncompatibleInvokeStructure(semantics, selector) |
| : new InvokeStructure(semantics, selector)); |
| } else { |
| selector = new Selector(SelectorKind.GETTER, name, CallStructure.NO_ARGS); |
| switch (semantics.kind) { |
| case AccessKind.STATIC_METHOD: |
| case AccessKind.TOPLEVEL_METHOD: |
| // TODO(johnniwinther): Method this should be registered as a |
| // closurization. |
| registry.registerStaticUse(semantics.element); |
| registry.registerGetOfStaticFunction(semantics.element); |
| break; |
| case AccessKind.STATIC_FIELD: |
| case AccessKind.FINAL_STATIC_FIELD: |
| case AccessKind.STATIC_GETTER: |
| case AccessKind.TOPLEVEL_FIELD: |
| case AccessKind.FINAL_TOPLEVEL_FIELD: |
| case AccessKind.TOPLEVEL_GETTER: |
| registry.registerStaticUse(semantics.element); |
| break; |
| case AccessKind.STATIC_SETTER: |
| case AccessKind.TOPLEVEL_SETTER: |
| case AccessKind.UNRESOLVED: |
| registry.registerThrowNoSuchMethod(); |
| member = reportAndCreateErroneousElement( |
| node.selector, name.text, |
| MessageKind.CANNOT_RESOLVE_GETTER, const {}); |
| break; |
| default: |
| internalError(node, |
| "Unexpected statically resolved access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, |
| new GetStructure(semantics, selector)); |
| if (member.isConst) { |
| FieldElement field = member; |
| result = new ConstantResult( |
| node, new VariableConstantExpression(field), element: field); |
| } else { |
| result = new ElementResult(member); |
| } |
| } |
| |
| // TODO(johnniwinther): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, member); |
| registry.setSelector(node, selector); |
| |
| return result; |
| } |
| |
| /// Handle access to resolved [element]. |
| ResolutionResult handleResolvedSend(Send node, Name name, Element element) { |
| if (element.isAmbiguous) { |
| return handleAmbiguousSend(node, name, element); |
| } |
| if (element.isErroneous) { |
| // This handles elements with parser errors. |
| // TODO(johnniwinther): Elements with parse error should not set |
| // [isErroneous] to `true`. |
| assert(invariant(node, element is! ErroneousElement, |
| message: "Unexpected erroneous element $element.")); |
| return handleErroneousAccess(node, name, element, |
| new StaticAccess.unresolved(element)); |
| } |
| if (element.isInstanceMember) { |
| if (inInstanceContext) { |
| // TODO(johnniwinther): Maybe use the found [element]. |
| return handleThisPropertyAccess(node, name); |
| } else { |
| return handleStaticInstanceSend(node, name, element); |
| } |
| } |
| if (element.isClass || element.isTypedef) { |
| return oldVisitSend(node); |
| } else if (element.isTypeVariable) { |
| return oldVisitSend(node); |
| } else if (element.isPrefix) { |
| return oldVisitSend(node); |
| } else if (element.isLocal) { |
| return handleLocalAccess(node, name, element); |
| } else if (element.isStatic || element.isTopLevel) { |
| return handleStaticOrTopLevelAccess(node, name, element); |
| } |
| return internalError(node, "Unexpected resolved send: $element"); |
| } |
| |
| /// Handle an unqualified [Send], that is where the `node.receiver` is null, |
| /// like `a`, `a()`, `this()`, `assert()`, and `(){}()`. |
| ResolutionResult handleUnqualifiedSend(Send node) { |
| Identifier selector = node.selector.asIdentifier(); |
| if (selector == null) { |
| // `(){}()` and `(foo)()`. |
| return handleExpressionInvoke(node); |
| } |
| String text = selector.source; |
| if (text == 'assert') { |
| // `assert()`. |
| return handleAssert(node); |
| } else if (text == 'this') { |
| // `this()`. |
| return handleThisAccess(node); |
| } else if (text == 'dynamic') { |
| // `dynamic` || `dynamic()`. |
| // TODO(johnniwinther): Handle dynamic type literal access. |
| return oldVisitSend(node); |
| } |
| // `name` or `name()` |
| Name name = new Name(text, enclosingElement.library); |
| Element element = lookupInScope(compiler, node, scope, text); |
| if (element == null) { |
| if (inInstanceContext) { |
| // Implicitly `this.name`. |
| return handleThisPropertyAccess(node, name); |
| } else { |
| // Create [ErroneousElement] for unresolved access. |
| ErroneousElement error = reportCannotResolve(node, text); |
| return handleUnresolvedAccess(node, name, error); |
| } |
| } else { |
| return handleResolvedSend(node, name, element); |
| } |
| } |
| |
| ResolutionResult visitSend(Send node) { |
| if (node.isOperator) { |
| // `a && b`, `a + b`, `-a`, or `a is T`. |
| return handleOperatorSend(node); |
| } else if (node.receiver != null) { |
| // `a.b`. |
| return handleQualifiedSend(node); |
| } else { |
| // `a`. |
| return handleUnqualifiedSend(node); |
| } |
| } |
| |
| /// Regigster read access of [target] inside a closure. |
| void registerPotentialAccessInClosure(Send node, Element target) { |
| if (isPotentiallyMutableTarget(target)) { |
| if (enclosingElement != target.enclosingElement) { |
| for (Node scope in promotionScope) { |
| registry.setAccessedByClosureIn(scope, target, node); |
| } |
| } |
| } |
| } |
| |
| ResolutionResult oldVisitSend(Send node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = node.isPropertyAccess || node.isCall; |
| |
| ResolutionResult result = resolveSend(node); |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| |
| Element target = result.element; |
| |
| if (target != null |
| && target == compiler.mirrorSystemGetNameFunction |
| && !compiler.mirrorUsageAnalyzerTask.hasMirrorUsage(enclosingElement)) { |
| compiler.reportHint( |
| node.selector, MessageKind.STATIC_FUNCTION_BLOAT, |
| {'class': compiler.mirrorSystemClass.name, |
| 'name': compiler.mirrorSystemGetNameFunction.name}); |
| } |
| |
| if (target != null) { |
| if (target.isErroneous) { |
| registry.registerThrowNoSuchMethod(); |
| } else if (target.isAbstractField) { |
| AbstractFieldElement field = target; |
| target = field.getter; |
| if (target == null) { |
| if (!inInstanceContext || field.isTopLevel || field.isStatic) { |
| registry.registerThrowNoSuchMethod(); |
| target = reportAndCreateErroneousElement(node.selector, field.name, |
| MessageKind.CANNOT_RESOLVE_GETTER, const {}); |
| } |
| } |
| } else if (target.isTypeVariable) { |
| ClassElement cls = target.enclosingClass; |
| assert(enclosingElement.enclosingClass == cls); |
| if (!Elements.hasAccessToTypeVariables(enclosingElement)) { |
| compiler.reportError(node, |
| MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, |
| {'typeVariableName': node.selector}); |
| } |
| registry.registerClassUsingVariableExpression(cls); |
| registry.registerTypeVariableExpression(); |
| registerTypeLiteralAccess(node, target); |
| } else if (target.impliesType && (!sendIsMemberAccess || node.isCall)) { |
| registerTypeLiteralAccess(node, target); |
| } |
| registerPotentialAccessInClosure(node, target); |
| } |
| |
| resolveArguments(node.argumentsNode); |
| |
| // If the selector is null, it means that we will not be generating |
| // code for this as a send. |
| Selector selector = registry.getSelector(node); |
| if (selector == null) return const NoneResult(); |
| |
| if (node.isCall) { |
| if (Elements.isUnresolved(target) || |
| target.isGetter || |
| target.isField || |
| Elements.isClosureSend(node, target)) { |
| // If we don't know what we're calling or if we are calling a getter, |
| // we need to register that fact that we may be calling a closure |
| // with the same arguments. |
| Selector call = new Selector.callClosureFrom(selector); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| } else if (target.impliesType) { |
| // We call 'call()' on a Type instance returned from the reference to a |
| // class or typedef literal. We do not need to register this call as a |
| // dynamic invocation, because we statically know what the target is. |
| } else { |
| if (target is FunctionElement) { |
| FunctionElement function = target; |
| function.computeType(compiler); |
| } |
| if (!selector.applies(target, compiler.world)) { |
| registry.registerThrowNoSuchMethod(); |
| if (node.isSuperCall) { |
| internalError(node, "Unexpected super call $node"); |
| } |
| } |
| } |
| |
| handleForeignCall(node, target, selector); |
| } |
| |
| registry.useElement(node, target); |
| registerSend(selector, target); |
| if (node.isPropertyAccess && Elements.isStaticOrTopLevelFunction(target)) { |
| registry.registerGetOfStaticFunction(target.declaration); |
| } |
| return node.isPropertyAccess |
| ? new ResolutionResult.forElement(target) : const NoneResult(); |
| } |
| |
| // TODO(johnniwinther): Move this to the backend resolution callbacks. |
| void handleForeignCall(Send node, Element target, Selector selector) { |
| if (target != null && compiler.backend.isForeign(target)) { |
| if (selector.name == 'JS') { |
| registry.registerJsCall(node, this); |
| } else if (selector.name == 'JS_EMBEDDED_GLOBAL') { |
| registry.registerJsEmbeddedGlobalCall(node, this); |
| } else if (selector.name == 'JS_BUILTIN') { |
| registry.registerJsBuiltinCall(node, this); |
| } else if (selector.name == 'JS_INTERCEPTOR_CONSTANT') { |
| if (!node.argumentsNode.isEmpty) { |
| Node argument = node.argumentsNode.nodes.head; |
| if (argumentsToJsInterceptorConstant == null) { |
| argumentsToJsInterceptorConstant = new Set<Node>(); |
| } |
| argumentsToJsInterceptorConstant.add(argument); |
| } |
| } |
| } |
| } |
| |
| /// Callback for native enqueuer to parse a type. Returns [:null:] on error. |
| DartType resolveTypeFromString(Node node, String typeName) { |
| Element element = lookupInScope(compiler, node, scope, typeName); |
| if (element == null) return null; |
| if (element is! ClassElement) return null; |
| ClassElement cls = element; |
| cls.ensureResolved(compiler); |
| return cls.computeType(compiler); |
| } |
| |
| ResolutionResult visitSendSet(SendSet node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = node.isPropertyAccess || node.isCall; |
| ResolutionResult result = resolveSend(node); |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| Element target = result.element; |
| Element setter = target; |
| Element getter = target; |
| String operatorName = node.assignmentOperator.source; |
| String source = operatorName; |
| bool isComplex = !identical(source, '='); |
| if (!(result is AssertResult || Elements.isUnresolved(target))) { |
| if (target.isAbstractField) { |
| AbstractFieldElement field = target; |
| setter = field.setter; |
| getter = field.getter; |
| if (setter == null) { |
| if (!inInstanceContext || getter.isTopLevel || getter.isStatic) { |
| setter = reportAndCreateErroneousElement(node.selector, field.name, |
| MessageKind.CANNOT_RESOLVE_SETTER, const {}); |
| registry.registerThrowNoSuchMethod(); |
| } |
| } |
| if (isComplex && getter == null && !inInstanceContext) { |
| getter = reportAndCreateErroneousElement(node.selector, field.name, |
| MessageKind.CANNOT_RESOLVE_GETTER, const {}); |
| registry.registerThrowNoSuchMethod(); |
| } |
| } else if (target.impliesType) { |
| if (node.isIfNullAssignment) { |
| setter = reportAndCreateErroneousElement(node.selector, target.name, |
| MessageKind.IF_NULL_ASSIGNING_TYPE, const {}); |
| // In this case, no assignment happens, the rest of the compiler can |
| // treat the expression `C ??= e` as if it's just reading `C`. |
| } else { |
| setter = reportAndCreateErroneousElement(node.selector, target.name, |
| MessageKind.ASSIGNING_TYPE, const {}); |
| registry.registerThrowNoSuchMethod(); |
| } |
| registerTypeLiteralAccess(node, target); |
| } else if (target.isFinal || target.isConst) { |
| if (Elements.isStaticOrTopLevelField(target) || target.isLocal) { |
| setter = reportAndCreateErroneousElement( |
| node.selector, target.name, MessageKind.CANNOT_RESOLVE_SETTER, |
| const {}); |
| } else if (node.isSuperCall) { |
| setter = reportAndCreateErroneousElement( |
| node.selector, target.name, MessageKind.SETTER_NOT_FOUND_IN_SUPER, |
| {'name': target.name, 'className': currentClass.name}); |
| registry.registerSuperNoSuchMethod(); |
| } else { |
| // For instance fields we don't report a warning here because the type |
| // checker will detect this as well and report a better error message |
| // with the context of the containing class. |
| } |
| registry.registerThrowNoSuchMethod(); |
| } else if (target.isFunction && target.name != '[]=') { |
| assert(!target.isSetter); |
| if (Elements.isStaticOrTopLevelFunction(target) || target.isLocal) { |
| setter = reportAndCreateErroneousElement( |
| node.selector, target.name, MessageKind.ASSIGNING_METHOD, |
| const {}); |
| } else if (node.isSuperCall) { |
| setter = reportAndCreateErroneousElement( |
| node.selector, target.name, MessageKind.ASSIGNING_METHOD_IN_SUPER, |
| {'name': target.name, |
| 'superclassName': target.enclosingElement.name}); |
| registry.registerSuperNoSuchMethod(); |
| } else { |
| // For instance methods we don't report a warning here because the |
| // type checker will detect this as well and report a better error |
| // message with the context of the containing class. |
| } |
| registry.registerThrowNoSuchMethod(); |
| } |
| if (isPotentiallyMutableTarget(target)) { |
| registry.registerPotentialMutation(target, node); |
| if (enclosingElement != target.enclosingElement) { |
| registry.registerPotentialMutationInClosure(target, node); |
| } |
| for (Node scope in promotionScope) { |
| registry.registerPotentialMutationIn(scope, target, node); |
| } |
| } |
| } |
| |
| resolveArguments(node.argumentsNode); |
| |
| Selector selector = registry.getSelector(node); |
| if (isComplex) { |
| Selector getterSelector; |
| if (selector.isSetter) { |
| getterSelector = new Selector.getterFrom(selector); |
| } else { |
| assert(selector.isIndexSet); |
| getterSelector = new Selector.index(); |
| } |
| registerSend(getterSelector, getter); |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| if (node.isSuperCall) { |
| getter = currentClass.lookupSuperByName(getterSelector.memberName); |
| if (getter == null) { |
| target = reportAndCreateErroneousElement( |
| node, selector.name, MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': selector.name}); |
| registry.registerSuperNoSuchMethod(); |
| } |
| } |
| registry.useElement(node.selector, getter); |
| |
| // Make sure we include the + and - operators if we are using |
| // the ++ and -- ones. Also, if op= form is used, include op itself. |
| void registerBinaryOperator(String name) { |
| Selector binop = new Selector.binaryOperator(name); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(binop, null)); |
| registry.setOperatorSelectorInComplexSendSet(node, binop); |
| } |
| if (identical(source, '++')) { |
| registerBinaryOperator('+'); |
| registry.registerInstantiatedClass(compiler.intClass); |
| } else if (identical(source, '--')) { |
| registerBinaryOperator('-'); |
| registry.registerInstantiatedClass(compiler.intClass); |
| } else if (source.endsWith('=')) { |
| registerBinaryOperator(Elements.mapToUserOperator(operatorName)); |
| } |
| } |
| |
| registerSend(selector, setter); |
| return new ResolutionResult.forElement(registry.useElement(node, setter)); |
| } |
| |
| void registerSend(Selector selector, Element target) { |
| if (target == null || target.isInstanceMember) { |
| if (selector.isGetter) { |
| registry.registerDynamicGetter( |
| new UniverseSelector(selector, null)); |
| } else if (selector.isSetter) { |
| registry.registerDynamicSetter( |
| new UniverseSelector(selector, null)); |
| } else { |
| registry.registerDynamicInvocation( |
| new UniverseSelector(selector, null)); |
| } |
| } else if (Elements.isStaticOrTopLevel(target)) { |
| // Avoid registration of type variables since they are not analyzable but |
| // instead resolved through their enclosing type declaration. |
| if (!target.isTypeVariable) { |
| // [target] might be the implementation element and only declaration |
| // elements may be registered. |
| registry.registerStaticUse(target.declaration); |
| } |
| } |
| } |
| |
| ConstantResult visitLiteralInt(LiteralInt node) { |
| registry.registerInstantiatedType(coreTypes.intType); |
| ConstantExpression constant = new IntConstantExpression(node.value); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralDouble(LiteralDouble node) { |
| registry.registerInstantiatedType(coreTypes.doubleType); |
| ConstantExpression constant = new DoubleConstantExpression(node.value); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralBool(LiteralBool node) { |
| registry.registerInstantiatedType(coreTypes.boolType); |
| ConstantExpression constant = new BoolConstantExpression(node.value); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ResolutionResult visitLiteralString(LiteralString node) { |
| registry.registerInstantiatedType(coreTypes.stringType); |
| if (node.dartString != null) { |
| // [dartString] might be null on parser errors. |
| ConstantExpression constant = |
| new StringConstantExpression(node.dartString.slowToString()); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ConstantResult visitLiteralNull(LiteralNull node) { |
| registry.registerInstantiatedType(coreTypes.nullType); |
| ConstantExpression constant = new NullConstantExpression(); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralSymbol(LiteralSymbol node) { |
| registry.registerInstantiatedClass(compiler.symbolClass); |
| registry.registerStaticUse(compiler.symbolConstructor.declaration); |
| String name = node.slowNameString; |
| registry.registerConstSymbol(name); |
| if (!validateSymbol(node, name, reportError: false)) { |
| compiler.reportError(node, |
| MessageKind.UNSUPPORTED_LITERAL_SYMBOL, |
| {'value': name}); |
| } |
| analyzeConstantDeferred(node); |
| ConstantExpression constant = new SymbolConstantExpression(name); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ResolutionResult visitStringJuxtaposition(StringJuxtaposition node) { |
| registry.registerInstantiatedType(coreTypes.stringType); |
| ResolutionResult first = visit(node.first); |
| ResolutionResult second = visit(node.second); |
| if (first.isConstant && second.isConstant) { |
| ConstantExpression constant = new ConcatenateConstantExpression( |
| <ConstantExpression>[first.constant, second.constant]); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNodeList(NodeList node) { |
| for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { |
| visit(link.head); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitRethrow(Rethrow node) { |
| if (!inCatchBlock) { |
| error(node, MessageKind.THROW_WITHOUT_EXPRESSION); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitReturn(Return node) { |
| Node expression = node.expression; |
| if (expression != null) { |
| if (enclosingElement.isGenerativeConstructor) { |
| // It is a compile-time error if a return statement of the form |
| // `return e;` appears in a generative constructor. (Dart Language |
| // Specification 13.12.) |
| compiler.reportError(expression, |
| MessageKind.CANNOT_RETURN_FROM_CONSTRUCTOR); |
| } else if (!node.isArrowBody && currentAsyncMarker.isYielding) { |
| compiler.reportError( |
| node, |
| MessageKind.RETURN_IN_GENERATOR, |
| {'modifier': currentAsyncMarker}); |
| } |
| } |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitYield(Yield node) { |
| compiler.streamClass.ensureResolved(compiler); |
| compiler.iterableClass.ensureResolved(compiler); |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitRedirectingFactoryBody(RedirectingFactoryBody node) { |
| final isSymbolConstructor = enclosingElement == compiler.symbolConstructor; |
| if (!enclosingElement.isFactoryConstructor) { |
| compiler.reportError( |
| node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY); |
| compiler.reportHint( |
| enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD); |
| } |
| ConstructorElementX constructor = enclosingElement; |
| bool isConstConstructor = constructor.isConst; |
| bool isValidAsConstant = isConstConstructor; |
| ConstructorElement redirectionTarget = resolveRedirectingFactory( |
| node, inConstContext: isConstConstructor); |
| constructor.immediateRedirectionTarget = redirectionTarget; |
| |
| Node constructorReference = node.constructorReference; |
| if (constructorReference is Send) { |
| constructor.redirectionDeferredPrefix = |
| compiler.deferredLoadTask.deferredPrefixElement(constructorReference, |
| registry.mapping); |
| } |
| |
| registry.setRedirectingTargetConstructor(node, redirectionTarget); |
| if (Elements.isUnresolved(redirectionTarget)) { |
| registry.registerThrowNoSuchMethod(); |
| return const NoneResult(); |
| } else { |
| if (isConstConstructor && |
| !redirectionTarget.isConst) { |
| compiler.reportError(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST); |
| isValidAsConstant = false; |
| } |
| if (redirectionTarget == constructor) { |
| compiler.reportError(node, MessageKind.CYCLIC_REDIRECTING_FACTORY); |
| // TODO(johnniwinther): Create constant constructor for this case and |
| // let evaluation detect the cyclicity. |
| isValidAsConstant = false; |
| } |
| } |
| |
| // Check that the target constructor is type compatible with the |
| // redirecting constructor. |
| ClassElement targetClass = redirectionTarget.enclosingClass; |
| InterfaceType type = registry.getType(node); |
| FunctionType targetType = redirectionTarget.computeType(compiler) |
| .subst(type.typeArguments, targetClass.typeVariables); |
| FunctionType constructorType = constructor.computeType(compiler); |
| bool isSubtype = compiler.types.isSubtype(targetType, constructorType); |
| if (!isSubtype) { |
| warning(node, MessageKind.NOT_ASSIGNABLE, |
| {'fromType': targetType, 'toType': constructorType}); |
| // TODO(johnniwinther): Handle this (potentially) erroneous case. |
| isValidAsConstant = false; |
| } |
| |
| redirectionTarget.computeType(compiler); |
| FunctionSignature targetSignature = redirectionTarget.functionSignature; |
| constructor.computeType(compiler); |
| FunctionSignature constructorSignature = constructor.functionSignature; |
| if (!targetSignature.isCompatibleWith(constructorSignature)) { |
| assert(!isSubtype); |
| registry.registerThrowNoSuchMethod(); |
| isValidAsConstant = false; |
| } |
| |
| // Register a post process to check for cycles in the redirection chain and |
| // set the actual generative constructor at the end of the chain. |
| addDeferredAction(constructor, () { |
| compiler.resolver.resolveRedirectionChain(constructor, node); |
| }); |
| |
| registry.registerStaticUse(redirectionTarget); |
| // TODO(johnniwinther): Register the effective target type instead. |
| registry.registerInstantiatedClass( |
| redirectionTarget.enclosingClass.declaration); |
| if (isSymbolConstructor) { |
| registry.registerSymbolConstructor(); |
| } |
| if (isValidAsConstant) { |
| List<String> names = <String>[]; |
| List<ConstantExpression> arguments = <ConstantExpression>[]; |
| int index = 0; |
| constructorSignature.forEachParameter((ParameterElement parameter) { |
| if (parameter.isNamed) { |
| String name = parameter.name; |
| names.add(name); |
| arguments.add(new NamedArgumentReference(name)); |
| } else { |
| arguments.add(new PositionalArgumentReference(index)); |
| } |
| index++; |
| }); |
| CallStructure callStructure = |
| new CallStructure(constructorSignature.parameterCount, names); |
| constructor.constantConstructor = |
| new RedirectingFactoryConstantConstructor( |
| new ConstructedConstantExpression( |
| type, |
| redirectionTarget, |
| callStructure, |
| arguments)); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitThrow(Throw node) { |
| registry.registerThrowExpression(); |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitAwait(Await node) { |
| compiler.futureClass.ensureResolved(compiler); |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitVariableDefinitions(VariableDefinitions node) { |
| DartType type; |
| if (node.type != null) { |
| type = resolveTypeAnnotation(node.type); |
| } else { |
| type = const DynamicType(); |
| } |
| VariableList variables = new VariableList.node(node, type); |
| VariableDefinitionsVisitor visitor = |
| new VariableDefinitionsVisitor(compiler, node, this, variables); |
| |
| Modifiers modifiers = node.modifiers; |
| void reportExtraModifier(String modifier) { |
| Node modifierNode; |
| for (Link<Node> nodes = modifiers.nodes.nodes; |
| !nodes.isEmpty; |
| nodes = nodes.tail) { |
| if (modifier == nodes.head.asIdentifier().source) { |
| modifierNode = nodes.head; |
| break; |
| } |
| } |
| assert(modifierNode != null); |
| compiler.reportError(modifierNode, MessageKind.EXTRANEOUS_MODIFIER, |
| {'modifier': modifier}); |
| } |
| if (modifiers.isFinal && (modifiers.isConst || modifiers.isVar)) { |
| reportExtraModifier('final'); |
| } |
| if (modifiers.isVar && (modifiers.isConst || node.type != null)) { |
| reportExtraModifier('var'); |
| } |
| if (enclosingElement.isFunction) { |
| if (modifiers.isAbstract) { |
| reportExtraModifier('abstract'); |
| } |
| if (modifiers.isStatic) { |
| reportExtraModifier('static'); |
| } |
| } |
| if (node.metadata != null) { |
| variables.metadata = |
| compiler.resolver.resolveMetadata(enclosingElement, node); |
| } |
| visitor.visit(node.definitions); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitWhile(While node) { |
| visit(node.condition); |
| visitLoopBodyIn(node, node.body, new BlockScope(scope)); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitParenthesizedExpression(ParenthesizedExpression node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| var oldCategory = allowedCategory; |
| allowedCategory = |
| ElementCategory.VARIABLE | |
| ElementCategory.FUNCTION | |
| ElementCategory.IMPLIES_TYPE; |
| ResolutionResult result = visit(node.expression); |
| allowedCategory = oldCategory; |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| if (result.kind == ResultKind.CONSTANT) { |
| return result; |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNewExpression(NewExpression node) { |
| bool isValidAsConstant = true; |
| FunctionElement constructor = resolveConstructor(node); |
| final bool isSymbolConstructor = constructor == compiler.symbolConstructor; |
| final bool isMirrorsUsedConstant = |
| node.isConst && (constructor == compiler.mirrorsUsedConstructor); |
| Selector callSelector = resolveSelector(node.send, constructor); |
| ArgumentsResult argumentsResult; |
| if (node.isConst) { |
| argumentsResult = |
| inConstantContext(() => resolveArguments(node.send.argumentsNode)); |
| } else { |
| argumentsResult = resolveArguments(node.send.argumentsNode); |
| } |
| registry.useElement(node.send, constructor); |
| if (Elements.isUnresolved(constructor)) { |
| return new ResolutionResult.forElement(constructor); |
| } |
| constructor.computeType(compiler); |
| if (!callSelector.applies(constructor, compiler.world)) { |
| registry.registerThrowNoSuchMethod(); |
| } |
| |
| // [constructor] might be the implementation element |
| // and only declaration elements may be registered. |
| registry.registerStaticUse(constructor.declaration); |
| ClassElement cls = constructor.enclosingClass; |
| if (cls.isEnumClass && currentClass != cls) { |
| compiler.reportError(node, |
| MessageKind.CANNOT_INSTANTIATE_ENUM, |
| {'enumName': cls.name}); |
| isValidAsConstant = false; |
| } |
| |
| InterfaceType type = registry.getType(node); |
| if (node.isConst && type.containsTypeVariables) { |
| compiler.reportError(node.send.selector, |
| MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| } |
| // TODO(johniwinther): Avoid registration of `type` in face of redirecting |
| // factory constructors. |
| registry.registerInstantiatedType(type); |
| if (constructor.isGenerativeConstructor && cls.isAbstract) { |
| warning(node, MessageKind.ABSTRACT_CLASS_INSTANTIATION); |
| registry.registerAbstractClassInstantiation(); |
| isValidAsConstant = false; |
| } |
| |
| if (isSymbolConstructor) { |
| if (node.isConst) { |
| Node argumentNode = node.send.arguments.head; |
| ConstantExpression constant = |
| compiler.resolver.constantCompiler.compileNode( |
| argumentNode, registry.mapping); |
| ConstantValue name = compiler.constants.getConstantValue(constant); |
| if (!name.isString) { |
| DartType type = name.getType(coreTypes); |
| compiler.reportError(argumentNode, MessageKind.STRING_EXPECTED, |
| {'type': type}); |
| } else { |
| StringConstantValue stringConstant = name; |
| String nameString = stringConstant.toDartString().slowToString(); |
| if (validateSymbol(argumentNode, nameString)) { |
| registry.registerConstSymbol(nameString); |
| } |
| } |
| } else { |
| if (!compiler.mirrorUsageAnalyzerTask.hasMirrorUsage( |
| enclosingElement)) { |
| compiler.reportHint( |
| node.newToken, MessageKind.NON_CONST_BLOAT, |
| {'name': compiler.symbolClass.name}); |
| } |
| registry.registerNewSymbol(); |
| } |
| } else if (isMirrorsUsedConstant) { |
| compiler.mirrorUsageAnalyzerTask.validate(node, registry.mapping); |
| } |
| if (node.isConst) { |
| analyzeConstantDeferred(node); |
| if (isValidAsConstant && |
| constructor.isConst && |
| argumentsResult.isValidAsConstant) { |
| CallStructure callStructure = argumentsResult.callStructure; |
| List<ConstantExpression> arguments = argumentsResult.constantArguments; |
| ConstructedConstantExpression constant = |
| new ConstructedConstantExpression( |
| type, |
| constructor, |
| callStructure, |
| arguments); |
| return new ConstantResult(node, constant); |
| } |
| } |
| |
| return const NoneResult(); |
| } |
| |
| void checkConstMapKeysDontOverrideEquals(Spannable spannable, |
| MapConstantValue map) { |
| for (ConstantValue key in map.keys) { |
| if (!key.isObject) continue; |
| ObjectConstantValue objectConstant = key; |
| DartType keyType = objectConstant.type; |
| ClassElement cls = keyType.element; |
| if (cls == compiler.stringClass) continue; |
| Element equals = cls.lookupMember('=='); |
| if (equals.enclosingClass != compiler.objectClass) { |
| compiler.reportError(spannable, |
| MessageKind.CONST_MAP_KEY_OVERRIDES_EQUALS, |
| {'type': keyType}); |
| } |
| } |
| } |
| |
| void analyzeConstant(Node node, {enforceConst: true}) { |
| ConstantExpression constant = |
| compiler.resolver.constantCompiler.compileNode( |
| node, registry.mapping, enforceConst: enforceConst); |
| |
| if (constant == null) { |
| assert(invariant(node, compiler.compilationFailed)); |
| return; |
| } |
| |
| ConstantValue value = compiler.constants.getConstantValue(constant); |
| if (value.isMap) { |
| checkConstMapKeysDontOverrideEquals(node, value); |
| } |
| |
| // The type constant that is an argument to JS_INTERCEPTOR_CONSTANT names |
| // a class that will be instantiated outside the program by attaching a |
| // native class dispatch record referencing the interceptor. |
| if (argumentsToJsInterceptorConstant != null && |
| argumentsToJsInterceptorConstant.contains(node)) { |
| if (value.isType) { |
| TypeConstantValue typeConstant = value; |
| if (typeConstant.representedType is InterfaceType) { |
| registry.registerInstantiatedType(typeConstant.representedType); |
| } else { |
| compiler.reportError(node, |
| MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); |
| } |
| } else { |
| compiler.reportError(node, |
| MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); |
| } |
| } |
| } |
| |
| void analyzeConstantDeferred(Node node, {bool enforceConst: true}) { |
| addDeferredAction(enclosingElement, () { |
| analyzeConstant(node, enforceConst: enforceConst); |
| }); |
| } |
| |
| bool validateSymbol(Node node, String name, {bool reportError: true}) { |
| if (name.isEmpty) return true; |
| if (name.startsWith('_')) { |
| if (reportError) { |
| compiler.reportError(node, MessageKind.PRIVATE_IDENTIFIER, |
| {'value': name}); |
| } |
| return false; |
| } |
| if (!symbolValidationPattern.hasMatch(name)) { |
| if (reportError) { |
| compiler.reportError(node, MessageKind.INVALID_SYMBOL, |
| {'value': name}); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Try to resolve the constructor that is referred to by [node]. |
| * Note: this function may return an ErroneousFunctionElement instead of |
| * [:null:], if there is no corresponding constructor, class or library. |
| */ |
| ConstructorElement resolveConstructor(NewExpression node) { |
| return node.accept(new ConstructorResolver(compiler, this)); |
| } |
| |
| ConstructorElement resolveRedirectingFactory(RedirectingFactoryBody node, |
| {bool inConstContext: false}) { |
| return node.accept(new ConstructorResolver(compiler, this, |
| inConstContext: inConstContext)); |
| } |
| |
| DartType resolveTypeAnnotation(TypeAnnotation node, |
| {bool malformedIsError: false, |
| bool deferredIsMalformed: true}) { |
| DartType type = typeResolver.resolveTypeAnnotation( |
| this, node, malformedIsError: malformedIsError, |
| deferredIsMalformed: deferredIsMalformed); |
| if (inCheckContext) { |
| registry.registerIsCheck(type); |
| registry.registerRequiredType(type, enclosingElement); |
| } |
| return type; |
| } |
| |
| ResolutionResult visitLiteralList(LiteralList node) { |
| bool isValidAsConstant = true; |
| sendIsMemberAccess = false; |
| |
| NodeList arguments = node.typeArguments; |
| DartType typeArgument; |
| if (arguments != null) { |
| Link<Node> nodes = arguments.nodes; |
| if (nodes.isEmpty) { |
| // The syntax [: <>[] :] is not allowed. |
| error(arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| isValidAsConstant = false; |
| } else { |
| typeArgument = resolveTypeAnnotation(nodes.head); |
| for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { |
| warning(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); |
| resolveTypeAnnotation(nodes.head); |
| } |
| } |
| } |
| DartType listType; |
| if (typeArgument != null) { |
| if (node.isConst && typeArgument.containsTypeVariables) { |
| compiler.reportError(arguments.nodes.head, |
| MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| } |
| listType = coreTypes.listType(typeArgument); |
| } else { |
| listType = coreTypes.listType(); |
| } |
| registry.setType(node, listType); |
| registry.registerInstantiatedType(listType); |
| registry.registerRequiredType(listType, enclosingElement); |
| if (node.isConst) { |
| List<ConstantExpression> constantExpressions = <ConstantExpression>[]; |
| inConstantContext(() { |
| for (Node element in node.elements) { |
| ResolutionResult elementResult = visit(element); |
| if (isValidAsConstant && elementResult.isConstant) { |
| constantExpressions.add(elementResult.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| }); |
| analyzeConstantDeferred(node); |
| sendIsMemberAccess = false; |
| if (isValidAsConstant) { |
| ConstantExpression constant = |
| new ListConstantExpression(listType, constantExpressions); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } else { |
| visit(node.elements); |
| sendIsMemberAccess = false; |
| } |
| return const NoneResult(); |
| |
| } |
| |
| ResolutionResult visitConditional(Conditional node) { |
| ResolutionResult conditionResult = |
| doInPromotionScope(node.condition, () => visit(node.condition)); |
| ResolutionResult thenResult = |
| doInPromotionScope(node.thenExpression, () => visit(node.thenExpression)); |
| ResolutionResult elseResult = visit(node.elseExpression); |
| if (conditionResult.isConstant && |
| thenResult.isConstant && |
| elseResult.isConstant) { |
| ConstantExpression constant = new ConditionalConstantExpression( |
| conditionResult.constant, |
| thenResult.constant, |
| elseResult.constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitStringInterpolation(StringInterpolation node) { |
| registry.registerInstantiatedType(coreTypes.stringType); |
| registry.registerStringInterpolation(); |
| registerImplicitInvocation('toString', 0); |
| |
| bool isValidAsConstant = true; |
| List<ConstantExpression> parts = <ConstantExpression>[]; |
| |
| void resolvePart(Node subnode) { |
| ResolutionResult result = visit(subnode); |
| if (isValidAsConstant && result.isConstant) { |
| parts.add(result.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| |
| resolvePart(node.string); |
| for (StringInterpolationPart part in node.parts) { |
| resolvePart(part.expression); |
| resolvePart(part.string); |
| } |
| |
| if (isValidAsConstant) { |
| ConstantExpression constant = new ConcatenateConstantExpression(parts); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitBreakStatement(BreakStatement node) { |
| JumpTarget target; |
| if (node.target == null) { |
| target = statementScope.currentBreakTarget(); |
| if (target == null) { |
| error(node, MessageKind.NO_BREAK_TARGET); |
| return const NoneResult(); |
| } |
| target.isBreakTarget = true; |
| } else { |
| String labelName = node.target.source; |
| LabelDefinition label = statementScope.lookupLabel(labelName); |
| if (label == null) { |
| error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); |
| return const NoneResult(); |
| } |
| target = label.target; |
| if (!target.statement.isValidBreakTarget()) { |
| error(node.target, MessageKind.INVALID_BREAK); |
| return const NoneResult(); |
| } |
| label.setBreakTarget(); |
| registry.useLabel(node, label); |
| } |
| registry.registerTargetOf(node, target); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitContinueStatement(ContinueStatement node) { |
| JumpTarget target; |
| if (node.target == null) { |
| target = statementScope.currentContinueTarget(); |
| if (target == null) { |
| error(node, MessageKind.NO_CONTINUE_TARGET); |
| return const NoneResult(); |
| } |
| target.isContinueTarget = true; |
| } else { |
| String labelName = node.target.source; |
| LabelDefinition label = statementScope.lookupLabel(labelName); |
| if (label == null) { |
| error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); |
| return const NoneResult(); |
| } |
| target = label.target; |
| if (!target.statement.isValidContinueTarget()) { |
| error(node.target, MessageKind.INVALID_CONTINUE); |
| } |
| label.setContinueTarget(); |
| registry.useLabel(node, label); |
| } |
| registry.registerTargetOf(node, target); |
| return const NoneResult(); |
| } |
| |
| registerImplicitInvocation(String name, int arity) { |
| Selector selector = new Selector.call(name, null, arity); |
| registry.registerDynamicInvocation(new UniverseSelector(selector, null)); |
| } |
| |
| ResolutionResult visitAsyncForIn(AsyncForIn node) { |
| registry.registerAsyncForIn(node); |
| registry.setCurrentSelector(node, compiler.currentSelector); |
| registry.registerDynamicGetter( |
| new UniverseSelector(compiler.currentSelector, null)); |
| registry.setMoveNextSelector(node, compiler.moveNextSelector); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(compiler.moveNextSelector, null)); |
| |
| visit(node.expression); |
| |
| Scope blockScope = new BlockScope(scope); |
| visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope); |
| visitLoopBodyIn(node, node.body, blockScope); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitSyncForIn(SyncForIn node) { |
| registry.registerSyncForIn(node); |
| registry.setIteratorSelector(node, compiler.iteratorSelector); |
| registry.registerDynamicGetter( |
| new UniverseSelector(compiler.iteratorSelector, null)); |
| registry.setCurrentSelector(node, compiler.currentSelector); |
| registry.registerDynamicGetter( |
| new UniverseSelector(compiler.currentSelector, null)); |
| registry.setMoveNextSelector(node, compiler.moveNextSelector); |
| registry.registerDynamicInvocation( |
| new UniverseSelector(compiler.moveNextSelector, null)); |
| |
| visit(node.expression); |
| |
| Scope blockScope = new BlockScope(scope); |
| visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope); |
| visitLoopBodyIn(node, node.body, blockScope); |
| return const NoneResult(); |
| } |
| |
| void visitForInDeclaredIdentifierIn( |
| Node declaration, |
| ForIn node, |
| Scope blockScope) { |
| LibraryElement library = enclosingElement.library; |
| |
| bool oldAllowFinalWithoutInitializer = allowFinalWithoutInitializer; |
| allowFinalWithoutInitializer = true; |
| visitIn(declaration, blockScope); |
| allowFinalWithoutInitializer = oldAllowFinalWithoutInitializer; |
| |
| Send send = declaration.asSend(); |
| VariableDefinitions variableDefinitions = |
| declaration.asVariableDefinitions(); |
| Element loopVariable; |
| Selector loopVariableSelector; |
| if (send != null) { |
| loopVariable = registry.getDefinition(send); |
| Identifier identifier = send.selector.asIdentifier(); |
| if (identifier == null) { |
| compiler.reportError(send.selector, MessageKind.INVALID_FOR_IN); |
| } else { |
| loopVariableSelector = new Selector.setter(identifier.source, library); |
| } |
| if (send.receiver != null) { |
| compiler.reportError(send.receiver, MessageKind.INVALID_FOR_IN); |
| } |
| } else if (variableDefinitions != null) { |
| Link<Node> nodes = variableDefinitions.definitions.nodes; |
| if (!nodes.tail.isEmpty) { |
| compiler.reportError(nodes.tail.head, MessageKind.INVALID_FOR_IN); |
| } |
| Node first = nodes.head; |
| Identifier identifier = first.asIdentifier(); |
| if (identifier == null) { |
| compiler.reportError(first, MessageKind.INVALID_FOR_IN); |
| } else { |
| loopVariableSelector = new Selector.setter(identifier.source, library); |
| loopVariable = registry.getDefinition(identifier); |
| } |
| } else { |
| compiler.reportError(declaration, MessageKind.INVALID_FOR_IN); |
| } |
| if (loopVariableSelector != null) { |
| registry.setSelector(declaration, loopVariableSelector); |
| registerSend(loopVariableSelector, loopVariable); |
| } else { |
| // The selector may only be null if we reported an error. |
| assert(invariant(declaration, compiler.compilationFailed)); |
| } |
| if (loopVariable != null) { |
| // loopVariable may be null if it could not be resolved. |
| registry.setForInVariable(node, loopVariable); |
| } |
| } |
| |
| visitLabel(Label node) { |
| // Labels are handled by their containing statements/cases. |
| } |
| |
| ResolutionResult visitLabeledStatement(LabeledStatement node) { |
| Statement body = node.statement; |
| JumpTarget targetElement = getOrDefineTarget(body); |
| Map<String, LabelDefinition> labelElements = <String, LabelDefinition>{}; |
| for (Label label in node.labels) { |
| String labelName = label.labelName; |
| if (labelElements.containsKey(labelName)) continue; |
| LabelDefinition element = targetElement.addLabel(label, labelName); |
| labelElements[labelName] = element; |
| } |
| statementScope.enterLabelScope(labelElements); |
| visit(node.statement); |
| statementScope.exitLabelScope(); |
| labelElements.forEach((String labelName, LabelDefinition element) { |
| if (element.isTarget) { |
| registry.defineLabel(element.label, element); |
| } else { |
| warning(element.label, MessageKind.UNUSED_LABEL, |
| {'labelName': labelName}); |
| } |
| }); |
| if (!targetElement.isTarget) { |
| registry.undefineTarget(body); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitLiteralMap(LiteralMap node) { |
| bool isValidAsConstant = true; |
| sendIsMemberAccess = false; |
| |
| NodeList arguments = node.typeArguments; |
| DartType keyTypeArgument; |
| DartType valueTypeArgument; |
| if (arguments != null) { |
| Link<Node> nodes = arguments.nodes; |
| if (nodes.isEmpty) { |
| // The syntax [: <>{} :] is not allowed. |
| error(arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| isValidAsConstant = false; |
| } else { |
| keyTypeArgument = resolveTypeAnnotation(nodes.head); |
| nodes = nodes.tail; |
| if (nodes.isEmpty) { |
| warning(arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| } else { |
| valueTypeArgument = resolveTypeAnnotation(nodes.head); |
| for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { |
| warning(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); |
| resolveTypeAnnotation(nodes.head); |
| } |
| } |
| } |
| } |
| DartType mapType; |
| if (valueTypeArgument != null) { |
| mapType = coreTypes.mapType(keyTypeArgument, valueTypeArgument); |
| } else { |
| mapType = coreTypes.mapType(); |
| } |
| if (node.isConst && mapType.containsTypeVariables) { |
| compiler.reportError(arguments, |
| MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| } |
| registry.registerMapLiteral(node, mapType, node.isConst); |
| registry.registerRequiredType(mapType, enclosingElement); |
| if (node.isConst) { |
| |
| List<ConstantExpression> keyExpressions = <ConstantExpression>[]; |
| List<ConstantExpression> valueExpressions = <ConstantExpression>[]; |
| inConstantContext(() { |
| for (LiteralMapEntry entry in node.entries) { |
| ResolutionResult keyResult = visit(entry.key); |
| ResolutionResult valueResult = visit(entry.value); |
| if (isValidAsConstant && |
| keyResult.isConstant && |
| valueResult.isConstant) { |
| keyExpressions.add(keyResult.constant); |
| valueExpressions.add(valueResult.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| }); |
| analyzeConstantDeferred(node); |
| sendIsMemberAccess = false; |
| if (isValidAsConstant) { |
| ConstantExpression constant = new MapConstantExpression( |
| mapType, keyExpressions, valueExpressions); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } else { |
| node.visitChildren(this); |
| sendIsMemberAccess = false; |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitLiteralMapEntry(LiteralMapEntry node) { |
| node.visitChildren(this); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNamedArgument(NamedArgument node) { |
| return visit(node.expression); |
| } |
| |
| DartType typeOfConstant(ConstantValue constant) { |
| if (constant.isInt) return compiler.intClass.rawType; |
| if (constant.isBool) return compiler.boolClass.rawType; |
| if (constant.isDouble) return compiler.doubleClass.rawType; |
| if (constant.isString) return compiler.stringClass.rawType; |
| if (constant.isNull) return compiler.nullClass.rawType; |
| if (constant.isFunction) return compiler.functionClass.rawType; |
| assert(constant.isObject); |
| ObjectConstantValue objectConstant = constant; |
| return objectConstant.type; |
| } |
| |
| bool overridesEquals(DartType type) { |
| ClassElement cls = type.element; |
| Element equals = cls.lookupMember('=='); |
| return equals.enclosingClass != compiler.objectClass; |
| } |
| |
| void checkCaseExpressions(SwitchStatement node) { |
| CaseMatch firstCase = null; |
| DartType firstCaseType = null; |
| bool hasReportedProblem = false; |
| |
| for (Link<Node> cases = node.cases.nodes; |
| !cases.isEmpty; |
| cases = cases.tail) { |
| SwitchCase switchCase = cases.head; |
| |
| for (Node labelOrCase in switchCase.labelsAndCases) { |
| CaseMatch caseMatch = labelOrCase.asCaseMatch(); |
| if (caseMatch == null) continue; |
| |
| // Analyze the constant. |
| ConstantExpression constant = |
| registry.getConstant(caseMatch.expression); |
| assert(invariant(node, constant != null, |
| message: 'No constant computed for $node')); |
| |
| ConstantValue value = compiler.constants.getConstantValue(constant); |
| DartType caseType = typeOfConstant(value); |
| |
| if (firstCaseType == null) { |
| firstCase = caseMatch; |
| firstCaseType = caseType; |
| |
| // We only report the bad type on the first class element. All others |
| // get a "type differs" error. |
| if (caseType.element == compiler.doubleClass) { |
| compiler.reportError(node, |
| MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS, |
| {'type': "double"}); |
| } else if (caseType.element == compiler.functionClass) { |
| compiler.reportError(node, MessageKind.SWITCH_CASE_FORBIDDEN, |
| {'type': "Function"}); |
| } else if (value.isObject && overridesEquals(caseType)) { |
| compiler.reportError(firstCase.expression, |
| MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS, |
| {'type': caseType}); |
| } |
| } else { |
| if (caseType != firstCaseType) { |
| if (!hasReportedProblem) { |
| compiler.reportError( |
| node, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL, |
| {'type': firstCaseType}); |
| compiler.reportInfo( |
| firstCase.expression, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| {'type': firstCaseType}); |
| hasReportedProblem = true; |
| } |
| compiler.reportInfo( |
| caseMatch.expression, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| {'type': caseType}); |
| } |
| } |
| } |
| } |
| } |
| |
| ResolutionResult visitSwitchStatement(SwitchStatement node) { |
| node.expression.accept(this); |
| |
| JumpTarget breakElement = getOrDefineTarget(node); |
| Map<String, LabelDefinition> continueLabels = <String, LabelDefinition>{}; |
| Link<Node> cases = node.cases.nodes; |
| while (!cases.isEmpty) { |
| SwitchCase switchCase = cases.head; |
| for (Node labelOrCase in switchCase.labelsAndCases) { |
| CaseMatch caseMatch = labelOrCase.asCaseMatch(); |
| if (caseMatch != null) { |
| analyzeConstantDeferred(caseMatch.expression); |
| continue; |
| } |
| Label label = labelOrCase; |
| String labelName = label.labelName; |
| |
| LabelDefinition existingElement = continueLabels[labelName]; |
| if (existingElement != null) { |
| // It's an error if the same label occurs twice in the same switch. |
| compiler.reportError( |
| label, |
| MessageKind.DUPLICATE_LABEL, {'labelName': labelName}); |
| compiler.reportInfo( |
| existingElement.label, |
| MessageKind.EXISTING_LABEL, {'labelName': labelName}); |
| } else { |
| // It's only a warning if it shadows another label. |
| existingElement = statementScope.lookupLabel(labelName); |
| if (existingElement != null) { |
| compiler.reportWarning( |
| label, |
| MessageKind.DUPLICATE_LABEL, {'labelName': labelName}); |
| compiler.reportInfo( |
| existingElement.label, |
| MessageKind.EXISTING_LABEL, {'labelName': labelName}); |
| } |
| } |
| |
| JumpTarget targetElement = getOrDefineTarget(switchCase); |
| LabelDefinition labelElement = targetElement.addLabel(label, labelName); |
| registry.defineLabel(label, labelElement); |
| continueLabels[labelName] = labelElement; |
| } |
| cases = cases.tail; |
| // Test that only the last case, if any, is a default case. |
| if (switchCase.defaultKeyword != null && !cases.isEmpty) { |
| error(switchCase, MessageKind.INVALID_CASE_DEFAULT); |
| } |
| } |
| |
| addDeferredAction(enclosingElement, () { |
| checkCaseExpressions(node); |
| }); |
| |
| statementScope.enterSwitch(breakElement, continueLabels); |
| node.cases.accept(this); |
| statementScope.exitSwitch(); |
| |
| // Clean-up unused labels. |
| continueLabels.forEach((String key, LabelDefinition label) { |
| if (!label.isContinueTarget) { |
| JumpTarget targetElement = label.target; |
| SwitchCase switchCase = targetElement.statement; |
| registry.undefineTarget(switchCase); |
| registry.undefineLabel(label.label); |
| } |
| }); |
| // TODO(15575): We should warn if we can detect a fall through |
| // error. |
| registry.registerFallThroughError(); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitSwitchCase(SwitchCase node) { |
| node.labelsAndCases.accept(this); |
| visitIn(node.statements, new BlockScope(scope)); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitCaseMatch(CaseMatch node) { |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitTryStatement(TryStatement node) { |
| visit(node.tryBlock); |
| if (node.catchBlocks.isEmpty && node.finallyBlock == null) { |
| error(node.getEndToken().next, MessageKind.NO_CATCH_NOR_FINALLY); |
| } |
| visit(node.catchBlocks); |
| visit(node.finallyBlock); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitCatchBlock(CatchBlock node) { |
| registry.registerCatchStatement(); |
| // Check that if catch part is present, then |
| // it has one or two formal parameters. |
| VariableDefinitions exceptionDefinition; |
| VariableDefinitions stackTraceDefinition; |
| if (node.formals != null) { |
| Link<Node> formalsToProcess = node.formals.nodes; |
| if (formalsToProcess.isEmpty) { |
| error(node, MessageKind.EMPTY_CATCH_DECLARATION); |
| } else { |
| exceptionDefinition = formalsToProcess.head.asVariableDefinitions(); |
| formalsToProcess = formalsToProcess.tail; |
| if (!formalsToProcess.isEmpty) { |
| stackTraceDefinition = formalsToProcess.head.asVariableDefinitions(); |
| formalsToProcess = formalsToProcess.tail; |
| if (!formalsToProcess.isEmpty) { |
| for (Node extra in formalsToProcess) { |
| error(extra, MessageKind.EXTRA_CATCH_DECLARATION); |
| } |
| } |
| registry.registerStackTraceInCatch(); |
| } |
| } |
| |
| // Check that the formals aren't optional and that they have no |
| // modifiers or type. |
| for (Link<Node> link = node.formals.nodes; |
| !link.isEmpty; |
| link = link.tail) { |
| // If the formal parameter is a node list, it means that it is a |
| // sequence of optional parameters. |
| NodeList nodeList = link.head.asNodeList(); |
| if (nodeList != null) { |
| error(nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH); |
| } else { |
| VariableDefinitions declaration = link.head; |
| for (Node modifier in declaration.modifiers.nodes) { |
| error(modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH); |
| } |
| TypeAnnotation type = declaration.type; |
| if (type != null) { |
| error(type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH); |
| } |
| } |
| } |
| } |
| |
| Scope blockScope = new BlockScope(scope); |
| doInCheckContext(() => visitIn(node.type, blockScope)); |
| visitIn(node.formals, blockScope); |
| var oldInCatchBlock = inCatchBlock; |
| inCatchBlock = true; |
| visitIn(node.block, blockScope); |
| inCatchBlock = oldInCatchBlock; |
| |
| if (node.type != null && exceptionDefinition != null) { |
| DartType exceptionType = registry.getType(node.type); |
| Node exceptionVariable = exceptionDefinition.definitions.nodes.head; |
| VariableElementX exceptionElement = |
| registry.getDefinition(exceptionVariable); |
| exceptionElement.variables.type = exceptionType; |
| } |
| if (stackTraceDefinition != null) { |
| Node stackTraceVariable = stackTraceDefinition.definitions.nodes.head; |
| VariableElementX stackTraceElement = |
| registry.getDefinition(stackTraceVariable); |
| registry.registerInstantiatedClass(compiler.stackTraceClass); |
| stackTraceElement.variables.type = compiler.stackTraceClass.rawType; |
| } |
| return const NoneResult(); |
| } |
| } |
| |
| /// Looks up [name] in [scope] and unwraps the result. |
| Element lookupInScope(Compiler compiler, Node node, |
| Scope scope, String name) { |
| return Elements.unwrap(scope.lookup(name), compiler, node); |
| } |