blob: 0b51c6fa36a1110ac8f31f2b593d1f28447d235e [file] [log] [blame]
// 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 'package:front_end/src/fasta/scanner.dart' show Token;
import '../../compiler_new.dart' as api;
import '../common.dart';
import '../common/backend_api.dart';
import '../common/names.dart';
import '../common/resolution.dart';
import '../common/tasks.dart';
import '../common/work.dart';
import '../common_elements.dart';
import '../compiler.dart';
import '../constants/expressions.dart' show ConstantExpression;
import '../constants/values.dart';
import '../deferred_load.dart' show DeferredLoadTask;
import '../elements/elements.dart';
import '../elements/entities.dart';
import '../elements/modelx.dart';
import '../elements/resolution_types.dart';
import '../elements/types.dart';
import '../enqueue.dart';
import '../environment.dart';
import '../frontend_strategy.dart';
import '../js_backend/backend.dart';
import '../js_backend/backend_usage.dart';
import '../js_backend/interceptor_data.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 '../js_backend/runtime_types.dart';
import '../library_loader.dart';
import '../native/enqueue.dart' show NativeResolutionEnqueuer;
import '../native/resolver.dart';
import '../patch_parser.dart';
import '../resolved_uri_translator.dart';
import '../tree/tree.dart' show Node;
import '../universe/call_structure.dart';
import '../universe/class_hierarchy_builder.dart';
import '../universe/use.dart';
import '../universe/world_builder.dart';
import '../universe/world_impact.dart';
import 'deferred_load.dart';
import 'no_such_method_resolver.dart';
/// [FrontendStrategy] that loads '.dart' files and creates a resolved element
/// model using the resolver.
class ResolutionFrontEndStrategy extends FrontendStrategyBase
with ComputeSpannableMixin {
final Compiler _compiler;
final _CompilerElementEnvironment _elementEnvironment;
final CommonElements commonElements;
RuntimeTypesNeedBuilder _runtimeTypesNeedBuilder;
AnnotationProcessor _annotationProcessor;
factory ResolutionFrontEndStrategy(Compiler compiler) {
ElementEnvironment elementEnvironment =
new _CompilerElementEnvironment(compiler);
CommonElements commonElements = new CommonElements(elementEnvironment);
return new ResolutionFrontEndStrategy.internal(
compiler, elementEnvironment, commonElements);
}
ResolutionFrontEndStrategy.internal(
this._compiler, this._elementEnvironment, this.commonElements);
ElementEnvironment get elementEnvironment => _elementEnvironment;
DartTypes get dartTypes => _compiler.resolution.types;
LibraryLoaderTask createLibraryLoader(
ResolvedUriTranslator uriTranslator,
ScriptLoader scriptLoader,
api.CompilerInput compilerInput,
ElementScanner scriptScanner,
PatchResolverFunction patchResolverFunc,
PatchParserTask patchParser,
Environment environment,
DiagnosticReporter reporter,
Measurer measurer) {
return new ResolutionLibraryLoaderTask(
uriTranslator,
scriptLoader,
scriptScanner,
patchResolverFunc,
patchParser,
environment,
reporter,
measurer);
}
AnnotationProcessor get annotationProcesser => _annotationProcessor ??=
new _ElementAnnotationProcessor(_compiler, nativeBasicDataBuilder);
DeferredLoadTask createDeferredLoadTask(Compiler compiler) =>
new AstDeferredLoadTask(_compiler);
@override
NativeClassFinder createNativeClassFinder(NativeBasicData nativeBasicData) {
return new ResolutionNativeClassFinder(
_compiler.resolution,
_compiler.reporter,
elementEnvironment,
commonElements,
nativeBasicData);
}
NoSuchMethodResolver createNoSuchMethodResolver() =>
new ResolutionNoSuchMethodResolver();
MirrorsDataBuilder createMirrorsDataBuilder() {
return new ResolutionMirrorsData(
_compiler, elementEnvironment, commonElements);
}
MirrorsResolutionAnalysis createMirrorsResolutionAnalysis(
JavaScriptBackend backend) =>
new MirrorsResolutionAnalysisImpl(backend, _compiler.resolution);
RuntimeTypesNeedBuilder createRuntimeTypesNeedBuilder() {
return _runtimeTypesNeedBuilder ??= _compiler.options.disableRtiOptimization
? const TrivialRuntimeTypesNeedBuilder()
: new ResolutionRuntimeTypesNeedBuilderImpl(
elementEnvironment, dartTypes);
}
RuntimeTypesNeedBuilder get runtimeTypesNeedBuilderForTesting =>
_runtimeTypesNeedBuilder;
ResolutionWorldBuilder createResolutionWorldBuilder(
NativeBasicData nativeBasicData,
NativeDataBuilder nativeDataBuilder,
InterceptorDataBuilder interceptorDataBuilder,
BackendUsageBuilder backendUsageBuilder,
RuntimeTypesNeedBuilder rtiNeedBuilder,
NativeResolutionEnqueuer nativeResolutionEnqueuer,
NoSuchMethodRegistry noSuchMethodRegistry,
SelectorConstraintsStrategy selectorConstraintsStrategy,
ClassHierarchyBuilder classHierarchyBuilder,
ClassQueries classQueries) {
return new ElementResolutionWorldBuilder(
_compiler.backend,
_compiler.resolution,
nativeBasicData,
nativeDataBuilder,
interceptorDataBuilder,
backendUsageBuilder,
rtiNeedBuilder,
nativeResolutionEnqueuer,
noSuchMethodRegistry,
selectorConstraintsStrategy,
classHierarchyBuilder,
classQueries);
}
WorkItemBuilder createResolutionWorkItemBuilder(
NativeBasicData nativeBasicData,
NativeDataBuilder nativeDataBuilder,
ImpactTransformer impactTransformer,
Map<Entity, WorldImpact> impactCache) {
return new ResolutionWorkItemBuilder(_compiler.resolution);
}
ClassQueries createClassQueries() {
return new ElementClassQueries(commonElements);
}
FunctionEntity computeMain(
covariant LibraryElement mainApp, WorldImpactBuilder impactBuilder) {
_elementEnvironment._mainLibrary = mainApp;
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 = commonElements.missingMain;
} else if (main.isError && main.isSynthesized) {
if (main is ErroneousElement) {
errorElement = main;
} else {
_compiler.reporter
.internalError(main, 'Problem with ${Identifiers.main}.');
}
mainFunction = commonElements.badMain;
} else if (!main.isFunction) {
errorElement = new ErroneousElementX(MessageKind.MAIN_NOT_A_FUNCTION,
{'main': Identifiers.main}, Identifiers.main, main);
mainFunction = commonElements.badMain;
} 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 = commonElements.mainHasTooManyParameters;
});
}
}
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;
}
_elementEnvironment._mainFunction = mainMethod;
return mainMethod;
}
}
class ComputeSpannableMixin {
SourceSpan spanFromToken(Element currentElement, Token token) =>
_spanFromTokens(currentElement, token, token);
SourceSpan _spanFromTokens(Element currentElement, Token begin, Token end,
[Uri uri]) {
if (begin == null || end == null) {
// TODO(ahe): We can almost always do better. Often it is only
// end that is null. Otherwise, we probably know the current
// URI.
throw 'Cannot find tokens to produce error message.';
}
if (uri == null && currentElement != null) {
if (currentElement is! Element) {
throw 'Can only find tokens from an Element.';
}
Element element = currentElement;
uri = element.compilationUnit.script.resourceUri;
String message;
assert(() {
bool sameToken(Token token, Token sought) {
if (token == sought) return true;
if (token.stringValue == '[') {
// `[` is converted to `[]` in the parser when needed.
return sought.stringValue == '[]' &&
token.charOffset <= sought.charOffset &&
sought.charOffset < token.charEnd;
}
if (token.stringValue == '>>') {
// `>>` is converted to `>` in the parser when needed.
return sought.stringValue == '>' &&
token.charOffset <= sought.charOffset &&
sought.charOffset < token.charEnd;
}
return false;
}
/// Check that [begin] and [end] can be found between [from] and [to].
validateToken(Token from, Token to) {
if (from == null || to == null) return true;
bool foundBegin = false;
bool foundEnd = false;
Token token = from;
while (true) {
if (sameToken(token, begin)) {
foundBegin = true;
}
if (sameToken(token, end)) {
foundEnd = true;
}
if (foundBegin && foundEnd) {
return true;
}
if (token == to || token == token.next || token.next == null) {
break;
}
token = token.next;
}
// Create a good message for when the tokens were not found.
StringBuffer sb = new StringBuffer();
sb.write('Invalid current element: $element. ');
sb.write('Looking for ');
sb.write('[${begin} (${begin.hashCode}),');
sb.write('${end} (${end.hashCode})] in');
token = from;
while (true) {
sb.write('\n ${token} (${token.hashCode})');
if (token == to || token == token.next || token.next == null) {
break;
}
token = token.next;
}
message = sb.toString();
return false;
}
if (element.enclosingClass != null &&
element.enclosingClass.isEnumClass) {
// Enums ASTs are synthesized (and give messed up messages).
return true;
}
if (element is AstElement) {
AstElement astElement = element;
if (astElement.hasNode) {
Token from = astElement.node.getBeginToken();
Token to = astElement.node.getEndToken();
if (astElement.metadata.isNotEmpty) {
if (!astElement.metadata.first.hasNode) {
// We might try to report an error while parsing the metadata
// itself.
return true;
}
from = astElement.metadata.first.node.getBeginToken();
}
return validateToken(from, to);
}
}
return true;
}(), failedAt(currentElement, message));
}
return new SourceSpan.fromTokens(uri, begin, end);
}
SourceSpan _spanFromNode(Element currentElement, Node node) {
return _spanFromTokens(
currentElement, node.getBeginToken(), node.getPrefixEndToken());
}
SourceSpan _spanFromElement(Element currentElement, Element element) {
if (element != null && element.sourcePosition != null) {
return element.sourcePosition;
}
while (element != null && element.isSynthesized) {
element = element.enclosingElement;
}
if (element != null &&
element.sourcePosition == null &&
!element.isLibrary &&
!element.isCompilationUnit) {
// Sometimes, the backend fakes up elements that have no
// position. So we use the enclosing element instead. It is
// not a good error location, but cancel really is "internal
// error" or "not implemented yet", so the vicinity is good
// enough for now.
element = element.enclosingElement;
// TODO(ahe): I plan to overhaul this infrastructure anyways.
}
if (element == null) {
element = currentElement;
}
if (element == null) {
return null;
}
if (element.sourcePosition != null) {
return element.sourcePosition;
}
Token position = element.position;
Uri uri = element.compilationUnit.script.resourceUri;
return (position == null)
? new SourceSpan(uri, 0, 0)
: _spanFromTokens(currentElement, position, position, uri);
}
SourceSpan spanFromSpannable(Spannable spannable, Entity currentElement) {
if (spannable is Node) {
return _spanFromNode(currentElement, spannable);
} else if (spannable is Element) {
return _spanFromElement(currentElement, spannable);
} else if (spannable is MetadataAnnotation) {
return spannable.sourcePosition;
} else if (spannable is LocalVariable) {
return spanFromSpannable(spannable.executableContext, currentElement);
}
return null;
}
}
/// An element environment base on a [Compiler].
class _CompilerElementEnvironment extends ElementEnvironment {
final Compiler _compiler;
LibraryEntity _mainLibrary;
FunctionEntity _mainFunction;
_CompilerElementEnvironment(this._compiler);
LibraryProvider get _libraryProvider => _compiler.libraryLoader;
Resolution get _resolution => _compiler.resolution;
ResolutionDynamicType get dynamicType => const ResolutionDynamicType();
@override
LibraryEntity get mainLibrary => _mainLibrary;
@override
FunctionEntity get mainFunction => _mainFunction;
@override
Iterable<LibraryEntity> get libraries => _compiler.libraryLoader.libraries;
@override
String getLibraryName(covariant LibraryElement library) =>
library.libraryName;
@override
ResolutionInterfaceType getThisType(covariant ClassElement cls) {
cls.ensureResolved(_resolution);
return cls.thisType;
}
@override
ResolutionInterfaceType getRawType(covariant ClassElement cls) {
cls.ensureResolved(_resolution);
return cls.rawType;
}
@override
bool isGenericClass(ClassEntity cls) {
return getThisType(cls).typeArguments.isNotEmpty;
}
@override
bool isMixinApplication(covariant ClassElement cls) {
return cls.isMixinApplication;
}
@override
bool isUnnamedMixinApplication(covariant ClassElement cls) {
return cls.isUnnamedMixinApplication;
}
@override
ClassEntity getEffectiveMixinClass(covariant ClassElement cls) {
if (!cls.isMixinApplication) return null;
do {
MixinApplicationElement mixinApplication = cls;
cls = mixinApplication.mixin;
} while (cls.isMixinApplication);
return cls;
}
@override
ResolutionDartType getTypeVariableBound(
covariant TypeVariableElement typeVariable) {
return typeVariable.bound;
}
@override
ResolutionInterfaceType createInterfaceType(covariant ClassElement cls,
covariant List<ResolutionDartType> typeArguments) {
cls.ensureResolved(_resolution);
return cls.thisType.createInstantiation(typeArguments);
}
@override
MemberElement lookupLocalClassMember(covariant 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) {
failedAt(
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(covariant ClassElement cls, String name,
{bool required: false}) {
cls.ensureResolved(_resolution);
ConstructorElement constructor = cls.implementation.lookupConstructor(name);
// TODO(johnniwinther): Skip redirecting factories.
if (constructor == null && required) {
throw new SpannableAssertionFailure(
cls,
"The class '${cls.name}' does not contain "
"required constructor: '$name'.");
}
return constructor?.declaration;
}
@override
void forEachLocalClassMember(
covariant ClassElement cls, void f(MemberElement member)) {
cls.ensureResolved(_resolution);
void handleMember(_member) {
MemberElement member = _member;
if (member.isSynthesized) return;
if (member.isMalformed) return;
if (member.isConstructor) return;
if (!member.isDeclaration) return;
f(member);
}
cls.forEachLocalMember(handleMember);
if (cls.isPatched) {
cls.implementation.forEachLocalMember(handleMember);
}
}
@override
void forEachInjectedClassMember(
covariant ClassElement cls, void f(MemberElement member)) {}
@override
void forEachClassMember(covariant ClassElement cls,
void f(ClassElement declarer, MemberElement member)) {
cls.ensureResolved(_resolution);
cls.forEachMember((ClassElement declarer, _member) {
MemberElement member = _member;
if (member.isSynthesized) return;
if (member.isMalformed) return;
if (member.isConstructor) return;
f(declarer, member);
}, includeSuperAndInjectedMembers: true);
}
@override
void forEachConstructor(
covariant ClassElement cls, void f(ConstructorEntity constructor),
{bool ensureResolved: true}) {
if (ensureResolved) cls.ensureResolved(_resolution);
for (ConstructorElement constructor in cls.implementation.constructors) {
if (ensureResolved) _resolution.ensureResolved(constructor.declaration);
if (constructor.isRedirectingFactory) continue;
f(constructor);
}
}
@override
void forEachConstructorBody(
covariant ClassElement cls, void f(ConstructorBodyEntity constructor)) {
cls.forEachConstructorBody(f);
}
@override
void forEachNestedClosure(
covariant MemberElement member, void f(FunctionEntity closure)) {
for (var closure in member.nestedClosures) {
f(closure);
}
}
@override
ClassEntity getSuperClass(covariant ClassElement cls,
{bool skipUnnamedMixinApplications: false}) {
cls.ensureResolved(_resolution);
ClassElement superclass = cls.superclass;
if (skipUnnamedMixinApplications) {
while (superclass != null && superclass.isUnnamedMixinApplication) {
superclass = superclass.superclass;
}
}
return superclass;
}
@override
void forEachSupertype(
covariant ClassElement cls, void f(ResolutionInterfaceType supertype)) {
cls.allSupertypes.forEach((InterfaceType supertype) => f(supertype));
}
@override
void forEachMixin(covariant ClassElement cls, void f(ClassElement mixin)) {
for (; cls != null; cls = cls.superclass) {
if (cls.isMixinApplication) {
MixinApplicationElement mixinApplication = cls;
f(mixinApplication.mixin);
}
}
}
@override
MemberElement lookupLibraryMember(
covariant 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) {
failedAt(
library,
"The library '${library.canonicalUri}' does not contain required "
"${setter ? 'setter' : 'getter'}: '$name'.");
}
}
if (member is! MemberElement) {
member = null;
}
if (member == null && required) {
failedAt(
library,
"The library '${library.canonicalUri}' does not "
"contain required member: '$name'.");
}
return member?.declaration;
}
@override
void forEachLibraryMember(
covariant LibraryElement library, void f(MemberEntity member)) {
library.implementation.forEachLocalMember((Element element) {
if (!element.isClass && !element.isTypedef) {
MemberElement member = element;
f(member);
}
});
}
@override
ClassElement lookupClass(covariant LibraryElement library, String name,
{bool required: false}) {
Element cls = library.implementation.findLocal(name);
if (cls is! ClassElement) {
cls = null;
}
if (cls == null && required) {
failedAt(
library,
"The library '${library.libraryName}' does not "
"contain required class: '$name'.");
}
return cls?.declaration;
}
@override
void forEachClass(covariant LibraryElement library, void f(ClassEntity cls)) {
library.implementation.forEachLocalMember((dynamic member) {
if (member.isClass) {
ClassElement cls = member;
f(cls);
}
});
}
@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) {
failedAt(NO_LOCATION_SPANNABLE, "The library '${uri}' was not found.");
}
return library;
}
@override
bool isDeferredLoadLibraryGetter(covariant MemberElement member) {
return member.isDeferredLoaderGetter;
}
@override
ResolutionFunctionType getFunctionType(covariant MethodElement method) {
if (method is ConstructorBodyElement) {
return method.constructor.type;
}
method.computeType(_resolution);
ResolutionFunctionType type = method.type;
if (method.isConstructor) {
ConstructorElement constructor = method;
if (constructor.definingConstructor != null) {
// The type of a defining constructor doesn't use the right type
// variables. Substitute the type variable of the defining class by the
// type variables of the enclosing class.
ClassElement definingClass =
constructor.definingConstructor.enclosingClass;
type = type.substByContext(
method.enclosingClass.thisType.asInstanceOf(definingClass));
}
}
return type;
}
@override
List<TypeVariableType> getFunctionTypeVariables(FunctionEntity function) {
return const <TypeVariableType>[];
}
@override
DartType getFunctionAsyncOrSyncStarElementType(FunctionEntity function) {
return dynamicType;
}
@override
DartType getAsyncOrSyncStarElementType(AsyncMarker marker, DartType type) {
return dynamicType;
}
@override
DartType getFieldType(covariant FieldElement field) {
field.computeType(_resolution);
return field.type;
}
@override
ResolutionFunctionType getLocalFunctionType(
covariant LocalFunctionElement function) {
return function.type;
}
@override
ConstantExpression getFieldConstant(covariant FieldElement field) {
return field.constant;
}
@override
ResolutionDartType getUnaliasedType(covariant ResolutionDartType type) {
type.computeUnaliased(_resolution);
return type.unaliased;
}
@override
Iterable<ConstantValue> getMemberMetadata(covariant MemberElement element,
{bool includeParameterMetadata: false}) {
List<ConstantValue> values = <ConstantValue>[];
values.addAll(_getMetadataOf(element));
if (includeParameterMetadata) {
if (element.isFunction || element.isConstructor || element.isSetter) {
MethodElement function = element.implementation;
function.functionSignature.forEachParameter(
(parameter) => values.addAll(_getMetadataOf(parameter)));
}
}
return values;
}
@override
Iterable<ConstantValue> getLibraryMetadata(covariant LibraryElement element) {
return _getMetadataOf(element);
}
@override
Iterable<ImportEntity> getImports(covariant LibraryElement library) {
return library.imports;
}
@override
Iterable<ConstantValue> getClassMetadata(covariant ClassElement element) {
return _getMetadataOf(element);
}
@override
Iterable<ConstantValue> getTypedefMetadata(covariant TypedefElement element) {
return _getMetadataOf(element);
}
Iterable<ConstantValue> _getMetadataOf(Element element) {
List<ConstantValue> constants = <ConstantValue>[];
_compiler.reporter.withCurrentElement(element, () {
for (MetadataAnnotation metadata in element.implementation.metadata) {
metadata.ensureResolved(_compiler.resolution);
assert(metadata.constant != null,
failedAt(metadata, "Unevaluated metadata constant."));
constants.add(
_compiler.backend.constants.getConstantValue(metadata.constant));
}
});
return constants;
}
@override
ResolutionFunctionType getFunctionTypeOfTypedef(
covariant TypedefElement typedef) {
var result = typedef.alias;
if (result.isMalformed) return null;
return result;
}
@override
ResolutionTypedefType getTypedefTypeOfTypedef(
covariant TypedefElement typedef) =>
typedef.thisType;
@override
bool isEnumClass(covariant ClassElement cls) => cls.isEnumClass;
}
/// 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 {
final Compiler _compiler;
final NativeBasicDataBuilder _nativeBasicDataBuilder;
_ElementAnnotationProcessor(this._compiler, this._nativeBasicDataBuilder);
CommonElements get _commonElements => _compiler.resolution.commonElements;
/// Check whether [cls] has a `@Native(...)` annotation, and if so, set its
/// native name from the annotation.
void extractNativeAnnotations(covariant LibraryElement library) {
library.forEachLocalMember((Element element) {
if (element.isClass) {
EagerAnnotationHandler.checkAnnotation(_compiler, element,
new NativeAnnotationHandler(_nativeBasicDataBuilder));
}
});
}
void extractJsInteropAnnotations(covariant LibraryElement library) {
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.stringValue;
} 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, _member) {
MemberElement member = _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) {
if (fn.functionSignature.requiredParameterCount > 0 ||
fn.functionSignature.hasOptionalParameters &&
!fn.functionSignature.optionalParametersAreNamed) {
_compiler.reporter.reportErrorMessage(
fn,
MessageKind
.JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS,
{'cls': classElement.name});
}
} else {
checkFunctionParameters(fn);
}
}
});
});
}
_compiler.libraryLoader.libraries
.forEach((LibraryEntity l) => processJsInteropAnnotationsInLibrary(l));
}
}
/// Builder that creates work item necessary for the resolution of a
/// [MemberElement].
class ResolutionWorkItemBuilder extends WorkItemBuilder {
final Resolution _resolution;
ResolutionWorkItemBuilder(this._resolution);
@override
WorkItem createWorkItem(MemberElement element) {
assert(element.isDeclaration, failedAt(element));
if (element.isMalformed) return null;
assert(element is AnalyzableElement,
failedAt(element, 'Element $element is not analyzable.'));
return _resolution.createWorkItem(element);
}
}
class ResolutionMirrorsData extends MirrorsDataImpl {
ResolutionMirrorsData(Compiler compiler,
ElementEnvironment elementEnvironment, CommonElements commonElements)
: super(compiler, elementEnvironment, commonElements);
@override
bool isClassInjected(covariant ClassElement cls) => cls.isInjected;
@override
bool isClassResolved(covariant ClassElement cls) => cls.isResolved;
@override
void forEachConstructor(
covariant ClassElement cls, void f(ConstructorEntity constructor)) {
cls.constructors.forEach((Element _constructor) {
ConstructorElement constructor = _constructor;
f(constructor);
});
}
}