| // 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 'package:front_end/src/fasta/scanner.dart' show isUserDefinableOperator; |
| |
| 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 '../common_elements.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/entities.dart' show AsyncMarker; |
| import '../elements/modelx.dart' |
| show |
| ConstructorElementX, |
| ErroneousElementX, |
| FunctionElementX, |
| JumpTargetX, |
| LabelDefinitionX, |
| LocalFunctionElementX, |
| LocalParameterElementX, |
| ParameterElementX, |
| VariableElementX, |
| VariableList; |
| import '../elements/jumps.dart'; |
| import '../elements/names.dart'; |
| import '../elements/operators.dart'; |
| import '../elements/resolution_types.dart'; |
| import '../options.dart'; |
| 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 '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); |
| |
| CommonElements get commonElements => resolution.commonElements; |
| 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(result != null, failedAt(node, "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( |
| !inInstanceContext, |
| failedAt( |
| node, |
| "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. |
| ClassElement typeClass = commonElements.typeClass; |
| element = typeClass; |
| // Set the type to be `dynamic` to mark that this is a type literal. |
| registry.setType(node, const ResolutionDynamicType()); |
| } |
| 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((ResolutionDartType 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((_element) { |
| ParameterElementX element = _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 { |
| registry.registerConstantLiteral( |
| 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((_parameter) { |
| ParameterElementX parameter = _parameter; |
| parameter.constant = |
| resolver.constantCompiler.compileConstant(parameter); |
| }); |
| }); |
| registry.registerCheckedModeCheck(functionSignature.returnType); |
| functionSignature.forEachParameter((_element) { |
| ParameterElement element = _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(target.isFunction, |
| failedAt(node, "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(setter.name == '[]=', |
| failedAt(node, "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter); |
| } else { |
| assert(setter.isSetter, |
| failedAt(node, "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter); |
| } |
| } else if (getter.isField) { |
| if (setter.isMalformed) { |
| assert( |
| getter.isFinal, |
| failedAt(node, |
| "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(setter.isSetter, |
| failedAt(node, "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(setter.isSetter, |
| failedAt(node, "Unexpected super setter '$setter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter); |
| } |
| } else { |
| assert(getter.isFunction, |
| failedAt(node, "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(setter.name == '[]=', |
| failedAt(node, "Unexpected super setter '$setter'.")); |
| assert(getter.name == '[]', |
| failedAt(node, "Unexpected super getter '$getter'.")); |
| return new CompoundAccessSemantics( |
| CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter); |
| } else { |
| assert(setter.isSetter, |
| failedAt(node, "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(target.isFunction, |
| failedAt(node, "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(target.isFunction, |
| failedAt(node, "Unexpected static target '$target'.")); |
| return new StaticAccess.staticMethod(target); |
| } |
| } else { |
| assert(target.isTopLevel, |
| failedAt(node, "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(target.isFunction, |
| failedAt(node, "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(); |
| ResolutionDartType 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; |
| ResolutionDartType 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) { |
| MethodElement superMethod = semantics.element.declaration; |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(superMethod, 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; |
| ResolutionDartType knownExpressionType = |
| expressionConstant.getKnownType(commonElements); |
| switch (operator.kind) { |
| case UnaryOperatorKind.COMPLEMENT: |
| isValidConstant = knownExpressionType == commonElements.intType; |
| break; |
| case UnaryOperatorKind.NEGATE: |
| isValidConstant = knownExpressionType == commonElements.intType || |
| knownExpressionType == commonElements.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(operator.kind == UnaryOperatorKind.NOT, failedAt(node)); |
| |
| Node expression = node.receiver; |
| ResolutionResult result = visitExpression(expression); |
| registry.registerSendStructure(node, const NotStructure()); |
| |
| if (result.isConstant) { |
| ConstantExpression expressionConstant = result.constant; |
| if (expressionConstant.getKnownType(commonElements) == |
| commonElements.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(commonElements) == |
| commonElements.boolType && |
| rightConstant.getKnownType(commonElements) == |
| commonElements.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(commonElements) == |
| commonElements.boolType && |
| rightConstant.getKnownType(commonElements) == |
| commonElements.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) { |
| MethodElement superMethod = semantics.element.declaration; |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(superMethod, 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; |
| ResolutionDartType knownLeftType = |
| leftConstant.getKnownType(commonElements); |
| ResolutionDartType knownRightType = |
| rightConstant.getKnownType(commonElements); |
| switch (operator.kind) { |
| case BinaryOperatorKind.EQ: |
| case BinaryOperatorKind.NOT_EQ: |
| isValidConstant = (knownLeftType == commonElements.intType || |
| knownLeftType == commonElements.doubleType || |
| knownLeftType == commonElements.stringType || |
| knownLeftType == commonElements.boolType || |
| knownLeftType == commonElements.nullType) && |
| (knownRightType == commonElements.intType || |
| knownRightType == commonElements.doubleType || |
| knownRightType == commonElements.stringType || |
| knownRightType == commonElements.boolType || |
| knownRightType == commonElements.nullType); |
| break; |
| case BinaryOperatorKind.ADD: |
| isValidConstant = (knownLeftType == commonElements.intType || |
| knownLeftType == commonElements.doubleType || |
| knownLeftType == commonElements.stringType) && |
| (knownRightType == commonElements.intType || |
| knownRightType == commonElements.doubleType || |
| knownRightType == commonElements.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 == commonElements.intType || |
| knownLeftType == commonElements.doubleType) && |
| (knownRightType == commonElements.intType || |
| knownRightType == commonElements.doubleType); |
| break; |
| case BinaryOperatorKind.SHL: |
| case BinaryOperatorKind.SHR: |
| case BinaryOperatorKind.AND: |
| case BinaryOperatorKind.OR: |
| case BinaryOperatorKind.XOR: |
| isValidConstant = knownLeftType == commonElements.intType && |
| knownRightType == commonElements.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(node.isCall, failedAt(node, "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.parameterStructure)) { |
| 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(superMethod, callStructure)); |
| } |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| MemberElement superMember = semantics.element; |
| registry.registerStaticUse(new StaticUse.superGet(superMember)); |
| 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. |
| MethodElement superMethod = semantics.element; |
| registry.registerStaticUse(new StaticUse.superTearOff(superMethod)); |
| break; |
| case AccessKind.SUPER_FIELD: |
| case AccessKind.SUPER_FINAL_FIELD: |
| case AccessKind.SUPER_GETTER: |
| MemberElement superMember = semantics.element; |
| registry.registerStaticUse(new StaticUse.superGet(superMember)); |
| 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`. |
| // ignore: MISSING_RETURN |
| 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(operator.isUserDefinable, |
| failedAt(node, "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.name, '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.name, '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, |
| ResolutionDartType 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, |
| ResolutionDartType 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); |
| ResolutionDartType type = typdef.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type, name.text); |
| 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); |
| ResolutionDartType type = typdef.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type, name.text); |
| 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) { |
| ResolutionDartType type = const ResolutionDynamicType(); |
| ConstantExpression constant = new TypeConstantExpression( |
| // TODO(johnniwinther): Use [type] when evaluation of constants is done |
| // directly on the constant expressions. |
| node.isCall ? commonElements.typeType : type, |
| 'dynamic'); |
| AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant); |
| ClassElement typeClass = commonElements.typeClass; |
| return handleConstantTypeLiteralAccess( |
| node, const PublicName('dynamic'), typeClass, type, semantics); |
| } |
| |
| /// Handle update to a type literal of the type 'dynamic'. Like `dynamic++` or |
| /// `dynamic = 0`. |
| ResolutionResult handleDynamicTypeLiteralUpdate(SendSet node) { |
| ResolutionDartType type = const ResolutionDynamicType(); |
| ConstantExpression constant = |
| new TypeConstantExpression(const ResolutionDynamicType(), 'dynamic'); |
| AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant); |
| ClassElement typeClass = commonElements.typeClass; |
| return handleConstantTypeLiteralUpdate( |
| node, const PublicName('dynamic'), 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); |
| ResolutionDartType type = cls.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type, name.text); |
| 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); |
| ResolutionDartType type = cls.rawType; |
| ConstantExpression constant = new TypeConstantExpression(type, name.text); |
| 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( |
| prefix.isDeferred, failedAt(node, "Prefix $prefix is not deferred.")); |
| SendStructure sendStructure = registry.getSendStructure(node); |
| assert( |
| sendStructure != null, failedAt(node, "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(node.isPropertyAccess, failedAt(node)); |
| 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(node.isPropertyAccess, failedAt(node)); |
| 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.parameterStructure)) { |
| 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) { |
| LocalVariableElement local = element; |
| result = new ConstantResult( |
| node, new LocalVariableConstantExpression(local), |
| 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(element.isFunction, failedAt(node, "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.parameterStructure)) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| registry.registerDynamicUse(new DynamicUse(selector, null)); |
| isIncompatibleInvoke = true; |
| } else { |
| registry.registerStaticUse( |
| new StaticUse.staticInvoke(method, 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: |
| MemberElement member = semantics.element; |
| registry.registerStaticUse(new StaticUse.staticGet(member)); |
| 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: |
| MethodElement method = semantics.element; |
| registry.registerStaticUse(new StaticUse.staticTearOff(method)); |
| 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: |
| MemberElement member = semantics.element; |
| registry.registerStaticUse(new StaticUse.staticGet(member)); |
| 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 FieldConstantExpression(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. |
| MethodElement method = member; |
| 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(method)); |
| } |
| semantics = member.isTopLevel |
| ? new StaticAccess.topLevelMethod(method) |
| : new StaticAccess.staticMethod(method); |
| } else { |
| // `a = b`, `a++` or `a += b` where `a` is a field. |
| assert(member.isField, failedAt(node, "Unexpected element: $member.")); |
| if (node.isComplex) { |
| // `a++` or `a += b` where `a` is a field. |
| registry.registerStaticUse(new StaticUse.staticGet(member)); |
| } |
| if (member.isFinal || member.isConst) { |
| reportAndCreateErroneousElement(node.selector, name.text, |
| MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name}); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| semantics = member.isTopLevel |
| ? new StaticAccess.finalTopLevelField(member) |
| : new StaticAccess.finalStaticField(member); |
| } else { |
| registry.registerStaticUse(new StaticUse.staticSet(member)); |
| semantics = member.isTopLevel |
| ? new StaticAccess.topLevelField(member) |
| : new StaticAccess.staticField(member); |
| } |
| } |
| } |
| return handleUpdate(node, name, semantics); |
| } |
| |
| /// Handle access to resolved [element]. |
| ResolutionResult handleResolvedSend(Send node, Name name, Element element) { |
| if (element.isAmbiguous) { |
| return handleAmbiguousSend(node, name, element); |
| } |
| if (element.isMalformed) { |
| // This handles elements with parser errors. |
| assert(element is! ErroneousElement, |
| failedAt(node, "Unexpected erroneous element $element.")); |
| return handleErroneousAccess( |
| node, name, new StaticAccess.unresolved(element)); |
| } |
| if (element.isInstanceMember) { |
| if (inInstanceContext) { |
| // TODO(johnniwinther): Maybe use the found [element]. |
| return handleThisPropertyAccess(node, name); |
| } else { |
| return handleErroneousAccess( |
| node, name, reportStaticInstanceAccess(node, name)); |
| } |
| } |
| if (element.isClass) { |
| // `C`, `C()`, or 'C.b` where 'C' is a class. |
| return handleClassSend(node, name, element); |
| } else if (element.isTypedef) { |
| // `F` or `F()` where 'F' is a typedef. |
| return handleTypedefTypeLiteralAccess(node, name, element); |
| } else if (element.isTypeVariable) { |
| return handleTypeVariableTypeLiteralAccess(node, name, element); |
| } else if (element.isPrefix) { |
| return handleLibraryPrefix(node, name, element); |
| } else if (element.isLocal) { |
| return handleLocalAccess(node, name, element); |
| } else if (element.isStatic || element.isTopLevel) { |
| return handleStaticOrTopLevelAccess(node, name, element); |
| } |
| return reporter.internalError(node, "Unexpected resolved send: $element"); |
| } |
| |
| /// Handle update to resolved [element]. |
| ResolutionResult handleResolvedSendSet( |
| SendSet node, Name name, Element element) { |
| if (element.isAmbiguous) { |
| return handleAmbiguousUpdate(node, name, element); |
| } |
| if (element.isMalformed) { |
| // This handles elements with parser errors.. |
| assert(element is! ErroneousElement, |
| failedAt(node, "Unexpected erroneous element $element.")); |
| return handleUpdate(node, name, new StaticAccess.unresolved(element)); |
| } |
| if (element.isInstanceMember) { |
| if (inInstanceContext) { |
| return handleThisPropertyUpdate(node, name, element); |
| } else { |
| return handleUpdate(node, name, reportStaticInstanceAccess(node, name)); |
| } |
| } |
| if (element.isClass) { |
| // `C = b`, `C++`, or 'C += b` where 'C' is a class. |
| return handleClassTypeLiteralUpdate(node, name, element); |
| } else if (element.isTypedef) { |
| // `C = b`, `C++`, or 'C += b` where 'F' is a typedef. |
| return handleTypedefTypeLiteralUpdate(node, name, element); |
| } else if (element.isTypeVariable) { |
| // `T = b`, `T++`, or 'T += b` where 'T' is a type variable. |
| return handleTypeVariableTypeLiteralUpdate(node, name, element); |
| } else if (element.isPrefix) { |
| // `p = b` where `p` is a prefix. |
| ErroneousElement error = reportAndCreateErroneousElement( |
| node, name.text, MessageKind.PREFIX_AS_EXPRESSION, {'prefix': name}, |
| isError: true); |
| registry.registerFeature(Feature.COMPILE_TIME_ERROR); |
| return handleUpdate(node, name, new StaticAccess.invalid(error)); |
| } else if (element.isLocal) { |
| return handleLocalUpdate(node, name, element); |
| } else if (element.isStatic || element.isTopLevel) { |
| return handleStaticOrTopLevelUpdate(node, name, element); |
| } |
| return reporter.internalError(node, "Unexpected resolved send: $element"); |
| } |
| |
| /// Handle an unqualified [Send], that is where the `node.receiver` is null, |
| /// like `a`, `a()`, `this()`, `assert()`, and `(){}()`. |
| ResolutionResult handleUnqualifiedSend(Send node) { |
| Identifier selector = node.selector.asIdentifier(); |
| if (selector == null) { |
| // `(){}()` and `(foo)()`. |
| return handleExpressionInvoke(node); |
| } |
| String text = selector.source; |
| if (text == 'this') { |
| // `this()`. |
| return handleThisAccess(node); |
| } |
| // `name` or `name()` |
| Name name = new Name(text, enclosingElement.library); |
| Element element = lookupInScope(reporter, node, scope, text); |
| if (element == null) { |
| if (text == 'dynamic') { |
| // `dynamic` or `dynamic()` where 'dynamic' is not declared in the |
| // current scope. |
| return handleDynamicTypeLiteralAccess(node); |
| } else if (inInstanceContext) { |
| // Implicitly `this.name`. |
| return handleThisPropertyAccess(node, name); |
| } else { |
| // Create [ErroneousElement] for unresolved access. |
| ErroneousElement error = reportCannotResolve(node, text); |
| return handleUnresolvedAccess(node, name, error); |
| } |
| } else { |
| return handleResolvedSend(node, name, element); |
| } |
| } |
| |
| /// Handle an unqualified [SendSet], that is where the `node.receiver` is |
| /// null, like `a = b`, `a++`, and `a += b`. |
| ResolutionResult handleUnqualifiedSendSet(SendSet node) { |
| Identifier selector = node.selector.asIdentifier(); |
| String text = selector.source; |
| Name name = new Name(text, enclosingElement.library); |
| Element element = lookupInScope(reporter, node, scope, text); |
| if (element == null) { |
| if (text == 'dynamic') { |
| // `dynamic = b`, `dynamic++`, or `dynamic += b` where 'dynamic' is not |
| // declared in the current scope. |
| return handleDynamicTypeLiteralUpdate(node); |
| } else if (inInstanceContext) { |
| // Left-hand side is implicitly `this.name`. |
| return handleThisPropertyUpdate(node, name, null); |
| } else { |
| // Create [ErroneousElement] for unresolved access. |
| ErroneousElement error = reportCannotResolve(node, text); |
| return handleUpdate(node, name, new StaticAccess.unresolved(error)); |
| } |
| } else { |
| return handleResolvedSendSet(node, name, element); |
| } |
| } |
| |
| ResolutionResult visitSend(Send node) { |
| // Resolve type arguments to ensure that these are well-formed types. |
| if (node.typeArgumentsNode != null) { |
| for (TypeAnnotation type in node.typeArgumentsNode.nodes) { |
| resolveTypeAnnotation(type, registerCheckedModeCheck: false); |
| } |
| } |
| if (node.isOperator) { |
| // `a && b`, `a + b`, `-a`, or `a is T`. |
| return handleOperatorSend(node); |
| } else if (node.receiver != null) { |
| // `a.b`. |
| return handleQualifiedSend(node); |
| } else { |
| // `a`. |
| return handleUnqualifiedSend(node); |
| } |
| } |
| |
| /// Register read access of [target] inside a closure. |
| void registerPotentialAccessInClosure(Send node, Element target) { |
| if (isPotentiallyMutableTarget(target)) { |
| if (enclosingElement != target.enclosingElement) { |
| for (Node scope in promotionScope) { |
| registry.setAccessedByClosureIn(scope, target, node); |
| } |
| } |
| } |
| } |
| |
| // TODO(johnniwinther): Move this to the backend resolution callbacks. |
| void handleForeignCall( |
| Send node, Element target, CallStructure callStructure) { |
| if (target != null && resolution.target.isForeign(target)) { |
| registry.registerForeignCall(node, target, callStructure, this); |
| } |
| } |
| |
| /// Callback for native enqueuer to parse a type. Returns [:null:] on error. |
| ResolutionDartType resolveTypeFromString(Node node, String typeName) { |
| Element element = lookupInScope(reporter, node, scope, typeName); |
| if (element == null) return null; |
| if (element is! ClassElement) return null; |
| ClassElement cls = element; |
| cls.ensureResolved(resolution); |
| cls.computeType(resolution); |
| return cls.rawType; |
| } |
| |
| /// Handle index operations like `a[b] = c`, `a[b] += c`, and `a[b]++`. |
| ResolutionResult handleIndexSendSet(SendSet node) { |
| String operatorText = node.assignmentOperator.source; |
| Node receiver = node.receiver; |
| Node index = node.arguments.head; |
| visitExpression(receiver); |
| visitExpression(index); |
| AccessSemantics semantics = const DynamicAccess.expression(); |
| if (node.isPrefix || node.isPostfix) { |
| // `a[b]++` or `++a[b]`. |
| IncDecOperator operator = IncDecOperator.parse(operatorText); |
| Selector getterSelector = new Selector.index(); |
| Selector setterSelector = new Selector.indexSet(); |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| registry.registerDynamicUse(new DynamicUse(getterSelector, null)); |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| |
| SendStructure sendStructure = node.isPrefix |
| ? new IndexPrefixStructure(semantics, operator) |
| : new IndexPostfixStructure(semantics, operator); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } else { |
| Node rhs = node.arguments.tail.head; |
| visitExpression(rhs); |
| |
| AssignmentOperator operator = AssignmentOperator.parse(operatorText); |
| if (operator.kind == AssignmentOperatorKind.ASSIGN) { |
| // `a[b] = c`. |
| Selector setterSelector = new Selector.indexSet(); |
| |
| // TODO(23998): Remove this when selectors are only accessed |
| // through the send structure. |
| registry.setSelector(node, setterSelector); |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| |
| SendStructure sendStructure = new IndexSetStructure(semantics); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } else { |
| // `a[b] += c`. |
| Selector getterSelector = new Selector.index(); |
| Selector setterSelector = new Selector.indexSet(); |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| registry.registerDynamicUse(new DynamicUse(getterSelector, null)); |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| |
| SendStructure sendStructure; |
| if (operator.kind == AssignmentOperatorKind.IF_NULL) { |
| sendStructure = new IndexSetIfNullStructure(semantics); |
| } else { |
| sendStructure = new CompoundIndexSetStructure(semantics, operator); |
| } |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } |
| } |
| } |
| |
| /// Handle super index operations like `super[a] = b`, `super[a] += b`, and |
| /// `super[a]++`. |
| // TODO(johnniwinther): Share code with [handleIndexSendSet]. |
| ResolutionResult handleSuperIndexSendSet(SendSet node) { |
| String operatorText = node.assignmentOperator.source; |
| Node index = node.arguments.head; |
| visitExpression(index); |
| |
| AccessSemantics semantics = checkSuperAccess(node); |
| if (node.isPrefix || node.isPostfix) { |
| // `super[a]++` or `++super[a]`. |
| IncDecOperator operator = IncDecOperator.parse(operatorText); |
| Selector getterSelector = new Selector.index(); |
| Selector setterSelector = new Selector.indexSet(); |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelectors( |
| node, getterSelector, setterSelector, |
| isIndex: true); |
| |
| if (!semantics.getter.isError) { |
| MethodElement getter = semantics.getter; |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(getter, getterSelector.callStructure)); |
| } |
| if (!semantics.setter.isError) { |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(setter, setterSelector.callStructure)); |
| } |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| registry.useElement(node.selector, semantics.getter); |
| } |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| |
| SendStructure sendStructure = node.isPrefix |
| ? new IndexPrefixStructure(semantics, operator) |
| : new IndexPostfixStructure(semantics, operator); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } else { |
| Node rhs = node.arguments.tail.head; |
| visitExpression(rhs); |
| |
| AssignmentOperator operator = AssignmentOperator.parse(operatorText); |
| if (operator.kind == AssignmentOperatorKind.ASSIGN) { |
| // `super[a] = b`. |
| Selector setterSelector = new Selector.indexSet(); |
| if (semantics == null) { |
| semantics = |
| computeSuperAccessSemanticsForSelector(node, setterSelector); |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| } |
| |
| // TODO(23998): Remove this when selectors are only accessed |
| // through the send structure. |
| registry.setSelector(node, setterSelector); |
| if (!semantics.setter.isError) { |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse( |
| new StaticUse.superInvoke(setter, setterSelector.callStructure)); |
| } |
| |
| SendStructure sendStructure = new IndexSetStructure(semantics); |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } else { |
| // `super[a] += b`. |
| Selector getterSelector = new Selector.index(); |
| Selector setterSelector = new Selector.indexSet(); |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelectors( |
| node, getterSelector, setterSelector, |
| isIndex: true); |
| |
| if (!semantics.getter.isError) { |
| MethodElement getter = semantics.getter; |
| registry.registerStaticUse(new StaticUse.superInvoke( |
| getter, getterSelector.callStructure)); |
| } |
| if (!semantics.setter.isError) { |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superInvoke( |
| setter, setterSelector.callStructure)); |
| } |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| registry.useElement(node.selector, semantics.getter); |
| } |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| |
| SendStructure sendStructure; |
| if (operator.kind == AssignmentOperatorKind.IF_NULL) { |
| sendStructure = new IndexSetIfNullStructure(semantics); |
| } else { |
| sendStructure = new CompoundIndexSetStructure(semantics, operator); |
| } |
| registry.registerSendStructure(node, sendStructure); |
| return const NoneResult(); |
| } |
| } |
| } |
| |
| /// Handle super index operations like `super.a = b`, `super.a += b`, and |
| /// `super.a++`. |
| // TODO(johnniwinther): Share code with [handleSuperIndexSendSet]. |
| ResolutionResult handleSuperSendSet(SendSet node) { |
| Identifier selector = node.selector.asIdentifier(); |
| String text = selector.source; |
| Name name = new Name(text, enclosingElement.library); |
| String operatorText = node.assignmentOperator.source; |
| Selector getterSelector = new Selector.getter(name); |
| Selector setterSelector = new Selector.setter(name); |
| |
| void registerStaticUses(AccessSemantics semantics) { |
| switch (semantics.kind) { |
| case AccessKind.SUPER_METHOD: |
| MethodElement method = semantics.element; |
| registry.registerStaticUse(new StaticUse.superTearOff(method)); |
| break; |
| case AccessKind.SUPER_GETTER: |
| MethodElement getter = semantics.getter; |
| registry.registerStaticUse(new StaticUse.superGet(getter)); |
| break; |
| case AccessKind.SUPER_SETTER: |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superSetterSet(setter)); |
| break; |
| case AccessKind.SUPER_FIELD: |
| FieldElement field = semantics.element; |
| registry.registerStaticUse(new StaticUse.superGet(field)); |
| registry.registerStaticUse(new StaticUse.superFieldSet(field)); |
| break; |
| case AccessKind.SUPER_FINAL_FIELD: |
| FieldElement field = semantics.element; |
| registry.registerStaticUse(new StaticUse.superGet(field)); |
| break; |
| case AccessKind.COMPOUND: |
| CompoundAccessSemantics compoundSemantics = semantics; |
| switch (compoundSemantics.compoundAccessKind) { |
| case CompoundAccessKind.SUPER_GETTER_FIELD: |
| case CompoundAccessKind.SUPER_FIELD_FIELD: |
| MemberElement getter = semantics.getter; |
| FieldElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superGet(getter)); |
| registry.registerStaticUse(new StaticUse.superFieldSet(setter)); |
| break; |
| case CompoundAccessKind.SUPER_FIELD_SETTER: |
| case CompoundAccessKind.SUPER_GETTER_SETTER: |
| MemberElement getter = semantics.getter; |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superGet(getter)); |
| registry.registerStaticUse(new StaticUse.superSetterSet(setter)); |
| break; |
| case CompoundAccessKind.SUPER_METHOD_SETTER: |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superSetterSet(setter)); |
| break; |
| case CompoundAccessKind.UNRESOLVED_SUPER_GETTER: |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superSetterSet(setter)); |
| break; |
| case CompoundAccessKind.UNRESOLVED_SUPER_SETTER: |
| MethodElement getter = semantics.getter; |
| registry.registerStaticUse(new StaticUse.superGet(getter)); |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| AccessSemantics semantics = checkSuperAccess(node); |
| if (node.isPrefix || node.isPostfix) { |
| // `super.a++` or `++super.a`. |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelectors( |
| node, getterSelector, setterSelector); |
| registerStaticUses(semantics); |
| } |
| return handleUpdate(node, name, semantics); |
| } else { |
| AssignmentOperator operator = AssignmentOperator.parse(operatorText); |
| if (operator.kind == AssignmentOperatorKind.ASSIGN) { |
| // `super.a = b`. |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelector( |
| node, setterSelector, |
| alternateName: name); |
| switch (semantics.kind) { |
| case AccessKind.SUPER_FINAL_FIELD: |
| reporter.reportWarningMessage( |
| node, MessageKind.ASSIGNING_FINAL_FIELD_IN_SUPER, { |
| 'name': name, |
| 'superclassName': semantics.setter.enclosingClass.name |
| }); |
| // TODO(johnniwinther): This shouldn't be needed. |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD); |
| break; |
| case AccessKind.SUPER_METHOD: |
| reporter.reportWarningMessage( |
| node, MessageKind.ASSIGNING_METHOD_IN_SUPER, { |
| 'name': name, |
| 'superclassName': semantics.setter.enclosingClass.name |
| }); |
| // TODO(johnniwinther): This shouldn't be needed. |
| registry.registerDynamicUse(new DynamicUse(setterSelector, null)); |
| registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD); |
| break; |
| case AccessKind.SUPER_FIELD: |
| FieldElement field = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superFieldSet(field)); |
| break; |
| case AccessKind.SUPER_SETTER: |
| MethodElement setter = semantics.setter; |
| registry.registerStaticUse(new StaticUse.superSetterSet(setter)); |
| break; |
| default: |
| break; |
| } |
| } |
| return handleUpdate(node, name, semantics); |
| } else { |
| // `super.a += b`. |
| if (semantics == null) { |
| semantics = computeSuperAccessSemanticsForSelectors( |
| node, getterSelector, setterSelector); |
| registerStaticUses(semantics); |
| } |
| return handleUpdate(node, name, semantics); |
| } |
| } |
| } |
| |
| /// Handle update of an entity defined by [semantics]. For instance `a = b`, |
| /// `a++` or `a += b` where [semantics] describe `a`. |
| ResolutionResult handleUpdate( |
| SendSet node, Name name, AccessSemantics semantics) { |
| String operatorText = node.assignmentOperator.source; |
| Selector getterSelector = new Selector.getter(name); |
| Selector setterSelector = new Selector.setter(name); |
| if (node.isPrefix || node.isPostfix) { |
| // `e++` or `++e`. |
| IncDecOperator operator = IncDecOperator.parse(operatorText); |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| registry.useElement(node.selector, semantics.getter); |
| |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| |
| SendStructure sendStructure = node.isPrefix |
| ? new PrefixStructure(semantics, operator) |
| : new PostfixStructure(semantics, operator); |
| registry.registerSendStructure(node, sendStructure); |
| registry.registerConstantLiteral(new IntConstantExpression(1)); |
| } else { |
| Node rhs = node.arguments.head; |
| visitExpression(rhs); |
| |
| AssignmentOperator operator = AssignmentOperator.parse(operatorText); |
| if (operator.kind == AssignmentOperatorKind.ASSIGN) { |
| // `e1 = e2`. |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| |
| // TODO(23998): Remove this when selectors are only accessed |
| // through the send structure. |
| registry.setSelector(node, setterSelector); |
| |
| SendStructure sendStructure = new SetStructure(semantics); |
| registry.registerSendStructure(node, sendStructure); |
| } else { |
| // `e1 += e2`. |
| Selector operatorSelector = |
| new Selector.binaryOperator(operator.selectorName); |
| |
| // TODO(23998): Remove these when elements are only accessed |
| // through the send structure. |
| registry.useElement(node, semantics.setter); |
| registry.useElement(node.selector, semantics.getter); |
| |
| // TODO(23998): Remove these when selectors are only accessed |
| // through the send structure. |
| registry.setGetterSelectorInComplexSendSet(node, getterSelector); |
| registry.setSelector(node, setterSelector); |
| registry.setOperatorSelectorInComplexSendSet(node, operatorSelector); |
| |
| SendStructure sendStructure; |
| if (operator.kind == AssignmentOperatorKind.IF_NULL) { |
| registry.registerConstantLiteral(new NullConstantExpression()); |
| registry.registerDynamicUse(new DynamicUse(Selectors.equals, null)); |
| sendStructure = new SetIfNullStructure(semantics); |
| } else { |
| registry.registerDynamicUse(new DynamicUse(operatorSelector, null)); |
| sendStructure = new CompoundStructure(semantics, operator); |
| } |
| registry.registerSendStructure(node, sendStructure); |
| } |
| } |
| return new ResolutionResult.forElement(semantics.setter); |
| } |
| |
| ResolutionResult visitSendSet(SendSet node) { |
| if (node.isIndex) { |
| // `a[b] = c` |
| if (node.isSuperCall) { |
| // `super[b] = c` |
| return handleSuperIndexSendSet(node); |
| } else { |
| return handleIndexSendSet(node); |
| } |
| } else if (node.isSuperCall) { |
| // `super.a = c` |
| return handleSuperSendSet(node); |
| } else if (node.receiver == null) { |
| // `a = c` |
| return handleUnqualifiedSendSet(node); |
| } else { |
| // `a.b = c` |
| return handleQualifiedSendSet(node); |
| } |
| } |
| |
| ConstantResult visitLiteralInt(LiteralInt node) { |
| ConstantExpression constant = new IntConstantExpression(node.value); |
| registry.registerConstantLiteral(constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralDouble(LiteralDouble node) { |
| ConstantExpression constant = new DoubleConstantExpression(node.value); |
| registry.registerConstantLiteral(constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralBool(LiteralBool node) { |
| ConstantExpression constant = new BoolConstantExpression(node.value); |
| registry.registerConstantLiteral(constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ResolutionResult visitLiteralString(LiteralString node) { |
| if (node.dartString != null) { |
| // [dartString] might be null on parser errors. |
| ConstantExpression constant = |
| new StringConstantExpression(node.dartString.slowToString()); |
| registry.registerConstantLiteral(constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ConstantResult visitLiteralNull(LiteralNull node) { |
| ConstantExpression constant = new NullConstantExpression(); |
| registry.registerConstantLiteral(constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ConstantResult visitLiteralSymbol(LiteralSymbol node) { |
| String name = node.slowNameString; |
| // TODO(johnniwinther): Use [registerConstantLiteral] instead. |
| registry.registerConstSymbol(name); |
| if (!validateSymbol(node, name, reportError: false)) { |
| reporter.reportErrorMessage( |
| node, MessageKind.UNSUPPORTED_LITERAL_SYMBOL, {'value': name}); |
| } |
| analyzeConstantDeferred(node); |
| ConstantExpression constant = new SymbolConstantExpression(name); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| |
| ResolutionResult visitStringJuxtaposition(StringJuxtaposition node) { |
| registry.registerFeature(Feature.STRING_JUXTAPOSITION); |
| ResolutionResult first = visit(node.first); |
| ResolutionResult second = visit(node.second); |
| if (first.isConstant && second.isConstant) { |
| ConstantExpression constant = new ConcatenateConstantExpression( |
| <ConstantExpression>[first.constant, second.constant]); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNodeList(NodeList node) { |
| for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { |
| visit(link.head); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitRethrow(Rethrow node) { |
| if (!inCatchBlock && node.throwToken.stringValue == 'rethrow') { |
| reporter.reportErrorMessage(node, MessageKind.RETHROW_OUTSIDE_CATCH); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitReturn(Return node) { |
| Node expression = node.expression; |
| if (expression != null) { |
| if (enclosingElement.isGenerativeConstructor) { |
| // It is a compile-time error if a return statement of the form |
| // `return e;` appears in a generative constructor. (Dart Language |
| // Specification 13.12.) |
| reporter.reportErrorMessage( |
| expression, MessageKind.RETURN_IN_GENERATIVE_CONSTRUCTOR); |
| } else if (!node.isArrowBody && currentAsyncMarker.isYielding) { |
| reporter.reportErrorMessage(node, MessageKind.RETURN_IN_GENERATOR, |
| {'modifier': currentAsyncMarker}); |
| } |
| } |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitYield(Yield node) { |
| if (!resolution.target.supportsAsyncAwait) { |
| reporter.reportErrorMessage(reporter.spanFromToken(node.yieldToken), |
| MessageKind.ASYNC_AWAIT_NOT_SUPPORTED); |
| } else { |
| if (!currentAsyncMarker.isYielding) { |
| reporter.reportErrorMessage(node, MessageKind.INVALID_YIELD); |
| } |
| ClassElement cls; |
| if (currentAsyncMarker.isAsync) { |
| cls = commonElements.streamClass; |
| } else { |
| cls = commonElements.iterableClass; |
| } |
| cls.ensureResolved(resolution); |
| } |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitRedirectingFactoryBody(RedirectingFactoryBody node) { |
| if (!enclosingElement.isFactoryConstructor) { |
| reporter.reportErrorMessage( |
| node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY); |
| reporter.reportHintMessage( |
| enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD); |
| } |
| |
| ConstructorElementX constructor = enclosingElement; |
| bool isConstConstructor = constructor.isConst; |
| bool isValidAsConstant = isConstConstructor; |
| ConstructorResult result = |
| resolveRedirectingFactory(node, inConstContext: isConstConstructor); |
| ConstructorElement redirectionTarget = result.element; |
| constructor.setImmediateRedirectionTarget( |
| redirectionTarget, result.isDeferred ? result.prefix : null); |
| |
| registry.setRedirectingTargetConstructor(node, redirectionTarget); |
| switch (result.kind) { |
| case ConstructorResultKind.GENERATIVE: |
| case ConstructorResultKind.FACTORY: |
| // Register a post process to check for cycles in the redirection chain |
| // and set the actual generative constructor at the end of the chain. |
| addDeferredAction(constructor, () { |
| resolver.resolveRedirectionChain(constructor, node); |
| }); |
| break; |
| case ConstructorResultKind.ABSTRACT: |
| case ConstructorResultKind.INVALID_TYPE: |
| case ConstructorResultKind.UNRESOLVED_CONSTRUCTOR: |
| case ConstructorResultKind.NON_CONSTANT: |
| isValidAsConstant = false; |
| constructor.setEffectiveTarget(result.element, result.type, |
| isMalformed: true); |
| break; |
| } |
| if (Elements.isUnresolved(redirectionTarget)) { |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| return const NoneResult(); |
| } else { |
| if (isConstConstructor && !redirectionTarget.isConst) { |
| reporter.reportErrorMessage(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST); |
| isValidAsConstant = false; |
| } |
| if (redirectionTarget == constructor) { |
| reporter.reportErrorMessage( |
| node, MessageKind.CYCLIC_REDIRECTING_FACTORY); |
| // TODO(johnniwinther): Create constant constructor for this case and |
| // let evaluation detect the cyclicity. |
| isValidAsConstant = false; |
| } |
| } |
| |
| // Check that the target constructor is type compatible with the |
| // redirecting constructor. |
| ClassElement targetClass = redirectionTarget.enclosingClass; |
| ResolutionInterfaceType type = registry.getType(node); |
| ResolutionFunctionType targetConstructorType = redirectionTarget |
| .computeType(resolution) |
| .subst(type.typeArguments, targetClass.typeVariables); |
| ResolutionFunctionType constructorType = |
| constructor.computeType(resolution); |
| bool isSubtype = |
| resolution.types.isSubtype(targetConstructorType, constructorType); |
| if (!isSubtype) { |
| reporter.reportWarningMessage(node, MessageKind.NOT_ASSIGNABLE, |
| {'fromType': targetConstructorType, 'toType': constructorType}); |
| // TODO(johnniwinther): Handle this (potentially) erroneous case. |
| isValidAsConstant = false; |
| } |
| if (type.typeArguments.any((ResolutionDartType type) => !type.isDynamic)) { |
| registry.registerFeature(Feature.TYPE_VARIABLE_BOUNDS_CHECK); |
| } |
| |
| redirectionTarget.computeType(resolution); |
| FunctionSignature targetSignature = redirectionTarget.functionSignature; |
| constructor.computeType(resolution); |
| FunctionSignature constructorSignature = constructor.functionSignature; |
| if (!targetSignature.isCompatibleWith(constructorSignature)) { |
| assert(!isSubtype); |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| isValidAsConstant = false; |
| } |
| |
| registry.registerStaticUse(new StaticUse.constructorRedirect( |
| redirectionTarget, |
| redirectionTarget.enclosingClass.thisType |
| .subst(type.typeArguments, targetClass.typeVariables))); |
| if (resolution.commonElements.isSymbolConstructor(constructor)) { |
| registry.registerFeature(Feature.SYMBOL_CONSTRUCTOR); |
| } |
| if (isValidAsConstant) { |
| List<String> names = <String>[]; |
| List<ConstantExpression> arguments = <ConstantExpression>[]; |
| int index = 0; |
| constructorSignature.forEachParameter((_parameter) { |
| ParameterElement parameter = _parameter; |
| if (parameter.isNamed) { |
| String name = parameter.name; |
| names.add(name); |
| arguments.add(new NamedArgumentReference(name)); |
| } else { |
| arguments.add(new PositionalArgumentReference(index)); |
| } |
| index++; |
| }); |
| CallStructure callStructure = |
| new CallStructure(constructorSignature.parameterCount, names); |
| constructor.constantConstructor = |
| new RedirectingFactoryConstantConstructor( |
| new ConstructedConstantExpression( |
| type, redirectionTarget, callStructure, arguments)); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitThrow(Throw node) { |
| registry.registerFeature(Feature.THROW_EXPRESSION); |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitAwait(Await node) { |
| if (!resolution.target.supportsAsyncAwait) { |
| reporter.reportErrorMessage(reporter.spanFromToken(node.awaitToken), |
| MessageKind.ASYNC_AWAIT_NOT_SUPPORTED); |
| } else { |
| if (!currentAsyncMarker.isAsync) { |
| reporter.reportErrorMessage(node, MessageKind.INVALID_AWAIT); |
| } |
| ClassElement futureClass = commonElements.futureClass; |
| futureClass.ensureResolved(resolution); |
| } |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitVariableDefinitions(VariableDefinitions node) { |
| ResolutionDartType type; |
| if (node.type != null) { |
| type = resolveTypeAnnotation(node.type); |
| } else { |
| type = const ResolutionDynamicType(); |
| } |
| VariableList variables = new VariableList.node(node, type); |
| VariableDefinitionsVisitor visitor = |
| new VariableDefinitionsVisitor(resolution, node, this, variables); |
| |
| Modifiers modifiers = node.modifiers; |
| void reportExtraModifier(String modifier) { |
| Node modifierNode; |
| for (Link<Node> nodes = modifiers.nodes.nodes; |
| !nodes.isEmpty; |
| nodes = nodes.tail) { |
| if (modifier == nodes.head.asIdentifier().source) { |
| modifierNode = nodes.head; |
| break; |
| } |
| } |
| assert(modifierNode != null); |
| reporter.reportErrorMessage(modifierNode, MessageKind.EXTRANEOUS_MODIFIER, |
| {'modifier': modifier}); |
| } |
| |
| if (modifiers.isFinal && (modifiers.isConst || modifiers.isVar)) { |
| reportExtraModifier('final'); |
| } |
| if (modifiers.isVar && (modifiers.isConst || node.type != null)) { |
| reportExtraModifier('var'); |
| } |
| if (enclosingElement.isFunction || enclosingElement.isConstructor) { |
| if (modifiers.isAbstract) { |
| reportExtraModifier('abstract'); |
| } |
| if (modifiers.isStatic) { |
| reportExtraModifier('static'); |
| } |
| } |
| if (node.metadata != null) { |
| variables.metadataInternal = |
| resolver.resolveMetadata(enclosingElement, node); |
| } |
| visitor.visit(node.definitions); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitWhile(While node) { |
| visit(node.condition); |
| visitLoopBodyIn(node, node.body, new BlockScope(scope)); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitParenthesizedExpression(ParenthesizedExpression node) { |
| bool oldSendIsMemberAccess = sendIsMemberAccess; |
| sendIsMemberAccess = false; |
| var oldCategory = allowedCategory; |
| allowedCategory = ElementCategory.VARIABLE | |
| ElementCategory.FUNCTION | |
| ElementCategory.IMPLIES_TYPE; |
| ResolutionResult result = visit(node.expression); |
| allowedCategory = oldCategory; |
| sendIsMemberAccess = oldSendIsMemberAccess; |
| if (result.kind == ResultKind.CONSTANT) { |
| return result; |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNewExpression(NewExpression node) { |
| ConstructorResult result = resolveConstructor(node); |
| ConstructorElement constructor = result.element; |
| if (resolution.commonElements.isSymbolConstructor(constructor)) { |
| registry.registerFeature(Feature.SYMBOL_CONSTRUCTOR); |
| } |
| ArgumentsResult argumentsResult; |
| if (node.isConst) { |
| argumentsResult = |
| inConstantContext(() => resolveArguments(node.send.argumentsNode)); |
| } else { |
| if (!node.isConst && constructor.isFromEnvironmentConstructor) { |
| // TODO(sigmund): consider turning this into a compile-time-error. |
| reporter.reportHintMessage( |
| node, |
| MessageKind.FROM_ENVIRONMENT_MUST_BE_CONST, |
| {'className': constructor.enclosingClass.name}); |
| registry.registerFeature(Feature.THROW_UNSUPPORTED_ERROR); |
| } |
| argumentsResult = resolveArguments(node.send.argumentsNode); |
| } |
| // TODO(johnniwinther): Avoid the need for a [Selector]. |
| Selector selector = resolveSelector(node.send, constructor); |
| CallStructure callStructure = selector.callStructure; |
| registry.useElement(node.send, constructor); |
| |
| ResolutionDartType type = result.type; |
| ConstructorAccessKind kind; |
| bool isInvalid = false; |
| switch (result.kind) { |
| case ConstructorResultKind.GENERATIVE: |
| // Ensure that the signature of [constructor] has been computed. |
| constructor.computeType(resolution); |
| if (!callStructure.signatureApplies(constructor.parameterStructure)) { |
| isInvalid = true; |
| kind = ConstructorAccessKind.INCOMPATIBLE; |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| } else { |
| kind = ConstructorAccessKind.GENERATIVE; |
| } |
| break; |
| case ConstructorResultKind.FACTORY: |
| // Ensure that the signature of [constructor] has been computed. |
| constructor.computeType(resolution); |
| if (!callStructure.signatureApplies(constructor.parameterStructure)) { |
| // The effective target might still be valid(!) so the is not an |
| // invalid case in itself. For instance |
| // |
| // class A { |
| // factory A() = A.a; |
| // A.a(a); |
| // } |
| // m() => new A(0); // This creates a warning but works at runtime. |
| // |
| registry.registerFeature(Feature.THROW_NO_SUCH_METHOD); |
| } |
| kind = ConstructorAccessKind.FACTORY; |
| break; |
| case ConstructorResultKind.ABSTRACT: |
| isInvalid = true; |
| kind = ConstructorAccessKind.ABSTRACT; |
| break; |
| case ConstructorResultKind.INVALID_TYPE: |
| isInvalid = true; |
| kind = ConstructorAccessKind.UNRESOLVED_TYPE; |
| break; |
| case ConstructorResultKind.UNRESOLVED_CONSTRUCTOR: |
| // TODO(johnniwinther): Unify codepaths to only have one return. |
| registry.registerNewStructure( |
| node, |
| new NewInvokeStructure( |
| new ConstructorAccessSemantics( |
| ConstructorAccessKind.UNRESOLVED_CONSTRUCTOR, |
| constructor, |
| type), |
| selector)); |
| return new ResolutionResult.forElement(constructor); |
| case ConstructorResultKind.NON_CONSTANT: |
| registry.registerNewStructure( |
| node, |
| new NewInvokeStructure( |
| new ConstructorAccessSemantics( |
| ConstructorAccessKind.NON_CONSTANT_CONSTRUCTOR, |
| constructor, |
| type), |
| selector)); |
| return new ResolutionResult.forElement(constructor); |
| } |
| |
| if (!isInvalid) { |
| // [constructor] might be the implementation element |
| // and only declaration elements may be registered. |
| // TODO(johniwinther): Avoid registration of `type` in face of redirecting |
| // factory constructors. |
| ConstructorElement declaration = constructor.declaration; |
| registry.registerStaticUse(node.isConst |
| ? new StaticUse.constConstructorInvoke( |
| declaration, callStructure, type) |
| : new StaticUse.typedConstructorInvoke( |
| constructor, callStructure, type)); |
| ResolutionInterfaceType interfaceType = type; |
| if (interfaceType.typeArguments |
| .any((ResolutionDartType type) => !type.isDynamic)) { |
| registry.registerFeature(Feature.TYPE_VARIABLE_BOUNDS_CHECK); |
| } |
| } |
| |
| ResolutionResult resolutionResult = const NoneResult(); |
| if (node.isConst) { |
| bool isValidAsConstant = !isInvalid && constructor.isConst; |
| |
| CommonElements commonElements = resolution.commonElements; |
| if (commonElements.isSymbolConstructor(constructor)) { |
| Node argumentNode = node.send.arguments.head; |
| ConstantExpression constant = resolver.constantCompiler |
| .compileNode(argumentNode, registry.mapping); |
| ConstantValue name = resolution.constants.getConstantValue(constant); |
| if (!name.isString) { |
| ResolutionDartType type = name.getType(commonElements); |
| reporter.reportErrorMessage( |
| argumentNode, MessageKind.STRING_EXPECTED, {'type': type}); |
| } else { |
| StringConstantValue stringConstant = name; |
| String nameString = stringConstant.primitiveValue; |
| if (validateSymbol(argumentNode, nameString)) { |
| registry.registerConstSymbol(nameString); |
| } |
| } |
| } else if (commonElements.isMirrorsUsedConstructor(constructor)) { |
| resolution.mirrorUsageAnalyzerTask.validate(node, registry.mapping); |
| } |
| |
| analyzeConstantDeferred(node); |
| |
| if (type.containsTypeVariables) { |
| reporter.reportErrorMessage( |
| node.send.selector, MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| isInvalid = true; |
| } |
| |
| if (result.isDeferred) { |
| isValidAsConstant = false; |
| } |
| |
| // Callback hook for when the compile-time constant evaluator has |
| // analyzed the constant. |
| // TODO(johnniwinther): Remove this when all constants are computed |
| // in resolution. |
| Function onAnalyzed; |
| if (isValidAsConstant && |
| argumentsResult.isValidAsConstant && |
| // TODO(johnniwinther): Remove this when all constants are computed |
| // in resolution. |
| !constructor.isFromEnvironmentConstructor) { |
| ResolutionInterfaceType interfaceType = type; |
| CallStructure callStructure = argumentsResult.callStructure; |
| List<ConstantExpression> arguments = argumentsResult.constantArguments; |
| |
| ConstructedConstantExpression constant = |
| new ConstructedConstantExpression( |
| interfaceType, constructor, callStructure, arguments); |
| registry.registerNewStructure(node, |
| new ConstInvokeStructure(ConstantInvokeKind.CONSTRUCTED, constant)); |
| resolutionResult = new ConstantResult(node, constant); |
| } else if (isInvalid) { |
| // Known to be non-constant. |
| kind == ConstructorAccessKind.NON_CONSTANT_CONSTRUCTOR; |
| registry.registerNewStructure( |
| node, |
| new NewInvokeStructure( |
| new ConstructorAccessSemantics(kind, constructor, type), |
| selector)); |
| } else { |
| // Might be valid but we don't know for sure. The compile-time constant |
| // evaluator will compute the actual constant as a deferred action. |
| LateConstInvokeStructure structure = |
| new LateConstInvokeStructure(registry.mapping); |
| // TODO(johnniwinther): Avoid registering the |
| // [LateConstInvokeStructure]; it might not be necessary. |
| registry.registerNewStructure(node, structure); |
| onAnalyzed = () { |
| registry.registerNewStructure(node, structure.resolve(node)); |
| }; |
| } |
| |
| analyzeConstantDeferred(node, onAnalyzed: onAnalyzed); |
| } else { |
| // Not constant. |
| if (resolution.commonElements.isSymbolConstructor(constructor) && |
| !resolution.mirrorUsageAnalyzerTask |
| .hasMirrorUsage(enclosingElement)) { |
| reporter.reportHintMessage( |
| reporter.spanFromToken(node.newToken), |
| MessageKind.NON_CONST_BLOAT, |
| {'name': commonElements.symbolClass.name}); |
| } |
| registry.registerNewStructure( |
| node, |
| new NewInvokeStructure( |
| new ConstructorAccessSemantics(kind, constructor, type), |
| selector)); |
| } |
| |
| return resolutionResult; |
| } |
| |
| void checkConstMapKeysDontOverrideEquals( |
| Spannable spannable, MapConstantValue map) { |
| for (ConstantValue key in map.keys) { |
| if (!key.isObject) continue; |
| ObjectConstantValue objectConstant = key; |
| ResolutionInterfaceType keyType = objectConstant.type; |
| ClassElement cls = keyType.element; |
| if (cls == commonElements.stringClass) continue; |
| Element equals = cls.lookupMember('=='); |
| if (equals.enclosingClass != commonElements.objectClass) { |
| reporter.reportErrorMessage(spannable, |
| MessageKind.CONST_MAP_KEY_OVERRIDES_EQUALS, {'type': keyType}); |
| } |
| } |
| } |
| |
| void analyzeConstant(Node node, {enforceConst: true}) { |
| ConstantExpression constant = resolver.constantCompiler |
| .compileNode(node, registry.mapping, enforceConst: enforceConst); |
| |
| if (constant == null) { |
| assert(reporter.hasReportedError, failedAt(node)); |
| return; |
| } |
| |
| ConstantValue value = resolution.constants.getConstantValue(constant); |
| if (value.isMap) { |
| checkConstMapKeysDontOverrideEquals(node, value); |
| } |
| } |
| |
| void analyzeConstantDeferred(Node node, |
| {bool enforceConst: true, void onAnalyzed()}) { |
| addDeferredAction(enclosingElement, () { |
| analyzeConstant(node, enforceConst: enforceConst); |
| if (onAnalyzed != null) { |
| onAnalyzed(); |
| } |
| }); |
| } |
| |
| bool validateSymbol(Node node, String name, {bool reportError: true}) { |
| if (name.isEmpty) return true; |
| if (name.startsWith('_')) { |
| if (reportError) { |
| reporter.reportErrorMessage( |
| node, MessageKind.PRIVATE_IDENTIFIER, {'value': name}); |
| } |
| return false; |
| } |
| if (!symbolValidationPattern.hasMatch(name)) { |
| if (reportError) { |
| reporter.reportErrorMessage( |
| node, MessageKind.INVALID_SYMBOL, {'value': name}); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Try to resolve the constructor that is referred to by [node]. |
| * Note: this function may return an ErroneousFunctionElement instead of |
| * [:null:], if there is no corresponding constructor, class or library. |
| */ |
| ConstructorResult resolveConstructor(NewExpression node) { |
| return node.accept(new ConstructorResolver(resolution, this, |
| inConstContext: node.isConst)); |
| } |
| |
| ConstructorResult resolveRedirectingFactory(RedirectingFactoryBody node, |
| {bool inConstContext: false}) { |
| return node.accept(new ConstructorResolver(resolution, this, |
| inConstContext: inConstContext)); |
| } |
| |
| ResolutionDartType resolveTypeAnnotation(TypeAnnotation node, |
| {bool malformedIsError: false, |
| bool deferredIsMalformed: true, |
| bool registerCheckedModeCheck: true}) { |
| ResolutionDartType type = typeResolver.resolveTypeAnnotation(this, node, |
| malformedIsError: malformedIsError, |
| deferredIsMalformed: deferredIsMalformed); |
| if (registerCheckedModeCheck) { |
| registry.registerCheckedModeCheck(type); |
| } |
| return type; |
| } |
| |
| ResolutionResult visitLiteralList(LiteralList node) { |
| bool isValidAsConstant = true; |
| sendIsMemberAccess = false; |
| |
| NodeList arguments = node.typeArguments; |
| ResolutionDartType typeArgument; |
| if (arguments != null) { |
| Link<Node> nodes = arguments.nodes; |
| if (nodes.isEmpty) { |
| // The syntax [: <>[] :] is not allowed. |
| reporter.reportErrorMessage( |
| arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| isValidAsConstant = false; |
| } else { |
| typeArgument = resolveTypeAnnotation(nodes.head); |
| for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { |
| reporter.reportWarningMessage( |
| nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); |
| resolveTypeAnnotation(nodes.head); |
| } |
| } |
| } |
| ResolutionInterfaceType listType; |
| if (typeArgument != null) { |
| if (node.isConst && typeArgument.containsTypeVariables) { |
| reporter.reportErrorMessage( |
| arguments.nodes.head, MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| } |
| listType = commonElements.listType(typeArgument); |
| } else { |
| listType = commonElements.listType(); |
| } |
| registry.registerLiteralList(node, listType, |
| isConstant: node.isConst, isEmpty: node.elements.isEmpty); |
| if (node.isConst) { |
| List<ConstantExpression> constantExpressions = <ConstantExpression>[]; |
| inConstantContext(() { |
| for (Node element in node.elements) { |
| ResolutionResult elementResult = visit(element); |
| if (isValidAsConstant && elementResult.isConstant) { |
| constantExpressions.add(elementResult.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| }); |
| analyzeConstantDeferred(node); |
| sendIsMemberAccess = false; |
| if (isValidAsConstant) { |
| ConstantExpression constant = |
| new ListConstantExpression(listType, constantExpressions); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } else { |
| visit(node.elements); |
| sendIsMemberAccess = false; |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitConditional(Conditional node) { |
| ResolutionResult conditionResult = |
| doInPromotionScope(node.condition, () => visit(node.condition)); |
| ResolutionResult thenResult = doInPromotionScope( |
| node.thenExpression, () => visit(node.thenExpression)); |
| ResolutionResult elseResult = visit(node.elseExpression); |
| if (conditionResult.isConstant && |
| thenResult.isConstant && |
| elseResult.isConstant) { |
| ConstantExpression constant = new ConditionalConstantExpression( |
| conditionResult.constant, thenResult.constant, elseResult.constant); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitStringInterpolation(StringInterpolation node) { |
| // TODO(johnniwinther): This should be a consequence of the registration |
| // of [registerStringInterpolation]. |
| registry.registerFeature(Feature.STRING_INTERPOLATION); |
| |
| bool isValidAsConstant = true; |
| List<ConstantExpression> parts = <ConstantExpression>[]; |
| |
| void resolvePart(Node subnode) { |
| ResolutionResult result = visit(subnode); |
| if (isValidAsConstant && result.isConstant) { |
| parts.add(result.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| |
| resolvePart(node.string); |
| for (StringInterpolationPart part in node.parts) { |
| resolvePart(part.expression); |
| resolvePart(part.string); |
| } |
| |
| if (isValidAsConstant) { |
| ConstantExpression constant = new ConcatenateConstantExpression(parts); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitBreakStatement(BreakStatement node) { |
| JumpTargetX target; |
| if (node.target == null) { |
| target = statementScope.currentBreakTarget(); |
| if (target == null) { |
| reporter.reportErrorMessage(node, MessageKind.NO_BREAK_TARGET); |
| return const NoneResult(); |
| } |
| target.isBreakTarget = true; |
| } else { |
| String labelName = node.target.source; |
| LabelDefinitionX label = statementScope.lookupLabel(labelName); |
| if (label == null) { |
| reporter.reportErrorMessage( |
| node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); |
| return const NoneResult(); |
| } |
| target = label.target; |
| if (!target.statement.isValidBreakTarget()) { |
| reporter.reportErrorMessage(node.target, MessageKind.INVALID_BREAK); |
| return const NoneResult(); |
| } |
| label.setBreakTarget(); |
| registry.useLabel(node, label); |
| } |
| registry.registerTargetOf(node, target); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitContinueStatement(ContinueStatement node) { |
| JumpTargetX target; |
| if (node.target == null) { |
| target = statementScope.currentContinueTarget(); |
| if (target == null) { |
| reporter.reportErrorMessage(node, MessageKind.NO_CONTINUE_TARGET); |
| return const NoneResult(); |
| } |
| target.isContinueTarget = true; |
| } else { |
| String labelName = node.target.source; |
| LabelDefinitionX label = statementScope.lookupLabel(labelName); |
| if (label == null) { |
| reporter.reportErrorMessage( |
| node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); |
| return const NoneResult(); |
| } |
| target = label.target; |
| if (!target.statement.isValidContinueTarget()) { |
| reporter.reportErrorMessage(node.target, MessageKind.INVALID_CONTINUE); |
| } |
| label.setContinueTarget(); |
| registry.useLabel(node, label); |
| } |
| registry.registerTargetOf(node, target); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitAsyncForIn(AsyncForIn node) { |
| if (!resolution.target.supportsAsyncAwait) { |
| reporter.reportErrorMessage(reporter.spanFromToken(node.awaitToken), |
| MessageKind.ASYNC_AWAIT_NOT_SUPPORTED); |
| } else { |
| if (!currentAsyncMarker.isAsync) { |
| reporter.reportErrorMessage(reporter.spanFromToken(node.awaitToken), |
| MessageKind.INVALID_AWAIT_FOR_IN); |
| } |
| registry.registerFeature(Feature.ASYNC_FOR_IN); |
| registry.registerDynamicUse(new DynamicUse(Selectors.current, null)); |
| registry.registerDynamicUse(new DynamicUse(Selectors.moveNext, null)); |
| } |
| visit(node.expression); |
| |
| Scope blockScope = new BlockScope(scope); |
| visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope); |
| visitLoopBodyIn(node, node.body, blockScope); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitSyncForIn(SyncForIn node) { |
| registry.registerFeature(Feature.SYNC_FOR_IN); |
| registry.registerDynamicUse(new DynamicUse(Selectors.iterator, null)); |
| registry.registerDynamicUse(new DynamicUse(Selectors.current, null)); |
| registry.registerDynamicUse(new DynamicUse(Selectors.moveNext, null)); |
| |
| visit(node.expression); |
| |
| Scope blockScope = new BlockScope(scope); |
| visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope); |
| visitLoopBodyIn(node, node.body, blockScope); |
| return const NoneResult(); |
| } |
| |
| void visitForInDeclaredIdentifierIn( |
| Node declaration, ForIn node, Scope blockScope) { |
| LibraryElement library = enclosingElement.library; |
| |
| bool oldAllowFinalWithoutInitializer = inLoopVariable; |
| inLoopVariable = true; |
| visitIn(declaration, blockScope); |
| inLoopVariable = oldAllowFinalWithoutInitializer; |
| |
| Send send = declaration.asSend(); |
| VariableDefinitions variableDefinitions = |
| declaration.asVariableDefinitions(); |
| Element loopVariable; |
| Selector loopVariableSelector; |
| if (send != null) { |
| loopVariable = registry.getDefinition(send); |
| Identifier identifier = send.selector.asIdentifier(); |
| if (identifier == null) { |
| reporter.reportErrorMessage(send.selector, MessageKind.INVALID_FOR_IN); |
| } else { |
| loopVariableSelector = |
| new Selector.setter(new Name(identifier.source, library)); |
| } |
| if (send.receiver != null) { |
| reporter.reportErrorMessage(send.receiver, MessageKind.INVALID_FOR_IN); |
| } |
| } else if (variableDefinitions != null) { |
| Link<Node> nodes = variableDefinitions.definitions.nodes; |
| if (!nodes.tail.isEmpty) { |
| reporter.reportErrorMessage( |
| nodes.tail.head, MessageKind.INVALID_FOR_IN); |
| } |
| Node first = nodes.head; |
| Identifier identifier = first.asIdentifier(); |
| if (identifier == null) { |
| reporter.reportErrorMessage(first, MessageKind.INVALID_FOR_IN); |
| } else { |
| loopVariableSelector = |
| new Selector.setter(new Name(identifier.source, library)); |
| loopVariable = registry.getDefinition(identifier); |
| } |
| } else { |
| reporter.reportErrorMessage(declaration, MessageKind.INVALID_FOR_IN); |
| } |
| if (loopVariableSelector != null) { |
| registry.setSelector(declaration, loopVariableSelector); |
| if (loopVariable == null || loopVariable.isInstanceMember) { |
| registry.registerDynamicUse(new DynamicUse(loopVariableSelector, null)); |
| } else if (loopVariable.isStatic || loopVariable.isTopLevel) { |
| MemberElement member = loopVariable.declaration; |
| registry.registerStaticUse(new StaticUse.staticSet(member)); |
| } |
| } else { |
| // The selector may only be null if we reported an error. |
| assert(reporter.hasReportedError, failedAt(declaration)); |
| } |
| if (loopVariable != null) { |
| // loopVariable may be null if it could not be resolved. |
| registry.setForInVariable(node, loopVariable); |
| } |
| } |
| |
| visitLabel(Label node) { |
| // Labels are handled by their containing statements/cases. |
| } |
| |
| ResolutionResult visitLabeledStatement(LabeledStatement node) { |
| Statement body = node.statement; |
| JumpTarget targetElement = getOrDefineTarget(body); |
| Map<String, LabelDefinitionX> labelElements = <String, LabelDefinitionX>{}; |
| for (Label label in node.labels) { |
| String labelName = label.labelName; |
| if (labelElements.containsKey(labelName)) continue; |
| LabelDefinition element = targetElement.addLabel(label, labelName); |
| labelElements[labelName] = element; |
| } |
| statementScope.enterLabelScope(labelElements); |
| visit(node.statement); |
| statementScope.exitLabelScope(); |
| labelElements.forEach((String labelName, LabelDefinitionX element) { |
| if (element.isTarget) { |
| registry.defineLabel(element.label, element); |
| } else { |
| reporter.reportWarningMessage( |
| element.label, MessageKind.UNUSED_LABEL, {'labelName': labelName}); |
| } |
| }); |
| if (!targetElement.isTarget) { |
| registry.undefineTarget(body); |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitLiteralMap(LiteralMap node) { |
| bool isValidAsConstant = true; |
| sendIsMemberAccess = false; |
| |
| NodeList arguments = node.typeArguments; |
| ResolutionDartType keyTypeArgument; |
| ResolutionDartType valueTypeArgument; |
| if (arguments != null) { |
| Link<Node> nodes = arguments.nodes; |
| if (nodes.isEmpty) { |
| // The syntax [: <>{} :] is not allowed. |
| reporter.reportErrorMessage( |
| arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| isValidAsConstant = false; |
| } else { |
| keyTypeArgument = resolveTypeAnnotation(nodes.head); |
| nodes = nodes.tail; |
| if (nodes.isEmpty) { |
| reporter.reportWarningMessage( |
| arguments, MessageKind.MISSING_TYPE_ARGUMENT); |
| } else { |
| valueTypeArgument = resolveTypeAnnotation(nodes.head); |
| for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { |
| reporter.reportWarningMessage( |
| nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); |
| resolveTypeAnnotation(nodes.head); |
| } |
| } |
| } |
| } |
| ResolutionInterfaceType mapType; |
| if (valueTypeArgument != null) { |
| mapType = commonElements.mapType(keyTypeArgument, valueTypeArgument); |
| } else { |
| mapType = commonElements.mapType(); |
| } |
| if (node.isConst && mapType.containsTypeVariables) { |
| reporter.reportErrorMessage( |
| arguments, MessageKind.TYPE_VARIABLE_IN_CONSTANT); |
| isValidAsConstant = false; |
| } |
| registry.registerMapLiteral(node, mapType, |
| isConstant: node.isConst, isEmpty: node.entries.isEmpty); |
| |
| if (node.isConst) { |
| List<ConstantExpression> keyExpressions = <ConstantExpression>[]; |
| List<ConstantExpression> valueExpressions = <ConstantExpression>[]; |
| inConstantContext(() { |
| for (LiteralMapEntry entry in node.entries) { |
| ResolutionResult keyResult = visit(entry.key); |
| ResolutionResult valueResult = visit(entry.value); |
| if (isValidAsConstant && |
| keyResult.isConstant && |
| valueResult.isConstant) { |
| keyExpressions.add(keyResult.constant); |
| valueExpressions.add(valueResult.constant); |
| } else { |
| isValidAsConstant = false; |
| } |
| } |
| }); |
| analyzeConstantDeferred(node); |
| sendIsMemberAccess = false; |
| if (isValidAsConstant) { |
| ConstantExpression constant = new MapConstantExpression( |
| mapType, keyExpressions, valueExpressions); |
| registry.setConstant(node, constant); |
| return new ConstantResult(node, constant); |
| } |
| } else { |
| node.visitChildren(this); |
| sendIsMemberAccess = false; |
| } |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitLiteralMapEntry(LiteralMapEntry node) { |
| node.visitChildren(this); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitNamedArgument(NamedArgument node) { |
| return visit(node.expression); |
| } |
| |
| ResolutionInterfaceType typeOfConstant(ConstantValue constant) { |
| if (constant.isInt) return commonElements.intType; |
| if (constant.isBool) return commonElements.boolType; |
| if (constant.isDouble) return commonElements.doubleType; |
| if (constant.isString) return commonElements.stringType; |
| if (constant.isNull) return commonElements.nullType; |
| if (constant.isFunction) return commonElements.functionType; |
| assert(constant.isObject); |
| ObjectConstantValue objectConstant = constant; |
| ResolutionInterfaceType type = objectConstant.type; |
| return type; |
| } |
| |
| bool overridesEquals(ResolutionDartType type) { |
| ClassElement cls = type.element; |
| Element equals = cls.lookupMember('=='); |
| return equals.enclosingClass != commonElements.objectClass; |
| } |
| |
| void checkCaseExpressions(SwitchStatement node) { |
| CaseMatch firstCase = null; |
| ResolutionDartType firstCaseType = null; |
| DiagnosticMessage error; |
| List<DiagnosticMessage> infos = <DiagnosticMessage>[]; |
| |
| for (Link<Node> cases = node.cases.nodes; |
| !cases.isEmpty; |
| cases = cases.tail) { |
| SwitchCase switchCase = cases.head; |
| |
| for (Node labelOrCase in switchCase.labelsAndCases) { |
| CaseMatch caseMatch = labelOrCase.asCaseMatch(); |
| if (caseMatch == null) continue; |
| |
| // Analyze the constant. |
| ConstantExpression constant = |
| registry.getConstant(caseMatch.expression); |
| assert( |
| constant != null, failedAt(node, 'No constant computed for $node')); |
| |
| ConstantValue value = resolution.constants.getConstantValue(constant); |
| ResolutionDartType caseType = |
| value.getType(commonElements); //typeOfConstant(value); |
| |
| if (firstCaseType == null) { |
| firstCase = caseMatch; |
| firstCaseType = caseType; |
| |
| // We only report the bad type on the first class element. All others |
| // get a "type differs" error. |
| if (caseType == commonElements.doubleType) { |
| reporter.reportErrorMessage( |
| node, |
| MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS, |
| {'type': "double"}); |
| } else if (caseType == commonElements.functionType) { |
| reporter.reportErrorMessage( |
| node, MessageKind.SWITCH_CASE_FORBIDDEN, {'type': "Function"}); |
| } else if (value.isObject && overridesEquals(caseType)) { |
| reporter.reportErrorMessage( |
| firstCase.expression, |
| MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS, |
| {'type': caseType}); |
| } |
| } else { |
| if (caseType != firstCaseType) { |
| if (error == null) { |
| error = reporter.createMessage( |
| node, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL, |
| {'type': firstCaseType}); |
| infos.add(reporter.createMessage( |
| firstCase.expression, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| {'type': firstCaseType})); |
| } |
| infos.add(reporter.createMessage( |
| caseMatch.expression, |
| MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| {'type': caseType})); |
| } |
| } |
| } |
| } |
| if (error != null) { |
| reporter.reportError(error, infos); |
| } |
| } |
| |
| ResolutionResult visitSwitchStatement(SwitchStatement node) { |
| node.expression.accept(this); |
| |
| JumpTarget breakElement = getOrDefineTarget(node); |
| Map<String, LabelDefinitionX> continueLabels = <String, LabelDefinitionX>{}; |
| Set<SwitchCase> switchCasesWithContinues = new Set<SwitchCase>(); |
| Link<Node> cases = node.cases.nodes; |
| while (!cases.isEmpty) { |
| SwitchCase switchCase = cases.head; |
| for (Node labelOrCase in switchCase.labelsAndCases) { |
| CaseMatch caseMatch = labelOrCase.asCaseMatch(); |
| if (caseMatch != null) { |
| analyzeConstantDeferred(caseMatch.expression); |
| continue; |
| } |
| Label label = labelOrCase; |
| String labelName = label.labelName; |
| |
| LabelDefinitionX existingElement = continueLabels[labelName]; |
| if (existingElement != null) { |
| // It's an error if the same label occurs twice in the same switch. |
| reporter.reportError( |
| reporter.createMessage( |
| label, MessageKind.DUPLICATE_LABEL, {'labelName': labelName}), |
| <DiagnosticMessage>[ |
| reporter.createMessage(existingElement.label, |
| MessageKind.EXISTING_LABEL, {'labelName': labelName}), |
| ]); |
| } else { |
| // It's only a warning if it shadows another label. |
| existingElement = statementScope.lookupLabel(labelName); |
| if (existingElement != null) { |
| reporter.reportWarning( |
| reporter.createMessage(label, MessageKind.DUPLICATE_LABEL, |
| {'labelName': labelName}), |
| <DiagnosticMessage>[ |
| reporter.createMessage(existingElement.label, |
| MessageKind.EXISTING_LABEL, {'labelName': labelName}), |
| ]); |
| } |
| } |
| |
| JumpTarget targetElement = getOrDefineTarget(switchCase); |
| LabelDefinition labelElement = targetElement.addLabel(label, labelName); |
| registry.defineLabel(label, labelElement); |
| continueLabels[labelName] = labelElement; |
| } |
| cases = cases.tail; |
| // Test that only the last case, if any, is a default case. |
| if (switchCase.defaultKeyword != null && !cases.isEmpty) { |
| reporter.reportErrorMessage( |
| switchCase, MessageKind.INVALID_CASE_DEFAULT); |
| } |
| if (cases.isNotEmpty && switchCase.statements.isNotEmpty) { |
| Node last = switchCase.statements.last; |
| if (last.asReturn() == null && |
| last.asBreakStatement() == null && |
| last.asContinueStatement() == null && |
| (last.asExpressionStatement() == null || |
| last.asExpressionStatement().expression.asThrow() == null)) { |
| registry.registerFeature(Feature.FALL_THROUGH_ERROR); |
| } |
| } |
| } |
| |
| addDeferredAction(enclosingElement, () { |
| checkCaseExpressions(node); |
| }); |
| |
| statementScope.enterSwitch(breakElement, continueLabels); |
| node.cases.accept(this); |
| statementScope.exitSwitch(); |
| |
| continueLabels.forEach((String key, LabelDefinition label) { |
| if (label.isContinueTarget) { |
| JumpTarget targetElement = label.target; |
| SwitchCase switchCase = targetElement.statement; |
| switchCasesWithContinues.add(switchCase); |
| } |
| }); |
| |
| // Clean-up unused labels. |
| continueLabels.forEach((String key, LabelDefinitionX label) { |
| if (!label.isContinueTarget) { |
| JumpTarget targetElement = label.target; |
| SwitchCase switchCase = targetElement.statement; |
| if (!switchCasesWithContinues.contains(switchCase)) { |
| registry.undefineTarget(switchCase); |
| } |
| registry.undefineLabel(label.label); |
| } |
| }); |
| // TODO(15575): We should warn if we can detect a fall through |
| // error. |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitSwitchCase(SwitchCase node) { |
| node.labelsAndCases.accept(this); |
| visitIn(node.statements, new BlockScope(scope)); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitCaseMatch(CaseMatch node) { |
| visit(node.expression); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitTryStatement(TryStatement node) { |
| // TODO(karlklose): also track the information about mutated variables, |
| // catch, and finally-block. |
| registry.registerTryStatement(); |
| |
| visit(node.tryBlock); |
| if (node.catchBlocks.isEmpty && node.finallyBlock == null) { |
| reporter.reportErrorMessage( |
| reporter.spanFromToken(node.getEndToken().next), |
| MessageKind.NO_CATCH_NOR_FINALLY); |
| } |
| visit(node.catchBlocks); |
| visit(node.finallyBlock); |
| return const NoneResult(); |
| } |
| |
| ResolutionResult visitCatchBlock(CatchBlock node) { |
| registry.registerFeature(Feature.CATCH_STATEMENT); |
| // Check that if catch part is present, then |
| // it has one or two formal parameters. |
| VariableDefinitions exceptionDefinition; |
| VariableDefinitions stackTraceDefinition; |
| if (node.formals != null) { |
| Link<Node> formalsToProcess = node.formals.nodes; |
| if (formalsToProcess.isEmpty) { |
| reporter.reportErrorMessage(node, MessageKind.EMPTY_CATCH_DECLARATION); |
| } else { |
| exceptionDefinition = formalsToProcess.head.asVariableDefinitions(); |
| formalsToProcess = formalsToProcess.tail; |
| if (!formalsToProcess.isEmpty) { |
| stackTraceDefinition = formalsToProcess.head.asVariableDefinitions(); |
| formalsToProcess = formalsToProcess.tail; |
| if (!formalsToProcess.isEmpty) { |
| for (Node extra in formalsToProcess) { |
| reporter.reportErrorMessage( |
| extra, MessageKind.EXTRA_CATCH_DECLARATION); |
| } |
| } |
| registry.registerFeature(Feature.STACK_TRACE_IN_CATCH); |
| } |
| } |
| |
| // Check that the formals aren't optional and that they have no |
| // modifiers or type. |
| for (Link<Node> link = node.formals.nodes; |
| !link.isEmpty; |
| link = link.tail) { |
| // If the formal parameter is a node list, it means that it is a |
| // sequence of optional parameters. |
| NodeList nodeList = link.head.asNodeList(); |
| if (nodeList != null) { |
| reporter.reportErrorMessage( |
| nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH); |
| } else { |
| VariableDefinitions declaration = link.head; |
| |
| for (Node modifier in declaration.modifiers.nodes) { |
| reporter.reportErrorMessage( |
| modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH); |
| } |
| TypeAnnotation type = declaration.type; |
| if (type != null) { |
| reporter.reportErrorMessage( |
| type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH); |
| } |
| } |
| } |
| } |
| |
| Scope blockScope = new BlockScope(scope); |
| inCatchParameters = true; |
| visitIn(node.formals, blockScope); |
| inCatchParameters = false; |
| var oldInCatchBlock = inCatchBlock; |
| inCatchBlock = true; |
| visitIn(node.block, blockScope); |
| inCatchBlock = oldInCatchBlock; |
| |
| if (node.type != null) { |
| ResolutionDartType exceptionType = |
| resolveTypeAnnotation(node.type, registerCheckedModeCheck: false); |
| if (exceptionDefinition != null) { |
| Node exceptionVariable = exceptionDefinition.definitions.nodes.head; |
| VariableElementX exceptionElement = |
| registry.getDefinition(exceptionVariable); |
| exceptionElement.variables.type = exceptionType; |
| } |
| registry.registerTypeUse(new TypeUse.catchType(exceptionType)); |
| } |
| if (stackTraceDefinition != null) { |
| Node stackTraceVariable = stackTraceDefinition.definitions.nodes.head; |
| VariableElementX stackTraceElement = |
| registry.getDefinition(stackTraceVariable); |
| ResolutionInterfaceType stackTraceType = commonElements.stackTraceType; |
| stackTraceElement.variables.type = stackTraceType; |
| } |
| return const NoneResult(); |
| } |
| } |
| |
| /// Looks up [name] in [scope] and unwraps the result. |
| Element lookupInScope( |
| DiagnosticReporter reporter, Node node, Scope scope, String name) { |
| return Elements.unwrap(scope.lookup(name), reporter, node); |
| } |