| // 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<a.Element, Reference>(); |
| final _typeParams = HashMap<a.TypeParameterElement, TypeParameter>(); |
| final _namespaceBuilder = a.NamespaceBuilder(); |
| |
| AnalyzerToKernel._(this._resynth, this._summaryData) |
| : types = _resynth.typeProvider, |
| rules = _resynth.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<String> summaryPaths) { |
| var summaryData = a.SummaryDataStore(summaryPaths, |
| resourceProvider: a.PhysicalResourceProvider.INSTANCE, |
| disallowOverlappingSummaries: false); |
| var resynthesizer = |
| _createSummaryResynthesizer(summaryData, analyzerSdkSummary); |
| return AnalyzerToKernel._(resynthesizer, 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<Component> 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 = <Library>[]; |
| var uriToSource = <Uri, Source>{}; |
| |
| void addCompilationUnit(a.CompilationUnitElement unit) { |
| uriToSource[unit.source.uri] = Source( |
| unit.lineInfo.lineStarts, |
| [], |
| unit.uri != null ? Uri.base.resolve(unit.uri) : unit.source.uri, |
| unit.source.uri); |
| } |
| |
| 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 = <Procedure>[]; |
| 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<TypeParameter> 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: true, |
| 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<Procedure> 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 <DartType>[]; |
| return InterfaceType.byReference(ref, newTypeArgs); |
| } |
| |
| var f = type as a.FunctionType; |
| if (f.name != null && f.name != '') { |
| var ref = ensureNode |
| ? visitFunctionTypeAliasElement( |
| f.element as a.FunctionTypeAliasElement) |
| .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 = <NamedType>[]; |
| 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<String> 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<TypeParameter> 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<a.ElementAnnotation> metadata, |
| void Function(Expression) addAnnotation) { |
| if (metadata.isEmpty) return; |
| |
| for (a.ElementAnnotation annotation in metadata) { |
| var ast = (annotation as a.ElementAnnotationImpl).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 (a.ConstructorElement 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 as a.ExecutableElement |
| : 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); |
| var resynthesizer = a.StoreBasedSummaryResynthesizer( |
| context, null, context.sourceFactory, /*strongMode*/ true, summaryData); |
| resynthesizer.finishCoreAsyncLibraries(); |
| context.typeProvider = resynthesizer.typeProvider; |
| return resynthesizer; |
| } |
| |
| /// 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): use RestrictedAnalysisContext. |
| 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? |
| return context; |
| } |