| // Copyright (c) 2014, 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 analyzer.src.generated.element_resolver; |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/syntactic_entity.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart' |
| show |
| ChildEntities, |
| IdentifierImpl, |
| PrefixedIdentifierImpl, |
| SimpleIdentifierImpl; |
| import 'package:analyzer/src/dart/ast/token.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| |
| /** |
| * An object used by instances of [ResolverVisitor] to resolve references within |
| * the AST structure to the elements being referenced. The requirements for the |
| * element resolver are: |
| * |
| * 1. Every [SimpleIdentifier] should be resolved to the element to which it |
| * refers. Specifically: |
| * * An identifier within the declaration of that name should resolve to the |
| * element being declared. |
| * * An identifier denoting a prefix should resolve to the element |
| * representing the import that defines the prefix (an [ImportElement]). |
| * * An identifier denoting a variable should resolve to the element |
| * representing the variable (a [VariableElement]). |
| * * An identifier denoting a parameter should resolve to the element |
| * representing the parameter (a [ParameterElement]). |
| * * An identifier denoting a field should resolve to the element |
| * representing the getter or setter being invoked (a |
| * [PropertyAccessorElement]). |
| * * An identifier denoting the name of a method or function being invoked |
| * should resolve to the element representing the method or function (an |
| * [ExecutableElement]). |
| * * An identifier denoting a label should resolve to the element |
| * representing the label (a [LabelElement]). |
| * The identifiers within directives are exceptions to this rule and are |
| * covered below. |
| * 2. Every node containing a token representing an operator that can be |
| * overridden ( [BinaryExpression], [PrefixExpression], [PostfixExpression]) |
| * should resolve to the element representing the method invoked by that |
| * operator (a [MethodElement]). |
| * 3. Every [FunctionExpressionInvocation] should resolve to the element |
| * representing the function being invoked (a [FunctionElement]). This will |
| * be the same element as that to which the name is resolved if the function |
| * has a name, but is provided for those cases where an unnamed function is |
| * being invoked. |
| * 4. Every [LibraryDirective] and [PartOfDirective] should resolve to the |
| * element representing the library being specified by the directive (a |
| * [LibraryElement]) unless, in the case of a part-of directive, the |
| * specified library does not exist. |
| * 5. Every [ImportDirective] and [ExportDirective] should resolve to the |
| * element representing the library being specified by the directive unless |
| * the specified library does not exist (an [ImportElement] or |
| * [ExportElement]). |
| * 6. The identifier representing the prefix in an [ImportDirective] should |
| * resolve to the element representing the prefix (a [PrefixElement]). |
| * 7. The identifiers in the hide and show combinators in [ImportDirective]s |
| * and [ExportDirective]s should resolve to the elements that are being |
| * hidden or shown, respectively, unless those names are not defined in the |
| * specified library (or the specified library does not exist). |
| * 8. Every [PartDirective] should resolve to the element representing the |
| * compilation unit being specified by the string unless the specified |
| * compilation unit does not exist (a [CompilationUnitElement]). |
| * |
| * Note that AST nodes that would represent elements that are not defined are |
| * not resolved to anything. This includes such things as references to |
| * undeclared variables (which is an error) and names in hide and show |
| * combinators that are not defined in the imported library (which is not an |
| * error). |
| */ |
| class ElementResolver extends SimpleAstVisitor<Object> { |
| /** |
| * The resolver driving this participant. |
| */ |
| final ResolverVisitor _resolver; |
| |
| /** |
| * The element for the library containing the compilation unit being visited. |
| */ |
| LibraryElement _definingLibrary; |
| |
| /** |
| * A flag indicating whether we should generate hints. |
| */ |
| bool _enableHints = false; |
| |
| /** |
| * A flag indicating whether we should strictly follow the specification when |
| * generating warnings on "call" methods (fixes dartbug.com/21938). |
| */ |
| bool _enableStrictCallChecks = false; |
| |
| /** |
| * The type representing the type 'dynamic'. |
| */ |
| DartType _dynamicType; |
| |
| /** |
| * The type representing the type 'type'. |
| */ |
| InterfaceType _typeType; |
| |
| /** |
| * A utility class for the resolver to answer the question of "what are my |
| * subtypes?". |
| */ |
| SubtypeManager _subtypeManager; |
| |
| /** |
| * The object keeping track of which elements have had their types promoted. |
| */ |
| TypePromotionManager _promoteManager; |
| |
| /** |
| * Initialize a newly created visitor to work for the given [_resolver] to |
| * resolve the nodes in a compilation unit. |
| */ |
| ElementResolver(this._resolver) { |
| this._definingLibrary = _resolver.definingLibrary; |
| AnalysisOptions options = _definingLibrary.context.analysisOptions; |
| _enableHints = options.hint; |
| _enableStrictCallChecks = options.enableStrictCallChecks; |
| _dynamicType = _resolver.typeProvider.dynamicType; |
| _typeType = _resolver.typeProvider.typeType; |
| _subtypeManager = new SubtypeManager(); |
| _promoteManager = _resolver.promoteManager; |
| } |
| |
| /** |
| * Return `true` iff the current enclosing function is a constant constructor |
| * declaration. |
| */ |
| bool get isInConstConstructor { |
| ExecutableElement function = _resolver.enclosingFunction; |
| if (function is ConstructorElement) { |
| return function.isConst; |
| } |
| return false; |
| } |
| |
| @override |
| Object visitAssignmentExpression(AssignmentExpression node) { |
| Token operator = node.operator; |
| TokenType operatorType = operator.type; |
| if (operatorType != TokenType.AMPERSAND_AMPERSAND_EQ && |
| operatorType != TokenType.BAR_BAR_EQ && |
| operatorType != TokenType.EQ && |
| operatorType != TokenType.QUESTION_QUESTION_EQ) { |
| operatorType = _operatorFromCompoundAssignment(operatorType); |
| Expression leftHandSide = node.leftHandSide; |
| if (leftHandSide != null) { |
| String methodName = operatorType.lexeme; |
| DartType staticType = _getStaticType(leftHandSide); |
| MethodElement staticMethod = |
| _lookUpMethod(leftHandSide, staticType, methodName); |
| node.staticElement = staticMethod; |
| DartType propagatedType = _getPropagatedType(leftHandSide); |
| MethodElement propagatedMethod = |
| _lookUpMethod(leftHandSide, propagatedType, methodName); |
| node.propagatedElement = propagatedMethod; |
| if (_shouldReportMissingMember(staticType, staticMethod)) { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_METHOD, |
| operator, |
| [methodName, staticType.displayName]); |
| } else if (_enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedMethod) && |
| !_memberFoundInSubclass( |
| propagatedType.element, methodName, true, false)) { |
| _recordUndefinedToken( |
| propagatedType.element, |
| HintCode.UNDEFINED_METHOD, |
| operator, |
| [methodName, propagatedType.displayName]); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitBinaryExpression(BinaryExpression node) { |
| Token operator = node.operator; |
| if (operator.isUserDefinableOperator) { |
| _resolveBinaryExpression(node, operator.lexeme); |
| } else if (operator.type == TokenType.BANG_EQ) { |
| _resolveBinaryExpression(node, TokenType.EQ_EQ.lexeme); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitBreakStatement(BreakStatement node) { |
| node.target = _lookupBreakOrContinueTarget(node, node.label, false); |
| return null; |
| } |
| |
| @override |
| Object visitClassDeclaration(ClassDeclaration node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitClassTypeAlias(ClassTypeAlias node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitCommentReference(CommentReference node) { |
| Identifier identifier = node.identifier; |
| if (identifier is SimpleIdentifier) { |
| Element element = _resolveSimpleIdentifier(identifier); |
| if (element == null) { |
| // |
| // This might be a reference to an imported name that is missing the |
| // prefix. |
| // |
| element = _findImportWithoutPrefix(identifier); |
| if (element is MultiplyDefinedElement) { |
| // TODO(brianwilkerson) Report this error? |
| element = null; |
| } |
| } |
| if (element == null) { |
| // TODO(brianwilkerson) Report this error? |
| // resolver.reportError( |
| // StaticWarningCode.UNDEFINED_IDENTIFIER, |
| // simpleIdentifier, |
| // simpleIdentifier.getName()); |
| } else { |
| if (element.library == null || element.library != _definingLibrary) { |
| // TODO(brianwilkerson) Report this error? |
| } |
| identifier.staticElement = element; |
| if (node.newKeyword != null) { |
| if (element is ClassElement) { |
| ConstructorElement constructor = element.unnamedConstructor; |
| if (constructor == null) { |
| // TODO(brianwilkerson) Report this error. |
| } else { |
| identifier.staticElement = constructor; |
| } |
| } else { |
| // TODO(brianwilkerson) Report this error. |
| } |
| } |
| } |
| } else if (identifier is PrefixedIdentifier) { |
| SimpleIdentifier prefix = identifier.prefix; |
| SimpleIdentifier name = identifier.identifier; |
| Element element = _resolveSimpleIdentifier(prefix); |
| if (element == null) { |
| // resolver.reportError(StaticWarningCode.UNDEFINED_IDENTIFIER, prefix, prefix.getName()); |
| } else { |
| prefix.staticElement = element; |
| if (element is PrefixElement) { |
| // TODO(brianwilkerson) Report this error? |
| element = _resolver.nameScope.lookup(identifier, _definingLibrary); |
| name.staticElement = element; |
| return null; |
| } |
| LibraryElement library = element.library; |
| if (library == null) { |
| // TODO(brianwilkerson) We need to understand how the library could |
| // ever be null. |
| AnalysisEngine.instance.logger |
| .logError("Found element with null library: ${element.name}"); |
| } else if (library != _definingLibrary) { |
| // TODO(brianwilkerson) Report this error. |
| } |
| if (node.newKeyword == null) { |
| if (element is ClassElement) { |
| Element memberElement = |
| _lookupGetterOrMethod(element.type, name.name); |
| if (memberElement == null) { |
| memberElement = element.getNamedConstructor(name.name); |
| if (memberElement == null) { |
| memberElement = _lookUpSetter(prefix, element.type, name.name); |
| } |
| } |
| if (memberElement == null) { |
| // reportGetterOrSetterNotFound(identifier, name, element.getDisplayName()); |
| } else { |
| name.staticElement = memberElement; |
| } |
| } else { |
| // TODO(brianwilkerson) Report this error. |
| } |
| } else { |
| if (element is ClassElement) { |
| ConstructorElement constructor = |
| element.getNamedConstructor(name.name); |
| if (constructor == null) { |
| // TODO(brianwilkerson) Report this error. |
| } else { |
| name.staticElement = constructor; |
| } |
| } else { |
| // TODO(brianwilkerson) Report this error. |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| super.visitConstructorDeclaration(node); |
| ConstructorElement element = node.element; |
| if (element is ConstructorElementImpl) { |
| ConstructorName redirectedNode = node.redirectedConstructor; |
| if (redirectedNode != null) { |
| // set redirected factory constructor |
| ConstructorElement redirectedElement = redirectedNode.staticElement; |
| element.redirectedConstructor = redirectedElement; |
| } else { |
| // set redirected generative constructor |
| for (ConstructorInitializer initializer in node.initializers) { |
| if (initializer is RedirectingConstructorInvocation) { |
| ConstructorElement redirectedElement = initializer.staticElement; |
| element.redirectedConstructor = redirectedElement; |
| } |
| } |
| } |
| resolveMetadata(node); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| SimpleIdentifier fieldName = node.fieldName; |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| FieldElement fieldElement = enclosingClass.getField(fieldName.name); |
| fieldName.staticElement = fieldElement; |
| return null; |
| } |
| |
| @override |
| Object visitConstructorName(ConstructorName node) { |
| DartType type = node.type.type; |
| if (type != null && type.isDynamic) { |
| // Nothing to do. |
| } else if (type is InterfaceType) { |
| // look up ConstructorElement |
| ConstructorElement constructor; |
| SimpleIdentifier name = node.name; |
| if (name == null) { |
| constructor = type.lookUpConstructor(null, _definingLibrary); |
| } else { |
| constructor = type.lookUpConstructor(name.name, _definingLibrary); |
| name.staticElement = constructor; |
| } |
| node.staticElement = constructor; |
| } else { |
| // TODO(brianwilkerson) Report these errors. |
| // ASTNode parent = node.getParent(); |
| // if (parent instanceof InstanceCreationExpression) { |
| // if (((InstanceCreationExpression) parent).isConst()) { |
| // // CompileTimeErrorCode.CONST_WITH_NON_TYPE |
| // } else { |
| // // StaticWarningCode.NEW_WITH_NON_TYPE |
| // } |
| // } else { |
| // // This is part of a redirecting factory constructor; not sure which error code to use |
| // } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitContinueStatement(ContinueStatement node) { |
| node.target = _lookupBreakOrContinueTarget(node, node.label, true); |
| return null; |
| } |
| |
| @override |
| Object visitDeclaredIdentifier(DeclaredIdentifier node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitEnumDeclaration(EnumDeclaration node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitExportDirective(ExportDirective node) { |
| ExportElement exportElement = node.element; |
| if (exportElement != null) { |
| // The element is null when the URI is invalid |
| // TODO(brianwilkerson) Figure out whether the element can ever be |
| // something other than an ExportElement |
| _resolveCombinators(exportElement.exportedLibrary, node.combinators); |
| resolveMetadata(node); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitFieldFormalParameter(FieldFormalParameter node) { |
| _resolveMetadataForParameter(node); |
| return super.visitFieldFormalParameter(node); |
| } |
| |
| @override |
| Object visitFunctionDeclaration(FunctionDeclaration node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| Expression function = node.function; |
| DartType staticInvokeType = _instantiateGenericMethod( |
| function.staticType, node.typeArguments, node); |
| DartType propagatedInvokeType = _instantiateGenericMethod( |
| function.propagatedType, node.typeArguments, node); |
| |
| node.staticInvokeType = staticInvokeType; |
| node.propagatedInvokeType = |
| _propagatedInvokeTypeIfBetter(propagatedInvokeType, staticInvokeType); |
| |
| List<ParameterElement> parameters = |
| _computeCorrespondingParameters(node.argumentList, staticInvokeType); |
| if (parameters != null) { |
| node.argumentList.correspondingStaticParameters = parameters; |
| } |
| |
| parameters = _computeCorrespondingParameters( |
| node.argumentList, propagatedInvokeType); |
| if (parameters != null) { |
| node.argumentList.correspondingPropagatedParameters = parameters; |
| } |
| return null; |
| } |
| |
| @override |
| Object visitFunctionTypeAlias(FunctionTypeAlias node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| _resolveMetadataForParameter(node); |
| return null; |
| } |
| |
| @override |
| Object visitImportDirective(ImportDirective node) { |
| SimpleIdentifier prefixNode = node.prefix; |
| if (prefixNode != null) { |
| String prefixName = prefixNode.name; |
| List<PrefixElement> prefixes = _definingLibrary.prefixes; |
| int count = prefixes.length; |
| for (int i = 0; i < count; i++) { |
| PrefixElement prefixElement = prefixes[i]; |
| if (prefixElement.displayName == prefixName) { |
| prefixNode.staticElement = prefixElement; |
| break; |
| } |
| } |
| } |
| ImportElement importElement = node.element; |
| if (importElement != null) { |
| // The element is null when the URI is invalid |
| LibraryElement library = importElement.importedLibrary; |
| if (library != null) { |
| _resolveCombinators(library, node.combinators); |
| } |
| resolveMetadata(node); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitIndexExpression(IndexExpression node) { |
| Expression target = node.realTarget; |
| DartType staticType = _getStaticType(target); |
| DartType propagatedType = _getPropagatedType(target); |
| String getterMethodName = TokenType.INDEX.lexeme; |
| String setterMethodName = TokenType.INDEX_EQ.lexeme; |
| bool isInGetterContext = node.inGetterContext(); |
| bool isInSetterContext = node.inSetterContext(); |
| if (isInGetterContext && isInSetterContext) { |
| // lookup setter |
| MethodElement setterStaticMethod = |
| _lookUpMethod(target, staticType, setterMethodName); |
| MethodElement setterPropagatedMethod = |
| _lookUpMethod(target, propagatedType, setterMethodName); |
| // set setter element |
| node.staticElement = setterStaticMethod; |
| node.propagatedElement = setterPropagatedMethod; |
| // generate undefined method warning |
| _checkForUndefinedIndexOperator( |
| node, |
| target, |
| getterMethodName, |
| setterStaticMethod, |
| setterPropagatedMethod, |
| staticType, |
| propagatedType); |
| // lookup getter method |
| MethodElement getterStaticMethod = |
| _lookUpMethod(target, staticType, getterMethodName); |
| MethodElement getterPropagatedMethod = |
| _lookUpMethod(target, propagatedType, getterMethodName); |
| // set getter element |
| AuxiliaryElements auxiliaryElements = |
| new AuxiliaryElements(getterStaticMethod, getterPropagatedMethod); |
| node.auxiliaryElements = auxiliaryElements; |
| // generate undefined method warning |
| _checkForUndefinedIndexOperator( |
| node, |
| target, |
| getterMethodName, |
| getterStaticMethod, |
| getterPropagatedMethod, |
| staticType, |
| propagatedType); |
| } else if (isInGetterContext) { |
| // lookup getter method |
| MethodElement staticMethod = |
| _lookUpMethod(target, staticType, getterMethodName); |
| MethodElement propagatedMethod = |
| _lookUpMethod(target, propagatedType, getterMethodName); |
| // set getter element |
| node.staticElement = staticMethod; |
| node.propagatedElement = propagatedMethod; |
| // generate undefined method warning |
| _checkForUndefinedIndexOperator(node, target, getterMethodName, |
| staticMethod, propagatedMethod, staticType, propagatedType); |
| } else if (isInSetterContext) { |
| // lookup setter method |
| MethodElement staticMethod = |
| _lookUpMethod(target, staticType, setterMethodName); |
| MethodElement propagatedMethod = |
| _lookUpMethod(target, propagatedType, setterMethodName); |
| // set setter element |
| node.staticElement = staticMethod; |
| node.propagatedElement = propagatedMethod; |
| // generate undefined method warning |
| _checkForUndefinedIndexOperator(node, target, setterMethodName, |
| staticMethod, propagatedMethod, staticType, propagatedType); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitInstanceCreationExpression(InstanceCreationExpression node) { |
| ConstructorElement invokedConstructor = node.constructorName.staticElement; |
| node.staticElement = invokedConstructor; |
| ArgumentList argumentList = node.argumentList; |
| List<ParameterElement> parameters = _resolveArgumentsToFunction( |
| node.isConst, argumentList, invokedConstructor); |
| if (parameters != null) { |
| argumentList.correspondingStaticParameters = parameters; |
| } |
| return null; |
| } |
| |
| @override |
| Object visitLibraryDirective(LibraryDirective node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitMethodInvocation(MethodInvocation node) { |
| SimpleIdentifier methodName = node.methodName; |
| // |
| // Synthetic identifiers have been already reported during parsing. |
| // |
| if (methodName.isSynthetic) { |
| return null; |
| } |
| // |
| // We have a method invocation of one of two forms: 'e.m(a1, ..., an)' or |
| // 'm(a1, ..., an)'. The first step is to figure out which executable is |
| // being invoked, using both the static and the propagated type information. |
| // |
| Expression target = node.realTarget; |
| if (target is SuperExpression && !_isSuperInValidContext(target)) { |
| return null; |
| } |
| Element staticElement; |
| Element propagatedElement; |
| if (target == null) { |
| staticElement = _resolveInvokedElement(methodName); |
| propagatedElement = null; |
| } else if (methodName.name == FunctionElement.LOAD_LIBRARY_NAME && |
| _isDeferredPrefix(target)) { |
| if (node.operator.type == TokenType.QUESTION_PERIOD) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, |
| target, |
| [(target as SimpleIdentifier).name]); |
| } |
| LibraryElement importedLibrary = _getImportedLibrary(target); |
| FunctionElement loadLibraryFunction = importedLibrary.loadLibraryFunction; |
| methodName.staticElement = loadLibraryFunction; |
| node.staticInvokeType = loadLibraryFunction.type; |
| return null; |
| } else { |
| // |
| // If this method invocation is of the form 'C.m' where 'C' is a class, |
| // then we don't call resolveInvokedElement(...) which walks up the class |
| // hierarchy, instead we just look for the member in the type only. This |
| // does not apply to conditional method invocation (i.e. 'C?.m(...)'). |
| // |
| bool isConditional = node.operator.type == TokenType.QUESTION_PERIOD; |
| ClassElement typeReference = getTypeReference(target); |
| if (typeReference != null) { |
| if (node.isCascaded) { |
| typeReference = _typeType.element; |
| } |
| staticElement = _resolveElement(typeReference, methodName); |
| } else { |
| DartType staticType = _resolver.strongMode |
| ? _getStaticTypeOrFunctionType(target) |
| : _getStaticType(target); |
| DartType propagatedType = _getPropagatedType(target); |
| staticElement = _resolveInvokedElementWithTarget( |
| target, staticType, methodName, isConditional); |
| // If we have propagated type information use it (since it should |
| // not be redundant with the staticType). Otherwise, don't produce |
| // a propagatedElement which duplicates the staticElement. |
| if (propagatedType is InterfaceType) { |
| propagatedElement = _resolveInvokedElementWithTarget( |
| target, propagatedType, methodName, isConditional); |
| } |
| } |
| } |
| |
| staticElement = _convertSetterToGetter(staticElement); |
| propagatedElement = _convertSetterToGetter(propagatedElement); |
| |
| // |
| // Given the elements, determine the type of the function we are invoking |
| // |
| DartType staticInvokeType = _getInvokeType(staticElement); |
| methodName.staticType = staticInvokeType; |
| |
| DartType propagatedInvokeType = _getInvokeType(propagatedElement); |
| methodName.propagatedType = |
| _propagatedInvokeTypeIfBetter(propagatedInvokeType, staticInvokeType); |
| |
| // |
| // Instantiate generic function or method if needed. |
| // |
| staticInvokeType = _instantiateGenericMethod( |
| staticInvokeType, node.typeArguments, node.methodName); |
| propagatedInvokeType = _instantiateGenericMethod( |
| propagatedInvokeType, node.typeArguments, node.methodName); |
| |
| // |
| // Record the results. |
| // |
| methodName.staticElement = staticElement; |
| methodName.propagatedElement = propagatedElement; |
| |
| node.staticInvokeType = staticInvokeType; |
| // |
| // Store the propagated invoke type if it's more specific than the static |
| // type. |
| // |
| // We still need to record the propagated parameter elements however, |
| // as they are used in propagatedType downwards inference of lambda |
| // parameters. So we don't want to clear the propagatedInvokeType variable. |
| // |
| node.propagatedInvokeType = |
| _propagatedInvokeTypeIfBetter(propagatedInvokeType, staticInvokeType); |
| |
| ArgumentList argumentList = node.argumentList; |
| if (staticInvokeType != null) { |
| List<ParameterElement> parameters = |
| _computeCorrespondingParameters(argumentList, staticInvokeType); |
| argumentList.correspondingStaticParameters = parameters; |
| } |
| if (propagatedInvokeType != null) { |
| List<ParameterElement> parameters = |
| _computeCorrespondingParameters(argumentList, propagatedInvokeType); |
| argumentList.correspondingPropagatedParameters = parameters; |
| } |
| // |
| // Then check for error conditions. |
| // |
| ErrorCode errorCode = _checkForInvocationError(target, true, staticElement); |
| if (errorCode != null && |
| target is SimpleIdentifier && |
| target.staticElement is PrefixElement) { |
| Identifier functionName = |
| new PrefixedIdentifierImpl.temp(target, methodName); |
| if (_resolver.nameScope.shouldIgnoreUndefined(functionName)) { |
| return null; |
| } |
| } |
| bool generatedWithTypePropagation = false; |
| if (_enableHints && errorCode == null && staticElement == null) { |
| // The method lookup may have failed because there were multiple |
| // incompatible choices. In this case we don't want to generate a hint. |
| errorCode = _checkForInvocationError(target, false, propagatedElement); |
| if (identical(errorCode, StaticTypeWarningCode.UNDEFINED_METHOD)) { |
| ClassElement classElementContext = null; |
| if (target == null) { |
| classElementContext = _resolver.enclosingClass; |
| } else { |
| DartType type = _getBestType(target); |
| if (type != null) { |
| Element element = type.element; |
| if (element is ClassElement) { |
| classElementContext = element; |
| } |
| } |
| } |
| if (classElementContext != null) { |
| _subtypeManager.ensureLibraryVisited(_definingLibrary); |
| HashSet<ClassElement> subtypeElements = |
| _subtypeManager.computeAllSubtypes(classElementContext); |
| for (ClassElement subtypeElement in subtypeElements) { |
| if (subtypeElement.getMethod(methodName.name) != null) { |
| errorCode = null; |
| } |
| } |
| } |
| } |
| generatedWithTypePropagation = true; |
| } |
| if (errorCode == null) { |
| return null; |
| } |
| if (identical( |
| errorCode, StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION) || |
| identical(errorCode, |
| CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT) || |
| identical(errorCode, StaticTypeWarningCode.UNDEFINED_FUNCTION)) { |
| if (!_resolver.nameScope.shouldIgnoreUndefined(methodName)) { |
| _resolver.errorReporter |
| .reportErrorForNode(errorCode, methodName, [methodName.name]); |
| } |
| } else if (identical(errorCode, StaticTypeWarningCode.UNDEFINED_METHOD)) { |
| String targetTypeName; |
| if (target == null) { |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| targetTypeName = enclosingClass.displayName; |
| ErrorCode proxyErrorCode = (generatedWithTypePropagation |
| ? HintCode.UNDEFINED_METHOD |
| : StaticTypeWarningCode.UNDEFINED_METHOD); |
| _recordUndefinedNode(_resolver.enclosingClass, proxyErrorCode, |
| methodName, [methodName.name, targetTypeName]); |
| } else { |
| // ignore Function "call" |
| // (if we are about to create a hint using type propagation, |
| // then we can use type propagation here as well) |
| DartType targetType = null; |
| if (!generatedWithTypePropagation) { |
| targetType = _getStaticType(target); |
| } else { |
| // choose the best type |
| targetType = _getPropagatedType(target); |
| if (targetType == null) { |
| targetType = _getStaticType(target); |
| } |
| } |
| if (!_enableStrictCallChecks && |
| targetType != null && |
| targetType.isDartCoreFunction && |
| methodName.name == FunctionElement.CALL_METHOD_NAME) { |
| // TODO(brianwilkerson) Can we ever resolve the function being |
| // invoked? |
| // resolveArgumentsToParameters(node.getArgumentList(), invokedFunction); |
| return null; |
| } |
| if (!node.isCascaded) { |
| ClassElement typeReference = getTypeReference(target); |
| if (typeReference != null) { |
| ConstructorElement constructor = |
| typeReference.getNamedConstructor(methodName.name); |
| if (constructor != null) { |
| _recordUndefinedNode( |
| typeReference, |
| StaticTypeWarningCode.UNDEFINED_METHOD_WITH_CONSTRUCTOR, |
| methodName, |
| [methodName.name, typeReference.name]); |
| return null; |
| } |
| } |
| } |
| targetTypeName = targetType?.displayName; |
| ErrorCode proxyErrorCode = (generatedWithTypePropagation |
| ? HintCode.UNDEFINED_METHOD |
| : StaticTypeWarningCode.UNDEFINED_METHOD); |
| _recordUndefinedNode(targetType.element, proxyErrorCode, methodName, |
| [methodName.name, targetTypeName]); |
| } |
| } else if (identical( |
| errorCode, StaticTypeWarningCode.UNDEFINED_SUPER_METHOD)) { |
| // Generate the type name. |
| // The error code will never be generated via type propagation |
| DartType getSuperType(DartType type) { |
| if (type is InterfaceType && !type.isObject) { |
| return type.superclass; |
| } |
| return type; |
| } |
| |
| DartType targetType = getSuperType(_getStaticType(target)); |
| String targetTypeName = targetType?.name; |
| _resolver.errorReporter.reportErrorForNode( |
| StaticTypeWarningCode.UNDEFINED_SUPER_METHOD, |
| methodName, |
| [methodName.name, targetTypeName]); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitPartDirective(PartDirective node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitPostfixExpression(PostfixExpression node) { |
| Expression operand = node.operand; |
| String methodName = _getPostfixOperator(node); |
| DartType staticType = _getStaticType(operand); |
| MethodElement staticMethod = _lookUpMethod(operand, staticType, methodName); |
| node.staticElement = staticMethod; |
| DartType propagatedType = _getPropagatedType(operand); |
| MethodElement propagatedMethod = |
| _lookUpMethod(operand, propagatedType, methodName); |
| node.propagatedElement = propagatedMethod; |
| if (_shouldReportMissingMember(staticType, staticMethod)) { |
| if (operand is SuperExpression) { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_SUPER_OPERATOR, |
| node.operator, |
| [methodName, staticType.displayName]); |
| } else { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_OPERATOR, |
| node.operator, |
| [methodName, staticType.displayName]); |
| } |
| } else if (_enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedMethod) && |
| !_memberFoundInSubclass( |
| propagatedType.element, methodName, true, false)) { |
| _recordUndefinedToken(propagatedType.element, HintCode.UNDEFINED_OPERATOR, |
| node.operator, [methodName, propagatedType.displayName]); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitPrefixedIdentifier(PrefixedIdentifier node) { |
| SimpleIdentifier prefix = node.prefix; |
| SimpleIdentifier identifier = node.identifier; |
| // |
| // First, check the "lib.loadLibrary" case |
| // |
| if (identifier.name == FunctionElement.LOAD_LIBRARY_NAME && |
| _isDeferredPrefix(prefix)) { |
| LibraryElement importedLibrary = _getImportedLibrary(prefix); |
| identifier.staticElement = importedLibrary.loadLibraryFunction; |
| return null; |
| } |
| // |
| // Check to see whether the prefix is really a prefix. |
| // |
| Element prefixElement = prefix.staticElement; |
| if (prefixElement is PrefixElement) { |
| Element element = _resolver.nameScope.lookup(node, _definingLibrary); |
| if (element == null && identifier.inSetterContext()) { |
| Identifier setterName = new PrefixedIdentifierImpl.temp( |
| node.prefix, |
| new SimpleIdentifierImpl(new StringToken(TokenType.STRING, |
| "${node.identifier.name}=", node.identifier.offset - 1))); |
| element = _resolver.nameScope.lookup(setterName, _definingLibrary); |
| } |
| if (element == null && _resolver.nameScope.shouldIgnoreUndefined(node)) { |
| return null; |
| } |
| if (element == null) { |
| if (identifier.inSetterContext()) { |
| _resolver.errorReporter.reportErrorForNode( |
| StaticWarningCode.UNDEFINED_SETTER, |
| identifier, |
| [identifier.name, prefixElement.name]); |
| return null; |
| } |
| AstNode parent = node.parent; |
| if (parent is Annotation) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_ANNOTATION, parent); |
| } else { |
| _resolver.errorReporter.reportErrorForNode( |
| StaticWarningCode.UNDEFINED_GETTER, |
| identifier, |
| [identifier.name, prefixElement.name]); |
| } |
| return null; |
| } |
| Element accessor = element; |
| if (accessor is PropertyAccessorElement && identifier.inSetterContext()) { |
| PropertyInducingElement variable = accessor.variable; |
| if (variable != null) { |
| PropertyAccessorElement setter = variable.setter; |
| if (setter != null) { |
| element = setter; |
| } |
| } |
| } |
| // TODO(brianwilkerson) The prefix needs to be resolved to the element for |
| // the import that defines the prefix, not the prefix's element. |
| identifier.staticElement = element; |
| // Validate annotation element. |
| AstNode parent = node.parent; |
| if (parent is Annotation) { |
| _resolveAnnotationElement(parent); |
| } |
| return null; |
| } |
| // May be annotation, resolve invocation of "const" constructor. |
| AstNode parent = node.parent; |
| if (parent is Annotation) { |
| _resolveAnnotationElement(parent); |
| } |
| // |
| // Otherwise, the prefix is really an expression that happens to be a simple |
| // identifier and this is really equivalent to a property access node. |
| // |
| _resolvePropertyAccess(prefix, identifier, false); |
| return null; |
| } |
| |
| @override |
| Object visitPrefixExpression(PrefixExpression node) { |
| Token operator = node.operator; |
| TokenType operatorType = operator.type; |
| if (operatorType.isUserDefinableOperator || |
| operatorType == TokenType.PLUS_PLUS || |
| operatorType == TokenType.MINUS_MINUS) { |
| Expression operand = node.operand; |
| String methodName = _getPrefixOperator(node); |
| DartType staticType = _getStaticType(operand); |
| MethodElement staticMethod = |
| _lookUpMethod(operand, staticType, methodName); |
| node.staticElement = staticMethod; |
| DartType propagatedType = _getPropagatedType(operand); |
| MethodElement propagatedMethod = |
| _lookUpMethod(operand, propagatedType, methodName); |
| node.propagatedElement = propagatedMethod; |
| if (_shouldReportMissingMember(staticType, staticMethod)) { |
| if (operand is SuperExpression) { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_SUPER_OPERATOR, |
| operator, |
| [methodName, staticType.displayName]); |
| } else { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_OPERATOR, |
| operator, |
| [methodName, staticType.displayName]); |
| } |
| } else if (_enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedMethod) && |
| !_memberFoundInSubclass( |
| propagatedType.element, methodName, true, false)) { |
| _recordUndefinedToken( |
| propagatedType.element, |
| HintCode.UNDEFINED_OPERATOR, |
| operator, |
| [methodName, propagatedType.displayName]); |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitPropertyAccess(PropertyAccess node) { |
| Expression target = node.realTarget; |
| if (target is SuperExpression && !_isSuperInValidContext(target)) { |
| return null; |
| } |
| SimpleIdentifier propertyName = node.propertyName; |
| _resolvePropertyAccess(target, propertyName, node.isCascaded); |
| return null; |
| } |
| |
| @override |
| Object visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (enclosingClass == null) { |
| // TODO(brianwilkerson) Report this error. |
| return null; |
| } |
| SimpleIdentifier name = node.constructorName; |
| ConstructorElement element; |
| if (name == null) { |
| element = enclosingClass.unnamedConstructor; |
| } else { |
| element = enclosingClass.getNamedConstructor(name.name); |
| } |
| if (element == null) { |
| // TODO(brianwilkerson) Report this error and decide what element to |
| // associate with the node. |
| return null; |
| } |
| if (name != null) { |
| name.staticElement = element; |
| } |
| node.staticElement = element; |
| ArgumentList argumentList = node.argumentList; |
| List<ParameterElement> parameters = |
| _resolveArgumentsToFunction(false, argumentList, element); |
| if (parameters != null) { |
| argumentList.correspondingStaticParameters = parameters; |
| } |
| return null; |
| } |
| |
| @override |
| Object visitSimpleFormalParameter(SimpleFormalParameter node) { |
| _resolveMetadataForParameter(node); |
| return null; |
| } |
| |
| @override |
| Object visitSimpleIdentifier(SimpleIdentifier node) { |
| // |
| // Synthetic identifiers have been already reported during parsing. |
| // |
| if (node.isSynthetic) { |
| return null; |
| } |
| // |
| // Ignore nodes that should have been resolved before getting here. |
| // |
| if (node.inDeclarationContext()) { |
| return null; |
| } |
| if (node.staticElement is LocalVariableElement || |
| node.staticElement is ParameterElement) { |
| return null; |
| } |
| AstNode parent = node.parent; |
| if (parent is FieldFormalParameter) { |
| return null; |
| } else if (parent is ConstructorFieldInitializer && |
| parent.fieldName == node) { |
| return null; |
| } else if (parent is Annotation && parent.constructorName == node) { |
| return null; |
| } |
| // |
| // The name dynamic denotes a Type object even though dynamic is not a |
| // class. |
| // |
| if (node.name == _dynamicType.name) { |
| node.staticElement = _dynamicType.element; |
| node.staticType = _typeType; |
| return null; |
| } |
| // |
| // Otherwise, the node should be resolved. |
| // |
| Element element = _resolveSimpleIdentifier(node); |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (_isFactoryConstructorReturnType(node) && |
| !identical(element, enclosingClass)) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_FACTORY_NAME_NOT_A_CLASS, node); |
| } else if (_isConstructorReturnType(node) && |
| !identical(element, enclosingClass)) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_CONSTRUCTOR_NAME, node); |
| element = null; |
| } else if (element == null || |
| (element is PrefixElement && !_isValidAsPrefix(node))) { |
| // TODO(brianwilkerson) Recover from this error. |
| if (_isConstructorReturnType(node)) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_CONSTRUCTOR_NAME, node); |
| } else if (parent is Annotation) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_ANNOTATION, parent); |
| } else if (element != null) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, |
| node, |
| [element.name]); |
| } else if (node.name == "await" && _resolver.enclosingFunction != null) { |
| _recordUndefinedNode( |
| _resolver.enclosingClass, |
| StaticWarningCode.UNDEFINED_IDENTIFIER_AWAIT, |
| node, |
| [_resolver.enclosingFunction.displayName]); |
| } else if (!_resolver.nameScope.shouldIgnoreUndefined(node)) { |
| _recordUndefinedNode(_resolver.enclosingClass, |
| StaticWarningCode.UNDEFINED_IDENTIFIER, node, [node.name]); |
| } |
| } |
| node.staticElement = element; |
| if (node.inSetterContext() && |
| node.inGetterContext() && |
| enclosingClass != null) { |
| InterfaceType enclosingType = enclosingClass.type; |
| AuxiliaryElements auxiliaryElements = new AuxiliaryElements( |
| _lookUpGetter(null, enclosingType, node.name), null); |
| node.auxiliaryElements = auxiliaryElements; |
| } |
| // |
| // Validate annotation element. |
| // |
| if (parent is Annotation) { |
| _resolveAnnotationElement(parent); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| ClassElementImpl enclosingClass = |
| AbstractClassElementImpl.getImpl(_resolver.enclosingClass); |
| if (enclosingClass == null) { |
| // TODO(brianwilkerson) Report this error. |
| return null; |
| } |
| InterfaceType superType = enclosingClass.supertype; |
| if (superType == null) { |
| // TODO(brianwilkerson) Report this error. |
| return null; |
| } |
| SimpleIdentifier name = node.constructorName; |
| String superName = name?.name; |
| ConstructorElement element = |
| superType.lookUpConstructor(superName, _definingLibrary); |
| if (element == null || |
| (!enclosingClass.doesMixinLackConstructors && |
| !enclosingClass.isSuperConstructorAccessible(element))) { |
| if (name != null) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER, |
| node, |
| [superType.displayName, name]); |
| } else { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT, |
| node, |
| [superType.displayName]); |
| } |
| return null; |
| } else { |
| if (element.isFactory) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_GENERATIVE_CONSTRUCTOR, node, [element]); |
| } |
| } |
| if (name != null) { |
| name.staticElement = element; |
| } |
| node.staticElement = element; |
| // TODO(brianwilkerson) Defer this check until we know there's an error (by |
| // in-lining _resolveArgumentsToFunction below). |
| ClassDeclaration declaration = |
| node.getAncestor((AstNode node) => node is ClassDeclaration); |
| Identifier superclassName = declaration.extendsClause?.superclass?.name; |
| if (superclassName != null && |
| _resolver.nameScope.shouldIgnoreUndefined(superclassName)) { |
| return null; |
| } |
| ArgumentList argumentList = node.argumentList; |
| List<ParameterElement> parameters = _resolveArgumentsToFunction( |
| isInConstConstructor, argumentList, element); |
| if (parameters != null) { |
| argumentList.correspondingStaticParameters = parameters; |
| } |
| return null; |
| } |
| |
| @override |
| Object visitSuperExpression(SuperExpression node) { |
| if (!_isSuperInValidContext(node)) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT, node); |
| } |
| return super.visitSuperExpression(node); |
| } |
| |
| @override |
| Object visitTypeParameter(TypeParameter node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| @override |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| resolveMetadata(node); |
| return null; |
| } |
| |
| /** |
| * Given that we have found code to invoke the given [element], return the |
| * error code that should be reported, or `null` if no error should be |
| * reported. The [target] is the target of the invocation, or `null` if there |
| * was no target. The flag [useStaticContext] should be `true` if the |
| * invocation is in a static constant (does not have access to instance state). |
| */ |
| ErrorCode _checkForInvocationError( |
| Expression target, bool useStaticContext, Element element) { |
| // Prefix is not declared, instead "prefix.id" are declared. |
| if (element is PrefixElement) { |
| return CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT; |
| } else if (element is PropertyAccessorElement) { |
| // |
| // This is really a function expression invocation. |
| // |
| // TODO(brianwilkerson) Consider the possibility of re-writing the AST. |
| FunctionType getterType = element.type; |
| if (getterType != null) { |
| DartType returnType = getterType.returnType; |
| if (!_isExecutableType(returnType)) { |
| return StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION; |
| } |
| } |
| } else if (element is ExecutableElement) { |
| return null; |
| } else if (element is MultiplyDefinedElement) { |
| // The error has already been reported |
| return null; |
| } else if (element == null && target is SuperExpression) { |
| // TODO(jwren) We should split the UNDEFINED_METHOD into two error codes, |
| // this one, and a code that describes the situation where the method was |
| // found, but it was not accessible from the current library. |
| return StaticTypeWarningCode.UNDEFINED_SUPER_METHOD; |
| } else { |
| // |
| // This is really a function expression invocation. |
| // |
| // TODO(brianwilkerson) Consider the possibility of re-writing the AST. |
| if (element is PropertyInducingElement) { |
| PropertyAccessorElement getter = element.getter; |
| FunctionType getterType = getter.type; |
| if (getterType != null) { |
| DartType returnType = getterType.returnType; |
| if (!_isExecutableType(returnType)) { |
| return StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION; |
| } |
| } |
| } else if (element is VariableElement) { |
| DartType variableType = element.type; |
| if (!_isExecutableType(variableType)) { |
| return StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION; |
| } |
| } else { |
| if (target == null) { |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (enclosingClass == null) { |
| return StaticTypeWarningCode.UNDEFINED_FUNCTION; |
| } else if (element == null) { |
| // Proxy-conditional warning, based on state of |
| // resolver.getEnclosingClass() |
| return StaticTypeWarningCode.UNDEFINED_METHOD; |
| } else { |
| return StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION; |
| } |
| } else { |
| DartType targetType; |
| if (useStaticContext) { |
| targetType = _getStaticType(target); |
| } else { |
| // Compute and use the propagated type, if it is null, then it may |
| // be the case that static type is some type, in which the static |
| // type should be used. |
| targetType = _getBestType(target); |
| } |
| if (targetType == null) { |
| if (target is Identifier && |
| _resolver.nameScope.shouldIgnoreUndefined(target)) { |
| return null; |
| } |
| return StaticTypeWarningCode.UNDEFINED_FUNCTION; |
| } else if (!targetType.isDynamic && target is! NullLiteral) { |
| // Proxy-conditional warning, based on state of |
| // targetType.getElement() |
| return StaticTypeWarningCode.UNDEFINED_METHOD; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Check that the given index [expression] was resolved, otherwise a |
| * [StaticTypeWarningCode.UNDEFINED_OPERATOR] is generated. The [target] is |
| * the target of the expression. The [methodName] is the name of the operator |
| * associated with the context of using of the given index expression. |
| */ |
| bool _checkForUndefinedIndexOperator( |
| IndexExpression expression, |
| Expression target, |
| String methodName, |
| MethodElement staticMethod, |
| MethodElement propagatedMethod, |
| DartType staticType, |
| DartType propagatedType) { |
| bool shouldReportMissingMember_static = |
| _shouldReportMissingMember(staticType, staticMethod); |
| bool shouldReportMissingMember_propagated = |
| !shouldReportMissingMember_static && |
| _enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedMethod) && |
| !_memberFoundInSubclass( |
| propagatedType.element, methodName, true, false); |
| if (shouldReportMissingMember_static || |
| shouldReportMissingMember_propagated) { |
| Token leftBracket = expression.leftBracket; |
| Token rightBracket = expression.rightBracket; |
| ErrorCode errorCode; |
| if (shouldReportMissingMember_static) { |
| if (target is SuperExpression) { |
| errorCode = StaticTypeWarningCode.UNDEFINED_SUPER_OPERATOR; |
| } else { |
| errorCode = StaticTypeWarningCode.UNDEFINED_OPERATOR; |
| } |
| } else { |
| errorCode = HintCode.UNDEFINED_OPERATOR; |
| } |
| DartType type = |
| shouldReportMissingMember_static ? staticType : propagatedType; |
| if (leftBracket == null || rightBracket == null) { |
| _recordUndefinedNode(type.element, errorCode, expression, |
| [methodName, type.displayName]); |
| } else { |
| int offset = leftBracket.offset; |
| int length = rightBracket.offset - offset + 1; |
| _recordUndefinedOffset(type.element, errorCode, offset, length, |
| [methodName, type.displayName]); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Given an [argumentList] and the executable [element] that will be invoked |
| * using those arguments, compute the list of parameters that correspond to |
| * the list of arguments. Return the parameters that correspond to the |
| * arguments, or `null` if no correspondence could be computed. |
| */ |
| List<ParameterElement> _computeCorrespondingParameters( |
| ArgumentList argumentList, DartType type) { |
| if (type is InterfaceType) { |
| MethodElement callMethod = |
| type.lookUpMethod(FunctionElement.CALL_METHOD_NAME, _definingLibrary); |
| if (callMethod != null) { |
| return _resolveArgumentsToFunction(false, argumentList, callMethod); |
| } |
| } else if (type is FunctionType) { |
| return _resolveArgumentsToParameters( |
| false, argumentList, type.parameters); |
| } |
| return null; |
| } |
| |
| /** |
| * If the given [element] is a setter, return the getter associated with it. |
| * Otherwise, return the element unchanged. |
| */ |
| Element _convertSetterToGetter(Element element) { |
| // TODO(brianwilkerson) Determine whether and why the element could ever be |
| // a setter. |
| if (element is PropertyAccessorElement) { |
| return element.variable.getter; |
| } |
| return element; |
| } |
| |
| /** |
| * Return `true` if the given [element] is not a proxy. See |
| * [ClassElement.isOrInheritsProxy]. |
| */ |
| bool _doesntHaveProxy(Element element) => |
| !(element is ClassElement && element.isOrInheritsProxy); |
| |
| /** |
| * Look for any declarations of the given [identifier] that are imported using |
| * a prefix. Return the element that was found, or `null` if the name is not |
| * imported using a prefix. |
| */ |
| Element _findImportWithoutPrefix(SimpleIdentifier identifier) { |
| Element element = null; |
| Scope nameScope = _resolver.nameScope; |
| List<ImportElement> imports = _definingLibrary.imports; |
| int length = imports.length; |
| for (int i = 0; i < length; i++) { |
| ImportElement importElement = imports[i]; |
| PrefixElement prefixElement = importElement.prefix; |
| if (prefixElement != null) { |
| Identifier prefixedIdentifier = new PrefixedIdentifierImpl.temp( |
| new SimpleIdentifierImpl(new StringToken(TokenType.STRING, |
| prefixElement.name, prefixElement.nameOffset)), |
| identifier); |
| Element importedElement = |
| nameScope.lookup(prefixedIdentifier, _definingLibrary); |
| if (importedElement != null) { |
| if (element == null) { |
| element = importedElement; |
| } else { |
| element = MultiplyDefinedElementImpl.fromElements( |
| _definingLibrary.context, element, importedElement); |
| } |
| } |
| } |
| } |
| return element; |
| } |
| |
| /** |
| * Return the best type of the given [expression] that is to be used for |
| * type analysis. |
| */ |
| DartType _getBestType(Expression expression) { |
| DartType bestType = _resolveTypeParameter(expression.bestType); |
| if (bestType is FunctionType) { |
| // |
| // All function types are subtypes of 'Function', which is itself a |
| // subclass of 'Object'. |
| // |
| bestType = _resolver.typeProvider.functionType; |
| } |
| return bestType; |
| } |
| |
| /** |
| * Assuming that the given [identifier] is a prefix for a deferred import, |
| * return the library that is being imported. |
| */ |
| LibraryElement _getImportedLibrary(SimpleIdentifier identifier) { |
| PrefixElement prefixElement = identifier.staticElement as PrefixElement; |
| List<ImportElement> imports = |
| prefixElement.enclosingElement.getImportsWithPrefix(prefixElement); |
| return imports[0].importedLibrary; |
| } |
| |
| /** |
| * Given an element, computes the type of the invocation. |
| * |
| * For executable elements (like methods, functions) this is just their type. |
| * |
| * For variables it is their type taking into account any type promotion. |
| * |
| * For calls to getters in Dart, we invoke the function that is returned by |
| * the getter, so the invoke type is the getter's returnType. |
| */ |
| DartType _getInvokeType(Element element) { |
| DartType invokeType; |
| if (element is PropertyAccessorElement) { |
| invokeType = element.returnType; |
| } else if (element is ExecutableElement) { |
| invokeType = element.type; |
| } else if (element is VariableElement) { |
| invokeType = _promoteManager.getStaticType(element); |
| } |
| return invokeType ?? DynamicTypeImpl.instance; |
| } |
| |
| /** |
| * Return the name of the method invoked by the given postfix [expression]. |
| */ |
| String _getPostfixOperator(PostfixExpression expression) => |
| (expression.operator.type == TokenType.PLUS_PLUS) |
| ? TokenType.PLUS.lexeme |
| : TokenType.MINUS.lexeme; |
| |
| /** |
| * Return the name of the method invoked by the given postfix [expression]. |
| */ |
| String _getPrefixOperator(PrefixExpression expression) { |
| Token operator = expression.operator; |
| TokenType operatorType = operator.type; |
| if (operatorType == TokenType.PLUS_PLUS) { |
| return TokenType.PLUS.lexeme; |
| } else if (operatorType == TokenType.MINUS_MINUS) { |
| return TokenType.MINUS.lexeme; |
| } else if (operatorType == TokenType.MINUS) { |
| return "unary-"; |
| } else { |
| return operator.lexeme; |
| } |
| } |
| |
| /** |
| * Return the propagated type of the given [expression] that is to be used for |
| * type analysis. |
| */ |
| DartType _getPropagatedType(Expression expression) { |
| DartType propagatedType = _resolveTypeParameter(expression.propagatedType); |
| if (propagatedType is FunctionType) { |
| // |
| // All function types are subtypes of 'Function', which is itself a |
| // subclass of 'Object'. |
| // |
| propagatedType = _resolver.typeProvider.functionType; |
| } |
| return propagatedType; |
| } |
| |
| /** |
| * Return the static type of the given [expression] that is to be used for |
| * type analysis. |
| */ |
| DartType _getStaticType(Expression expression) { |
| DartType staticType = _getStaticTypeOrFunctionType(expression); |
| if (staticType is FunctionType) { |
| // |
| // All function types are subtypes of 'Function', which is itself a |
| // subclass of 'Object'. |
| // |
| staticType = _resolver.typeProvider.functionType; |
| } |
| return staticType; |
| } |
| |
| DartType _getStaticTypeOrFunctionType(Expression expression) { |
| if (expression is NullLiteral) { |
| return _resolver.typeProvider.nullType; |
| } |
| return _resolveTypeParameter(expression.staticType); |
| } |
| |
| /** |
| * Check for a generic method & apply type arguments if any were passed. |
| */ |
| DartType _instantiateGenericMethod( |
| DartType invokeType, TypeArgumentList typeArguments, AstNode node) { |
| // TODO(jmesserly): support generic "call" methods on InterfaceType. |
| if (invokeType is FunctionType) { |
| List<TypeParameterElement> parameters = invokeType.typeFormals; |
| |
| NodeList<TypeAnnotation> arguments = typeArguments?.arguments; |
| if (arguments != null && arguments.length != parameters.length) { |
| if (_resolver.strongMode) { |
| _resolver.errorReporter.reportErrorForNode( |
| StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_METHOD, |
| node, |
| [invokeType, parameters.length, arguments?.length ?? 0]); |
| } else { |
| _resolver.errorReporter.reportErrorForNode( |
| HintCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_METHOD, |
| node, |
| [invokeType, parameters.length, arguments?.length ?? 0]); |
| } |
| // Wrong number of type arguments. Ignore them. |
| arguments = null; |
| } |
| if (parameters.isNotEmpty) { |
| if (arguments == null) { |
| return _resolver.typeSystem.instantiateToBounds(invokeType); |
| } else { |
| return invokeType.instantiate(arguments.map((n) => n.type).toList()); |
| } |
| } |
| } |
| return invokeType; |
| } |
| |
| /** |
| * Return `true` if the given [expression] is a prefix for a deferred import. |
| */ |
| bool _isDeferredPrefix(Expression expression) { |
| if (expression is SimpleIdentifier) { |
| Element element = expression.staticElement; |
| if (element is PrefixElement) { |
| List<ImportElement> imports = |
| element.enclosingElement.getImportsWithPrefix(element); |
| if (imports.length != 1) { |
| return false; |
| } |
| return imports[0].isDeferred; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given [type] represents an object that could be |
| * invoked using the call operator '()'. |
| */ |
| bool _isExecutableType(DartType type) { |
| type = type?.resolveToBound(_resolver.typeProvider.objectType); |
| if (type.isDynamic || type is FunctionType) { |
| return true; |
| } else if (!_enableStrictCallChecks && |
| (type.isDartCoreFunction || type.isObject)) { |
| return true; |
| } else if (type is InterfaceType) { |
| ClassElement classElement = type.element; |
| // 16078 from Gilad: If the type is a Functor with the @proxy annotation, |
| // treat it as an executable type. |
| // example code: NonErrorResolverTest. |
| // test_invocationOfNonFunction_proxyOnFunctionClass() |
| if (classElement.isProxy && |
| type.isSubtypeOf(_resolver.typeProvider.functionType)) { |
| return true; |
| } |
| MethodElement methodElement = classElement.lookUpMethod( |
| FunctionElement.CALL_METHOD_NAME, _definingLibrary); |
| return methodElement != null; |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given [element] is a static element. |
| */ |
| bool _isStatic(Element element) { |
| if (element is ExecutableElement) { |
| return element.isStatic; |
| } else if (element is PropertyInducingElement) { |
| return element.isStatic; |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given [node] can validly be resolved to a prefix: |
| * * it is the prefix in an import directive, or |
| * * it is the prefix in a prefixed identifier. |
| */ |
| bool _isValidAsPrefix(SimpleIdentifier node) { |
| AstNode parent = node.parent; |
| if (parent is ImportDirective) { |
| return identical(parent.prefix, node); |
| } else if (parent is PrefixedIdentifier) { |
| return true; |
| } else if (parent is MethodInvocation) { |
| return identical(parent.target, node); |
| } |
| return false; |
| } |
| |
| /** |
| * Return the target of a break or continue statement, and update the static |
| * element of its label (if any). The [parentNode] is the AST node of the |
| * break or continue statement. The [labelNode] is the label contained in that |
| * statement (if any). The flag [isContinue] is `true` if the node being |
| * visited is a continue statement. |
| */ |
| AstNode _lookupBreakOrContinueTarget( |
| AstNode parentNode, SimpleIdentifier labelNode, bool isContinue) { |
| if (labelNode == null) { |
| return _resolver.implicitLabelScope.getTarget(isContinue); |
| } else { |
| LabelScope labelScope = _resolver.labelScope; |
| if (labelScope == null) { |
| // There are no labels in scope, so by definition the label is |
| // undefined. |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.LABEL_UNDEFINED, labelNode, [labelNode.name]); |
| return null; |
| } |
| LabelScope definingScope = labelScope.lookup(labelNode.name); |
| if (definingScope == null) { |
| // No definition of the given label name could be found in any |
| // enclosing scope. |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.LABEL_UNDEFINED, labelNode, [labelNode.name]); |
| return null; |
| } |
| // The target has been found. |
| labelNode.staticElement = definingScope.element; |
| ExecutableElement labelContainer = definingScope.element |
| .getAncestor((element) => element is ExecutableElement); |
| if (!identical(labelContainer, _resolver.enclosingFunction)) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.LABEL_IN_OUTER_SCOPE, |
| labelNode, |
| [labelNode.name]); |
| } |
| return definingScope.node; |
| } |
| } |
| |
| /** |
| * Look up the getter with the given [getterName] in the given [type]. Return |
| * the element representing the getter that was found, or `null` if there is |
| * no getter with the given name. The [target] is the target of the |
| * invocation, or `null` if there is no target. |
| */ |
| PropertyAccessorElement _lookUpGetter( |
| Expression target, DartType type, String getterName) { |
| type = _resolveTypeParameter(type); |
| if (type is InterfaceType) { |
| return type.lookUpInheritedGetter(getterName, |
| library: _definingLibrary, thisType: target is! SuperExpression); |
| } |
| return null; |
| } |
| |
| /** |
| * Look up the method or getter with the given [memberName] in the given |
| * [type]. Return the element representing the method or getter that was |
| * found, or `null` if there is no method or getter with the given name. |
| */ |
| ExecutableElement _lookupGetterOrMethod(DartType type, String memberName) { |
| type = _resolveTypeParameter(type); |
| if (type is InterfaceType) { |
| return type.lookUpInheritedGetterOrMethod(memberName, |
| library: _definingLibrary); |
| } |
| return null; |
| } |
| |
| /** |
| * Look up the method with the given [methodName] in the given [type]. Return |
| * the element representing the method that was found, or `null` if there is |
| * no method with the given name. The [target] is the target of the |
| * invocation, or `null` if there is no target. |
| */ |
| MethodElement _lookUpMethod( |
| Expression target, DartType type, String methodName) { |
| type = _resolveTypeParameter(type); |
| if (type is InterfaceType) { |
| return type.lookUpInheritedMethod(methodName, |
| library: _definingLibrary, thisType: target is! SuperExpression); |
| } |
| return null; |
| } |
| |
| /** |
| * Look up the setter with the given [setterName] in the given [type]. Return |
| * the element representing the setter that was found, or `null` if there is |
| * no setter with the given name. The [target] is the target of the |
| * invocation, or `null` if there is no target. |
| */ |
| PropertyAccessorElement _lookUpSetter( |
| Expression target, DartType type, String setterName) { |
| type = _resolveTypeParameter(type); |
| if (type is InterfaceType) { |
| return type.lookUpInheritedSetter(setterName, |
| library: _definingLibrary, thisType: target is! SuperExpression); |
| } |
| return null; |
| } |
| |
| /** |
| * Given some class [element], this method uses [_subtypeManager] to find the |
| * set of all subtypes; the subtypes are then searched for a member (method, |
| * getter, or setter), that has the given [memberName]. The flag [asMethod] |
| * should be `true` if the methods should be searched for in the subtypes. The |
| * flag [asAccessor] should be `true` if the accessors (getters and setters) |
| * should be searched for in the subtypes. |
| */ |
| bool _memberFoundInSubclass( |
| Element element, String memberName, bool asMethod, bool asAccessor) { |
| if (element is ClassElement) { |
| _subtypeManager.ensureLibraryVisited(_definingLibrary); |
| HashSet<ClassElement> subtypeElements = |
| _subtypeManager.computeAllSubtypes(element); |
| for (ClassElement subtypeElement in subtypeElements) { |
| if (asMethod && subtypeElement.getMethod(memberName) != null) { |
| return true; |
| } else if (asAccessor && |
| (subtypeElement.getGetter(memberName) != null || |
| subtypeElement.getSetter(memberName) != null)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return the binary operator that is invoked by the given compound assignment |
| * [operator]. |
| */ |
| TokenType _operatorFromCompoundAssignment(TokenType operator) { |
| if (operator == TokenType.AMPERSAND_EQ) { |
| return TokenType.AMPERSAND; |
| } else if (operator == TokenType.BAR_EQ) { |
| return TokenType.BAR; |
| } else if (operator == TokenType.CARET_EQ) { |
| return TokenType.CARET; |
| } else if (operator == TokenType.GT_GT_EQ) { |
| return TokenType.GT_GT; |
| } else if (operator == TokenType.LT_LT_EQ) { |
| return TokenType.LT_LT; |
| } else if (operator == TokenType.MINUS_EQ) { |
| return TokenType.MINUS; |
| } else if (operator == TokenType.PERCENT_EQ) { |
| return TokenType.PERCENT; |
| } else if (operator == TokenType.PLUS_EQ) { |
| return TokenType.PLUS; |
| } else if (operator == TokenType.SLASH_EQ) { |
| return TokenType.SLASH; |
| } else if (operator == TokenType.STAR_EQ) { |
| return TokenType.STAR; |
| } else if (operator == TokenType.TILDE_SLASH_EQ) { |
| return TokenType.TILDE_SLASH; |
| } else { |
| // Internal error: Unmapped assignment operator. |
| AnalysisEngine.instance.logger.logError( |
| "Failed to map ${operator.lexeme} to it's corresponding operator"); |
| return operator; |
| } |
| } |
| |
| /** |
| * Determines if the [propagatedType] of the invoke is better (more specific) |
| * than the [staticType]. If so it will be returned, otherwise returns null. |
| */ |
| // TODO(jmesserly): can we refactor Resolver.recordPropagatedTypeIfBetter to |
| // get some code sharing? Right now, this method is to support |
| // `staticInvokeType` and `propagatedInvokeType`, and the one in Resolver is |
| // for `staticType` and `propagatedType` on Expression. |
| DartType _propagatedInvokeTypeIfBetter( |
| DartType propagatedType, DartType staticType) { |
| if (_resolver.strongMode || propagatedType == null) { |
| return null; |
| } |
| if (staticType == null || propagatedType.isMoreSpecificThan(staticType)) { |
| return propagatedType; |
| } |
| return null; |
| } |
| |
| /** |
| * Record that the given [node] is undefined, causing an error to be reported |
| * if appropriate. The [declaringElement] is the element inside which no |
| * declaration was found. If this element is a proxy, no error will be |
| * reported. If null, then an error will always be reported. The [errorCode] |
| * is the error code to report. The [arguments] are the arguments to the error |
| * message. |
| */ |
| void _recordUndefinedNode(Element declaringElement, ErrorCode errorCode, |
| AstNode node, List<Object> arguments) { |
| if (_doesntHaveProxy(declaringElement)) { |
| _resolver.errorReporter.reportErrorForNode(errorCode, node, arguments); |
| } |
| } |
| |
| /** |
| * Record that the given [offset]/[length] is undefined, causing an error to |
| * be reported if appropriate. The [declaringElement] is the element inside |
| * which no declaration was found. If this element is a proxy, no error will |
| * be reported. If null, then an error will always be reported. The |
| * [errorCode] is the error code to report. The [arguments] are arguments to |
| * the error message. |
| */ |
| void _recordUndefinedOffset(Element declaringElement, ErrorCode errorCode, |
| int offset, int length, List<Object> arguments) { |
| if (_doesntHaveProxy(declaringElement)) { |
| _resolver.errorReporter |
| .reportErrorForOffset(errorCode, offset, length, arguments); |
| } |
| } |
| |
| /** |
| * Record that the given [token] is undefined, causing an error to be reported |
| * if appropriate. The [declaringElement] is the element inside which no |
| * declaration was found. If this element is a proxy, no error will be |
| * reported. If null, then an error will always be reported. The [errorCode] |
| * is the error code to report. The [arguments] are arguments to the error |
| * message. |
| */ |
| void _recordUndefinedToken(Element declaringElement, ErrorCode errorCode, |
| Token token, List<Object> arguments) { |
| if (_doesntHaveProxy(declaringElement)) { |
| _resolver.errorReporter.reportErrorForToken(errorCode, token, arguments); |
| } |
| } |
| |
| void _resolveAnnotationConstructorInvocationArguments( |
| Annotation annotation, ConstructorElement constructor) { |
| ArgumentList argumentList = annotation.arguments; |
| // error will be reported in ConstantVerifier |
| if (argumentList == null) { |
| return; |
| } |
| // resolve arguments to parameters |
| List<ParameterElement> parameters = |
| _resolveArgumentsToFunction(true, argumentList, constructor); |
| if (parameters != null) { |
| argumentList.correspondingStaticParameters = parameters; |
| } |
| } |
| |
| /** |
| * Continues resolution of the given [annotation]. |
| */ |
| void _resolveAnnotationElement(Annotation annotation) { |
| SimpleIdentifier nameNode1; |
| SimpleIdentifier nameNode2; |
| { |
| Identifier annName = annotation.name; |
| if (annName is PrefixedIdentifier) { |
| nameNode1 = annName.prefix; |
| nameNode2 = annName.identifier; |
| } else { |
| nameNode1 = annName as SimpleIdentifier; |
| nameNode2 = null; |
| } |
| } |
| SimpleIdentifier nameNode3 = annotation.constructorName; |
| ConstructorElement constructor = null; |
| // |
| // CONST or Class(args) |
| // |
| if (nameNode1 != null && nameNode2 == null && nameNode3 == null) { |
| Element element1 = nameNode1.staticElement; |
| // CONST |
| if (element1 is PropertyAccessorElement) { |
| _resolveAnnotationElementGetter(annotation, element1); |
| return; |
| } |
| // Class(args) |
| if (element1 is ClassElement) { |
| constructor = new InterfaceTypeImpl(element1) |
| .lookUpConstructor(null, _definingLibrary); |
| } |
| } |
| // |
| // prefix.CONST or prefix.Class() or Class.CONST or Class.constructor(args) |
| // |
| if (nameNode1 != null && nameNode2 != null && nameNode3 == null) { |
| Element element1 = nameNode1.staticElement; |
| Element element2 = nameNode2.staticElement; |
| // Class.CONST - not resolved yet |
| if (element1 is ClassElement) { |
| element2 = element1.lookUpGetter(nameNode2.name, _definingLibrary); |
| } |
| // prefix.CONST or Class.CONST |
| if (element2 is PropertyAccessorElement) { |
| nameNode2.staticElement = element2; |
| annotation.element = element2; |
| _resolveAnnotationElementGetter(annotation, element2); |
| return; |
| } |
| // prefix.Class() |
| if (element2 is ClassElement) { |
| constructor = element2.unnamedConstructor; |
| } |
| // Class.constructor(args) |
| if (element1 is ClassElement) { |
| constructor = new InterfaceTypeImpl(element1) |
| .lookUpConstructor(nameNode2.name, _definingLibrary); |
| nameNode2.staticElement = constructor; |
| } |
| } |
| // |
| // prefix.Class.CONST or prefix.Class.constructor(args) |
| // |
| if (nameNode1 != null && nameNode2 != null && nameNode3 != null) { |
| Element element2 = nameNode2.staticElement; |
| // element2 should be ClassElement |
| if (element2 is ClassElement) { |
| String name3 = nameNode3.name; |
| // prefix.Class.CONST |
| PropertyAccessorElement getter = |
| element2.lookUpGetter(name3, _definingLibrary); |
| if (getter != null) { |
| nameNode3.staticElement = getter; |
| annotation.element = getter; |
| _resolveAnnotationElementGetter(annotation, getter); |
| return; |
| } |
| // prefix.Class.constructor(args) |
| constructor = new InterfaceTypeImpl(element2) |
| .lookUpConstructor(name3, _definingLibrary); |
| nameNode3.staticElement = constructor; |
| } |
| } |
| // we need constructor |
| if (constructor == null) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_ANNOTATION, annotation); |
| return; |
| } |
| // record element |
| annotation.element = constructor; |
| // resolve arguments |
| _resolveAnnotationConstructorInvocationArguments(annotation, constructor); |
| } |
| |
| void _resolveAnnotationElementGetter( |
| Annotation annotation, PropertyAccessorElement accessorElement) { |
| // accessor should be synthetic |
| if (!accessorElement.isSynthetic) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_ANNOTATION, annotation); |
| return; |
| } |
| // variable should be constant |
| VariableElement variableElement = accessorElement.variable; |
| if (!variableElement.isConst) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_ANNOTATION, annotation); |
| } |
| // no arguments |
| if (annotation.arguments != null) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.ANNOTATION_WITH_NON_CLASS, |
| annotation.name, |
| [annotation.name]); |
| } |
| // OK |
| return; |
| } |
| |
| /** |
| * Given an [argumentList] and the [executableElement] that will be invoked |
| * using those argument, compute the list of parameters that correspond to the |
| * list of arguments. An error will be reported if any of the arguments cannot |
| * be matched to a parameter. The flag [reportAsError] should be `true` if a |
| * compile-time error should be reported; or `false` if a compile-time warning |
| * should be reported. Return the parameters that correspond to the arguments, |
| * or `null` if no correspondence could be computed. |
| */ |
| List<ParameterElement> _resolveArgumentsToFunction(bool reportAsError, |
| ArgumentList argumentList, ExecutableElement executableElement) { |
| if (executableElement == null) { |
| return null; |
| } |
| List<ParameterElement> parameters = executableElement.parameters; |
| return _resolveArgumentsToParameters( |
| reportAsError, argumentList, parameters); |
| } |
| |
| /** |
| * Given an [argumentList] and the [parameters] related to the element that |
| * will be invoked using those arguments, compute the list of parameters that |
| * correspond to the list of arguments. An error will be reported if any of |
| * the arguments cannot be matched to a parameter. The flag [reportAsError] |
| * should be `true` if a compile-time error should be reported; or `false` if |
| * a compile-time warning should be reported. Return the parameters that |
| * correspond to the arguments. |
| */ |
| List<ParameterElement> _resolveArgumentsToParameters(bool reportAsError, |
| ArgumentList argumentList, List<ParameterElement> parameters) { |
| return ResolverVisitor.resolveArgumentsToParameters( |
| argumentList, parameters, _resolver.errorReporter.reportErrorForNode, |
| reportAsError: reportAsError); |
| } |
| |
| void _resolveBinaryExpression(BinaryExpression node, String methodName) { |
| Expression leftOperand = node.leftOperand; |
| if (leftOperand != null) { |
| DartType staticType = _getStaticType(leftOperand); |
| MethodElement staticMethod = |
| _lookUpMethod(leftOperand, staticType, methodName); |
| node.staticElement = staticMethod; |
| DartType propagatedType = _getPropagatedType(leftOperand); |
| MethodElement propagatedMethod = |
| _lookUpMethod(leftOperand, propagatedType, methodName); |
| node.propagatedElement = propagatedMethod; |
| if (_shouldReportMissingMember(staticType, staticMethod)) { |
| if (leftOperand is SuperExpression) { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_SUPER_OPERATOR, |
| node.operator, |
| [methodName, staticType.displayName]); |
| } else { |
| _recordUndefinedToken( |
| staticType.element, |
| StaticTypeWarningCode.UNDEFINED_OPERATOR, |
| node.operator, |
| [methodName, staticType.displayName]); |
| } |
| } else if (_enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedMethod) && |
| !_memberFoundInSubclass( |
| propagatedType.element, methodName, true, false)) { |
| _recordUndefinedToken( |
| propagatedType.element, |
| HintCode.UNDEFINED_OPERATOR, |
| node.operator, |
| [methodName, propagatedType.displayName]); |
| } |
| } |
| } |
| |
| /** |
| * Resolve the names in the given [combinators] in the scope of the given |
| * [library]. |
| */ |
| void _resolveCombinators( |
| LibraryElement library, NodeList<Combinator> combinators) { |
| if (library == null) { |
| // |
| // The library will be null if the directive containing the combinators |
| // has a URI that is not valid. |
| // |
| return; |
| } |
| Namespace namespace = |
| new NamespaceBuilder().createExportNamespaceForLibrary(library); |
| for (Combinator combinator in combinators) { |
| NodeList<SimpleIdentifier> names; |
| if (combinator is HideCombinator) { |
| names = combinator.hiddenNames; |
| } else { |
| names = (combinator as ShowCombinator).shownNames; |
| } |
| for (SimpleIdentifier name in names) { |
| String nameStr = name.name; |
| Element element = namespace.get(nameStr) ?? namespace.get("$nameStr="); |
| if (element != null) { |
| // Ensure that the name always resolves to a top-level variable |
| // rather than a getter or setter |
| if (element is PropertyAccessorElement) { |
| name.staticElement = element.variable; |
| } else { |
| name.staticElement = element; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Given that we are accessing a property of the given [classElement] with the |
| * given [propertyName], return the element that represents the property. |
| */ |
| Element _resolveElement( |
| ClassElement classElement, SimpleIdentifier propertyName) { |
| String name = propertyName.name; |
| Element element = null; |
| if (propertyName.inSetterContext()) { |
| element = classElement.getSetter(name); |
| } |
| if (element == null) { |
| element = classElement.getGetter(name); |
| } |
| if (element == null) { |
| element = classElement.getMethod(name); |
| } |
| if (element != null && element.isAccessibleIn(_definingLibrary)) { |
| return element; |
| } |
| return null; |
| } |
| |
| /** |
| * Given an invocation of the form 'm(a1, ..., an)', resolve 'm' to the |
| * element being invoked. If the returned element is a method, then the method |
| * will be invoked. If the returned element is a getter, the getter will be |
| * invoked without arguments and the result of that invocation will then be |
| * invoked with the arguments. The [methodName] is the name of the method |
| * being invoked ('m'). |
| */ |
| Element _resolveInvokedElement(SimpleIdentifier methodName) { |
| // |
| // Look first in the lexical scope. |
| // |
| Element element = _resolver.nameScope.lookup(methodName, _definingLibrary); |
| if (element == null) { |
| // |
| // If it isn't defined in the lexical scope, and the invocation is within |
| // a class, then look in the inheritance scope. |
| // |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (enclosingClass != null) { |
| InterfaceType enclosingType = enclosingClass.type; |
| element = _lookUpMethod(null, enclosingType, methodName.name); |
| if (element == null) { |
| // |
| // If there's no method, then it's possible that 'm' is a getter that |
| // returns a function. |
| // |
| element = _lookUpGetter(null, enclosingType, methodName.name); |
| } |
| } |
| } |
| // TODO(brianwilkerson) Report this error. |
| return element; |
| } |
| |
| /** |
| * Given an invocation of the form 'e.m(a1, ..., an)', resolve 'e.m' to the |
| * element being invoked. If the returned element is a method, then the method |
| * will be invoked. If the returned element is a getter, the getter will be |
| * invoked without arguments and the result of that invocation will then be |
| * invoked with the arguments. The [target] is the target of the invocation |
| * ('e'). The [targetType] is the type of the target. The [methodName] is th |
| * name of the method being invoked ('m'). [isConditional] indicates |
| * whether the invocatoin uses a '?.' operator. |
| */ |
| Element _resolveInvokedElementWithTarget(Expression target, |
| DartType targetType, SimpleIdentifier methodName, bool isConditional) { |
| String name = methodName.name; |
| if (targetType is InterfaceType) { |
| Element element = _lookUpMethod(target, targetType, name); |
| if (element == null) { |
| // |
| // If there's no method, then it's possible that 'm' is a getter that |
| // returns a function. |
| // |
| // TODO (collinsn): need to add union type support here too, in the |
| // style of [lookUpMethod]. |
| element = _lookUpGetter(target, targetType, name); |
| } |
| return element; |
| } else if (targetType is FunctionType && |
| _resolver.typeProvider.isObjectMethod(name)) { |
| return _resolver.typeProvider.objectType.element.getMethod(name); |
| } else if (target is SimpleIdentifier) { |
| Element targetElement = target.staticElement; |
| if (targetType is FunctionType && |
| name == FunctionElement.CALL_METHOD_NAME) { |
| return targetElement; |
| } |
| if (targetElement is PrefixElement) { |
| if (isConditional) { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, |
| target, |
| [target.name]); |
| } |
| // |
| // Look to see whether the name of the method is really part of a |
| // prefixed identifier for an imported top-level function or top-level |
| // getter that returns a function. |
| // |
| Identifier functionName = |
| new PrefixedIdentifierImpl.temp(target, methodName); |
| Element element = |
| _resolver.nameScope.lookup(functionName, _definingLibrary); |
| if (element != null) { |
| // TODO(brianwilkerson) This isn't a method invocation, it's a |
| // function invocation where the function name is a prefixed |
| // identifier. Consider re-writing the AST. |
| return element; |
| } |
| } |
| } |
| // TODO(brianwilkerson) Report this error. |
| return null; |
| } |
| |
| /** |
| * Given a [node] that can have annotations associated with it, resolve the |
| * annotations in the element model representing annotations to the node. |
| */ |
| void _resolveMetadataForParameter(NormalFormalParameter node) { |
| _resolveAnnotations(node.metadata); |
| } |
| |
| /** |
| * Given that we are accessing a property of the given [targetType] with the |
| * given [propertyName], return the element that represents the property. The |
| * [target] is the target of the invocation ('e'). |
| */ |
| ExecutableElement _resolveProperty( |
| Expression target, DartType targetType, SimpleIdentifier propertyName) { |
| ExecutableElement memberElement = null; |
| if (propertyName.inSetterContext()) { |
| memberElement = _lookUpSetter(target, targetType, propertyName.name); |
| } |
| if (memberElement == null) { |
| memberElement = _lookUpGetter(target, targetType, propertyName.name); |
| } |
| if (memberElement == null) { |
| memberElement = _lookUpMethod(target, targetType, propertyName.name); |
| } |
| return memberElement; |
| } |
| |
| void _resolvePropertyAccess( |
| Expression target, SimpleIdentifier propertyName, bool isCascaded) { |
| DartType staticType = _getStaticType(target); |
| DartType propagatedType = _getPropagatedType(target); |
| Element staticElement = null; |
| Element propagatedElement = null; |
| // |
| // If this property access is of the form 'C.m' where 'C' is a class, |
| // then we don't call resolveProperty(...) which walks up the class |
| // hierarchy, instead we just look for the member in the type only. This |
| // does not apply to conditional property accesses (i.e. 'C?.m'). |
| // |
| ClassElement typeReference = getTypeReference(target); |
| if (typeReference != null) { |
| if (isCascaded) { |
| typeReference = _typeType.element; |
| } |
| // TODO(brianwilkerson) Why are we setting the propagated element here? |
| // It looks wrong. |
| staticElement = |
| propagatedElement = _resolveElement(typeReference, propertyName); |
| } else { |
| staticElement = _resolveProperty(target, staticType, propertyName); |
| propagatedElement = |
| _resolveProperty(target, propagatedType, propertyName); |
| } |
| // May be part of annotation, record property element only if exists. |
| // Error was already reported in validateAnnotationElement(). |
| if (target.parent.parent is Annotation) { |
| if (staticElement != null) { |
| propertyName.staticElement = staticElement; |
| } |
| return; |
| } |
| propertyName.staticElement = staticElement; |
| propertyName.propagatedElement = propagatedElement; |
| bool shouldReportMissingMember_static = |
| _shouldReportMissingMember(staticType, staticElement); |
| bool shouldReportMissingMember_propagated = |
| !shouldReportMissingMember_static && |
| _enableHints && |
| _shouldReportMissingMember(propagatedType, propagatedElement) && |
| !_memberFoundInSubclass( |
| propagatedType.element, propertyName.name, false, true); |
| if (shouldReportMissingMember_static || |
| shouldReportMissingMember_propagated) { |
| DartType staticOrPropagatedType = |
| shouldReportMissingMember_static ? staticType : propagatedType; |
| Element staticOrPropagatedEnclosingElt = staticOrPropagatedType.element; |
| bool isStaticProperty = _isStatic(staticOrPropagatedEnclosingElt); |
| DartType displayType = |
| staticOrPropagatedType ?? propagatedType ?? staticType; |
| // Special getter cases. |
| if (propertyName.inGetterContext()) { |
| if (!isStaticProperty && |
| staticOrPropagatedEnclosingElt is ClassElement) { |
| InterfaceType targetType = staticOrPropagatedEnclosingElt.type; |
| if (!_enableStrictCallChecks && |
| targetType != null && |
| targetType.isDartCoreFunction && |
| propertyName.name == FunctionElement.CALL_METHOD_NAME) { |
| // TODO(brianwilkerson) Can we ever resolve the function being |
| // invoked? |
| // resolveArgumentsToParameters(node.getArgumentList(), invokedFunction); |
| return; |
| } else if (staticOrPropagatedEnclosingElt.isEnum && |
| propertyName.name == "_name") { |
| _resolver.errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.ACCESS_PRIVATE_ENUM_FIELD, |
| propertyName, |
| [propertyName.name]); |
| return; |
| } |
| } |
| } |
| Element declaringElement = |
| staticType.isVoid ? null : staticOrPropagatedEnclosingElt; |
| if (propertyName.inSetterContext()) { |
| ErrorCode errorCode; |
| if (shouldReportMissingMember_static) { |
| if (target is SuperExpression) { |
| if (isStaticProperty && !staticType.isVoid) { |
| errorCode = StaticWarningCode.UNDEFINED_SUPER_SETTER; |
| } else { |
| errorCode = StaticTypeWarningCode.UNDEFINED_SUPER_SETTER; |
| } |
| } else { |
| if (isStaticProperty && !staticType.isVoid) { |
| errorCode = StaticWarningCode.UNDEFINED_SETTER; |
| } else { |
| errorCode = StaticTypeWarningCode.UNDEFINED_SETTER; |
| } |
| } |
| } else { |
| errorCode = HintCode.UNDEFINED_SETTER; |
| } |
| _recordUndefinedNode(declaringElement, errorCode, propertyName, |
| [propertyName.name, displayType.displayName]); |
| } else if (propertyName.inGetterContext()) { |
| ErrorCode errorCode; |
| if (shouldReportMissingMember_static) { |
| if (target is SuperExpression) { |
| if (isStaticProperty && !staticType.isVoid) { |
| errorCode = StaticWarningCode.UNDEFINED_SUPER_GETTER; |
| } else { |
| errorCode = StaticTypeWarningCode.UNDEFINED_SUPER_GETTER; |
| } |
| } else { |
| if (isStaticProperty && !staticType.isVoid) { |
| errorCode = StaticWarningCode.UNDEFINED_GETTER; |
| } else { |
| errorCode = StaticTypeWarningCode.UNDEFINED_GETTER; |
| } |
| } |
| } else { |
| errorCode = HintCode.UNDEFINED_GETTER; |
| } |
| _recordUndefinedNode(declaringElement, errorCode, propertyName, |
| [propertyName.name, displayType.displayName]); |
| } else { |
| _recordUndefinedNode( |
| declaringElement, |
| StaticWarningCode.UNDEFINED_IDENTIFIER, |
| propertyName, |
| [propertyName.name]); |
| } |
| } |
| } |
| |
| /** |
| * Resolve the given simple [identifier] if possible. Return the element to |
| * which it could be resolved, or `null` if it could not be resolved. This |
| * does not record the results of the resolution. |
| */ |
| Element _resolveSimpleIdentifier(SimpleIdentifier identifier) { |
| Element element = _resolver.nameScope.lookup(identifier, _definingLibrary); |
| if (element is PropertyAccessorElement && identifier.inSetterContext()) { |
| PropertyInducingElement variable = |
| (element as PropertyAccessorElement).variable; |
| if (variable != null) { |
| PropertyAccessorElement setter = variable.setter; |
| if (setter == null) { |
| // |
| // Check to see whether there might be a locally defined getter and |
| // an inherited setter. |
| // |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (enclosingClass != null) { |
| setter = _lookUpSetter(null, enclosingClass.type, identifier.name); |
| } |
| } |
| if (setter != null) { |
| element = setter; |
| } |
| } |
| } else if (element == null && |
| (identifier.inSetterContext() || |
| identifier.parent is CommentReference)) { |
| Identifier setterId = |
| new SyntheticIdentifier('${identifier.name}=', identifier); |
| element = _resolver.nameScope.lookup(setterId, _definingLibrary); |
| identifier.setProperty(LibraryImportScope.conflictingSdkElements, |
| setterId.getProperty(LibraryImportScope.conflictingSdkElements)); |
| } |
| ClassElement enclosingClass = _resolver.enclosingClass; |
| if (element == null && enclosingClass != null) { |
| InterfaceType enclosingType = enclosingClass.type; |
| if (element == null && |
| (identifier.inSetterContext() || |
| identifier.parent is CommentReference)) { |
| element = _lookUpSetter(null, enclosingType, identifier.name); |
| } |
| if (element == null && identifier.inGetterContext()) { |
| element = _lookUpGetter(null, enclosingType, identifier.name); |
| } |
| if (element == null) { |
| element = _lookUpMethod(null, enclosingType, identifier.name); |
| } |
| } |
| return element; |
| } |
| |
| /** |
| * If the given [type] is a type parameter, resolve it to the type that should |
| * be used when looking up members. Otherwise, return the original type. |
| */ |
| DartType _resolveTypeParameter(DartType type) => |
| type?.resolveToBound(_resolver.typeProvider.objectType); |
| |
| /** |
| * Return `true` if we should report an error as a result of looking up a |
| * [member] in the given [type] and not finding any member. |
| */ |
| bool _shouldReportMissingMember(DartType type, Element member) { |
| return member == null && |
| type != null && |
| !type.isDynamic && |
| !type.isDartCoreNull; |
| } |
| |
| /** |
| * Checks whether the given [expression] is a reference to a class. If it is |
| * then the element representing the class is returned, otherwise `null` is |
| * returned. |
| */ |
| static ClassElement getTypeReference(Expression expression) { |
| if (expression is Identifier) { |
| Element staticElement = expression.staticElement; |
| if (staticElement is ClassElement) { |
| return staticElement; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given a [node] that can have annotations associated with it, resolve the |
| * annotations in the element model representing the annotations on the node. |
| */ |
| static void resolveMetadata(AnnotatedNode node) { |
| _resolveAnnotations(node.metadata); |
| if (node is VariableDeclaration) { |
| AstNode parent = node.parent; |
| if (parent is VariableDeclarationList) { |
| _resolveAnnotations(parent.metadata); |
| AstNode grandParent = parent.parent; |
| if (grandParent is FieldDeclaration) { |
| _resolveAnnotations(grandParent.metadata); |
| } else if (grandParent is TopLevelVariableDeclaration) { |
| _resolveAnnotations(grandParent.metadata); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Return `true` if the given [identifier] is the return type of a constructor |
| * declaration. |
| */ |
| static bool _isConstructorReturnType(SimpleIdentifier identifier) { |
| AstNode parent = identifier.parent; |
| if (parent is ConstructorDeclaration) { |
| return identical(parent.returnType, identifier); |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given [identifier] is the return type of a factory |
| * constructor. |
| */ |
| static bool _isFactoryConstructorReturnType(SimpleIdentifier identifier) { |
| AstNode parent = identifier.parent; |
| if (parent is ConstructorDeclaration) { |
| return identical(parent.returnType, identifier) && |
| parent.factoryKeyword != null; |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given 'super' [expression] is used in a valid context. |
| */ |
| static bool _isSuperInValidContext(SuperExpression expression) { |
| for (AstNode node = expression; node != null; node = node.parent) { |
| if (node is CompilationUnit) { |
| return false; |
| } else if (node is ConstructorDeclaration) { |
| return node.factoryKeyword == null; |
| } else if (node is ConstructorFieldInitializer) { |
| return false; |
| } else if (node is MethodDeclaration) { |
| return !node.isStatic; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Resolve each of the annotations in the given list of [annotations]. |
| */ |
| static void _resolveAnnotations(NodeList<Annotation> annotations) { |
| for (Annotation annotation in annotations) { |
| ElementAnnotationImpl elementAnnotation = annotation.elementAnnotation; |
| elementAnnotation.element = annotation.element; |
| } |
| } |
| } |
| |
| /** |
| * An identifier that can be used to look up names in the lexical scope when |
| * there is no identifier in the AST structure. There is no identifier in the |
| * AST when the parser could not distinguish between a method invocation and an |
| * invocation of a top-level function imported with a prefix. |
| */ |
| class SyntheticIdentifier extends IdentifierImpl { |
| /** |
| * The name of the synthetic identifier. |
| */ |
| @override |
| final String name; |
| |
| /** |
| * The identifier to be highlighted in case of an error |
| */ |
| final Identifier targetIdentifier; |
| |
| /** |
| * Initialize a newly created synthetic identifier to have the given [name] |
| * and [targetIdentifier]. |
| */ |
| SyntheticIdentifier(this.name, this.targetIdentifier); |
| |
| @override |
| Token get beginToken => null; |
| |
| @override |
| Element get bestElement => null; |
| |
| @override |
| Iterable<SyntacticEntity> get childEntities { |
| // Should never be called, since a SyntheticIdentifier never appears in the |
| // AST--it is just used for lookup. |
| assert(false); |
| return new ChildEntities(); |
| } |
| |
| @override |
| Token get endToken => null; |
| |
| @override |
| int get length => targetIdentifier.length; |
| |
| @override |
| int get offset => targetIdentifier.offset; |
| |
| @override |
| int get precedence => 16; |
| |
| @override |
| Element get propagatedElement => null; |
| |
| @override |
| Element get staticElement => null; |
| |
| @override |
| dynamic/*=E*/ accept/*<E>*/(AstVisitor/*<E>*/ visitor) => null; |
| |
| @override |
| void visitChildren(AstVisitor visitor) {} |
| } |