| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library dart2js.resolution; |
| |
| import 'dart:collection' show Queue; |
| |
| import '../common.dart'; |
| import '../common/names.dart' show Identifiers; |
| import '../common/resolution.dart' |
| show ParsingContext, Resolution, ResolutionImpact, Target; |
| import '../common/tasks.dart' show CompilerTask, Measurer; |
| import '../compile_time_constants.dart' show ConstantCompiler; |
| import '../constants/expressions.dart' |
| show |
| ConstantExpression, |
| ConstantExpressionKind, |
| ConstructedConstantExpression, |
| ErroneousConstantExpression; |
| import '../constants/values.dart' show ConstantValue; |
| import '../core_types.dart' show CoreClasses, CoreTypes, CommonElements; |
| import '../dart_types.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/modelx.dart' |
| show |
| BaseClassElementX, |
| BaseFunctionElementX, |
| ConstructorElementX, |
| FieldElementX, |
| FunctionElementX, |
| GetterElementX, |
| MetadataAnnotationX, |
| MixinApplicationElementX, |
| ParameterMetadataAnnotation, |
| SetterElementX, |
| TypedefElementX; |
| import '../enqueue.dart'; |
| import '../options.dart'; |
| import '../tokens/token.dart' |
| show |
| isBinaryOperator, |
| isMinusOperator, |
| isTernaryOperator, |
| isUnaryOperator, |
| isUserDefinableOperator; |
| import '../tree/tree.dart'; |
| import '../universe/call_structure.dart' show CallStructure; |
| import '../universe/feature.dart' show Feature; |
| import '../universe/use.dart' show StaticUse, TypeUse; |
| import '../universe/world_impact.dart' show WorldImpact; |
| import '../util/util.dart' show Link, Setlet; |
| import '../world.dart'; |
| import 'class_hierarchy.dart'; |
| import 'class_members.dart' show MembersCreator; |
| import 'constructors.dart'; |
| import 'members.dart'; |
| import 'registry.dart'; |
| import 'resolution_result.dart'; |
| import 'signatures.dart'; |
| import 'tree_elements.dart'; |
| import 'typedefs.dart'; |
| |
| class ResolverTask extends CompilerTask { |
| final ConstantCompiler constantCompiler; |
| final Resolution resolution; |
| |
| ResolverTask(this.resolution, this.constantCompiler, Measurer measurer) |
| : super(measurer); |
| |
| String get name => 'Resolver'; |
| |
| DiagnosticReporter get reporter => resolution.reporter; |
| Target get target => resolution.target; |
| CoreTypes get coreTypes => resolution.coreTypes; |
| CoreClasses get coreClasses => resolution.coreClasses; |
| CommonElements get commonElements => resolution.commonElements; |
| ParsingContext get parsingContext => resolution.parsingContext; |
| CompilerOptions get options => resolution.options; |
| ResolutionEnqueuer get enqueuer => resolution.enqueuer; |
| OpenWorld get world => enqueuer.universe.openWorld; |
| |
| ResolutionImpact resolve(Element element) { |
| return measure(() { |
| if (Elements.isMalformed(element)) { |
| // TODO(johnniwinther): Add a predicate for this. |
| assert(invariant(element, element is! ErroneousElement, |
| message: "Element $element expected to have parse errors.")); |
| _ensureTreeElements(element); |
| return const ResolutionImpact(); |
| } |
| |
| WorldImpact processMetadata([WorldImpact result]) { |
| for (MetadataAnnotation metadata in element.implementation.metadata) { |
| metadata.ensureResolved(resolution); |
| } |
| return result; |
| } |
| |
| if (element.isConstructor || |
| element.isFunction || |
| element.isGetter || |
| element.isSetter) { |
| return processMetadata(resolveMethodElement(element)); |
| } |
| |
| if (element.isField) { |
| return processMetadata(resolveField(element)); |
| } |
| if (element.isClass) { |
| ClassElement cls = element; |
| cls.ensureResolved(resolution); |
| return processMetadata(const ResolutionImpact()); |
| } else if (element.isTypedef) { |
| TypedefElement typdef = element; |
| return processMetadata(resolveTypedef(typdef)); |
| } |
| |
| reporter.internalError(element, "resolve($element) not implemented."); |
| }); |
| } |
| |
| void resolveRedirectingConstructor(InitializerResolver resolver, Node node, |
| FunctionElement constructor, FunctionElement redirection) { |
| assert(invariant(node, constructor.isImplementation, |
| message: 'Redirecting constructors must be resolved on implementation ' |
| 'elements.')); |
| Setlet<FunctionElement> seen = new Setlet<FunctionElement>(); |
| seen.add(constructor); |
| while (redirection != null) { |
| // Ensure that we follow redirections through implementation elements. |
| redirection = redirection.implementation; |
| if (redirection.isError) { |
| break; |
| } |
| if (seen.contains(redirection)) { |
| reporter.reportErrorMessage( |
| node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE); |
| return; |
| } |
| seen.add(redirection); |
| redirection = resolver.visitor.resolveConstructorRedirection(redirection); |
| } |
| } |
| |
| static void processAsyncMarker(Resolution resolution, |
| BaseFunctionElementX element, ResolutionRegistry registry) { |
| DiagnosticReporter reporter = resolution.reporter; |
| CoreClasses coreClasses = resolution.coreClasses; |
| FunctionExpression functionExpression = element.node; |
| AsyncModifier asyncModifier = functionExpression.asyncModifier; |
| if (asyncModifier != null) { |
| if (!resolution.target.supportsAsyncAwait) { |
| reporter.reportErrorMessage(functionExpression.asyncModifier, |
| MessageKind.ASYNC_AWAIT_NOT_SUPPORTED); |
| } else { |
| if (asyncModifier.isAsynchronous) { |
| element.asyncMarker = asyncModifier.isYielding |
| ? AsyncMarker.ASYNC_STAR |
| : AsyncMarker.ASYNC; |
| } else { |
| element.asyncMarker = AsyncMarker.SYNC_STAR; |
| } |
| if (element.isAbstract) { |
| reporter.reportErrorMessage( |
| asyncModifier, |
| MessageKind.ASYNC_MODIFIER_ON_ABSTRACT_METHOD, |
| {'modifier': element.asyncMarker}); |
| } else if (element.isConstructor) { |
| reporter.reportErrorMessage( |
| asyncModifier, |
| MessageKind.ASYNC_MODIFIER_ON_CONSTRUCTOR, |
| {'modifier': element.asyncMarker}); |
| } else { |
| if (element.isSetter) { |
| reporter.reportErrorMessage( |
| asyncModifier, |
| MessageKind.ASYNC_MODIFIER_ON_SETTER, |
| {'modifier': element.asyncMarker}); |
| } |
| if (functionExpression.body.asReturn() != null && |
| element.asyncMarker.isYielding) { |
| reporter.reportErrorMessage( |
| asyncModifier, |
| MessageKind.YIELDING_MODIFIER_ON_ARROW_BODY, |
| {'modifier': element.asyncMarker}); |
| } |
| } |
| switch (element.asyncMarker) { |
| case AsyncMarker.ASYNC: |
| registry.registerFeature(Feature.ASYNC); |
| coreClasses.futureClass.ensureResolved(resolution); |
| break; |
| case AsyncMarker.ASYNC_STAR: |
| registry.registerFeature(Feature.ASYNC_STAR); |
| coreClasses.streamClass.ensureResolved(resolution); |
| break; |
| case AsyncMarker.SYNC_STAR: |
| registry.registerFeature(Feature.SYNC_STAR); |
| coreClasses.iterableClass.ensureResolved(resolution); |
| break; |
| } |
| } |
| } |
| } |
| |
| bool _isNativeClassOrExtendsNativeClass(ClassElement classElement) { |
| assert(classElement != null); |
| while (classElement != null) { |
| if (target.isNative(classElement)) return true; |
| classElement = classElement.superclass; |
| } |
| return false; |
| } |
| |
| WorldImpact resolveMethodElementImplementation( |
| FunctionElement element, FunctionExpression tree) { |
| return reporter.withCurrentElement(element, () { |
| if (element.isExternal && tree.hasBody) { |
| reporter.reportErrorMessage(element, MessageKind.EXTERNAL_WITH_BODY, |
| {'functionName': element.name}); |
| } |
| if (element.isConstructor) { |
| if (tree.returnType != null) { |
| reporter.reportErrorMessage( |
| tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE); |
| } |
| if (tree.hasBody && element.isConst) { |
| if (element.isGenerativeConstructor) { |
| reporter.reportErrorMessage( |
| tree, MessageKind.CONST_CONSTRUCTOR_WITH_BODY); |
| } else if (!tree.isRedirectingFactory) { |
| reporter.reportErrorMessage(tree, MessageKind.CONST_FACTORY); |
| } |
| } |
| } |
| |
| ResolverVisitor visitor = visitorFor(element); |
| ResolutionRegistry registry = visitor.registry; |
| registry.defineFunction(tree, element); |
| visitor.setupFunction(tree, element); // Modifies the scope. |
| processAsyncMarker(resolution, element, registry); |
| |
| if (element.isGenerativeConstructor) { |
| // Even if there is no initializer list we still have to do the |
| // resolution in case there is an implicit super constructor call. |
| InitializerResolver resolver = |
| new InitializerResolver(visitor, element, tree); |
| FunctionElement redirection = resolver.resolveInitializers(); |
| if (redirection != null) { |
| resolveRedirectingConstructor(resolver, tree, element, redirection); |
| } |
| } else if (tree.initializers != null) { |
| reporter.reportErrorMessage( |
| tree, MessageKind.FUNCTION_WITH_INITIALIZER); |
| } |
| |
| if (!options.analyzeSignaturesOnly || tree.isRedirectingFactory) { |
| // We need to analyze the redirecting factory bodies to ensure that |
| // we can analyze compile-time constants. |
| visitor.visit(tree.body); |
| } |
| |
| // Get the resolution tree and check that the resolved |
| // function doesn't use 'super' if it is mixed into another |
| // class. This is the part of the 'super' mixin check that |
| // happens when a function is resolved after the mixin |
| // application has been performed. |
| TreeElements resolutionTree = registry.mapping; |
| ClassElement enclosingClass = element.enclosingClass; |
| if (enclosingClass != null) { |
| // TODO(johnniwinther): Find another way to obtain mixin uses. |
| Iterable<MixinApplicationElement> mixinUses = |
| world.allMixinUsesOf(enclosingClass); |
| ClassElement mixin = enclosingClass; |
| for (MixinApplicationElement mixinApplication in mixinUses) { |
| checkMixinSuperUses(resolutionTree, mixinApplication, mixin); |
| } |
| } |
| |
| // TODO(9631): support noSuchMethod on native classes. |
| if (element.isFunction && |
| element.isInstanceMember && |
| element.name == Identifiers.noSuchMethod_ && |
| _isNativeClassOrExtendsNativeClass(enclosingClass)) { |
| reporter.reportErrorMessage(tree, MessageKind.NO_SUCH_METHOD_IN_NATIVE); |
| } |
| |
| resolution.target.resolveNativeElement(element, registry.impactBuilder); |
| |
| return registry.impactBuilder; |
| }); |
| } |
| |
| WorldImpact resolveMethodElement(FunctionElementX element) { |
| assert(invariant(element, element.isDeclaration)); |
| return reporter.withCurrentElement(element, () { |
| if (enqueuer.hasBeenProcessed(element)) { |
| // TODO(karlklose): Remove the check for [isConstructor]. [elememts] |
| // should never be non-null, not even for constructors. |
| assert(invariant(element, element.isConstructor, |
| message: 'Non-constructor element $element ' |
| 'has already been analyzed.')); |
| return const ResolutionImpact(); |
| } |
| if (element.isSynthesized) { |
| if (element.isGenerativeConstructor) { |
| ResolutionRegistry registry = |
| new ResolutionRegistry(this.target, _ensureTreeElements(element)); |
| ConstructorElement constructor = element.asFunctionElement(); |
| ConstructorElement target = constructor.definingConstructor; |
| // Ensure the signature of the synthesized element is |
| // resolved. This is the only place where the resolver is |
| // seeing this element. |
| FunctionType type = element.computeType(resolution); |
| if (!target.isMalformed) { |
| registry.registerStaticUse(new StaticUse.superConstructorInvoke( |
| // TODO(johnniwinther): Provide the right call structure for |
| // forwarding constructors. |
| target, |
| CallStructure.NO_ARGS)); |
| } |
| // TODO(johnniwinther): Remove this substitution when synthesized |
| // constructors handle type variables correctly. |
| type = type.substByContext( |
| constructor.enclosingClass.asInstanceOf(target.enclosingClass)); |
| type.parameterTypes.forEach(registry.registerCheckedModeCheck); |
| type.optionalParameterTypes |
| .forEach(registry.registerCheckedModeCheck); |
| type.namedParameterTypes.forEach(registry.registerCheckedModeCheck); |
| return registry.impactBuilder; |
| } else { |
| assert(element.isDeferredLoaderGetter || element.isMalformed); |
| _ensureTreeElements(element); |
| return const ResolutionImpact(); |
| } |
| } else { |
| element.parseNode(resolution.parsingContext); |
| element.computeType(resolution); |
| FunctionElementX implementation = element; |
| if (element.isExternal) { |
| implementation = target.resolveExternalFunction(element); |
| } |
| return resolveMethodElementImplementation( |
| implementation, implementation.node); |
| } |
| }); |
| } |
| |
| /// Creates a [ResolverVisitor] for resolving an AST in context of [element]. |
| /// If [useEnclosingScope] is `true` then the initial scope of the visitor |
| /// does not include inner scope of [element]. |
| /// |
| /// This method should only be used by this library (or tests of |
| /// this library). |
| ResolverVisitor visitorFor(Element element, {bool useEnclosingScope: false}) { |
| return new ResolverVisitor(resolution, element, |
| new ResolutionRegistry(target, _ensureTreeElements(element)), |
| useEnclosingScope: useEnclosingScope); |
| } |
| |
| WorldImpact resolveField(FieldElementX element) { |
| return reporter.withCurrentElement(element, () { |
| VariableDefinitions tree = element.parseNode(parsingContext); |
| if (element.modifiers.isStatic && element.isTopLevel) { |
| reporter.reportErrorMessage(element.modifiers.getStatic(), |
| MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC); |
| } |
| ResolverVisitor visitor = visitorFor(element); |
| ResolutionRegistry registry = visitor.registry; |
| // TODO(johnniwinther): Maybe remove this when placeholderCollector migrates |
| // to the backend ast. |
| registry.defineElement(element.definition, element); |
| // TODO(johnniwinther): Share the resolved type between all variables |
| // declared in the same declaration. |
| if (tree.type != null) { |
| DartType type = visitor.resolveTypeAnnotation(tree.type); |
| assert(invariant( |
| element, |
| element.variables.type == null || |
| // Crude check but we have no equivalence relation that |
| // equates malformed types, like matching creations of type |
| // `Foo<Unresolved>`. |
| element.variables.type.toString() == type.toString(), |
| message: "Unexpected type computed for $element. " |
| "Was ${element.variables.type}, computed $type.")); |
| element.variables.type = type; |
| } else if (element.variables.type == null) { |
| // Only assign the dynamic type if the element has no known type. This |
| // happens for enum fields where the type is known but is not in the |
| // synthesized AST. |
| element.variables.type = const DynamicType(); |
| } else { |
| registry.registerCheckedModeCheck(element.variables.type); |
| } |
| |
| Expression initializer = element.initializer; |
| Modifiers modifiers = element.modifiers; |
| if (initializer != null) { |
| // TODO(johnniwinther): Avoid analyzing initializers if |
| // [Compiler.analyzeSignaturesOnly] is set. |
| ResolutionResult result = visitor.visit(initializer); |
| if (result.isConstant) { |
| element.constant = result.constant; |
| } |
| } else if (modifiers.isConst) { |
| reporter.reportErrorMessage( |
| element, MessageKind.CONST_WITHOUT_INITIALIZER); |
| } else if (modifiers.isFinal && !element.isInstanceMember) { |
| reporter.reportErrorMessage( |
| element, MessageKind.FINAL_WITHOUT_INITIALIZER); |
| } else { |
| registry.registerFeature(Feature.FIELD_WITHOUT_INITIALIZER); |
| } |
| |
| if (Elements.isStaticOrTopLevelField(element)) { |
| visitor.addDeferredAction(element, () { |
| if (element.modifiers.isConst) { |
| element.constant = constantCompiler.compileConstant(element); |
| } else { |
| element.constant = constantCompiler.compileVariable(element); |
| } |
| }); |
| if (initializer != null) { |
| if (!element.modifiers.isConst && |
| initializer.asLiteralNull() == null) { |
| // TODO(johnniwinther): Determine the const-ness eagerly to avoid |
| // unnecessary registrations. |
| registry.registerFeature(Feature.LAZY_FIELD); |
| } |
| } |
| } |
| |
| // Perform various checks as side effect of "computing" the type. |
| element.computeType(resolution); |
| |
| resolution.target.resolveNativeElement(element, registry.impactBuilder); |
| |
| return registry.impactBuilder; |
| }); |
| } |
| |
| DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) { |
| DartType type = _resolveReturnType(element, annotation); |
| if (type.isVoid) { |
| reporter.reportErrorMessage(annotation, MessageKind.VOID_NOT_ALLOWED); |
| } |
| return type; |
| } |
| |
| DartType _resolveReturnType(Element element, TypeAnnotation annotation) { |
| if (annotation == null) return const DynamicType(); |
| DartType result = visitorFor(element).resolveTypeAnnotation(annotation); |
| assert(invariant(annotation, result != null, |
| message: "No type computed for $annotation.")); |
| if (result == null) { |
| // TODO(karklose): warning. |
| return const DynamicType(); |
| } |
| return result; |
| } |
| |
| void resolveRedirectionChain(ConstructorElement constructor, Spannable node) { |
| ConstructorElement target = constructor; |
| DartType targetType; |
| List<ConstructorElement> seen = new List<ConstructorElement>(); |
| bool isMalformed = false; |
| // Follow the chain of redirections and check for cycles. |
| while (target.isRedirectingFactory) { |
| if (target.hasEffectiveTarget) { |
| // We found a constructor that already has been processed. |
| // TODO(johnniwinther): Should `effectiveTargetType` be part of the |
| // interface? |
| targetType = |
| target.computeEffectiveTargetType(target.enclosingClass.thisType); |
| assert(invariant(target, targetType != null, |
| message: 'Redirection target type has not been computed for ' |
| '$target')); |
| target = target.effectiveTarget; |
| break; |
| } |
| |
| Element nextTarget = target.immediateRedirectionTarget; |
| |
| if (seen.contains(nextTarget)) { |
| reporter.reportErrorMessage( |
| node, MessageKind.CYCLIC_REDIRECTING_FACTORY); |
| targetType = target.enclosingClass.thisType; |
| isMalformed = true; |
| break; |
| } |
| seen.add(target); |
| target = nextTarget; |
| } |
| |
| if (target.isGenerativeConstructor && target.enclosingClass.isAbstract) { |
| isMalformed = true; |
| } |
| if (target.isMalformed) { |
| isMalformed = true; |
| } |
| |
| if (targetType == null) { |
| assert(!target.isRedirectingFactory); |
| targetType = target.enclosingClass.thisType; |
| } |
| |
| // [target] is now the actual target of the redirections. Run through |
| // the constructors again and set their [redirectionTarget], so that we |
| // do not have to run the loop for these constructors again. Furthermore, |
| // compute [redirectionTargetType] for each factory by computing the |
| // substitution of the target type with respect to the factory type. |
| while (!seen.isEmpty) { |
| ConstructorElementX factory = seen.removeLast(); |
| ResolvedAst resolvedAst = factory.resolvedAst; |
| assert(invariant(node, resolvedAst != null, |
| message: 'No ResolvedAst for $factory.')); |
| FunctionExpression functionNode = resolvedAst.node; |
| RedirectingFactoryBody redirectionNode = resolvedAst.body; |
| DartType factoryType = resolvedAst.elements.getType(redirectionNode); |
| if (!factoryType.isDynamic) { |
| targetType = targetType.substByContext(factoryType); |
| } |
| factory.setEffectiveTarget(target, targetType, isMalformed: isMalformed); |
| } |
| } |
| |
| /** |
| * Load and resolve the supertypes of [cls]. |
| * |
| * Warning: do not call this method directly. It should only be |
| * called by [resolveClass] and [ClassSupertypeResolver]. |
| */ |
| void loadSupertypes(BaseClassElementX cls, Spannable from) { |
| measure(() { |
| if (cls.supertypeLoadState == STATE_DONE) return; |
| if (cls.supertypeLoadState == STATE_STARTED) { |
| reporter.reportErrorMessage( |
| from, MessageKind.CYCLIC_CLASS_HIERARCHY, {'className': cls.name}); |
| cls.supertypeLoadState = STATE_DONE; |
| cls.hasIncompleteHierarchy = true; |
| cls.allSupertypesAndSelf = coreClasses.objectClass.allSupertypesAndSelf |
| .extendClass(cls.computeType(resolution)); |
| cls.supertype = cls.allSupertypes.head; |
| assert(invariant(from, cls.supertype != null, |
| message: 'Missing supertype on cyclic class $cls.')); |
| cls.interfaces = const Link<DartType>(); |
| return; |
| } |
| cls.supertypeLoadState = STATE_STARTED; |
| reporter.withCurrentElement(cls, () { |
| // TODO(ahe): Cache the node in cls. |
| cls |
| .parseNode(parsingContext) |
| .accept(new ClassSupertypeResolver(resolution, cls)); |
| if (cls.supertypeLoadState != STATE_DONE) { |
| cls.supertypeLoadState = STATE_DONE; |
| } |
| }); |
| }); |
| } |
| |
| // TODO(johnniwinther): Remove this queue when resolution has been split into |
| // syntax and semantic resolution. |
| TypeDeclarationElement currentlyResolvedTypeDeclaration; |
| Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>(); |
| Queue<ClassElement> pendingClassesToBePostProcessed = |
| new Queue<ClassElement>(); |
| |
| /// Resolve [element] using [resolveTypeDeclaration]. |
| /// |
| /// This methods ensure that class declarations encountered through type |
| /// annotations during the resolution of [element] are resolved after |
| /// [element] has been resolved. |
| // TODO(johnniwinther): Encapsulate this functionality in a |
| // 'TypeDeclarationResolver'. |
| _resolveTypeDeclaration( |
| TypeDeclarationElement element, resolveTypeDeclaration()) { |
| return reporter.withCurrentElement(element, () { |
| return measure(() { |
| TypeDeclarationElement previousResolvedTypeDeclaration = |
| currentlyResolvedTypeDeclaration; |
| currentlyResolvedTypeDeclaration = element; |
| var result = resolveTypeDeclaration(); |
| if (previousResolvedTypeDeclaration == null) { |
| do { |
| while (!pendingClassesToBeResolved.isEmpty) { |
| pendingClassesToBeResolved |
| .removeFirst() |
| .ensureResolved(resolution); |
| } |
| while (!pendingClassesToBePostProcessed.isEmpty) { |
| _postProcessClassElement( |
| pendingClassesToBePostProcessed.removeFirst()); |
| } |
| } while (!pendingClassesToBeResolved.isEmpty); |
| assert(pendingClassesToBeResolved.isEmpty); |
| assert(pendingClassesToBePostProcessed.isEmpty); |
| } |
| currentlyResolvedTypeDeclaration = previousResolvedTypeDeclaration; |
| return result; |
| }); |
| }); |
| } |
| |
| /** |
| * Resolve the class [element]. |
| * |
| * Before calling this method, [element] was constructed by the |
| * scanner and most fields are null or empty. This method fills in |
| * these fields and also ensure that the supertypes of [element] are |
| * resolved. |
| * |
| * Warning: Do not call this method directly. Instead use |
| * [:element.ensureResolved(resolution):]. |
| */ |
| TreeElements resolveClass(BaseClassElementX element) { |
| return _resolveTypeDeclaration(element, () { |
| // TODO(johnniwinther): Store the mapping in the resolution enqueuer. |
| ResolutionRegistry registry = new ResolutionRegistry( |
| resolution.target, _ensureTreeElements(element)); |
| resolveClassInternal(element, registry); |
| return element.treeElements; |
| }); |
| } |
| |
| void ensureClassWillBeResolvedInternal(ClassElement element) { |
| if (currentlyResolvedTypeDeclaration == null) { |
| element.ensureResolved(resolution); |
| } else { |
| pendingClassesToBeResolved.add(element); |
| } |
| } |
| |
| void resolveClassInternal( |
| BaseClassElementX element, ResolutionRegistry registry) { |
| if (!element.isPatch) { |
| reporter.withCurrentElement( |
| element, |
| () => measure(() { |
| assert(element.resolutionState == STATE_NOT_STARTED); |
| element.resolutionState = STATE_STARTED; |
| Node tree = element.parseNode(parsingContext); |
| loadSupertypes(element, tree); |
| |
| ClassResolverVisitor visitor = |
| new ClassResolverVisitor(resolution, element, registry); |
| visitor.visit(tree); |
| element.resolutionState = STATE_DONE; |
| pendingClassesToBePostProcessed.add(element); |
| })); |
| if (element.isPatched) { |
| // Ensure handling patch after origin. |
| element.patch.ensureResolved(resolution); |
| } |
| } else { |
| // Handle patch classes: |
| element.resolutionState = STATE_STARTED; |
| // Ensure handling origin before patch. |
| element.origin.ensureResolved(resolution); |
| // Ensure that the type is computed. |
| element.computeType(resolution); |
| // Copy class hierarchy from origin. |
| element.supertype = element.origin.supertype; |
| element.interfaces = element.origin.interfaces; |
| element.allSupertypesAndSelf = element.origin.allSupertypesAndSelf; |
| // Stepwise assignment to ensure invariant. |
| element.supertypeLoadState = STATE_STARTED; |
| element.supertypeLoadState = STATE_DONE; |
| element.resolutionState = STATE_DONE; |
| // TODO(johnniwinther): Check matching type variables and |
| // empty extends/implements clauses. |
| } |
| } |
| |
| void _postProcessClassElement(BaseClassElementX element) { |
| for (MetadataAnnotation metadata in element.implementation.metadata) { |
| metadata.ensureResolved(resolution); |
| ConstantValue value = |
| resolution.constants.getConstantValue(metadata.constant); |
| if (!element.isProxy && resolution.isProxyConstant(value)) { |
| element.isProxy = true; |
| } |
| } |
| |
| // Force resolution of metadata on non-instance members since they may be |
| // inspected by the backend while emitting. Metadata on instance members is |
| // handled as a result of processing instantiated class members in the |
| // enqueuer. |
| // TODO(ahe): Avoid this eager resolution. |
| element.forEachMember((_, Element member) { |
| if (!member.isInstanceMember) { |
| reporter.withCurrentElement(member, () { |
| for (MetadataAnnotation metadata in member.implementation.metadata) { |
| metadata.ensureResolved(resolution); |
| } |
| }); |
| } |
| }); |
| |
| computeClassMember(element, Identifiers.call); |
| } |
| |
| void computeClassMembers(ClassElement element) { |
| MembersCreator.computeAllClassMembers(resolution, element); |
| } |
| |
| void computeClassMember(ClassElement element, String name) { |
| MembersCreator.computeClassMembersByName(resolution, element, name); |
| } |
| |
| void checkClass(ClassElement element) { |
| computeClassMembers(element); |
| if (element.isMixinApplication) { |
| checkMixinApplication(element); |
| } else { |
| checkClassMembers(element); |
| } |
| } |
| |
| void checkMixinApplication(MixinApplicationElementX mixinApplication) { |
| Modifiers modifiers = mixinApplication.modifiers; |
| int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT; |
| if (illegalFlags != 0) { |
| Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags); |
| reporter.reportErrorMessage( |
| modifiers, |
| MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS, |
| {'modifiers': illegalModifiers}); |
| } |
| |
| // In case of cyclic mixin applications, the mixin chain will have |
| // been cut. If so, we have already reported the error to the |
| // user so we just return from here. |
| ClassElement mixin = mixinApplication.mixin; |
| if (mixin == null) return; |
| |
| // Check that we're not trying to use Object as a mixin. |
| if (mixin.superclass == null) { |
| reporter.reportErrorMessage( |
| mixinApplication, MessageKind.ILLEGAL_MIXIN_OBJECT); |
| // Avoid reporting additional errors for the Object class. |
| return; |
| } |
| |
| if (mixin.isEnumClass) { |
| // Mixing in an enum has already caused a compile-time error. |
| return; |
| } |
| |
| // Check that the mixed in class has Object as its superclass. |
| if (!mixin.superclass.isObject) { |
| reporter.reportErrorMessage(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS); |
| } |
| |
| // Check that the mixed in class doesn't have any constructors and |
| // make sure we aren't mixing in methods that use 'super'. |
| mixin.forEachLocalMember((AstElement member) { |
| if (member.isGenerativeConstructor && !member.isSynthesized) { |
| reporter.reportErrorMessage( |
| member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR); |
| } else { |
| // Get the resolution tree and check that the resolved member |
| // doesn't use 'super'. This is the part of the 'super' mixin |
| // check that happens when a function is resolved before the |
| // mixin application has been performed. |
| // TODO(johnniwinther): Obtain the [TreeElements] for [member] |
| // differently. |
| if (resolution.enqueuer.hasBeenProcessed(member)) { |
| if (member.resolvedAst.kind == ResolvedAstKind.PARSED) { |
| checkMixinSuperUses( |
| member.resolvedAst.elements, mixinApplication, mixin); |
| } |
| } |
| } |
| }); |
| } |
| |
| void checkMixinSuperUses(TreeElements elements, |
| MixinApplicationElement mixinApplication, ClassElement mixin) { |
| // TODO(johnniwinther): Avoid the use of [TreeElements] here. |
| Iterable<SourceSpan> superUses = elements.superUses; |
| if (superUses.isEmpty) return; |
| DiagnosticMessage error = reporter.createMessage(mixinApplication, |
| MessageKind.ILLEGAL_MIXIN_WITH_SUPER, {'className': mixin.name}); |
| // Show the user the problematic uses of 'super' in the mixin. |
| List<DiagnosticMessage> infos = <DiagnosticMessage>[]; |
| for (SourceSpan use in superUses) { |
| infos.add( |
| reporter.createMessage(use, MessageKind.ILLEGAL_MIXIN_SUPER_USE)); |
| } |
| reporter.reportError(error, infos); |
| } |
| |
| void checkClassMembers(ClassElement cls) { |
| assert(invariant(cls, cls.isDeclaration)); |
| if (cls.isObject) return; |
| // TODO(johnniwinther): Should this be done on the implementation element as |
| // well? |
| List<Element> constConstructors = <Element>[]; |
| List<Element> nonFinalInstanceFields = <Element>[]; |
| cls.forEachMember((holder, member) { |
| reporter.withCurrentElement(member, () { |
| // Perform various checks as side effect of "computing" the type. |
| member.computeType(resolution); |
| |
| // Check modifiers. |
| if (member.isFunction && member.modifiers.isFinal) { |
| reporter.reportErrorMessage( |
| member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER); |
| } |
| if (member.isConstructor) { |
| final mismatchedFlagsBits = member.modifiers.flags & |
| (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT); |
| if (mismatchedFlagsBits != 0) { |
| final mismatchedFlags = |
| new Modifiers.withFlags(null, mismatchedFlagsBits); |
| reporter.reportErrorMessage( |
| member, |
| MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS, |
| {'modifiers': mismatchedFlags}); |
| } |
| if (member.modifiers.isConst) { |
| constConstructors.add(member); |
| } |
| } |
| if (member.isField) { |
| if (member.modifiers.isConst && !member.modifiers.isStatic) { |
| reporter.reportErrorMessage( |
| member, MessageKind.ILLEGAL_CONST_FIELD_MODIFIER); |
| } |
| if (!member.modifiers.isStatic && !member.modifiers.isFinal) { |
| nonFinalInstanceFields.add(member); |
| } |
| } |
| checkAbstractField(member); |
| checkUserDefinableOperator(member); |
| }); |
| }); |
| if (!constConstructors.isEmpty && !nonFinalInstanceFields.isEmpty) { |
| Spannable span = |
| constConstructors.length > 1 ? cls : constConstructors[0]; |
| DiagnosticMessage error = reporter.createMessage( |
| span, |
| MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS, |
| {'className': cls.name}); |
| List<DiagnosticMessage> infos = <DiagnosticMessage>[]; |
| if (constConstructors.length > 1) { |
| for (Element constructor in constConstructors) { |
| infos.add(reporter.createMessage(constructor, |
| MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_CONSTRUCTOR)); |
| } |
| } |
| for (Element field in nonFinalInstanceFields) { |
| infos.add(reporter.createMessage( |
| field, MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_FIELD)); |
| } |
| reporter.reportError(error, infos); |
| } |
| } |
| |
| void checkAbstractField(Element member) { |
| // Only check for getters. The test can only fail if there is both a setter |
| // and a getter with the same name, and we only need to check each abstract |
| // field once, so we just ignore setters. |
| if (!member.isGetter) return; |
| |
| // Find the associated abstract field. |
| ClassElement classElement = member.enclosingClass; |
| Element lookupElement = classElement.lookupLocalMember(member.name); |
| if (lookupElement == null) { |
| reporter.internalError(member, "No abstract field for accessor"); |
| } else if (!lookupElement.isAbstractField) { |
| if (lookupElement.isMalformed || lookupElement.isAmbiguous) return; |
| reporter.internalError( |
| member, "Inaccessible abstract field for accessor"); |
| } |
| AbstractFieldElement field = lookupElement; |
| |
| GetterElementX getter = field.getter; |
| if (getter == null) return; |
| SetterElementX setter = field.setter; |
| if (setter == null) return; |
| int getterFlags = getter.modifiers.flags | Modifiers.FLAG_ABSTRACT; |
| int setterFlags = setter.modifiers.flags | Modifiers.FLAG_ABSTRACT; |
| if (getterFlags != setterFlags) { |
| final mismatchedFlags = |
| new Modifiers.withFlags(null, getterFlags ^ setterFlags); |
| reporter.reportWarningMessage(field.getter, MessageKind.GETTER_MISMATCH, |
| {'modifiers': mismatchedFlags}); |
| reporter.reportWarningMessage(field.setter, MessageKind.SETTER_MISMATCH, |
| {'modifiers': mismatchedFlags}); |
| } |
| } |
| |
| void checkUserDefinableOperator(Element member) { |
| FunctionElement function = member.asFunctionElement(); |
| if (function == null) return; |
| String value = member.name; |
| if (value == null) return; |
| if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return; |
| |
| bool isMinus = false; |
| int requiredParameterCount; |
| MessageKind messageKind; |
| if (identical(value, 'unary-')) { |
| isMinus = true; |
| messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 0; |
| } else if (isMinusOperator(value)) { |
| isMinus = true; |
| messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 1; |
| } else if (isUnaryOperator(value)) { |
| messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 0; |
| } else if (isBinaryOperator(value)) { |
| messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 1; |
| if (identical(value, '==')) checkOverrideHashCode(member); |
| } else if (isTernaryOperator(value)) { |
| messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY; |
| requiredParameterCount = 2; |
| } else { |
| reporter.internalError( |
| function, 'Unexpected user defined operator $value'); |
| } |
| checkArity(function, requiredParameterCount, messageKind, isMinus); |
| } |
| |
| void checkOverrideHashCode(FunctionElement operatorEquals) { |
| if (operatorEquals.isAbstract) return; |
| ClassElement cls = operatorEquals.enclosingClass; |
| Element hashCodeImplementation = cls.lookupLocalMember('hashCode'); |
| if (hashCodeImplementation != null) return; |
| reporter.reportHintMessage(operatorEquals, |
| MessageKind.OVERRIDE_EQUALS_NOT_HASH_CODE, {'class': cls.name}); |
| } |
| |
| void checkArity(FunctionElement function, int requiredParameterCount, |
| MessageKind messageKind, bool isMinus) { |
| FunctionExpression node = function.node; |
| FunctionSignature signature = function.functionSignature; |
| if (signature.requiredParameterCount != requiredParameterCount) { |
| Node errorNode = node; |
| if (node.parameters != null) { |
| if (isMinus || |
| signature.requiredParameterCount < requiredParameterCount) { |
| // If there are too few parameters, point to the whole parameter list. |
| // For instance |
| // |
| // int operator +() {} |
| // ^^ |
| // |
| // int operator []=(value) {} |
| // ^^^^^^^ |
| // |
| // For operator -, always point the whole parameter list, like |
| // |
| // int operator -(a, b) {} |
| // ^^^^^^ |
| // |
| // instead of |
| // |
| // int operator -(a, b) {} |
| // ^ |
| // |
| // since the correction might not be to remove 'b' but instead to |
| // remove 'a, b'. |
| errorNode = node.parameters; |
| } else { |
| errorNode = node.parameters.nodes.skip(requiredParameterCount).head; |
| } |
| } |
| reporter.reportErrorMessage( |
| errorNode, messageKind, {'operatorName': function.name}); |
| } |
| if (signature.optionalParameterCount != 0) { |
| Node errorNode = |
| node.parameters.nodes.skip(signature.requiredParameterCount).head; |
| if (signature.optionalParametersAreNamed) { |
| reporter.reportErrorMessage( |
| errorNode, |
| MessageKind.OPERATOR_NAMED_PARAMETERS, |
| {'operatorName': function.name}); |
| } else { |
| reporter.reportErrorMessage( |
| errorNode, |
| MessageKind.OPERATOR_OPTIONAL_PARAMETERS, |
| {'operatorName': function.name}); |
| } |
| } |
| } |
| |
| reportErrorWithContext(Element errorneousElement, MessageKind errorMessage, |
| Element contextElement, MessageKind contextMessage) { |
| reporter.reportError( |
| reporter.createMessage(errorneousElement, errorMessage, { |
| 'memberName': contextElement.name, |
| 'className': contextElement.enclosingClass.name |
| }), |
| <DiagnosticMessage>[ |
| reporter.createMessage(contextElement, contextMessage), |
| ]); |
| } |
| |
| FunctionSignature resolveSignature(FunctionElementX element) { |
| MessageKind defaultValuesError = null; |
| if (element.isFactoryConstructor) { |
| FunctionExpression body = element.parseNode(parsingContext); |
| if (body.isRedirectingFactory) { |
| defaultValuesError = MessageKind.REDIRECTING_FACTORY_WITH_DEFAULT; |
| } |
| } |
| return reporter.withCurrentElement(element, () { |
| FunctionExpression node = element.parseNode(parsingContext); |
| return measure(() => SignatureResolver.analyze( |
| resolution, |
| element.enclosingElement.buildScope(), |
| node.typeVariables, |
| node.parameters, |
| node.returnType, |
| element, |
| new ResolutionRegistry( |
| resolution.target, _ensureTreeElements(element)), |
| defaultValuesError: defaultValuesError, |
| createRealParameters: true)); |
| }); |
| } |
| |
| WorldImpact resolveTypedef(TypedefElementX element) { |
| if (element.isResolved) return const ResolutionImpact(); |
| world.registerTypedef(element); |
| return _resolveTypeDeclaration(element, () { |
| ResolutionRegistry registry = new ResolutionRegistry( |
| resolution.target, _ensureTreeElements(element)); |
| return reporter.withCurrentElement(element, () { |
| return measure(() { |
| assert(element.resolutionState == STATE_NOT_STARTED); |
| element.resolutionState = STATE_STARTED; |
| Typedef node = element.parseNode(parsingContext); |
| TypedefResolverVisitor visitor = |
| new TypedefResolverVisitor(resolution, element, registry); |
| visitor.visit(node); |
| element.resolutionState = STATE_DONE; |
| return registry.impactBuilder; |
| }); |
| }); |
| }); |
| } |
| |
| void resolveMetadataAnnotation(MetadataAnnotationX annotation) { |
| reporter.withCurrentElement( |
| annotation.annotatedElement, |
| () => measure(() { |
| assert(annotation.resolutionState == STATE_NOT_STARTED); |
| annotation.resolutionState = STATE_STARTED; |
| |
| Node node = annotation.parseNode(parsingContext); |
| Element annotatedElement = annotation.annotatedElement; |
| AnalyzableElement context = annotatedElement.analyzableElement; |
| ClassElement classElement = annotatedElement.enclosingClass; |
| if (classElement != null) { |
| // The annotation is resolved in the scope of [classElement]. |
| classElement.ensureResolved(resolution); |
| } |
| assert(invariant(node, context != null, |
| message: "No context found for metadata annotation " |
| "on $annotatedElement.")); |
| ResolverVisitor visitor = |
| visitorFor(context, useEnclosingScope: true); |
| ResolutionRegistry registry = visitor.registry; |
| node.accept(visitor); |
| // TODO(johnniwinther): Avoid passing the [TreeElements] to |
| // [compileMetadata]. |
| ConstantExpression constant = constantCompiler.compileMetadata( |
| annotation, node, registry.mapping); |
| switch (constant.kind) { |
| case ConstantExpressionKind.CONSTRUCTED: |
| ConstructedConstantExpression constructedConstant = constant; |
| if (constructedConstant.type.isGeneric && |
| !constructedConstant.type.isRaw) { |
| // Const constructor calls cannot have type arguments. |
| // TODO(24312): Remove this. |
| reporter.reportErrorMessage( |
| node, MessageKind.INVALID_METADATA_GENERIC); |
| constant = new ErroneousConstantExpression(); |
| } |
| break; |
| case ConstantExpressionKind.VARIABLE: |
| case ConstantExpressionKind.ERRONEOUS: |
| break; |
| default: |
| reporter.reportErrorMessage( |
| node, MessageKind.INVALID_METADATA); |
| constant = new ErroneousConstantExpression(); |
| break; |
| } |
| annotation.constant = constant; |
| |
| constantCompiler.evaluate(annotation.constant); |
| // TODO(johnniwinther): Register the relation between the annotation |
| // and the annotated element instead. This will allow the backend to |
| // retrieve the backend constant and only register metadata on the |
| // elements for which it is needed. (Issue 17732). |
| annotation.resolutionState = STATE_DONE; |
| })); |
| } |
| |
| List<MetadataAnnotation> resolveMetadata( |
| Element element, VariableDefinitions node) { |
| List<MetadataAnnotation> metadata = <MetadataAnnotation>[]; |
| for (Metadata annotation in node.metadata.nodes) { |
| ParameterMetadataAnnotation metadataAnnotation = |
| new ParameterMetadataAnnotation(annotation); |
| metadataAnnotation.annotatedElement = element; |
| metadata.add(metadataAnnotation.ensureResolved(resolution)); |
| } |
| return metadata; |
| } |
| } |
| |
| TreeElements _ensureTreeElements(AnalyzableElementX element) { |
| if (element._treeElements == null) { |
| element._treeElements = new TreeElementMapping(element); |
| } |
| return element._treeElements; |
| } |
| |
| abstract class AnalyzableElementX implements AnalyzableElement { |
| TreeElements _treeElements; |
| |
| bool get hasTreeElements => _treeElements != null; |
| |
| TreeElements get treeElements { |
| assert(invariant(this, _treeElements != null, |
| message: "TreeElements have not been computed for $this.")); |
| return _treeElements; |
| } |
| |
| void reuseElement() { |
| _treeElements = null; |
| } |
| } |