// Copyright (c) 2018, 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. import 'dart:core' hide MapEntry; import 'dart:collection'; import 'package:analyzer/dart/element/element.dart' as a; import 'package:analyzer/dart/element/type.dart' as a; import 'package:analyzer/file_system/physical_file_system.dart' as a; import 'package:analyzer/src/context/context.dart' as a; import 'package:analyzer/src/dart/element/element.dart' as a; import 'package:analyzer/src/dart/element/member.dart' as a; import 'package:analyzer/src/dart/element/type.dart' as a; import 'package:analyzer/src/generated/constant.dart' as a; import 'package:analyzer/src/generated/engine.dart' as a; import 'package:analyzer/src/generated/source.dart' as a; import 'package:analyzer/src/generated/type_system.dart' as a; import 'package:analyzer/src/summary/idl.dart' as a; import 'package:analyzer/src/summary/package_bundle_reader.dart' as a; import 'package:analyzer/src/summary/summary_sdk.dart' as a; import 'package:analyzer/src/generated/resolver.dart' as a show NamespaceBuilder, TypeProvider; import 'package:front_end/src/api_unstable/ddc.dart' show RedirectingFactoryBody; import 'package:kernel/kernel.dart'; import 'package:kernel/type_algebra.dart'; import 'type_table.dart'; /// Converts an Analyzer summary file to a Kernel [Component]. /// /// The first step is to use Analyzer's [a.StoreBasedSummaryResynthesizer] to /// deserialize the summary file into an [a.Element] model (that way we don't /// depend directly on the file format). Once we have elements, we visit them /// and construct the corresponding Kernel [Node]s. /// /// The main entry points are [convertSdk] and [convertSummaries], which /// convert the SDK and input summaries, respectively. /// /// Because we only need to convert summaries, we do not need to handle method /// bodies. This lets us avoid the complexity of converting Analyzer AST nodes /// (e.g. expressions, statements). /// /// For constants we use Analyzer's constant evaluator compute the value from /// the data in the summary, and then create the appropriate Kernel node to /// reconstruct the constant (e.g. ListLiteral, ConstructorInvocation, etc). /// See [_visitConstant] for more information. /// /// When something refers to an element, we normally create the [Reference] but /// leave its corresponding [NamedNode] empty until that element is visited and /// creates the Kernel node. This takes care of cycles, and avoids recursing too /// deeply as we convert elements. /// /// Sometimes we need to convert an element eagerly (e.g. if we need to call /// members on an [InterfaceType] or [Supertype], we need to create its [Class] /// node). In that case we handle cycles in the visit method (e.g. /// [visitClassElement]) by creating the node and linking it to its reference /// before visiting anything else that might recurse. /// /// Special care must be taken to make sure we link up all [Reference]s with /// their corresponding [NamedNode]. If we don't do this [verifyReferences] /// will throw an error. The fix is to figure out why we didn't visit the /// element for that reference (often this is due to Analyzer's synthetic /// fields/accessor elements; care must be taken to always reference the real /// element). /// /// Because we're using Analyzer's summary resynthesizer, conversion is all or /// nothing: all summaries must be in Analyzer format, including the SDK. /// Now that we have this implementation, it may be possible to port code from /// Analyzer and modify it to resynthesize directly into Kernel trees, if we /// ever need to support a mix of Kernel and Analyzer summary files. class AnalyzerToKernel { final a.StoreBasedSummaryResynthesizer _resynth; final a.SummaryDataStore _summaryData; final a.TypeProvider types; final a.Dart2TypeSystem rules; final _references = HashMap(); final _typeParams = HashMap(); final _namespaceBuilder = a.NamespaceBuilder(); AnalyzerToKernel._(a.AnalysisContextImpl context, this._summaryData) : _resynth = (context.resultProvider as a.InputPackagesResultProvider) .resynthesizer, types = context.typeProvider, rules = context.typeSystem as a.Dart2TypeSystem; /// Create an Analyzer summary to Kernel tree converter, using the provided /// [analyzerSdkSummary] and [summaryPaths]. /// /// Once the converter is created, [convertSdk] should be called to convert /// & return the SDK, followed by [convertSummaries] to convert & return the /// converted summaries. factory AnalyzerToKernel( String analyzerSdkSummary, List summaryPaths) { var summaryData = a.SummaryDataStore(summaryPaths, resourceProvider: a.PhysicalResourceProvider.INSTANCE, disallowOverlappingSummaries: false); var resynthesizer = _createSummaryResynthesizer(summaryData, analyzerSdkSummary); return AnalyzerToKernel._(resynthesizer.context, summaryData); } /// Converts the SDK summary to a Kernel component and returns it. Component convertSdk() { // _createContextForSummaries puts the SDK summary last in the summary data. var sdkBundle = _summaryData.bundles.last; assert(sdkBundle.linkedLibraryUris.every((u) => u.startsWith('dart:'))); var result = _toComponent(sdkBundle); verifyReferences(); return result; } /// Converts the input summaries to Kernel components and return them. /// /// [convertSdk] must be called before this. List convertSummaries() { // Take all summaries except the SDK one, which is placed last in the list // by _createContextForSummaries. var bundles = _summaryData.bundles.take(_summaryData.bundles.length - 1); var result = bundles.map(_toComponent).toList(); verifyReferences(); // assumption: convertSdk() is called first return result; } /// Dispose the Analysis Context used for summary conversion. void dispose() => _resynth.context.dispose(); void verifyReferences() { _references.forEach((element, reference) { // Ensure each reference has a corresponding node. // // If it's missing a node, CFE will fail and it is difficult to debug at // that point because the name and element cannot be accessed. // // Typically this error means: // - we didn't visit an element. // - we didn't set the `reference: _reference(e)` for the Kernel node. // - we referenced a synthetic element by mistake, such as referencing the // synthetic getter/setter, when we should've used the field. if (reference.node == null) { throw StateError('missing node for reference, element was: $element' + (element.isSynthetic ? ' (synthetic)' : '')); } }); } Component _toComponent(a.PackageBundle bundle) { var libraries = []; var uriToSource = {}; void addCompilationUnit(a.CompilationUnitElement unit) { uriToSource[unit.source.uri] = Source(unit.lineInfo.lineStarts, []); } for (var uri in bundle.unlinkedUnitUris) { var unitInfo = _resynth.getUnlinkedSummary(uri); if (unitInfo.isPartOf) { // Library parts are handled by their corresponding library. continue; } var element = _resynth.getLibraryElement(uri); libraries.add(visitLibraryElement(element)); addCompilationUnit(element.definingCompilationUnit); element.parts.forEach(addCompilationUnit); } return Component(libraries: libraries, uriToSource: uriToSource); } Class visitClassElement(a.ClassElement e, [Library library]) { var ref = _reference(e); if (ref.node != null) return ref.asClass; // Construct the Class first and link the reference. This ensures the // (not yet finished) Class node will be returned on the line above, if we // happen to re-enter this visit method. var class_ = Class( name: e.name, isAbstract: e.isAbstract, fileUri: e.source.uri, reference: ref); // Classes can be visited before their library (e.g. because they're a // supertype of another class), so make sure to visit the library now. library ??= visitLibraryElement(e.library); library.addClass(class_); class_.isMixinDeclaration = e.isMixin; class_.typeParameters .addAll(e.typeParameters.map(visitTypeParameterElement)); setParents(class_.typeParameters, class_); class_.implementedTypes.addAll(e.interfaces.map(_typeToSupertype)); var fields = class_.fields; var constructors = class_.constructors; var procedures = class_.procedures; fields.addAll(e.fields.where((f) => !f.isSynthetic).map(visitFieldElement)); var redirectingFactories = []; for (var ctor in e.constructors) { if (ctor.isFactory) { var factory_ = _visitFactory(ctor); procedures.add(factory_); if (ctor.redirectedConstructor != null) { redirectingFactories.add(factory_); } } else { constructors.add(visitConstructorElement(ctor)); } } if (redirectingFactories.isNotEmpty) { fields.add(_createRedirectingFactoryField(redirectingFactories, e)); } procedures.addAll(e.methods.map(visitMethodElement)); procedures.addAll(e.accessors .where((a) => !a.isSynthetic) .map(visitPropertyAccessorElement)); setParents(fields, class_); setParents(constructors, class_); setParents(procedures, class_); if (e.isMixinApplication) { class_.mixedInType = _typeToSupertype(e.mixins.last); } var supertype = _typeToSupertype(e.supertype); class_.supertype = _unrollMixinClasses(e, supertype, library); _visitAnnotations(e.metadata, class_.addAnnotation); // TODO(jmesserly): do we need covariance check stubs? We may be okay as // since we're only handling dependencies here. // // But this may lead to redundant stubs (if CFE doesn't see one on a // superclass) and/or break some assumptions in CFE. return class_; } Supertype _unrollMixinClasses( a.ClassElement e, Supertype supertype, Library library) { // TODO(jmesserly): is this enough for mixin desugaring? It only does // enough to create the intermediate classes. // Documentation below assumes the given mixin application is in one of // these forms: // // class C extends S with M1, M2, M3; // class Named = S with M1, M2, M3; // // When we refer to the subclass, we mean `C` or `Named`. /// The number of mixin classes to unroll. /// /// Named mixin applications have one less class. This can be illustrated /// here: /// /// class C extends S with M1, M2, M3 {} /// class Named = S with M1, M2, M3; /// /// For `C` we unroll 3 classes: _C&S&M1, _C&S&M1&M2, _C&S&M1&M2&M3. /// For `Named` we unroll 2 classes: _Named&S&M1, _Named&S&M1&M2. /// /// The classes themselves will be generated as: /// /// class C extends _C&S&M1&M2&M3 {} /// class Named = _Named&S&M1&M2 with M3; /// var unrollLength = e.mixins.length; if (e.isMixinApplication) unrollLength--; if (unrollLength <= 0) return supertype; /// The mixin application's synthetic name. /// /// The full name of the mixin application is obtained by prepending the /// name of the subclass (`C` or `Named` in the above examples) to the /// running name. For the example `C`, that leads to these names: /// /// 1. `_C&S&M1` /// 2. `_C&S&M1&M2` /// 3. `_C&S&M1&M2&M3`. var runningName = '_${e.name}&${e.supertype.name}'; /// The type variables used in the current supertype and mixin, or null /// if this class doesn't have any type parameters. var usedTypeVars = e.typeParameters.isNotEmpty ? freeTypeParameters(supertype.asInterfaceType) : null; for (int i = 0; i < unrollLength; i++) { var mixin = e.mixins[i]; runningName += "&${mixin.name}"; var mixedInType = _typeToSupertype(mixin); List typeParameters; if (usedTypeVars != null) { // Any type params used by superclasses will continue to be used, plus // anything additional that this mixin uses. usedTypeVars.addAll(freeTypeParameters(mixedInType.asInterfaceType)); if (usedTypeVars.isNotEmpty) { // Make fresh type parameters for this class, and then substitute them // into supertype and mixin type arguments (if any). var fresh = getFreshTypeParameters(usedTypeVars.toList()); typeParameters = fresh.freshTypeParameters; supertype = fresh.substituteSuper(supertype); mixedInType = fresh.substituteSuper(mixedInType); } } var c = Class( name: runningName, isAbstract: e.isAbstract, mixedInType: mixedInType, supertype: supertype, typeParameters: typeParameters, fileUri: e.source.uri); library.addClass(c); // Compute the superclass to use for the next iteration of this loop. // // Any type arguments are in terms of the original class type parameters. // This allows us to perform consistent substitutions and have the correct // type arguments for the final supertype (that we return). supertype = Supertype( c, typeParameters != null ? List.of(usedTypeVars.map((t) => TypeParameterType(t))) : []); } return supertype; } Constructor visitConstructorElement(a.ConstructorElement e) { assert(!e.isFactory); var ref = _reference(e); if (ref.node != null) return ref.asConstructor; // By convention, instance constructors return `void` in Kernel. var function = _createFunction(e)..returnType = const VoidType(); var result = Constructor(function, name: _getName(e), isConst: e.isConst, isExternal: e.isExternal, isSynthetic: e.isSynthetic, fileUri: e.source.uri, reference: ref); if (!result.isSynthetic) { // TODO(jmesserly): CFE does not respect the synthetic bit on constructors // so we set a bogus offset. This causes CFE to treat it as not synthetic. // // (The bug is in DillMemberBuilder.isSynthetic. Synthetic constructors // have different semantics/optimizations in some cases, so it is // important that the constructor is correctly marked.) result.fileOffset = 1; } _visitAnnotations(e.metadata, result.addAnnotation); return result; } Procedure _visitFactory(a.ConstructorElement e) { var ref = _reference(e); if (ref.node != null) return ref.asProcedure; var result = Procedure.byReference(_getName(e), ProcedureKind.Factory, null, isExternal: e.isExternal, isConst: e.isConst, isStatic: true, fileUri: e.source.uri, reference: ref); _visitAnnotations(e.metadata, result.addAnnotation); // Since the factory is static, we need to create fresh type parameters that // match the ones in the enclosing class. FreshTypeParameters fresh; DartType Function(a.DartType) visitType; if (e.enclosingElement.typeParameters.isNotEmpty) { fresh = getFreshTypeParameters( visitClassElement(e.enclosingElement).typeParameters); visitType = (t) => fresh.substitute(_visitDartType(t, ensureNode: true)); } else { visitType = _visitDartType; } result.function = _createFunction(e, fresh?.freshTypeParameters, visitType); result.function.parent = result; var redirect = e.redirectedConstructor; if (redirect == null) return result; // Get the raw constructor element before the type is applied. var rawRedirect = redirect is a.ConstructorMember ? redirect.baseElement : redirect; // TODO(jmesserly): conceptually we only need a reference here, but // RedirectingFactoryBody requires the complete node. var ctor = rawRedirect.isFactory ? _visitFactory(rawRedirect) : visitConstructorElement(rawRedirect); var redirectedType = redirect.type.returnType as a.InterfaceType; var typeArgs = redirectedType.typeArguments.map(visitType).toList(); result.function.body = RedirectingFactoryBody(ctor, typeArgs); return result; } Field _createRedirectingFactoryField( List factories, a.ClassElement c) { return Field(_getName(c, "_redirecting#"), isStatic: true, initializer: ListLiteral(List.of(factories.map((f) => StaticGet(f)))), fileUri: c.source.uri); } LibraryDependency visitExportElement(a.ExportElement e) => LibraryDependency.byReference( LibraryDependency.ExportFlag, const [], _reference(e.exportedLibrary), null, e.combinators.map(_visitCombinator).toList()); Field visitFieldElement(a.FieldElement e) { var result = Field(_getName(e), type: _visitDartType(e.type), initializer: null, isFinal: e.isFinal, isConst: e.isConst, isStatic: e.isStatic, fileUri: e.source.uri, reference: _reference(e)); if (!e.isFinal && !e.isConst) { var class_ = e.enclosingElement; if (class_.typeParameters.isNotEmpty) { result.isGenericCovariantImpl = _isGenericCovariant(class_, e.type); } } _visitAnnotations(e.metadata, result.addAnnotation); return result; } Procedure visitFunctionElement(a.FunctionElement e) { var result = Procedure.byReference( _getName(e), ProcedureKind.Method, _createFunction(e), isExternal: e.isExternal, fileUri: e.source.uri, isStatic: true, reference: _reference(e)); _visitAnnotations(e.metadata, result.addAnnotation); return result; } Typedef visitFunctionTypeAliasElement(a.FunctionTypeAliasElement e, [Library library]) { var ref = _reference(e); if (ref.node != null) return ref.asTypedef; var t = Typedef(e.name, null, reference: ref, fileUri: e.source.uri); library ??= visitLibraryElement(e.library); library.addTypedef(t); a.FunctionType type; var typeParams = e.typeParameters; if (e is a.GenericTypeAliasElement) { type = e.function.type; } else { type = e.type; if (typeParams.isNotEmpty) { // Skip past the type formals, we'll add them back below, so these // type parameter names will end up in scope in the generated JS. type = type.instantiate(typeParams.map((f) => f.type).toList()); } } t.typeParameters.addAll(typeParams.map(visitTypeParameterElement)); setParents(t.typeParameters, t); t.type = _visitDartType(type, originTypedef: t.thisType); _visitAnnotations(e.metadata, t.addAnnotation); return t; } LibraryDependency visitImportElement(a.ImportElement e) => LibraryDependency.byReference(0, const [], _reference(e.importedLibrary), null, e.combinators.map(_visitCombinator).toList()); Library visitLibraryElement(a.LibraryElement e) { var ref = _reference(e); if (ref.node != null) return ref.asLibrary; var library = Library(e.source.uri, name: e.name, fileUri: e.definingCompilationUnit.source.uri, reference: ref); library.fileOffset = 0; _visitAnnotations(e.metadata, library.addAnnotation); e.imports.map(visitImportElement).forEach(library.addDependency); e.exports.map(visitExportElement).forEach(library.addDependency); e.parts.map((p) => LibraryPart(const [], p.uri)).forEach(library.addPart); _visitUnit(a.CompilationUnitElement u) { for (var t in u.types) { visitClassElement(t, library); } for (var t in u.mixins) { visitClassElement(t, library); } for (var t in u.functionTypeAliases) { visitFunctionTypeAliasElement(t, library); } u.functions.map(visitFunctionElement).forEach(library.addMember); u.accessors .where((a) => !a.isSynthetic) .map(visitPropertyAccessorElement) .forEach(library.addMember); u.topLevelVariables .map(visitTopLevelVariableElement) .forEach(library.addMember); } _visitUnit(e.definingCompilationUnit); e.parts.forEach(_visitUnit); var libraryImpl = e as a.LibraryElementImpl; libraryImpl.publicNamespace ??= _namespaceBuilder.createPublicNamespaceForLibrary(e); libraryImpl.exportNamespace ??= _namespaceBuilder.createExportNamespaceForLibrary(e); var publicNames = libraryImpl.publicNamespace.definedNames; var exportNames = libraryImpl.exportNamespace.definedNames; exportNames.forEach((name, value) { if (!publicNames.containsKey(name)) { value = value is a.PropertyAccessorElement && value.isSynthetic ? value.variable : value; library.additionalExports.add(_reference(value)); } }); return library; } Procedure visitMethodElement(a.MethodElement e) { var result = Procedure.byReference( _getName(e), e.isOperator ? ProcedureKind.Operator : ProcedureKind.Method, _createFunction(e), isAbstract: e.isAbstract, isStatic: e.isStatic, isExternal: e.isExternal, fileUri: e.source.uri, reference: _reference(e)); _visitAnnotations(e.metadata, result.addAnnotation); return result; } Procedure visitPropertyAccessorElement(a.PropertyAccessorElement e) { var result = Procedure.byReference( _getName(e, e.variable.name), e.isGetter ? ProcedureKind.Getter : ProcedureKind.Setter, _createFunction(e), isAbstract: e.isAbstract, isStatic: e.isStatic, isExternal: e.isExternal, fileUri: e.source.uri, reference: _reference(e)); _visitAnnotations(e.metadata, result.addAnnotation); return result; } Field visitTopLevelVariableElement(a.TopLevelVariableElement e) { var result = Field(_getName(e), type: _visitDartType(e.type), initializer: null, isFinal: e.isFinal, isConst: e.isConst, isStatic: e.isStatic, fileUri: e.source.uri, reference: _reference(e)); _visitAnnotations(e.metadata, result.addAnnotation); return result; } TypeParameter visitTypeParameterElement(a.TypeParameterElement e) { var t = _typeParams[e]; if (t != null) return t; _typeParams[e] = t = TypeParameter(e.name); var hasBound = e.bound != null; t.bound = hasBound ? _visitDartType(e.bound) : _visitDartType(types.objectType); t.defaultType = hasBound ? t.bound : const DynamicType(); var enclosingElement = e.enclosingElement; if (hasBound && enclosingElement is a.ClassMemberElement) { var class_ = enclosingElement.enclosingElement; if (class_ != null && class_.typeParameters.isNotEmpty) { t.isGenericCovariantImpl = _isGenericCovariant(class_, e.bound); } } return t; } Name _getName(a.Element e, [String name]) { name ??= e.name; return Name.byReference( name, name.startsWith('_') ? _reference(e.library) : null); } /// Converts an Analyzer [type] to a Kernel type. /// /// If [ensureNode] is set, the reference to the [Class] or [Typedef] will /// populated with the node (creating it if needed). Many members on /// [InterfaceType] and [TypedefType] rely on having a node present, so this /// enables the use of those members if they're needed by the converter. DartType _visitDartType(a.DartType type, {bool ensureNode = false, TypedefType originTypedef}) { if (type.isVoid) { return const VoidType(); } else if (type.isDynamic) { return const DynamicType(); } else if (type.isBottom) { return const BottomType(); } else if (type is a.TypeParameterType) { return TypeParameterType(visitTypeParameterElement(type.element)); } visit(a.DartType t) => _visitDartType(t, ensureNode: ensureNode); if (type is a.InterfaceType) { var ref = ensureNode ? visitClassElement(type.element).reference : _reference(type.element); var typeArgs = type.typeArguments; var newTypeArgs = typeArgs.isNotEmpty ? typeArgs.map(visit).toList() : const []; return InterfaceType.byReference(ref, newTypeArgs); } var f = type as a.FunctionType; if (f.name != null && f.name != '') { var ref = ensureNode ? visitFunctionTypeAliasElement(f.element).reference : _reference(f.element); return TypedefType.byReference(ref, f.typeArguments.map(visit).toList()); } var params = f.parameters; var positional = f.normalParameterTypes.map(visit).toList(); positional.addAll(f.optionalParameterTypes.map(visit)); var named = []; f.namedParameterTypes.forEach((name, type) { named.add(NamedType(name, visit(type))); }); return FunctionType(positional, visit(f.returnType), typeParameters: f.typeFormals.map(visitTypeParameterElement).toList(), namedParameters: named, requiredParameterCount: params.where((p) => !p.isOptional).length, typedefType: originTypedef); } Supertype _typeToSupertype(a.InterfaceType t) { if (t == null) return null; return Supertype( visitClassElement(t.element), t.typeArguments .map((a) => _visitDartType(a, ensureNode: true)) .toList()); } Combinator _visitCombinator(a.NamespaceCombinator combinator) { bool isShow; List names; if (combinator is a.ShowElementCombinator) { isShow = true; names = combinator.shownNames; } else { isShow = false; names = (combinator as a.HideElementCombinator).hiddenNames; } return Combinator(isShow, names); } /// Creates a function node for the executable element [e], optionally using /// the supplied [typeParameters] and calling [visitType] so it can perform /// any necessary substitutions. FunctionNode _createFunction(a.ExecutableElement e, [List typeParameters, DartType Function(a.DartType) visitType]) { visitType ??= _visitDartType; var enclosingElement = e.enclosingElement; var class_ = enclosingElement is a.ClassElement ? enclosingElement : null; visitParameter(a.ParameterElement e) { var result = VariableDeclaration(e.name, type: visitType(e.type), isFinal: e.isFinal, isFieldFormal: e.isInitializingFormal, isCovariant: e.isCovariant, initializer: e.isOptional ? _visitConstant(e.computeConstantValue()) : null); if (class_ != null && class_.typeParameters.isNotEmpty) { result.isGenericCovariantImpl = _isGenericCovariant(class_, e.type); } return result; } var params = e.parameters; var asyncMarker = _getAsyncMarker(e); return FunctionNode(null, typeParameters: typeParameters ?? e.typeParameters.map(visitTypeParameterElement).toList(), positionalParameters: params.where((p) => !p.isNamed).map(visitParameter).toList(), namedParameters: params.where((p) => p.isNamed).map(visitParameter).toList(), requiredParameterCount: params.where((p) => !p.isOptional).length, returnType: visitType(e.returnType), asyncMarker: asyncMarker, dartAsyncMarker: asyncMarker); } Reference _reference(a.Element e) { if (e == null) throw ArgumentError('null element'); return _references.putIfAbsent(e, () => Reference()); } bool _isGenericCovariant(a.ClassElement c, a.DartType type) { var classUpperBound = rules.instantiateToBounds(c.type) as a.InterfaceType; var typeUpperBound = type.substitute2(classUpperBound.typeArguments, a.TypeParameterTypeImpl.getTypes(classUpperBound.typeParameters)); // Is it safe to assign the upper bound of the field/parameter to it? // If not then we'll need a runtime check. return !rules.isSubtypeOf(typeUpperBound, type); } /// Transforms a metadata annotation from Analyzer to Kernel format. /// /// If needed this uses Analyzer's constant evaluation to evaluate the AST, /// and then converts the resulting constant value into a Kernel tree. /// By first computing the expression's constant value, we avoid having to /// convert a bunch of Analyzer ASTs nodes. Instead we can convert the more /// limited set of constant values allowed in Dart (see [_visitConstant]). void _visitAnnotations(List metadata, void Function(Expression) addAnnotation) { if (metadata.isEmpty) return; for (a.ElementAnnotationImpl annotation in metadata) { var ast = annotation.annotationAst; var arguments = ast.arguments; if (arguments == null) { var e = ast.element; e = e is a.PropertyAccessorElement && e.isSynthetic ? e.variable : e; addAnnotation(StaticGet.byReference(_reference(e))); } else { // Use Analyzer's constant evaluation to produce the constant, then // emit the resulting value. We do this to avoid handling all of the // AST nodes that might be needed for constant evaluation. Instead we // just serialize the resulting value to a Kernel expression that will // reproduce it. addAnnotation(_visitConstant(annotation.computeConstantValue())); } } } /// Converts an Analyzer constant value in [obj] to a Kernel expression /// (usually a Literal or ConstructorInvocation) that will recreate that /// constant value. Expression _visitConstant(a.DartObject obj) { if (obj == null || obj.isNull || !obj.hasKnownValue) return NullLiteral(); var type = obj.type; if (identical(type, types.boolType)) { var value = obj.toBoolValue(); return value != null ? BoolLiteral(value) : NullLiteral(); } if (identical(type, types.intType)) { return IntLiteral(obj.toIntValue()); } if (identical(type, types.doubleType)) { return DoubleLiteral(obj.toDoubleValue()); } if (identical(type, types.stringType)) { return StringLiteral(obj.toStringValue()); } if (identical(type, types.symbolType)) { return SymbolLiteral(obj.toSymbolValue()); } if (identical(type, types.typeType)) { return TypeLiteral(_visitDartType(obj.toTypeValue())); } if (type is a.InterfaceType) { if (type.element == types.listType.element) { return ListLiteral(obj.toListValue().map(_visitConstant).toList(), typeArgument: _visitDartType(type.typeArguments[0]), isConst: true); } if (type.element == types.mapType.element) { var entries = obj .toMapValue() .entries .map( (e) => MapEntry(_visitConstant(e.key), _visitConstant(e.value))) .toList(); return MapLiteral(entries, keyType: _visitDartType(type.typeArguments[0]), valueType: _visitDartType(type.typeArguments[1]), isConst: true); } if (obj is a.DartObjectImpl && obj.isUserDefinedObject) { var classElem = type.element; if (classElem.isEnum) { // TODO(jmesserly): we should be able to use `getField('index')` but // in some cases Analyzer uses the name of the static field that // contains the enum, rather than the `index` field, due to a bug. // // So we just grab the one instance field, regardless of its name. var index = obj.fields.values.single.toIntValue(); var field = classElem.fields.where((f) => f.type == type).elementAt(index); return StaticGet.byReference(_reference(field)); } var invocation = obj.getInvocation(); var constructor = invocation.constructor; // For a redirecting const factory, the constant constructor will be // from the original one, but the `type` will match the redirected type. // // This leads to mismatch in how we call this constructor. So we need to // find the redirected one. for (var rc; (rc = constructor.redirectedConstructor) != null;) { constructor = rc; } constructor = constructor is a.ConstructorMember ? constructor.baseElement : constructor; return ConstructorInvocation.byReference( _reference(constructor), Arguments( invocation.positionalArguments.map(_visitConstant).toList(), named: invocation.namedArguments.entries .map((e) => NamedExpression(e.key, _visitConstant(e.value))) .toList(), types: type.typeArguments.map(_visitDartType).toList()), isConst: true); } } if (obj is a.DartObjectImpl && type is a.FunctionType) { var e = obj.toFunctionValue(); e = e is a.PropertyAccessorElement && e.isSynthetic ? e.variable : e; // TODO(jmesserly): support generic tear-off implicit instantiation. return StaticGet.byReference(_reference(e)); } throw UnsupportedError('unknown constant type `$type`: $obj'); } } AsyncMarker _getAsyncMarker(a.ExecutableElement e) { return e.isGenerator ? (e.isAsynchronous ? AsyncMarker.AsyncStar : AsyncMarker.SyncStar) : (e.isAsynchronous ? AsyncMarker.Async : AsyncMarker.Sync); } a.StoreBasedSummaryResynthesizer _createSummaryResynthesizer( a.SummaryDataStore summaryData, String dartSdkPath) { var context = _createContextForSummaries(summaryData, dartSdkPath); return a.StoreBasedSummaryResynthesizer( context, null, context.sourceFactory, /*strongMode*/ true, summaryData); } /// Creates a dummy Analyzer context so we can use summary resynthesizer. /// /// This is similar to Analyzer's `LibraryContext._createResynthesizingContext`. a.AnalysisContextImpl _createContextForSummaries( a.SummaryDataStore summaryData, String dartSdkPath) { var sdk = a.SummaryBasedDartSdk(dartSdkPath, true, resourceProvider: a.PhysicalResourceProvider.INSTANCE); var sdkSummaryBundle = sdk.getLinkedBundle(); if (sdkSummaryBundle != null) { summaryData.addBundle(null, sdkSummaryBundle); } // TODO(jmesserly): can we avoid creating an analysis context entirely? // It doesn't look like StoreBasedSummaryResynthesizer uses much of it. var context = a.AnalysisEngine.instance.createAnalysisContext() as a.AnalysisContextImpl; context.sourceFactory = a.SourceFactory( [a.DartUriResolver(sdk), a.InSummaryUriResolver(null, summaryData)]); context.useSdkCachePartition = false; // TODO(jmesserly): do we need to set analysisOptions or declaredVariables? context.resultProvider = a.InputPackagesResultProvider(context, summaryData); return context; }