| // 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. |
| |
| library dart2js.resolution.members; |
| |
| import '../common.dart'; |
| import '../common/names.dart' show Selectors; |
| import '../common/resolution.dart' show Resolution; |
| import '../compile_time_constants.dart'; |
| import '../constants/constructors.dart' |
| show RedirectingFactoryConstantConstructor; |
| import '../constants/expressions.dart'; |
| import '../constants/values.dart'; |
| import '../core_types.dart'; |
| import '../dart_types.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/modelx.dart' |
| show |
| ConstructorElementX, |
| ErroneousElementX, |
| FunctionElementX, |
| JumpTargetX, |
| LocalFunctionElementX, |
| LocalParameterElementX, |
| ParameterElementX, |
| VariableElementX, |
| VariableList; |
| import '../options.dart'; |
| import '../tokens/token.dart' show isUserDefinableOperator; |
| import '../tree/tree.dart'; |
| import '../universe/call_structure.dart' show CallStructure; |
| import '../universe/feature.dart' show Feature; |
| import '../universe/selector.dart' show Selector; |
| import '../universe/use.dart' show DynamicUse, StaticUse, TypeUse; |
| import '../util/util.dart' show Link; |
| import 'access_semantics.dart'; |
| import 'class_members.dart' show MembersCreator; |
| import 'constructors.dart' |
| show ConstructorResolver, ConstructorResult, ConstructorResultKind; |
| import 'label_scope.dart' show StatementScope; |
| import 'operators.dart'; |
| import 'registry.dart' show ResolutionRegistry; |
| import 'resolution.dart' show ResolverTask; |
| import 'resolution_common.dart' show MappingVisitor; |
| import 'resolution_result.dart'; |
| import 'scope.dart' show BlockScope, MethodScope, Scope; |
| import 'send_structure.dart'; |
| import 'signatures.dart' show SignatureResolver; |
| import 'variables.dart' show VariableDefinitionsVisitor; |
| |
| /// 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 inCatchParameters = false; |
| bool inCatchBlock; |
| ConstantState constantState; |
| |
| Scope scope; |
| ClassElement currentClass; |
| ExpressionStatement currentExpressionStatement; |
| |
| /// `true` if a [Send] or [SendSet] is visited as the prefix of member access. |
| /// For instance `Class` in `Class.staticField` or `prefix.Class` in |
| /// `prefix.Class.staticMethod()`. |
| bool sendIsMemberAccess = false; |
| |
| StatementScope statementScope; |
| int allowedCategory = ElementCategory.VARIABLE | |
| ElementCategory.FUNCTION | |
| ElementCategory.IMPLIES_TYPE; |
| |
| /// 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 inLoopVariable = 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.isRegularParameter) && |
| !(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( |
| Resolution resolution, Element element, ResolutionRegistry registry, |
| {Scope scope, 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(), |
| this.scope = 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 = resolution.options.enableTypeAssertions && |
| !element.isLibrary && |
| !element.isTypedef && |
| !element.enclosingElement.isTypedef, |
| inCatchBlock = false, |
| constantState = element.isConst |
| ? ConstantState.CONSTANT |
| : ConstantState.NON_CONSTANT, |
| super(resolution, registry); |
| |
| CoreClasses get coreClasses => resolution.coreClasses; |
| CoreTypes get coreTypes => resolution.coreTypes; |
| ConstantEnvironment get constants => resolution.constants; |
| ResolverTask get resolver => resolution.resolver; |
| CompilerOptions get options => resolution.options; |
| |
| 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) { |
| reporter.reportErrorMessage( |
| 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; |
| return reportAndCreateErroneousElement( |
| node, name, ambiguous.messageKind, ambiguous.messageArguments, |
| infos: ambiguous.computeInfos(enclosingElement, reporter), |
| isError: true); |
| } |
| } |
| 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; |
| } |
| |
| 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, |
| {List<DiagnosticMessage> infos: const <DiagnosticMessage>[], |
| bool isError: false}) { |
| if (isError) { |
| reporter.reportError( |
| reporter.createMessage(node, kind, arguments), infos); |
| } else { |
| reporter.reportWarning( |
| reporter.createMessage(node, kind, arguments), infos); |
| } |
| // 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.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| return reportAndCreateErroneousElement(node, name, kind, arguments, |
| isError: inInitializer); |
| } |
| |
| ResolutionResult visitIdentifier(Identifier node) { |
| if (node.isThis()) { |
| if (!inInstanceContext) { |
| reporter.reportErrorMessage( |
| node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node}); |
| } |
| return const NoneResult(); |
| } else if (node.isSuper()) { |
| if (!inInstanceContext) { |
| reporter.reportErrorMessage(node, MessageKind.NO_SUPER_IN_STATIC); |
| } |
| if ((ElementCategory.SUPER & allowedCategory) == 0) { |
| reporter.reportErrorMessage(node, MessageKind.INVALID_USE_OF_SUPER); |
| } |
| return const NoneResult(); |
| } else { |
| String name = node.source; |
| Element element = lookupInScope(reporter, 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 = coreClasses.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.isMalformed) { |
| // Use the malformed 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(resolution); |
| } |
| if (element != null) { |
| registry.useElement(node, element); |
| if (element.isPrefix) { |
| return new PrefixResult(element, null); |
| } else if (element.isClass && sendIsMemberAccess) { |
| return new PrefixResult(null, element); |
| } |
| return new ElementResult(element); |
| } |
| return const NoneResult(); |
| } |
| } |
| |
| TypeResult visitTypeAnnotation(TypeAnnotation node) { |
| return new TypeResult(resolveTypeAnnotation(node)); |
| } |
| |
| bool isNamedConstructor(Send node) => node.receiver != null; |
| |
| Name getRedirectingThisOrSuperConstructorName(Send node) { |
| if (isNamedConstructor(node)) { |
| String constructorName = node.selector.asIdentifier().source; |
| return new Name(constructorName, enclosingElement.library); |
| } else { |
| return const PublicName(''); |
| } |
| } |
| |
| FunctionElement resolveConstructorRedirection(FunctionElementX constructor) { |
| FunctionExpression node = constructor.parseNode(resolution.parsingContext); |
| |
| // 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)) { |
| Name name = getRedirectingThisOrSuperConstructorName(initializers.head); |
| final ClassElement classElement = constructor.enclosingClass; |
| return classElement.lookupConstructor(name.text); |
| } |
| return null; |
| } |
| |
| void setupFunction(FunctionExpression node, FunctionElement function) { |
| Element enclosingElement = function.enclosingElement; |
| if (node.modifiers.isStatic && enclosingElement.kind != ElementKind.CLASS) { |
| reporter.reportErrorMessage(node, MessageKind.ILLEGAL_STATIC); |
| } |
| FunctionSignature functionSignature = function.functionSignature; |
| |
| // Create the scope where the type variables are introduced, if any. |
| scope = new MethodScope(scope, function); |
| functionSignature.typeVariables |
| .forEach((DartType type) => addToScope(type.element)); |
| |
| // Create the scope for the function body, and put the parameters in scope. |
| scope = new BlockScope(scope); |
| Link<Node> parameterNodes = |
| (node.parameters == null) ? const Link<Node>() : node.parameters.nodes; |
| functionSignature.forEachParameter((ParameterElementX element) { |
| // TODO(karlklose): should be a list of [FormalElement]s, but the actual |
| // implementation uses [Element]. |
| List<Element> optionals = functionSignature.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, () { |
| functionSignature.forEachOptionalParameter((ParameterElementX parameter) { |
| parameter.constant = |
| resolver.constantCompiler.compileConstant(parameter); |
| }); |
| }); |
| registry.registerCheckedModeCheck(functionSignature.returnType); |
| functionSignature.forEachParameter((ParameterElement element) { |
| registry.registerCheckedModeCheck(element.type); |
| }); |
| } |
| |
| ResolutionResult visitAssert(Assert node) { |
| // TODO(sra): We could completely ignore the assert in production mode if we |
| // didn't need it to be resolved for type checking. |
| registry.registerFeature( |
| node.hasMessage ? Feature.ASSERT_WITH_MESSAGE : Feature.ASSERT); |
| visit(node.condition); |
| visit(node.message); |
| return const NoneResult(); |
| } |
| |
| 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) { |
| reporter.reportErrorMessage(node.name, |
| MessageKind.NAMED_FUNCTION_EXPRESSION, {'name': node.name}); |
| } |
| 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(resolution, function, registry); |
| function.functionSignature = SignatureResolver.analyze( |
| resolution, |
| scope, |
| node.typeVariables, |
| 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.registerStaticUse(new StaticUse.closure(function)); |
| 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(); |
| } |
| |
| static Selector computeSendSelector( |
| Send node, LibraryElement library, Element element) { |
| // First determine if this is part of an assignment. |
| bool isSet = node.asSendSet() != null; |
| |
| if (node.isIndex) { |
| return isSet ? new Selector.indexSet() : new Selector.index(); |
| } |
| |
| if (node.isOperator) { |
| String source = node.selector.asOperator().source; |
| String string = source; |
| if (identical(string, '!') || |
| identical(string, '&&') || |
| identical(string, '||') || |
| identical(string, 'is') || |
| identical(string, 'as') || |
| identical(string, '?') || |
| identical(string, '??')) { |
| return null; |
| } |
| String op = source; |
| if (!isUserDefinableOperator(source)) { |
| op = Elements.mapToUserOperatorOrNull(source); |
| } |
| if (op == null) { |
| // Unsupported operator. An error has been reported during parsing. |
| return new Selector.call(new Name(source, library), |
| new CallStructure.unnamed(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(new Name(identifier.source, library)); |
| } else if (isSet) { |
| return new Selector.setter( |
| new Name(identifier.source, library, isSetter: true)); |
| } |
| |
| // 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( |
| new Name(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(new Name(identifier.source, library), |
| new CallStructure(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) { |
| reporter.reportErrorMessage( |
| argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); |
| isValidAsConstant = false; |
| } |
| argumentCount++; |
| } |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| return new ArgumentsResult( |
| new CallStructure(argumentCount, namedArguments), argumentResults, |
| isValidAsConstant: isValidAsConstant); |
| } |
| |
| /// Check that access to `super` is currently allowed. Returns an |
| /// [AccessSemantics] in case of an error, `null` otherwise. |
| AccessSemantics checkSuperAccess(Send node) { |
| if (!inInstanceContext) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, 'super', MessageKind.NO_SUPER_IN_STATIC, {}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return new StaticAccess.invalid(error); |
| } |
| if (node.isConditional) { |
| // `super?.foo` is not allowed. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, 'super', MessageKind.INVALID_USE_OF_SUPER, {}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return new StaticAccess.invalid(error); |
| } |
| if (currentClass.supertype == null) { |
| // This is just to guard against internal errors, so no need |
| // for a real error message. |
| ErroneousElement error = reportAndCreateErroneousElement(node, 'super', |
| MessageKind.GENERIC, {'text': "Object has no superclass"}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return new StaticAccess.invalid(error); |
| } |
| registry.registerSuperUse(reporter.spanFromSpannable(node)); |
| return null; |
| } |
| |
| /// Check that access to `this` is currently allowed. Returns an |
| /// [AccessSemantics] in case of an error, `null` otherwise. |
| AccessSemantics checkThisAccess(Send node) { |
| if (!inInstanceContext) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, 'this', MessageKind.NO_THIS_AVAILABLE, const {}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return new StaticAccess.invalid(error); |
| } |
| return null; |
| } |
| |
| /// Compute the [AccessSemantics] corresponding to a super access of [target]. |
| AccessSemantics computeSuperAccessSemantics(Spannable node, Element target) { |
| if (target.isMalformed) { |
| 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 compound super access |
| /// reading from [getter] and writing to [setter]. |
| AccessSemantics computeCompoundSuperAccessSemantics( |
| Spannable node, Element getter, Element setter, |
| {bool isIndex: false}) { |
| if (getter.isMalformed) { |
| if (setter.isMalformed) { |
| return new StaticAccess.unresolvedSuper(getter); |
| } else if (setter.isFunction) { |
| assert(invariant(node, setter.name == '[]=', |
| message: "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter); |
| } else { |
| assert(invariant(node, setter.isSetter, |
| message: "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter); |
| } |
| } else if (getter.isField) { |
| if (setter.isMalformed) { |
| assert(invariant(node, getter.isFinal, |
| message: "Unexpected super setter '$setter' for getter '$getter.")); |
| return new StaticAccess.superFinalField(getter); |
| } else if (setter.isField) { |
| if (getter == setter) { |
| return new StaticAccess.superField(getter); |
| } else { |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_FIELD_FIELD, getter, setter); |
| } |
| } else { |
| // Either the field is accessible directly, or a setter shadows the |
| // setter access. If there was another instance member it would shadow |
| // the field. |
| assert(invariant(node, setter.isSetter, |
| message: "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_FIELD_SETTER, getter, setter); |
| } |
| } else if (getter.isGetter) { |
| if (setter.isMalformed) { |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_SETTER, getter, setter); |
| } else if (setter.isField) { |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_GETTER_FIELD, getter, setter); |
| } else { |
| assert(invariant(node, setter.isSetter, |
| message: "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter); |
| } |
| } else { |
| assert(invariant(node, getter.isFunction, |
| message: "Unexpected super getter '$getter'.")); |
| if (setter.isMalformed) { |
| if (isIndex) { |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_SETTER, getter, setter); |
| } else { |
| return new StaticAccess.superMethod(getter); |
| } |
| } else if (setter.isFunction) { |
| assert(invariant(node, setter.name == '[]=', |
| message: "Unexpected super setter '$setter'.")); |
| assert(invariant(node, getter.name == '[]', |
| message: "Unexpected super getter '$getter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter); |
| } else { |
| assert(invariant(node, setter.isSetter, |
| message: "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_METHOD_SETTER, getter, setter); |
| } |
| } |
| } |
| |
| /// Compute the [AccessSemantics] corresponding to a local access of [target]. |
| AccessSemantics computeLocalAccessSemantics( |
| Spannable node, LocalElement target) { |
| if (target.isRegularParameter) { |
| if (target.isFinal || target.isConst) { |
| return new StaticAccess.finalParameter(target); |
| } else { |
| return new StaticAccess.parameter(target); |
| } |
| } else if (target.isInitializingFormal) { |
| return new StaticAccess.finalParameter(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.isMalformed) { |
| // This handles elements with parser errors. |
| 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) { |
| if (alternateName != null) { |
| target = currentClass.lookupSuperByName(alternateName); |
| } |
| Element error; |
| if (selector.isSetter) { |
| error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.UNDEFINED_SUPER_SETTER, |
| {'className': currentClass.name, 'memberName': name}); |
| } else { |
| error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': name}); |
| } |
| 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.registerDynamicUse(new DynamicUse(selector, null)); |
| registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD); |
| } |
| return computeSuperAccessSemantics(node, 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 computeSuperAccessSemanticsForSelectors( |
| Spannable node, Selector getterSelector, Selector setterSelector, |
| {bool isIndex: false}) { |
| bool getterError = false; |
| bool setterError = false; |
| |
| // TODO(johnniwinther): Ensure correct behavior if currentClass is a |
| // patch. |
| Element getter = currentClass.lookupSuperByName(getterSelector.memberName); |
| // [target] may be null which means invoking noSuchMethod on super. |
| if (getter == null) { |
| getter = reportAndCreateErroneousElement( |
| node, |
| getterSelector.name, |
| MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': getterSelector.name}); |
| getterError = true; |
| } |
| Element setter = currentClass.lookupSuperByName(setterSelector.memberName); |
| // [target] may be null which means invoking noSuchMethod on super. |
| if (setter == null) { |
| setter = reportAndCreateErroneousElement( |
| node, |
| setterSelector.name, |
| MessageKind.NO_SUCH_SUPER_MEMBER, |
| {'className': currentClass.name, 'memberName': setterSelector.name}); |
| setterError = true; |
| } else if (getter == setter) { |
| if (setter.isFunction) { |
| setter = reportAndCreateErroneousElement( |
| node, setterSelector.name, MessageKind.ASSIGNING_METHOD_IN_SUPER, { |
| 'superclassName': setter.enclosingClass.name, |
| 'name': setterSelector.name |
| }); |
| setterError = true; |
| } else if (setter.isField && setter.isFinal) { |
| setter = reportAndCreateErroneousElement(node, setterSelector.name, |
| MessageKind.ASSIGNING_FINAL_FIELD_IN_SUPER, { |
| 'superclassName': setter.enclosingClass.name, |
| 'name': setterSelector.name |
| }); |
| setterError = true; |
| } |
| } |
| if (getterError) { |
| // We still need to register the invocation, because we might |
| // call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn]. |
| registry.registerDynamicUse(new DynamicUse(getterSelector, null)); |
| } |
| if (setterError) { |
| // We still need to register the invocation, because we might |
| // call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn]. |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| } |
| if (getterError || setterError) { |
| registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD); |
| } |
| return computeCompoundSuperAccessSemantics(node, getter, setter, |
| isIndex: isIndex); |
| } |
| |
| /// 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; |
| } |
| |
| /// 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, registerCheckedModeCheck: false); |
| sendStructure = new IsNotStructure(type); |
| } else { |
| // `e is T`. |
| Node typeNode = node.arguments.head; |
| type = resolveTypeAnnotation(typeNode, registerCheckedModeCheck: false); |
| sendStructure = new IsStructure(type); |
| } |
| |
| // GENERIC_METHODS: Method type variables are not reified so we must warn |
| // about the error which will occur at runtime. |
| if (type is MethodTypeVariableType) { |
| reporter.reportWarningMessage( |
| node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED); |
| } |
| |
| registry.registerTypeUse(new TypeUse.isCheck(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, registerCheckedModeCheck: false); |
| |
| // GENERIC_METHODS: Method type variables are not reified, so we must inform |
| // the developer about the potentially bug-inducing semantics. |
| if (type is MethodTypeVariableType) { |
| reporter.reportHintMessage( |
| node, MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC); |
| } |
| |
| registry.registerTypeUse(new TypeUse.asCast(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(23998): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| AccessSemantics semantics; |
| if (node.isSuperCall) { |
| semantics = checkSuperAccess(node); |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelector(node, selector); |
| // TODO(johnniwinther): Add information to [AccessSemantics] about |
| // whether it is erroneous. |
| if (semantics.kind == AccessKind.SUPER_METHOD) { |
| registry.registerStaticUse(new StaticUse.superInvoke( |
| semantics.element.declaration, selector.callStructure)); |
| } |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, semantics.element); |
| } |
| } else { |
| ResolutionResult expressionResult = visitExpression(expression); |
| semantics = const DynamicAccess.expression(); |
| registry.registerDynamicUse(new DynamicUse(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: |
| reporter.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) { |
| 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, const NotStructure()); |
| |
| 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.registerConstantLiteral(new NullConstantExpression()); |
| registry.registerDynamicUse(new DynamicUse(Selectors.equals, null)); |
| 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(23998): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| if (node.isSuperCall) { |
| semantics = checkSuperAccess(node); |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelector(node, selector); |
| // TODO(johnniwinther): Add information to [AccessSemantics] about |
| // whether it is erroneous. |
| if (semantics.kind == AccessKind.SUPER_METHOD) { |
| registry.registerStaticUse(new StaticUse.superInvoke( |
| semantics.element.declaration, selector.callStructure)); |
| } |
| // TODO(23998): 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.registerDynamicUse(new DynamicUse(selector, null)); |
| semantics = const DynamicAccess.expression(); |
| |
| if (leftResult.isConstant && rightResult.isConstant) { |
| bool isValidConstant; |
| ConstantExpression leftConstant = leftResult.constant; |
| ConstantExpression rightConstant = rightResult.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: |
| reporter.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: |
| reporter.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(23998): Remove this when all information goes through the |
| // [SendStructure]. |
| registry.setSelector(node, selector); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| registry.registerSendStructure( |
| node, new InvokeStructure(const DynamicAccess.expression(), selector)); |
| return const NoneResult(); |
| } |
| |
| /// 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 DynamicAccess.thisProperty(name); |
| return handleDynamicAccessSemantics(node, name, semantics); |
| } |
| |
| /// Handle update of a property of [name] on `this`, like `this.name = b` and |
| /// `this.name++`, or `name = b` and `name++` in instance context. |
| ResolutionResult handleThisPropertyUpdate( |
| SendSet node, Name name, Element element) { |
| AccessSemantics semantics = new DynamicAccess.thisProperty(name); |
| return handleDynamicUpdateSemantics(node, name, element, semantics); |
| } |
| |
| /// Handle access on `this`, like `this()` and `this` when it is parsed as a |
| /// [Send] node. |
| ResolutionResult handleThisAccess(Send node) { |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| Selector selector = callStructure.callSelector; |
| // TODO(johnniwinther): Handle invalid this access as an |
| // [AccessSemantics]. |
| AccessSemantics accessSemantics = checkThisAccess(node); |
| if (accessSemantics == null) { |
| accessSemantics = const DynamicAccess.thisAccess(); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| } |
| registry.registerSendStructure( |
| node, new InvokeStructure(accessSemantics, selector)); |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| return const NoneResult(); |
| } else { |
| // TODO(johnniwinther): Handle get of `this` when it is a [Send] node. |
| reporter.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; |
| if (node.isCall) { |
| callStructure = resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector.call(name, callStructure); |
| } else { |
| selector = new Selector.getter(name); |
| } |
| AccessSemantics semantics = checkSuperAccess(node); |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelector(node, selector, |
| alternateName: name.setter); |
| } |
| if (node.isCall) { |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.SUPER_METHOD: |
| MethodElement superMethod = semantics.element; |
| superMethod.computeType(resolution); |
| if (!callStructure.signatureApplies(superMethod.functionSignature)) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD); |
| isIncompatibleInvoke = true; |
| } else { |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(semantics.element, callStructure)); |
| } |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| registry.registerStaticUse(new StaticUse.superGet(semantics.element)); |
| selector = callStructure.callSelector; |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| break; |
| case AccessKind.SUPER_SETTER: |
| case AccessKind.UNRESOLVED_SUPER: |
| // NoSuchMethod registered in [computeSuperSemantics]. |
| break; |
| case AccessKind.INVALID: |
| // 'super' is not allowed. |
| break; |
| default: |
| reporter.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(new StaticUse.superTearOff(semantics.element)); |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| registry.registerStaticUse(new StaticUse.superGet(semantics.element)); |
| break; |
| case AccessKind.SUPER_SETTER: |
| case AccessKind.UNRESOLVED_SUPER: |
| // NoSuchMethod registered in [computeSuperSemantics]. |
| break; |
| case AccessKind.INVALID: |
| // 'super' is not allowed. |
| break; |
| default: |
| reporter.internalError( |
| node, "Unexpected super property access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, new GetStructure(semantics)); |
| } |
| target = semantics.element; |
| |
| // TODO(23998): 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); |
| } |
| } |
| } 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.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // 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.UNDEFINED_GETTER, |
| {'className': receiverClass.name, 'memberName': name.text}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleErroneousAccess( |
| node, name, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified update to an unresolved static class member, like |
| /// `a.b = c` or `a.b++` where `a` is a class and `b` is unresolved. |
| ResolutionResult handleUnresolvedStaticMemberUpdate( |
| SendSet node, Name name, ClassElement receiverClass) { |
| // TODO(johnniwinther): Share code with [handleStaticInstanceMemberUpdate] |
| // and [handlePrivateStaticMemberUpdate]. |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // 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(johnniwinther): Produce a different error for complex update. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.UNDEFINED_GETTER, |
| {'className': receiverClass.name, 'memberName': name.text}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleUpdate(node, name, 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.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // 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, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified update of an instance member, like `a.b = c` or `a.b++` |
| /// where `a` is a class and `b` is a non-static member. |
| ResolutionResult handleStaticInstanceMemberUpdate( |
| SendSet node, Name name, ClassElement receiverClass, Element member) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // TODO(johnniwinther): With the simplified [TreeElements] invariant, |
| // try to resolve injected elements if [currentClass] is in the patch |
| // library of [receiverClass]. |
| |
| // TODO(johnniwinther): Produce a different error for complex update. |
| 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 handleUpdate(node, name, 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.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.PRIVATE_ACCESS, |
| {'libraryName': member.library.libraryOrScriptName, 'name': name}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleErroneousAccess( |
| node, name, new StaticAccess.unresolved(error)); |
| } |
| |
| /// Handle qualified update of an inaccessible private static class member, |
| /// like `a._b = c` or `a._b++` where `a` is class, `_b` is static member of |
| /// `a` but `a` is not defined in the current library. |
| ResolutionResult handlePrivateStaticMemberUpdate( |
| SendSet node, Name name, ClassElement receiverClass, Element member) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.PRIVATE_ACCESS, |
| {'libraryName': member.library.libraryOrScriptName, 'name': name}); |
| // TODO(johnniwinther): Add an [AccessSemantics] for unresolved static |
| // member access. |
| return handleUpdate(node, name, 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(resolution); |
| 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( |
| resolution, 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 update to a static member, like `a.b = c` or `a.b++` |
| /// where `a` is a class and `b` is a static member of `a`. |
| ResolutionResult handleStaticMemberUpdate( |
| Send node, Name memberName, ClassElement receiverClass) { |
| String name = memberName.text; |
| receiverClass.ensureResolved(resolution); |
| MembersCreator.computeClassMembersByName( |
| resolution, receiverClass.declaration, name); |
| Element member = receiverClass.lookupLocalMember(name); |
| if (member == null) { |
| return handleUnresolvedStaticMemberUpdate( |
| node, memberName, receiverClass); |
| } else if (member.isAmbiguous) { |
| return handleAmbiguousUpdate(node, memberName, member); |
| } else if (member.isInstanceMember) { |
| return handleStaticInstanceMemberUpdate( |
| node, memberName, receiverClass, member); |
| } else if (memberName.isPrivate && memberName.library != member.library) { |
| return handlePrivateStaticMemberUpdate( |
| node, memberName, receiverClass, member); |
| } else { |
| return handleStaticOrTopLevelUpdate(node, memberName, member); |
| } |
| } |
| |
| /// Handle access to a type literal of type variable [element]. Like `T` or |
| /// `T()` where 'T' is type variable. |
| // TODO(johnniwinther): Remove [name] when [Selector] is not required for the |
| // the [GetStructure]. |
| // TODO(johnniwinther): Remove [element] when it is no longer needed for |
| // evaluating constants. |
| ResolutionResult handleTypeVariableTypeLiteralAccess( |
| Send node, Name name, TypeVariableElement element) { |
| AccessSemantics semantics; |
| if (!Elements.hasAccessToTypeVariable(enclosingElement, element)) { |
| // TODO(johnniwinther): Add another access semantics for this. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, |
| {'typeVariableName': name}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| semantics = new StaticAccess.invalid(error); |
| // TODO(johnniwinther): Clean up registration of elements and selectors |
| // for this case. |
| } else { |
| // GENERIC_METHODS: Method type variables are not reified so we must warn |
| // about the error which will occur at runtime. |
| if (element.type is MethodTypeVariableType) { |
| reporter.reportWarningMessage( |
| node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED); |
| } |
| semantics = new StaticAccess.typeParameterTypeLiteral(element); |
| } |
| |
| registry.useElement(node, element); |
| registry.registerTypeLiteral(node, element.type); |
| |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| Selector selector = callStructure.callSelector; |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| registry.registerSendStructure( |
| node, new InvokeStructure(semantics, selector)); |
| } else { |
| // TODO(johnniwinther): Avoid the need for a [Selector] here. |
| registry.registerSendStructure(node, new GetStructure(semantics)); |
| } |
| return const NoneResult(); |
| } |
| |
| /// Handle access to a type literal of type variable [element]. Like `T = b`, |
| /// `T++` or `T += b` where 'T' is type variable. |
| ResolutionResult handleTypeVariableTypeLiteralUpdate( |
| SendSet node, Name name, TypeVariableElement element) { |
| AccessSemantics semantics; |
| if (!Elements.hasAccessToTypeVariable(enclosingElement, element)) { |
| // TODO(johnniwinther): Add another access semantics for this. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, |
| {'typeVariableName': name}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| semantics = new StaticAccess.invalid(error); |
| } else { |
| ErroneousElement error; |
| if (node.isIfNullAssignment) { |
| error = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.IF_NULL_ASSIGNING_TYPE, const {}); |
| // TODO(23998): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node.selector, element); |
| } else { |
| error = reportAndCreateErroneousElement( |
| node.selector, name.text, MessageKind.ASSIGNING_TYPE, const {}); |
| } |
| |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, error); |
| // TODO(johnniwinther): Register only on read? |
| registry.registerTypeLiteral(node, element.type); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| semantics = new StaticAccess.typeParameterTypeLiteral(element); |
| } |
| return handleUpdate(node, name, semantics); |
| } |
| |
| /// Handle access to a constant type literal of [type]. |
| // TODO(johnniwinther): Remove [name] when [Selector] is not required for the |
| // the [GetStructure]. |
| // TODO(johnniwinther): Remove [element] when it is no longer needed for |
| // evaluating constants. |
| ResolutionResult handleConstantTypeLiteralAccess(Send node, Name name, |
| TypeDeclarationElement element, DartType type, ConstantAccess semantics) { |
| registry.useElement(node, element); |
| registry.registerTypeLiteral(node, type); |
| |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| Selector selector = callStructure.callSelector; |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| |
| // The node itself is not a constant but we register the selector (the |
| // identifier that refers to the class/typedef) as a constant. |
| registry.useElement(node.selector, element); |
| analyzeConstantDeferred(node.selector, enforceConst: false); |
| |
| registry.registerSendStructure( |
| node, new InvokeStructure(semantics, selector)); |
| return const NoneResult(); |
| } else { |
| analyzeConstantDeferred(node, enforceConst: false); |
| |
| registry.setConstant(node, semantics.constant); |
| registry.registerSendStructure(node, new GetStructure(semantics)); |
| return new ConstantResult(node, semantics.constant); |
| } |
| } |
| |
| /// Handle access to a constant type literal of [type]. |
| // TODO(johnniwinther): Remove [name] when [Selector] is not required for the |
| // the [GetStructure]. |
| // TODO(johnniwinther): Remove [element] when it is no longer needed for |
| // evaluating constants. |
| ResolutionResult handleConstantTypeLiteralUpdate(SendSet node, Name name, |
| TypeDeclarationElement element, DartType type, ConstantAccess semantics) { |
| // TODO(johnniwinther): Remove this when all constants are evaluated. |
| resolver.constantCompiler.evaluate(semantics.constant); |
| |
| ErroneousElement error; |
| if (node.isIfNullAssignment) { |
| error = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.IF_NULL_ASSIGNING_TYPE, const {}); |
| // TODO(23998): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.setConstant(node.selector, semantics.constant); |
| registry.useElement(node.selector, element); |
| } else { |
| error = reportAndCreateErroneousElement( |
| node.selector, name.text, MessageKind.ASSIGNING_TYPE, const {}); |
| } |
| |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, error); |
| registry.registerTypeLiteral(node, type); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| |
| return handleUpdate(node, name, semantics); |
| } |
| |
| /// Handle access to a type literal of a typedef. Like `F` or |
| /// `F()` where 'F' is typedef. |
| ResolutionResult handleTypedefTypeLiteralAccess( |
| Send node, Name name, TypedefElement typdef) { |
| typdef.ensureResolved(resolution); |
| DartType type = typdef.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type); |
| AccessSemantics semantics = new ConstantAccess.typedefTypeLiteral(constant); |
| return handleConstantTypeLiteralAccess(node, name, typdef, type, semantics); |
| } |
| |
| /// Handle access to a type literal of a typedef. Like `F = b`, `F++` or |
| /// `F += b` where 'F' is typedef. |
| ResolutionResult handleTypedefTypeLiteralUpdate( |
| SendSet node, Name name, TypedefElement typdef) { |
| typdef.ensureResolved(resolution); |
| DartType type = typdef.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type); |
| AccessSemantics semantics = new ConstantAccess.typedefTypeLiteral(constant); |
| return handleConstantTypeLiteralUpdate(node, name, typdef, type, semantics); |
| } |
| |
| /// Handle access to a type literal of the type 'dynamic'. Like `dynamic` or |
| /// `dynamic()`. |
| ResolutionResult handleDynamicTypeLiteralAccess(Send node) { |
| DartType type = const DynamicType(); |
| ConstantExpression constant = new TypeConstantExpression( |
| // TODO(johnniwinther): Use [type] when evaluation of constants is done |
| // directly on the constant expressions. |
| node.isCall ? coreTypes.typeType : type); |
| AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant); |
| return handleConstantTypeLiteralAccess(node, const PublicName('dynamic'), |
| coreClasses.typeClass, type, semantics); |
| } |
| |
| /// Handle update to a type literal of the type 'dynamic'. Like `dynamic++` or |
| /// `dynamic = 0`. |
| ResolutionResult handleDynamicTypeLiteralUpdate(SendSet node) { |
| DartType type = const DynamicType(); |
| ConstantExpression constant = |
| new TypeConstantExpression(const DynamicType()); |
| AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant); |
| return handleConstantTypeLiteralUpdate(node, const PublicName('dynamic'), |
| coreClasses.typeClass, type, semantics); |
| } |
| |
| /// Handle access to a type literal of a class. Like `C` or |
| /// `C()` where 'C' is class. |
| ResolutionResult handleClassTypeLiteralAccess( |
| Send node, Name name, ClassElement cls) { |
| cls.ensureResolved(resolution); |
| DartType type = cls.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type); |
| AccessSemantics semantics = new ConstantAccess.classTypeLiteral(constant); |
| return handleConstantTypeLiteralAccess(node, name, cls, type, semantics); |
| } |
| |
| /// Handle access to a type literal of a class. Like `C = b`, `C++` or |
| /// `C += b` where 'C' is class. |
| ResolutionResult handleClassTypeLiteralUpdate( |
| SendSet node, Name name, ClassElement cls) { |
| cls.ensureResolved(resolution); |
| DartType type = cls.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type); |
| AccessSemantics semantics = new ConstantAccess.classTypeLiteral(constant); |
| return handleConstantTypeLiteralUpdate(node, name, cls, type, semantics); |
| } |
| |
| /// Handle a [Send] that resolves to a [prefix]. Like `prefix` in |
| /// `prefix.Class` or `prefix` in `prefix()`, the latter being a compile time |
| /// error. |
| ResolutionResult handleClassSend(Send node, Name name, ClassElement cls) { |
| cls.ensureResolved(resolution); |
| if (sendIsMemberAccess) { |
| registry.useElement(node, cls); |
| return new PrefixResult(null, cls); |
| } else { |
| // `C` or `C()` where 'C' is a class. |
| return handleClassTypeLiteralAccess(node, name, cls); |
| } |
| } |
| |
| /// Compute a [DeferredPrefixStructure] for [node]. |
| ResolutionResult handleDeferredAccess( |
| Send node, PrefixElement prefix, ResolutionResult result) { |
| assert(invariant(node, prefix.isDeferred, |
| message: "Prefix $prefix is not deferred.")); |
| SendStructure sendStructure = registry.getSendStructure(node); |
| assert(invariant(node, sendStructure != null, |
| message: "No SendStructure for $node.")); |
| registry.registerSendStructure( |
| node, new DeferredPrefixStructure(prefix, sendStructure)); |
| if (result.isConstant) { |
| ConstantExpression constant = |
| new DeferredConstantExpression(result.constant, prefix); |
| registry.setConstant(node, constant); |
| result = new ConstantResult(node, constant); |
| } |
| return result; |
| } |
| |
| /// Handle qualified [Send] where the receiver resolves to a [prefix], |
| /// like `prefix.toplevelFunction()` or `prefix.Class.staticField` where |
| /// `prefix` is a library prefix. |
| ResolutionResult handleLibraryPrefixSend( |
| Send node, Name name, PrefixElement prefix) { |
| ResolutionResult result; |
| Element member = prefix.lookupLocalMember(name.text); |
| if (member == null) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| Element error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.NO_SUCH_LIBRARY_MEMBER, |
| {'libraryName': prefix.name, 'memberName': name}); |
| result = handleUnresolvedAccess(node, name, error); |
| } else { |
| result = handleResolvedSend(node, name, member); |
| } |
| if (result.kind == ResultKind.PREFIX) { |
| // [member] is a class prefix of a static access like `prefix.Class` of |
| // `prefix.Class.foo`. No need to call [handleDeferredAccess]; it will |
| // called on the parent `prefix.Class.foo` node. |
| result = new PrefixResult(prefix, result.element); |
| } else if (prefix.isDeferred && |
| (member == null || !member.isDeferredLoaderGetter)) { |
| result = handleDeferredAccess(node, prefix, result); |
| } |
| return result; |
| } |
| |
| /// Handle qualified [SendSet] where the receiver resolves to a [prefix], |
| /// like `prefix.toplevelField = b` or `prefix.Class.staticField++` where |
| /// `prefix` is a library prefix. |
| ResolutionResult handleLibraryPrefixSendSet( |
| SendSet node, Name name, PrefixElement prefix) { |
| ResolutionResult result; |
| Element member = prefix.lookupLocalMember(name.text); |
| if (member == null) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| Element error = reportAndCreateErroneousElement( |
| node, |
| name.text, |
| MessageKind.NO_SUCH_LIBRARY_MEMBER, |
| {'libraryName': prefix.name, 'memberName': name}); |
| return handleUpdate(node, name, new StaticAccess.unresolved(error)); |
| } else { |
| result = handleResolvedSendSet(node, name, member); |
| } |
| if (result.kind == ResultKind.PREFIX) { |
| // [member] is a class prefix of a static access like `prefix.Class` of |
| // `prefix.Class.foo`. No need to call [handleDeferredAccess]; it will |
| // called on the parent `prefix.Class.foo` node. |
| result = new PrefixResult(prefix, result.element); |
| } else if (prefix.isDeferred && |
| (member == null || !member.isDeferredLoaderGetter)) { |
| result = handleDeferredAccess(node, prefix, result); |
| } |
| return result; |
| } |
| |
| /// Handle a [Send] that resolves to a [prefix]. Like `prefix` in |
| /// `prefix.Class` or `prefix` in `prefix()`, the latter being a compile time |
| /// error. |
| ResolutionResult handleLibraryPrefix( |
| Send node, Name name, PrefixElement prefix) { |
| if ((ElementCategory.PREFIX & allowedCategory) == 0) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.PREFIX_AS_EXPRESSION, {'prefix': name}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return handleErroneousAccess(node, name, new StaticAccess.invalid(error)); |
| } |
| if (prefix.isDeferred) { |
| // TODO(23998): Remove this when deferred access is detected |
| // through a [SendStructure]. |
| registry.useElement(node.selector, prefix); |
| } |
| registry.useElement(node, prefix); |
| return new PrefixResult(prefix, null); |
| } |
| |
| /// Handle qualified [Send] where the receiver resolves to an [Element], like |
| /// `a.b` where `a` is a prefix or a class. |
| ResolutionResult handlePrefixSend( |
| Send node, Name name, PrefixResult prefixResult) { |
| Element element = prefixResult.element; |
| if (element.isPrefix) { |
| if (node.isConditional) { |
| return handleLibraryPrefix(node, name, element); |
| } else { |
| return handleLibraryPrefixSend(node, name, element); |
| } |
| } else { |
| assert(element.isClass); |
| ResolutionResult result = handleStaticMemberAccess(node, name, element); |
| if (prefixResult.isDeferred) { |
| result = handleDeferredAccess(node, prefixResult.prefix, result); |
| } |
| return result; |
| } |
| } |
| |
| /// Handle qualified [SendSet] where the receiver resolves to an [Element], |
| /// like `a.b = c` where `a` is a prefix or a class. |
| ResolutionResult handlePrefixSendSet( |
| SendSet node, Name name, PrefixResult prefixResult) { |
| Element element = prefixResult.element; |
| if (element.isPrefix) { |
| if (node.isConditional) { |
| return handleLibraryPrefix(node, name, element); |
| } else { |
| return handleLibraryPrefixSendSet(node, name, element); |
| } |
| } else { |
| assert(element.isClass); |
| ResolutionResult result = handleStaticMemberUpdate(node, name, element); |
| if (prefixResult.isDeferred) { |
| result = handleDeferredAccess(node, prefixResult.prefix, result); |
| } |
| return result; |
| } |
| } |
| |
| /// 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.call(name, callStructure); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| sendStructure = new InvokeStructure(semantics, selector); |
| } else { |
| assert(invariant(node, node.isPropertyAccess)); |
| selector = new Selector.getter(name); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| sendStructure = new GetStructure(semantics); |
| } |
| registry.registerSendStructure(node, sendStructure); |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| return const NoneResult(); |
| } |
| |
| /// Handle dynamic update of [semantics]. |
| ResolutionResult handleDynamicUpdateSemantics( |
| SendSet node, Name name, Element element, AccessSemantics semantics) { |
| Selector getterSelector = new Selector.getter(name); |
| Selector setterSelector = new Selector.setter(name.setter); |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| if (node.isComplex) { |
| registry.registerDynamicUse(new DynamicUse(getterSelector, null)); |
| } |
| |
| // TODO(23998): Remove these when elements are only accessed through the |
| // send structure. |
| Element getter = element; |
| Element setter = element; |
| if (element != null && element.isAbstractField) { |
| AbstractFieldElement abstractField = element; |
| getter = abstractField.getter; |
| setter = abstractField.setter; |
| } |
| if (setter != null) { |
| registry.useElement(node, setter); |
| if (getter != null && node.isComplex) { |
| registry.useElement(node.selector, getter); |
| } |
| } |
| |
| return handleUpdate(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); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| AccessSemantics accessSemantics = new StaticAccess.invalid(error); |
| return handleErroneousAccess(node, name, 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()) { |
| AccessSemantics semantics = checkThisAccess(node); |
| if (semantics == null) { |
| return handleThisPropertyAccess(node, name); |
| } else { |
| // TODO(johnniwinther): Handle invalid this access as an |
| // [AccessSemantics]. |
| return handleErroneousAccess(node, name, semantics); |
| } |
| } |
| ResolutionResult result = visitExpressionPrefix(node.receiver); |
| if (result.kind == ResultKind.PREFIX) { |
| return handlePrefixSend(node, name, result); |
| } else if (node.isConditional) { |
| registry.registerConstantLiteral(new NullConstantExpression()); |
| registry.registerDynamicUse(new DynamicUse(Selectors.equals, null)); |
| return handleDynamicAccessSemantics( |
| node, name, new DynamicAccess.ifNotNullProperty(name)); |
| } else { |
| // Handle dynamic property access, like `a.b` or `a.b()` where `a` is not |
| // a prefix or class. |
| // TODO(johnniwinther): Use the `element` of [result]. |
| return handleDynamicAccessSemantics( |
| node, name, new DynamicAccess.dynamicProperty(name)); |
| } |
| } |
| |
| /// Handle a qualified [SendSet], that is where the receiver is non-null, like |
| /// `a.b = c`, `a.b++`, and `a.b += c`. |
| ResolutionResult handleQualifiedSendSet(SendSet 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.receiver.isThis()) { |
| AccessSemantics semantics = checkThisAccess(node); |
| if (semantics == null) { |
| return handleThisPropertyUpdate(node, name, null); |
| } else { |
| // TODO(johnniwinther): Handle invalid this access as an |
| // [AccessSemantics]. |
| return handleUpdate(node, name, semantics); |
| } |
| } |
| ResolutionResult result = visitExpressionPrefix(node.receiver); |
| if (result.kind == ResultKind.PREFIX) { |
| return handlePrefixSendSet(node, name, result); |
| } else if (node.isConditional) { |
| registry.registerConstantLiteral(new NullConstantExpression()); |
| registry.registerDynamicUse(new DynamicUse(Selectors.equals, null)); |
| return handleDynamicUpdateSemantics( |
| node, name, null, new DynamicAccess.ifNotNullProperty(name)); |
| } else { |
| // Handle dynamic property access, like `a.b = c`, `a.b++` or `a.b += c` |
| // where `a` is not a prefix or class. |
| // TODO(johnniwinther): Use the `element` of [result]. |
| return handleDynamicUpdateSemantics( |
| node, name, null, new DynamicAccess.dynamicProperty(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 semantics = new StaticAccess.unresolved(element); |
| return handleErroneousAccess(node, name, semantics); |
| } |
| |
| /// Handle erroneous access of [element] of the given [semantics]. |
| ResolutionResult handleErroneousAccess( |
| Send node, Name name, AccessSemantics semantics) { |
| SendStructure sendStructure; |
| Selector selector; |
| if (node.isCall) { |
| CallStructure callStructure = |
| resolveArguments(node.argumentsNode).callStructure; |
| selector = new Selector.call(name, callStructure); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| sendStructure = new InvokeStructure(semantics, selector); |
| } else { |
| assert(invariant(node, node.isPropertyAccess)); |
| selector = new Selector.getter(name); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| sendStructure = new GetStructure(semantics); |
| } |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.setSelector(node, selector); |
| registry.useElement(node, semantics.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) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, element.messageKind, element.messageArguments, |
| infos: element.computeInfos(enclosingElement, reporter)); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| |
| // TODO(johnniwinther): Support ambiguous access as an [AccessSemantics]. |
| AccessSemantics semantics = new StaticAccess.unresolved(error); |
| return handleErroneousAccess(node, name, semantics); |
| } |
| |
| /// Handle update to an ambiguous element, that is, a name imported twice. |
| ResolutionResult handleAmbiguousUpdate( |
| SendSet node, Name name, AmbiguousElement element) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, element.messageKind, element.messageArguments, |
| infos: element.computeInfos(enclosingElement, reporter)); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| |
| // TODO(johnniwinther): Support ambiguous access as an [AccessSemantics]. |
| AccessSemantics accessSemantics = new StaticAccess.unresolved(error); |
| return handleUpdate(node, name, accessSemantics); |
| } |
| |
| /// Report access of an instance [member] from a non-instance context. |
| AccessSemantics reportStaticInstanceAccess(Send node, Name name) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}, |
| isError: true); |
| // TODO(johnniwinther): Support static instance access as an |
| // [AccessSemantics]. |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return new StaticAccess.invalid(error); |
| } |
| |
| /// 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.call(name, callStructure); |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.LOCAL_FUNCTION: |
| LocalFunctionElementX function = semantics.element; |
| function.computeType(resolution); |
| if (!callStructure.signatureApplies(function.functionSignature)) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| registry.registerDynamicUse(new DynamicUse(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.registerDynamicUse(new DynamicUse(selector, null)); |
| break; |
| default: |
| reporter.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: |
| reporter.internalError(node, "Unexpected local access $semantics."); |
| break; |
| } |
| selector = new Selector.getter(name); |
| registry.registerSendStructure(node, new GetStructure(semantics)); |
| } |
| |
| // TODO(23998): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, element); |
| registry.setSelector(node, selector); |
| |
| registerPotentialAccessInClosure(node, element); |
| |
| return result; |
| } |
| |
| /// Handle update of a parameter, local variable or local function. |
| ResolutionResult handleLocalUpdate(Send node, Name name, Element element) { |
| AccessSemantics semantics; |
| ErroneousElement error; |
| if (element.isRegularParameter) { |
| if (element.isFinal) { |
| error = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name}); |
| semantics = new StaticAccess.finalParameter(element); |
| } else { |
| semantics = new StaticAccess.parameter(element); |
| } |
| } else if (element.isInitializingFormal) { |
| error = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name}); |
| semantics = new StaticAccess.finalParameter(element); |
| } else if (element.isVariable) { |
| if (element.isFinal || element.isConst) { |
| error = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name}); |
| semantics = new StaticAccess.finalLocalVariable(element); |
| } else { |
| semantics = new StaticAccess.localVariable(element); |
| } |
| } else { |
| assert(invariant(node, element.isFunction, |
| message: "Unexpected local $element.")); |
| error = reportAndCreateErroneousElement( |
| node.selector, name.text, MessageKind.ASSIGNING_METHOD, const {}); |
| semantics = new StaticAccess.localFunction(element); |
| } |
| if (isPotentiallyMutableTarget(element)) { |
| registry.registerPotentialMutation(element, node); |
| if (enclosingElement != element.enclosingElement) { |
| registry.registerPotentialMutationInClosure(element, node); |
| } |
| for (Node scope in promotionScope) { |
| registry.registerPotentialMutationIn(scope, element, node); |
| } |
| } |
| |
| ResolutionResult result = handleUpdate(node, name, semantics); |
| if (error != null) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // TODO(23998): Remove this when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, error); |
| } |
| 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(resolution); |
| |
| if (resolution.commonElements.isMirrorSystemGetNameFunction(member) && |
| !resolution.mirrorUsageAnalyzerTask.hasMirrorUsage(enclosingElement)) { |
| reporter.reportHintMessage( |
| node.selector, MessageKind.STATIC_FUNCTION_BLOAT, { |
| 'class': resolution.commonElements.mirrorSystemClass.name, |
| 'name': member.name |
| }); |
| } |
| |
| Selector selector; |
| AccessSemantics semantics = |
| computeStaticOrTopLevelAccessSemantics(node, member); |
| if (node.isCall) { |
| ArgumentsResult argumentsResult = resolveArguments(node.argumentsNode); |
| CallStructure callStructure = argumentsResult.callStructure; |
| selector = new Selector.call(name, callStructure); |
| |
| bool isIncompatibleInvoke = false; |
| switch (semantics.kind) { |
| case AccessKind.STATIC_METHOD: |
| case AccessKind.TOPLEVEL_METHOD: |
| MethodElement method = semantics.element; |
| method.computeType(resolution); |
| if (!callStructure.signatureApplies(method.functionSignature)) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| isIncompatibleInvoke = true; |
| } else { |
| registry.registerStaticUse( |
| new StaticUse.staticInvoke(semantics.element, callStructure)); |
| handleForeignCall(node, semantics.element, callStructure); |
| if (method == resolution.commonElements.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(new StaticUse.staticGet(semantics.element)); |
| selector = callStructure.callSelector; |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| break; |
| case AccessKind.STATIC_SETTER: |
| case AccessKind.TOPLEVEL_SETTER: |
| case AccessKind.UNRESOLVED: |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| member = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_GETTER_BUT_SETTER, {'name': name}); |
| break; |
| default: |
| reporter.internalError( |
| node, "Unexpected statically resolved access $semantics."); |
| break; |
| } |
| registry.registerSendStructure( |
| node, |
| isIncompatibleInvoke |
| ? new IncompatibleInvokeStructure(semantics, selector) |
| : new InvokeStructure(semantics, selector)); |
| } else { |
| selector = new Selector.getter(name); |
| switch (semantics.kind) { |
| case AccessKind.STATIC_METHOD: |
| case AccessKind.TOPLEVEL_METHOD: |
| registry.registerStaticUse( |
| new StaticUse.staticTearOff(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(new StaticUse.staticGet(semantics.element)); |
| break; |
| case AccessKind.STATIC_SETTER: |
| case AccessKind.TOPLEVEL_SETTER: |
| case AccessKind.UNRESOLVED: |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| member = reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_GETTER_BUT_SETTER, {'name': name}); |
| break; |
| default: |
| reporter.internalError( |
| node, "Unexpected statically resolved access $semantics."); |
| break; |
| } |
| registry.registerSendStructure(node, new GetStructure(semantics)); |
| if (member.isConst) { |
| FieldElement field = member; |
| result = new ConstantResult(node, new VariableConstantExpression(field), |
| element: field); |
| } else { |
| result = new ElementResult(member); |
| } |
| } |
| |
| // TODO(23998): Remove these when all information goes through |
| // the [SendStructure]. |
| registry.useElement(node, member); |
| registry.setSelector(node, selector); |
| |
| return result; |
| } |
| |
| /// Handle update of a static or top level [element]. |
| ResolutionResult handleStaticOrTopLevelUpdate( |
| SendSet node, Name name, Element element) { |
| AccessSemantics semantics; |
| if (element.isAbstractField) { |
| AbstractFieldElement abstractField = element; |
| if (abstractField.setter == null) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node.selector, |
| name.text, |
| MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, |
| {'name': name}); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| |
| if (node.isComplex) { |
| // `a++` or `a += b` where `a` has no setter. |
| semantics = new CompoundAccessSemantics( |
| element.isTopLevel |
| ? CompoundAccessKind.UNRESOLVED_TOPLEVEL_SETTER |
| : CompoundAccessKind.UNRESOLVED_STATIC_SETTER, |
| abstractField.getter, |
| error); |
| } else { |
| // `a = b` where `a` has no setter. |
| semantics = element.isTopLevel |
| ? new StaticAccess.topLevelGetter(abstractField.getter) |
| : new StaticAccess.staticGetter(abstractField.getter); |
| } |
| registry |
| .registerStaticUse(new StaticUse.staticGet(abstractField.getter)); |
| } else if (node.isComplex) { |
| if (abstractField.getter == null) { |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node.selector, |
| name.text, |
| MessageKind.UNDEFINED_STATIC_GETTER_BUT_SETTER, |
| {'name': name}); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| // `a++` or `a += b` where `a` has no getter. |
| semantics = new CompoundAccessSemantics( |
| element.isTopLevel |
| ? CompoundAccessKind.UNRESOLVED_TOPLEVEL_GETTER |
| : CompoundAccessKind.UNRESOLVED_STATIC_GETTER, |
| error, |
| abstractField.setter); |
| registry |
| .registerStaticUse(new StaticUse.staticSet(abstractField.setter)); |
| } else { |
| // `a++` or `a += b` where `a` has both a getter and a setter. |
| semantics = new CompoundAccessSemantics( |
| element.isTopLevel |
| ? CompoundAccessKind.TOPLEVEL_GETTER_SETTER |
| : CompoundAccessKind.STATIC_GETTER_SETTER, |
| abstractField.getter, |
| abstractField.setter); |
| registry |
| .registerStaticUse(new StaticUse.staticGet(abstractField.getter)); |
| registry |
| .registerStaticUse(new StaticUse.staticSet(abstractField.setter)); |
| } |
| } else { |
| // `a = b` where `a` has a setter. |
| semantics = element.isTopLevel |
| ? new StaticAccess.topLevelSetter(abstractField.setter) |
| : new StaticAccess.staticSetter(abstractField.setter); |
| registry |
| .registerStaticUse(new StaticUse.staticSet(abstractField.setter)); |
| } |
| } else { |
| MemberElement member = element; |
| // TODO(johnniwinther): Needed to provoke a parsing and with it discovery |
| // of parse errors to make [element] erroneous. Fix this! |
| member.computeType(resolution); |
| if (member.isMalformed) { |
| // [member] has parse errors. |
| semantics = new StaticAccess.unresolved(member); |
| } else if (member.isFunction) { |
| // `a = b`, `a++` or `a += b` where `a` is a function. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node.selector, name.text, MessageKind.ASSIGNING_METHOD, const {}); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| if (node.isComplex) { |
| // `a++` or `a += b` where `a` is a function. |
| registry.registerStaticUse(new StaticUse.staticTearOff(element)); |
| } |
| semantics = member.isTopLevel |
| ? new StaticAccess.topLevelMethod(member) |
| : new StaticAccess.staticMethod(member); |
| } else { |
| |