| // Copyright (c) 2017, 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_strategy; |
| |
| import '../common.dart'; |
| import '../common_elements.dart'; |
| import '../common/backend_api.dart'; |
| import '../common/names.dart'; |
| import '../common/resolution.dart'; |
| import '../common/tasks.dart'; |
| import '../compiler.dart'; |
| import '../constants/values.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/modelx.dart'; |
| import '../elements/resolution_types.dart'; |
| import '../environment.dart'; |
| import '../enqueue.dart'; |
| import '../frontend_strategy.dart'; |
| import '../js_backend/backend.dart'; |
| import '../js_backend/backend_usage.dart'; |
| import '../js_backend/custom_elements_analysis.dart'; |
| import '../js_backend/mirrors_analysis.dart'; |
| import '../js_backend/mirrors_data.dart'; |
| import '../js_backend/native_data.dart'; |
| import '../js_backend/no_such_method_registry.dart'; |
| import '../library_loader.dart'; |
| import '../native/resolver.dart'; |
| import '../serialization/task.dart'; |
| import '../patch_parser.dart'; |
| import '../resolved_uri_translator.dart'; |
| import '../universe/call_structure.dart'; |
| import '../universe/use.dart'; |
| import '../universe/world_builder.dart'; |
| import '../universe/world_impact.dart'; |
| |
| /// [FrontendStrategy] that loads '.dart' files and creates a resolved element |
| /// model using the resolver. |
| class ResolutionFrontEndStrategy implements FrontEndStrategy { |
| final Compiler _compiler; |
| final ElementEnvironment elementEnvironment; |
| AnnotationProcessor _annotationProcessor; |
| |
| ResolutionFrontEndStrategy(this._compiler) |
| : elementEnvironment = new _CompilerElementEnvironment(_compiler); |
| |
| LibraryLoaderTask createLibraryLoader( |
| ResolvedUriTranslator uriTranslator, |
| ScriptLoader scriptLoader, |
| ElementScanner scriptScanner, |
| LibraryDeserializer deserializer, |
| PatchResolverFunction patchResolverFunc, |
| PatchParserTask patchParser, |
| Environment environment, |
| DiagnosticReporter reporter, |
| Measurer measurer) { |
| return new ResolutionLibraryLoaderTask( |
| uriTranslator, |
| scriptLoader, |
| scriptScanner, |
| deserializer, |
| patchResolverFunc, |
| patchParser, |
| environment, |
| reporter, |
| measurer); |
| } |
| |
| AnnotationProcessor get annotationProcesser => |
| _annotationProcessor ??= new _ElementAnnotationProcessor(_compiler); |
| |
| @override |
| NativeClassFinder createNativeClassFinder(NativeBasicData nativeBasicData) { |
| return new ResolutionNativeClassFinder( |
| _compiler.resolution, |
| _compiler.reporter, |
| elementEnvironment, |
| _compiler.commonElements, |
| nativeBasicData); |
| } |
| |
| NoSuchMethodResolver createNoSuchMethodResolver() => |
| new NoSuchMethodResolverImpl(); |
| |
| CustomElementsResolutionAnalysis createCustomElementsResolutionAnalysis( |
| NativeBasicData nativeBasicData, |
| BackendUsageBuilder backendUsageBuilder) { |
| return new CustomElementsResolutionAnalysis( |
| _compiler.resolution, |
| _compiler.backend.constantSystem, |
| _compiler.commonElements, |
| nativeBasicData, |
| backendUsageBuilder); |
| } |
| |
| MirrorsDataBuilder createMirrorsDataBuilder() { |
| return new MirrorsDataImpl( |
| _compiler, _compiler.options, _compiler.commonElements); |
| } |
| |
| MirrorsResolutionAnalysis createMirrorsResolutionAnalysis( |
| JavaScriptBackend backend) => |
| new MirrorsResolutionAnalysisImpl(backend, _compiler.resolution); |
| |
| RuntimeTypesNeedBuilder createRuntimeTypesNeedBuilder() { |
| return new ResolutionRuntimeTypesNeedBuilderImpl( |
| elementEnvironment, _compiler.types); |
| } |
| |
| ResolutionWorldBuilder createResolutionWorldBuilder( |
| NativeBasicData nativeBasicData, |
| SelectorConstraintsStrategy selectorConstraintsStrategy) { |
| return new ElementResolutionWorldBuilder( |
| _compiler.backend, _compiler.resolution, selectorConstraintsStrategy); |
| } |
| |
| WorkItemBuilder createResolutionWorkItemBuilder( |
| ImpactTransformer impactTransformer) { |
| return new ResolutionWorkItemBuilder(_compiler.resolution); |
| } |
| |
| FunctionEntity computeMain( |
| LibraryElement mainApp, WorldImpactBuilder impactBuilder) { |
| if (mainApp == null) return null; |
| MethodElement mainFunction; |
| Element main = mainApp.findExported(Identifiers.main); |
| ErroneousElement errorElement = null; |
| if (main == null) { |
| if (_compiler.options.analyzeOnly) { |
| if (!_compiler.analyzeAll) { |
| errorElement = new ErroneousElementX(MessageKind.CONSIDER_ANALYZE_ALL, |
| {'main': Identifiers.main}, Identifiers.main, mainApp); |
| } |
| } else { |
| // Compilation requires a main method. |
| errorElement = new ErroneousElementX(MessageKind.MISSING_MAIN, |
| {'main': Identifiers.main}, Identifiers.main, mainApp); |
| } |
| mainFunction = _compiler.backend.helperForMissingMain(); |
| } else if (main.isError && main.isSynthesized) { |
| if (main is ErroneousElement) { |
| errorElement = main; |
| } else { |
| _compiler.reporter |
| .internalError(main, 'Problem with ${Identifiers.main}.'); |
| } |
| mainFunction = _compiler.backend.helperForBadMain(); |
| } else if (!main.isFunction) { |
| errorElement = new ErroneousElementX(MessageKind.MAIN_NOT_A_FUNCTION, |
| {'main': Identifiers.main}, Identifiers.main, main); |
| mainFunction = _compiler.backend.helperForBadMain(); |
| } else { |
| mainFunction = main; |
| mainFunction.computeType(_compiler.resolution); |
| FunctionSignature parameters = mainFunction.functionSignature; |
| if (parameters.requiredParameterCount > 2) { |
| int index = 0; |
| parameters.orderedForEachParameter((Element parameter) { |
| if (index++ < 2) return; |
| errorElement = new ErroneousElementX( |
| MessageKind.MAIN_WITH_EXTRA_PARAMETER, |
| {'main': Identifiers.main}, |
| Identifiers.main, |
| parameter); |
| // Don't warn about main not being used: |
| impactBuilder.registerStaticUse( |
| new StaticUse.staticInvoke(mainFunction, CallStructure.NO_ARGS)); |
| |
| mainFunction = _compiler.backend.helperForMainArity(); |
| }); |
| } |
| } |
| if (mainFunction == null) { |
| if (errorElement == null && |
| !_compiler.options.analyzeOnly && |
| !_compiler.analyzeAll) { |
| _compiler.reporter |
| .internalError(mainApp, "Problem with '${Identifiers.main}'."); |
| } else { |
| mainFunction = errorElement; |
| } |
| } |
| if (errorElement != null && |
| errorElement.isSynthesized && |
| !mainApp.isSynthesized) { |
| _compiler.reporter.reportWarningMessage(errorElement, |
| errorElement.messageKind, errorElement.messageArguments); |
| } |
| MethodElement mainMethod; |
| if (mainFunction != null && !mainFunction.isMalformed) { |
| mainFunction.computeType(_compiler.resolution); |
| mainMethod = mainFunction; |
| } |
| return mainMethod; |
| } |
| } |
| |
| /// An element environment base on a [Compiler]. |
| class _CompilerElementEnvironment implements ElementEnvironment { |
| final Compiler _compiler; |
| |
| _CompilerElementEnvironment(this._compiler); |
| |
| LibraryProvider get _libraryProvider => _compiler.libraryLoader; |
| Resolution get _resolution => _compiler.resolution; |
| |
| ResolutionDynamicType get dynamicType => const ResolutionDynamicType(); |
| |
| @override |
| LibraryEntity get mainLibrary => _compiler.mainApp; |
| |
| @override |
| FunctionEntity get mainFunction => _compiler.mainFunction; |
| |
| @override |
| Iterable<LibraryEntity> get libraries => _compiler.libraryLoader.libraries; |
| |
| @override |
| ResolutionInterfaceType getThisType(ClassElement cls) { |
| cls.ensureResolved(_resolution); |
| return cls.thisType; |
| } |
| |
| @override |
| ResolutionInterfaceType getRawType(ClassElement cls) { |
| cls.ensureResolved(_resolution); |
| return cls.rawType; |
| } |
| |
| @override |
| bool isGenericClass(ClassEntity cls) { |
| return getThisType(cls).typeArguments.isNotEmpty; |
| } |
| |
| @override |
| ResolutionDartType getTypeVariableBound(TypeVariableElement typeVariable) { |
| return typeVariable.bound; |
| } |
| |
| @override |
| ResolutionInterfaceType createInterfaceType( |
| ClassElement cls, List<ResolutionDartType> typeArguments) { |
| cls.ensureResolved(_resolution); |
| return cls.thisType.createInstantiation(typeArguments); |
| } |
| |
| @override |
| bool isSubtype(ResolutionDartType a, ResolutionDartType b) { |
| return _compiler.types.isSubtype(a, b); |
| } |
| |
| @override |
| MemberElement lookupClassMember(ClassElement cls, String name, |
| {bool setter: false, bool required: false}) { |
| cls.ensureResolved(_resolution); |
| Element member = cls.implementation.lookupLocalMember(name); |
| if (member != null && member.isAbstractField) { |
| AbstractFieldElement abstractField = member; |
| if (setter) { |
| member = abstractField.setter; |
| } else { |
| member = abstractField.getter; |
| } |
| if (member == null && required) { |
| throw new SpannableAssertionFailure( |
| cls, |
| "The class '${cls.name}' does not contain required " |
| "${setter ? 'setter' : 'getter'}: '$name'."); |
| } |
| } |
| if (member == null && required) { |
| throw new SpannableAssertionFailure( |
| cls, |
| "The class '${cls.name}' does not " |
| "contain required member: '$name'."); |
| } |
| return member?.declaration; |
| } |
| |
| @override |
| ConstructorElement lookupConstructor(ClassElement cls, String name, |
| {bool required: false}) { |
| cls.ensureResolved(_resolution); |
| ConstructorElement constructor = cls.implementation.lookupConstructor(name); |
| if (constructor == null && required) { |
| throw new SpannableAssertionFailure( |
| cls, |
| "The class '${cls.name}' does not contain " |
| "required constructor: '$name'."); |
| } |
| return constructor?.declaration; |
| } |
| |
| @override |
| void forEachClassMember( |
| ClassElement cls, void f(ClassElement declarer, MemberElement member)) { |
| cls.ensureResolved(_resolution); |
| cls.forEachMember((ClassElement declarer, MemberElement member) { |
| if (member.isSynthesized) return; |
| if (member.isMalformed) return; |
| f(declarer, member); |
| }, includeSuperAndInjectedMembers: true); |
| } |
| |
| @override |
| ClassEntity getSuperClass(ClassElement cls) { |
| cls.ensureResolved(_resolution); |
| return cls.superclass; |
| } |
| |
| @override |
| void forEachSupertype( |
| ClassElement cls, void f(ResolutionInterfaceType supertype)) { |
| cls.allSupertypes |
| .forEach((ResolutionInterfaceType supertype) => f(supertype)); |
| } |
| |
| @override |
| void forEachMixin(ClassElement cls, void f(ClassElement mixin)) { |
| for (; cls != null; cls = cls.superclass) { |
| if (cls.isMixinApplication) { |
| MixinApplicationElement mixinApplication = cls; |
| f(mixinApplication.mixin); |
| } |
| } |
| } |
| |
| @override |
| MemberElement lookupLibraryMember(LibraryElement library, String name, |
| {bool setter: false, bool required: false}) { |
| Element member = library.implementation.findLocal(name); |
| if (member != null && member.isAbstractField) { |
| AbstractFieldElement abstractField = member; |
| if (setter) { |
| member = abstractField.setter; |
| } else { |
| member = abstractField.getter; |
| } |
| if (member == null && required) { |
| throw new SpannableAssertionFailure( |
| library, |
| "The library '${library.canonicalUri}' does not contain required " |
| "${setter ? 'setter' : 'getter'}: '$name'."); |
| } |
| } |
| if (member == null && required) { |
| throw new SpannableAssertionFailure( |
| member, |
| "The library '${library.libraryName}' does not " |
| "contain required member: '$name'."); |
| } |
| return member?.declaration; |
| } |
| |
| @override |
| ClassElement lookupClass(LibraryElement library, String name, |
| {bool required: false}) { |
| ClassElement cls = library.implementation.findLocal(name); |
| if (cls == null && required) { |
| throw new SpannableAssertionFailure( |
| library, |
| "The library '${library.libraryName}' does not " |
| "contain required class: '$name'."); |
| } |
| return cls?.declaration; |
| } |
| |
| @override |
| void forEachClass(LibraryElement library, void f(ClassElement cls)) { |
| library.implementation.forEachLocalMember((member) { |
| if (member.isClass) { |
| f(member); |
| } |
| }); |
| } |
| |
| @override |
| LibraryElement lookupLibrary(Uri uri, {bool required: false}) { |
| LibraryElement library = _libraryProvider.lookupLibrary(uri); |
| // If the script of the library is synthesized, the library does not exist |
| // and we do not try to load the helpers. |
| // |
| // This could for example happen if dart:async is disabled, then loading it |
| // should not try to find the given element. |
| if (library != null && library.isSynthesized) { |
| return null; |
| } |
| if (library == null && required) { |
| throw new SpannableAssertionFailure( |
| NO_LOCATION_SPANNABLE, "The library '${uri}' was not found."); |
| } |
| return library; |
| } |
| |
| @override |
| CallStructure getCallStructure(MethodElement method) { |
| ResolutionFunctionType type = method.computeType(_resolution); |
| return new CallStructure( |
| type.parameterTypes.length + |
| type.optionalParameterTypes.length + |
| type.namedParameterTypes.length, |
| type.namedParameters); |
| } |
| |
| @override |
| bool isDeferredLoadLibraryGetter(MemberElement member) { |
| return member.isDeferredLoaderGetter; |
| } |
| |
| @override |
| ResolutionFunctionType getFunctionType(MethodElement method) { |
| method.computeType(_resolution); |
| return method.type; |
| } |
| |
| @override |
| ResolutionFunctionType getLocalFunctionType(LocalFunctionElement function) { |
| return function.type; |
| } |
| |
| @override |
| ResolutionDartType getUnaliasedType(ResolutionDartType type) { |
| type.computeUnaliased(_resolution); |
| return type.unaliased; |
| } |
| |
| @override |
| Iterable<ConstantValue> getMemberMetadata(MemberElement element) { |
| List<ConstantValue> values = <ConstantValue>[]; |
| _compiler.reporter.withCurrentElement(element, () { |
| for (MetadataAnnotation metadata in element.implementation.metadata) { |
| metadata.ensureResolved(_compiler.resolution); |
| assert(invariant(metadata, metadata.constant != null, |
| message: "Unevaluated metadata constant.")); |
| ConstantValue value = |
| _compiler.constants.getConstantValue(metadata.constant); |
| values.add(value); |
| } |
| }); |
| return values; |
| } |
| } |
| |
| /// AST-based logic for processing annotations. These annotations are processed |
| /// very early in the compilation pipeline, typically this is before resolution |
| /// is complete. Because of that this processor does a lightweight parse of the |
| /// annotation (which is restricted to a limited subset of the annotation |
| /// syntax), and, once resolution completes, it validates that the parsed |
| /// annotations correspond to the correct element. |
| class _ElementAnnotationProcessor implements AnnotationProcessor { |
| Compiler _compiler; |
| |
| _ElementAnnotationProcessor(this._compiler); |
| |
| CommonElements get _commonElements => _compiler.commonElements; |
| |
| /// Check whether [cls] has a `@Native(...)` annotation, and if so, set its |
| /// native name from the annotation. |
| void extractNativeAnnotations( |
| LibraryElement library, NativeBasicDataBuilder nativeBasicDataBuilder) { |
| library.forEachLocalMember((Element element) { |
| if (element.isClass) { |
| EagerAnnotationHandler.checkAnnotation(_compiler, element, |
| new NativeAnnotationHandler(nativeBasicDataBuilder)); |
| } |
| }); |
| } |
| |
| void extractJsInteropAnnotations( |
| LibraryElement library, NativeBasicDataBuilder nativeBasicDataBuilder) { |
| bool checkJsInteropAnnotation(Element element) { |
| return EagerAnnotationHandler.checkAnnotation( |
| _compiler, element, const JsInteropAnnotationHandler()); |
| } |
| |
| if (checkJsInteropAnnotation(library)) { |
| nativeBasicDataBuilder.markAsJsInteropLibrary(library); |
| } |
| library.forEachLocalMember((Element element) { |
| if (element.isClass) { |
| ClassElement cls = element; |
| if (checkJsInteropAnnotation(element)) { |
| nativeBasicDataBuilder.markAsJsInteropClass(cls); |
| } |
| } |
| }); |
| } |
| |
| void processJsInteropAnnotations( |
| NativeBasicData nativeBasicData, NativeDataBuilder nativeDataBuilder) { |
| if (_commonElements.jsAnnotationClass == null) return; |
| |
| ClassElement cls = _commonElements.jsAnnotationClass; |
| FieldElement nameField = cls.lookupMember('name'); |
| |
| /// Resolves the metadata of [element] and returns the name of the `JS(...)` |
| /// annotation for js interop, if found. |
| String processJsInteropAnnotation(Element element) { |
| for (MetadataAnnotation annotation in element.implementation.metadata) { |
| // TODO(johnniwinther): Avoid processing unresolved elements. |
| if (annotation.constant == null) continue; |
| ConstantValue constant = |
| _compiler.constants.getConstantValue(annotation.constant); |
| if (constant == null || constant is! ConstructedConstantValue) continue; |
| ConstructedConstantValue constructedConstant = constant; |
| if (constructedConstant.type.element == |
| _commonElements.jsAnnotationClass) { |
| ConstantValue value = constructedConstant.fields[nameField]; |
| String name; |
| if (value.isString) { |
| StringConstantValue stringValue = value; |
| name = stringValue.primitiveValue.slowToString(); |
| } else { |
| // TODO(jacobr): report a warning if the value is not a String. |
| name = ''; |
| } |
| return name; |
| } |
| } |
| return null; |
| } |
| |
| void checkFunctionParameters(MethodElement fn) { |
| if (fn.hasFunctionSignature && |
| fn.functionSignature.optionalParametersAreNamed) { |
| _compiler.reporter.reportErrorMessage( |
| fn, |
| MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS, |
| {'method': fn.name}); |
| } |
| } |
| |
| bool hasAnonymousAnnotation(Element element) { |
| if (_commonElements.jsAnonymousClass == null) return false; |
| return element.metadata.any((MetadataAnnotation annotation) { |
| ConstantValue constant = |
| _compiler.constants.getConstantValue(annotation.constant); |
| if (constant == null || constant is! ConstructedConstantValue) |
| return false; |
| ConstructedConstantValue constructedConstant = constant; |
| return constructedConstant.type.element == |
| _commonElements.jsAnonymousClass; |
| }); |
| } |
| |
| void processJsInteropAnnotationsInLibrary(LibraryElement library) { |
| String libraryName = processJsInteropAnnotation(library); |
| if (libraryName != null) { |
| nativeDataBuilder.setJsInteropLibraryName(library, libraryName); |
| } |
| library.implementation.forEachLocalMember((Element element) { |
| if (element is MemberElement) { |
| String memberName = processJsInteropAnnotation(element); |
| if (memberName != null) { |
| nativeDataBuilder.setJsInteropMemberName(element, memberName); |
| if (element is MethodElement) { |
| checkFunctionParameters(element); |
| } |
| } |
| } |
| |
| if (!element.isClass) return; |
| |
| ClassElement classElement = element; |
| String className = processJsInteropAnnotation(classElement); |
| if (className != null) { |
| nativeDataBuilder.setJsInteropClassName(classElement, className); |
| } |
| if (!nativeBasicData.isJsInteropClass(classElement)) return; |
| |
| bool isAnonymous = hasAnonymousAnnotation(classElement); |
| if (isAnonymous) { |
| nativeDataBuilder.markJsInteropClassAsAnonymous(classElement); |
| } |
| |
| // Skip classes that are completely unreachable. This should only happen |
| // when all of jsinterop types are unreachable from main. |
| if (!_compiler.resolutionWorldBuilder.isImplemented(classElement)) { |
| return; |
| } |
| |
| if (!classElement |
| .implementsInterface(_commonElements.jsJavaScriptObjectClass)) { |
| _compiler.reporter.reportErrorMessage(classElement, |
| MessageKind.JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS, { |
| 'cls': classElement.name, |
| 'superclass': classElement.superclass.name |
| }); |
| } |
| |
| classElement |
| .forEachMember((ClassElement classElement, MemberElement member) { |
| String memberName = processJsInteropAnnotation(member); |
| if (memberName != null) { |
| nativeDataBuilder.setJsInteropMemberName(member, memberName); |
| } |
| |
| if (!member.isSynthesized && |
| nativeBasicData.isJsInteropClass(classElement) && |
| member is MethodElement) { |
| MethodElement fn = member; |
| if (!fn.isExternal && |
| !fn.isAbstract && |
| !fn.isConstructor && |
| !fn.isStatic) { |
| _compiler.reporter.reportErrorMessage( |
| fn, |
| MessageKind.JS_INTEROP_CLASS_NON_EXTERNAL_MEMBER, |
| {'cls': classElement.name, 'member': member.name}); |
| } |
| |
| if (fn.isFactoryConstructor && isAnonymous) { |
| fn.functionSignature |
| .orderedForEachParameter((ParameterElement parameter) { |
| if (!parameter.isNamed) { |
| _compiler.reporter.reportErrorMessage( |
| parameter, |
| MessageKind |
| .JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS, |
| {'parameter': parameter.name, 'cls': classElement.name}); |
| } |
| }); |
| } else { |
| checkFunctionParameters(fn); |
| } |
| } |
| }); |
| }); |
| } |
| |
| _compiler.libraryLoader.libraries |
| .forEach(processJsInteropAnnotationsInLibrary); |
| } |
| } |