e14726a3f4
We change the type of FileUriNode.fileUri from String to Uri, which in principle doesn't change the binary format. However, we did notice that LibraryParts weren't serialized as specified in binary.md, so we also fixed that. Since fileUris are stored as strings in a separate table, Uri.parse is only called once per unique URI. Fasta only uses relativizeUri when printing diagnostics, and URIs stored in expectation files (golden files) are relativized using String.replaceAll. Change-Id: Ib2dc1b80c03a0cdaf84e48b8b3ba73b16bdf8a40 Reviewed-on: https://dart-review.googlesource.com/25421 Commit-Queue: Peter von der Ahé <ahe@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
1027 lines
37 KiB
Dart
1027 lines
37 KiB
Dart
// Copyright (c) 2016, 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 kernel.analyzer.loader;
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:analyzer/analyzer.dart';
|
|
import 'package:analyzer/dart/element/element.dart';
|
|
import 'package:analyzer/dart/element/type.dart';
|
|
import 'package:analyzer/file_system/file_system.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/source/package_map_resolver.dart';
|
|
import 'package:analyzer/src/dart/element/member.dart';
|
|
import 'package:analyzer/src/dart/scanner/scanner.dart';
|
|
import 'package:analyzer/src/dart/sdk/sdk.dart';
|
|
import 'package:analyzer/src/generated/engine.dart';
|
|
import 'package:analyzer/src/generated/parser.dart';
|
|
import 'package:analyzer/src/generated/sdk.dart';
|
|
import 'package:analyzer/src/generated/source_io.dart';
|
|
import 'package:analyzer/src/summary/summary_sdk.dart';
|
|
import 'package:kernel/application_root.dart';
|
|
import 'package:package_config/discovery.dart';
|
|
import 'package:package_config/packages.dart';
|
|
|
|
import 'package:kernel/ast.dart' as ast;
|
|
import 'package:kernel/target/targets.dart' show Target;
|
|
import 'package:kernel/type_algebra.dart';
|
|
import 'package:analyzer/src/kernel/ast_from_analyzer.dart';
|
|
|
|
/// Options passed to the Dart frontend.
|
|
class DartOptions {
|
|
/// True if user code should be loaded in strong mode.
|
|
bool strongMode;
|
|
|
|
/// True if the Dart SDK should be loaded in strong mode.
|
|
bool strongModeSdk;
|
|
|
|
/// Path to the sdk sources, ignored if sdkSummary is provided.
|
|
String sdk;
|
|
|
|
/// Path to a summary of the sdk sources.
|
|
String sdkSummary;
|
|
|
|
/// Path to the `.packages` file.
|
|
String packagePath;
|
|
|
|
/// Root used to relativize app file-urls, making them machine agnostic.
|
|
ApplicationRoot applicationRoot;
|
|
|
|
Map<Uri, Uri> customUriMappings;
|
|
|
|
/// Environment definitions provided via `-Dkey=value`.
|
|
Map<String, String> declaredVariables;
|
|
|
|
DartOptions(
|
|
{bool strongMode: false,
|
|
bool strongModeSdk,
|
|
this.sdk,
|
|
this.sdkSummary,
|
|
this.packagePath,
|
|
ApplicationRoot applicationRoot,
|
|
Map<Uri, Uri> customUriMappings,
|
|
Map<String, String> declaredVariables})
|
|
: this.customUriMappings = customUriMappings ?? <Uri, Uri>{},
|
|
this.declaredVariables = declaredVariables ?? <String, String>{},
|
|
this.strongMode = strongMode,
|
|
this.strongModeSdk = strongModeSdk ?? strongMode,
|
|
this.applicationRoot = applicationRoot ?? new ApplicationRoot.none();
|
|
}
|
|
|
|
abstract class ReferenceLevelLoader {
|
|
ast.Library getLibraryReference(LibraryElement element);
|
|
ast.Class getClassReference(ClassElement element);
|
|
ast.Member getMemberReference(Element element);
|
|
ast.Class getRootClassReference();
|
|
ast.Constructor getRootClassConstructorReference();
|
|
ast.Class getCoreClassReference(String className);
|
|
ast.Constructor getCoreClassConstructorReference(String className,
|
|
{String constructorName, String library});
|
|
ast.TypeParameter tryGetClassTypeParameter(TypeParameterElement element);
|
|
ast.Class getSharedMixinApplicationClass(
|
|
ast.Library library, ast.Class supertype, ast.Class mixin);
|
|
bool get strongMode;
|
|
|
|
/// Whether or not to include redirecting factories in the output.
|
|
bool get ignoreRedirectingFactories;
|
|
}
|
|
|
|
class DartLoader implements ReferenceLevelLoader {
|
|
final ast.Program program;
|
|
final ApplicationRoot applicationRoot;
|
|
final Bimap<ClassElement, ast.Class> _classes =
|
|
new Bimap<ClassElement, ast.Class>();
|
|
final Bimap<Element, ast.Member> _members = new Bimap<Element, ast.Member>();
|
|
final Map<TypeParameterElement, ast.TypeParameter> _classTypeParameters =
|
|
<TypeParameterElement, ast.TypeParameter>{};
|
|
final Map<ast.Library, Map<String, ast.Class>> _mixinApplications =
|
|
<ast.Library, Map<String, ast.Class>>{};
|
|
final Map<LibraryElement, ast.Library> _libraries =
|
|
<LibraryElement, ast.Library>{};
|
|
final AnalysisContext context;
|
|
LibraryElement _dartCoreLibrary;
|
|
final List errors = [];
|
|
final List libraryElements = [];
|
|
|
|
/// Classes that have been referenced, and must be promoted to type level
|
|
/// so as not to expose partially initialized classes.
|
|
final List<ast.Class> temporaryClassWorklist = [];
|
|
|
|
final Map<LibraryElement, List<ClassElement>> mixinLibraryWorklist = {};
|
|
|
|
final bool ignoreRedirectingFactories;
|
|
|
|
LibraryElement _libraryBeingLoaded = null;
|
|
ClassElement _classBeingPromotedToMixin = null;
|
|
|
|
bool get strongMode => context.analysisOptions.strongMode;
|
|
|
|
DartLoader(this.program, DartOptions options, Packages packages,
|
|
{DartSdk dartSdk,
|
|
AnalysisContext context,
|
|
this.ignoreRedirectingFactories: true})
|
|
: this.context =
|
|
context ?? createContext(options, packages, dartSdk: dartSdk),
|
|
this.applicationRoot = options.applicationRoot;
|
|
|
|
String getLibraryName(LibraryElement element) {
|
|
return element.name.isEmpty ? null : element.name;
|
|
}
|
|
|
|
LibraryElement getLibraryElementFromUri(Uri uri) {
|
|
var source = context.sourceFactory.forUri2(uri);
|
|
if (source == null) return null;
|
|
return context.computeLibraryElement(source);
|
|
}
|
|
|
|
ast.Library getLibraryReference(LibraryElement element) {
|
|
var uri = applicationRoot.relativeUri(element.source.uri);
|
|
var library = _libraries[element];
|
|
if (library == null) {
|
|
library = new ast.Library(uri)
|
|
..isExternal = true
|
|
..name = getLibraryName(element)
|
|
..fileUri = element.source.uri;
|
|
program.libraries.add(library..parent = program);
|
|
_libraries[element] = library;
|
|
}
|
|
return library;
|
|
}
|
|
|
|
ast.Library getLibraryReferenceFromUri(Uri uri) {
|
|
return getLibraryReference(getLibraryElementFromUri(uri));
|
|
}
|
|
|
|
void _buildTopLevelMember(
|
|
ast.Member member, Element element, Declaration astNode) {
|
|
assert(member.parent != null);
|
|
new MemberBodyBuilder(this, member, element).build(astNode);
|
|
}
|
|
|
|
/// True if [element] is in the process of being loaded by
|
|
/// [_buildLibraryBody].
|
|
///
|
|
/// If this is the case, we should avoid adding new members to the classes
|
|
/// in the library, since the AST builder will rebuild the member lists.
|
|
bool isLibraryBeingLoaded(LibraryElement element) {
|
|
return _libraryBeingLoaded == element;
|
|
}
|
|
|
|
bool isClassBeingPromotedToMixin(ClassElement element) {
|
|
return _classBeingPromotedToMixin == element;
|
|
}
|
|
|
|
void _buildLibraryBody(LibraryElement element, ast.Library library,
|
|
List<CompilationUnit> units) {
|
|
assert(_libraryBeingLoaded == null);
|
|
_libraryBeingLoaded = element;
|
|
var classes = <ast.Class>[];
|
|
var procedures = <ast.Procedure>[];
|
|
var fields = <ast.Field>[];
|
|
|
|
void loadClass(NamedCompilationUnitMember declaration) {
|
|
// [declaration] can be a ClassDeclaration, EnumDeclaration, or a
|
|
// ClassTypeAlias.
|
|
ClassElement element = declaration.element;
|
|
var node = getClassReference(element);
|
|
promoteToBodyLevel(node, element, declaration);
|
|
classes.add(node);
|
|
}
|
|
|
|
void loadProcedure(FunctionDeclaration declaration) {
|
|
var element = declaration.element;
|
|
var node = getMemberReference(element);
|
|
_buildTopLevelMember(node, element, declaration);
|
|
procedures.add(node);
|
|
}
|
|
|
|
void loadField(TopLevelVariableDeclaration declaration) {
|
|
for (var field in declaration.variables.variables) {
|
|
var element = field.element;
|
|
// Ignore fields inserted through error recovery.
|
|
if (element.name == '') continue;
|
|
var node = getMemberReference(element);
|
|
_buildTopLevelMember(node, element, field);
|
|
fields.add(node);
|
|
}
|
|
}
|
|
|
|
for (var unit in units) {
|
|
for (CompilationUnitMember declaration in unit.declarations) {
|
|
if (declaration is ClassDeclaration ||
|
|
declaration is EnumDeclaration ||
|
|
declaration is ClassTypeAlias) {
|
|
loadClass(declaration);
|
|
} else if (declaration is FunctionDeclaration) {
|
|
loadProcedure(declaration);
|
|
} else if (declaration is TopLevelVariableDeclaration) {
|
|
loadField(declaration);
|
|
} else if (declaration is FunctionTypeAlias) {
|
|
// Nothing to do. Typedefs are handled lazily while constructing type
|
|
// references.
|
|
} else {
|
|
throw "unexpected node: ${declaration.runtimeType} $declaration";
|
|
}
|
|
}
|
|
}
|
|
libraryElements.add(element);
|
|
_iterateTemporaryClassWorklist();
|
|
// Ensure everything is stored in the original declaration order.
|
|
library.classes
|
|
..clear()
|
|
..addAll(classes)
|
|
..addAll(_mixinApplications[library]?.values ?? const []);
|
|
library.fields
|
|
..clear()
|
|
..addAll(fields);
|
|
library.procedures
|
|
..clear()
|
|
..addAll(procedures);
|
|
_libraryBeingLoaded = null;
|
|
}
|
|
|
|
LibraryElement getDartCoreLibrary() {
|
|
return _dartCoreLibrary ??= _findLibraryElement('dart:core');
|
|
}
|
|
|
|
LibraryElement _findLibraryElement(String uri) {
|
|
var source = context.sourceFactory.forUri(uri);
|
|
if (source == null) return null;
|
|
return context.computeLibraryElement(source);
|
|
}
|
|
|
|
ast.Class getRootClassReference() {
|
|
return getCoreClassReference('Object');
|
|
}
|
|
|
|
ast.Constructor getRootClassConstructorReference() {
|
|
var element = getDartCoreLibrary().getType('Object').constructors[0];
|
|
return getMemberReference(element);
|
|
}
|
|
|
|
ast.Class getCoreClassReference(String className) {
|
|
return getClassReference(getDartCoreLibrary().getType(className));
|
|
}
|
|
|
|
ast.Constructor getCoreClassConstructorReference(String className,
|
|
{String constructorName, String library}) {
|
|
LibraryElement libraryElement =
|
|
library != null ? _findLibraryElement(library) : getDartCoreLibrary();
|
|
ClassElement element = libraryElement.getType(className);
|
|
if (element == null) {
|
|
throw 'Missing core class $className from ${libraryElement.name}';
|
|
}
|
|
var constructor = element.constructors.firstWhere((constructor) {
|
|
return (constructorName == null)
|
|
? (constructor.nameLength == 0)
|
|
: (constructor.name == constructorName);
|
|
});
|
|
return getMemberReference(constructor);
|
|
}
|
|
|
|
ClassElement getClassElement(ast.Class node) {
|
|
return _classes.inverse[node];
|
|
}
|
|
|
|
void addMixinClassToLibrary(ast.Class class_, ast.Library library) {
|
|
assert(class_.parent == null);
|
|
library.addClass(class_);
|
|
var map =
|
|
_mixinApplications.putIfAbsent(library, () => <String, ast.Class>{});
|
|
map[class_.name] = class_;
|
|
}
|
|
|
|
/// Returns the IR for a class, at a temporary loading level.
|
|
///
|
|
/// The returned class has the correct name, flags, type parameter arity,
|
|
/// and enclosing library.
|
|
ast.Class getClassReference(ClassElement element) {
|
|
var classNode = _classes[element];
|
|
if (classNode != null) return classNode;
|
|
_classes[element] = classNode = new ast.Class(
|
|
name: element.name,
|
|
isAbstract: element.isAbstract,
|
|
fileUri: element.source.uri)
|
|
..fileOffset = element.nameOffset;
|
|
classNode.level = ast.ClassLevel.Temporary;
|
|
var library = getLibraryReference(element.library);
|
|
library.addClass(classNode);
|
|
// Initialize type parameter list without bounds.
|
|
for (var parameter in element.typeParameters) {
|
|
var parameterNode = new ast.TypeParameter(parameter.name);
|
|
_classTypeParameters[parameter] = parameterNode;
|
|
classNode.typeParameters.add(parameterNode);
|
|
parameterNode.parent = classNode;
|
|
}
|
|
// Ensure the class is at least promoted to type level before exposing it
|
|
// to kernel consumers.
|
|
temporaryClassWorklist.add(classNode);
|
|
return classNode;
|
|
}
|
|
|
|
/// Ensures the supertypes and type parameter bounds have been generated for
|
|
/// the given class.
|
|
void promoteToTypeLevel(ast.Class classNode) {
|
|
if (classNode.level.index >= ast.ClassLevel.Type.index) return;
|
|
classNode.level = ast.ClassLevel.Type;
|
|
var element = getClassElement(classNode);
|
|
assert(element != null);
|
|
var library = getLibraryReference(element.library);
|
|
var scope = new ClassScope(this, library);
|
|
// Initialize bounds on type parameters.
|
|
for (int i = 0; i < classNode.typeParameters.length; ++i) {
|
|
var parameter = element.typeParameters[i];
|
|
var parameterNode = classNode.typeParameters[i];
|
|
parameterNode.bound = parameter.bound == null
|
|
? scope.defaultTypeParameterBound
|
|
: scope.buildType(parameter.bound);
|
|
}
|
|
// Initialize supertypes.
|
|
Iterable<InterfaceType> mixins = element.mixins;
|
|
if (element.isMixinApplication && mixins.isNotEmpty) {
|
|
classNode.mixedInType = scope.buildSupertype(mixins.last);
|
|
mixins = mixins.take(mixins.length - 1);
|
|
}
|
|
if (element.supertype != null) {
|
|
ast.Supertype supertype = scope.buildSupertype(element.supertype);
|
|
bool useSharedMixin = true;
|
|
for (var mixin in mixins) {
|
|
var mixinType = scope.buildSupertype(mixin);
|
|
if (useSharedMixin &&
|
|
areDistinctUnboundTypeVariables(supertype, mixinType)) {
|
|
// Use a shared mixin application class for this library.
|
|
var mixinClass = getSharedMixinApplicationClass(
|
|
scope.currentLibrary, supertype.classNode, mixinType.classNode);
|
|
if (mixinClass.fileOffset < 0) {
|
|
mixinClass.fileOffset = element.nameOffset;
|
|
}
|
|
supertype = new ast.Supertype(
|
|
mixinClass,
|
|
supertype.typeArguments.length > mixinType.typeArguments.length
|
|
? supertype.typeArguments
|
|
: mixinType.typeArguments);
|
|
} else {
|
|
// Generate a new class specific for this mixin application.
|
|
var freshParameters =
|
|
getFreshTypeParameters(classNode.typeParameters);
|
|
var mixinClass = new ast.Class(
|
|
name: '${classNode.name}^${mixinType.classNode.name}',
|
|
isAbstract: true,
|
|
typeParameters: freshParameters.freshTypeParameters,
|
|
supertype: freshParameters.substituteSuper(supertype),
|
|
mixedInType: freshParameters.substituteSuper(mixinType),
|
|
fileUri: classNode.fileUri)
|
|
..fileOffset = element.nameOffset;
|
|
mixinClass.level = ast.ClassLevel.Type;
|
|
addMixinClassToLibrary(mixinClass, classNode.enclosingLibrary);
|
|
supertype = new ast.Supertype(mixinClass,
|
|
classNode.typeParameters.map(makeTypeParameterType).toList());
|
|
// This class cannot be used from anywhere else, so don't try to
|
|
// generate shared mixin applications using it.
|
|
useSharedMixin = false;
|
|
}
|
|
}
|
|
classNode.supertype = supertype;
|
|
for (var implementedType in element.interfaces) {
|
|
classNode.implementedTypes.add(scope.buildSupertype(implementedType));
|
|
}
|
|
}
|
|
}
|
|
|
|
void promoteToHierarchyLevel(ast.Class classNode) {
|
|
if (classNode.level.index >= ast.ClassLevel.Hierarchy.index) return;
|
|
promoteToTypeLevel(classNode);
|
|
classNode.level = ast.ClassLevel.Hierarchy;
|
|
var element = getClassElement(classNode);
|
|
if (element != null) {
|
|
// Ensure all instance members are at present.
|
|
for (var field in element.fields) {
|
|
if (!field.isStatic && !field.isSynthetic) {
|
|
getMemberReference(field);
|
|
}
|
|
}
|
|
for (var accessor in element.accessors) {
|
|
if (!accessor.isStatic && !accessor.isSynthetic) {
|
|
getMemberReference(accessor);
|
|
}
|
|
}
|
|
for (var method in element.methods) {
|
|
if (!method.isStatic && !method.isSynthetic) {
|
|
getMemberReference(method);
|
|
}
|
|
}
|
|
}
|
|
for (var supertype in classNode.supers) {
|
|
promoteToHierarchyLevel(supertype.classNode);
|
|
}
|
|
}
|
|
|
|
void promoteToMixinLevel(ast.Class classNode, ClassElement element,
|
|
NamedCompilationUnitMember astNode) {
|
|
if (classNode.level.index >= ast.ClassLevel.Mixin.index) return;
|
|
_classBeingPromotedToMixin = element;
|
|
promoteToHierarchyLevel(classNode);
|
|
classNode.level = ast.ClassLevel.Mixin;
|
|
// Clear out the member references that were put in the class.
|
|
// The AST builder will load them all put back in the right order.
|
|
classNode..fields.clear()..procedures.clear()..constructors.clear();
|
|
new ClassBodyBuilder(this, classNode, element).build(astNode);
|
|
_classBeingPromotedToMixin = null;
|
|
|
|
// Ensure mixed-in classes are available.
|
|
for (var mixin in element.mixins) {
|
|
_ensureMixinBecomesLoaded(mixin.element);
|
|
}
|
|
}
|
|
|
|
/// Ensures that [element] eventually becomes loaded at least at mixin level.
|
|
void _ensureMixinBecomesLoaded(ClassElement element) {
|
|
if (isClassBeingPromotedToMixin(element)) {
|
|
return;
|
|
}
|
|
var class_ = getClassReference(element);
|
|
if (class_.level.index >= ast.ClassLevel.Mixin.index) {
|
|
return;
|
|
}
|
|
var list = mixinLibraryWorklist[element.library] ??= <ClassElement>[];
|
|
list.add(element);
|
|
}
|
|
|
|
void promoteToBodyLevel(ast.Class classNode, ClassElement element,
|
|
NamedCompilationUnitMember astNode) {
|
|
if (classNode.level == ast.ClassLevel.Body) return;
|
|
promoteToMixinLevel(classNode, element, astNode);
|
|
classNode.level = ast.ClassLevel.Body;
|
|
// This frontend delivers the same contents for classes at body and mixin
|
|
// levels, even though as specified, the mixin level does not require all
|
|
// the static members to be present. So no additional work is needed.
|
|
}
|
|
|
|
ast.TypeParameter tryGetClassTypeParameter(TypeParameterElement element) {
|
|
return _classTypeParameters[element];
|
|
}
|
|
|
|
Element getMemberElement(ast.Member node) {
|
|
return _members.inverse[node];
|
|
}
|
|
|
|
ast.Member getMemberReference(Element element) {
|
|
assert(element != null);
|
|
assert(element is! Member); // Use the "base element".
|
|
return _members[element] ??= _buildMemberReference(element);
|
|
}
|
|
|
|
ast.Member _buildMemberReference(Element element) {
|
|
assert(element != null);
|
|
var member = _buildOrphanedMemberReference(element);
|
|
// Set the parent pointer and store it in the enclosing class or library.
|
|
// If the enclosing library is being built from the AST, do not add the
|
|
// member, since the AST builder will put it in there.
|
|
var parent = element.enclosingElement;
|
|
if (parent is ClassElement) {
|
|
var class_ = getClassReference(parent);
|
|
member.parent = class_;
|
|
if (!isLibraryBeingLoaded(element.library)) {
|
|
class_.addMember(member);
|
|
}
|
|
} else {
|
|
var library = getLibraryReference(element.library);
|
|
member.parent = library;
|
|
if (!isLibraryBeingLoaded(element.library)) {
|
|
library.addMember(member);
|
|
}
|
|
}
|
|
return member;
|
|
}
|
|
|
|
ast.Member _buildOrphanedMemberReference(Element element) {
|
|
assert(element != null);
|
|
ClassElement classElement = element.enclosingElement is ClassElement
|
|
? element.enclosingElement
|
|
: null;
|
|
TypeScope scope = classElement != null
|
|
? new ClassScope(this, getLibraryReference(element.library))
|
|
: new TypeScope(this);
|
|
if (classElement != null) {
|
|
getClassReference(classElement);
|
|
}
|
|
switch (element.kind) {
|
|
case ElementKind.CONSTRUCTOR:
|
|
ConstructorElement constructor = element;
|
|
if (constructor.isFactory) {
|
|
return new ast.Procedure(
|
|
_nameOfMember(constructor),
|
|
ast.ProcedureKind.Factory,
|
|
scope.buildFunctionInterface(constructor),
|
|
isAbstract: false,
|
|
isStatic: true,
|
|
isExternal: constructor.isExternal,
|
|
isConst: constructor.isConst,
|
|
fileUri: element.source.uri)
|
|
..fileOffset = element.nameOffset;
|
|
}
|
|
return new ast.Constructor(scope.buildFunctionInterface(constructor),
|
|
name: _nameOfMember(element),
|
|
isConst: constructor.isConst,
|
|
isExternal: constructor.isExternal,
|
|
isSyntheticDefault: constructor.isSynthetic)
|
|
..fileOffset = element.nameOffset;
|
|
|
|
case ElementKind.FIELD:
|
|
case ElementKind.TOP_LEVEL_VARIABLE:
|
|
VariableElement variable = element;
|
|
return new ast.Field(_nameOfMember(variable),
|
|
isStatic: variable.isStatic,
|
|
isFinal: variable.isFinal,
|
|
isConst: variable.isConst,
|
|
type: scope.buildType(variable.type),
|
|
fileUri: element.source.uri)
|
|
..fileOffset = element.nameOffset;
|
|
|
|
case ElementKind.METHOD:
|
|
case ElementKind.GETTER:
|
|
case ElementKind.SETTER:
|
|
case ElementKind.FUNCTION:
|
|
if (element is FunctionElement &&
|
|
element.enclosingElement is! CompilationUnitElement) {
|
|
throw 'Function $element is nested in ${element.enclosingElement} '
|
|
'and hence is not a member';
|
|
}
|
|
ExecutableElement executable = element;
|
|
return new ast.Procedure(
|
|
_nameOfMember(element),
|
|
_procedureKindOf(executable),
|
|
scope.buildFunctionInterface(executable),
|
|
isAbstract: executable.isAbstract,
|
|
isStatic: executable.isStatic,
|
|
isExternal: executable.isExternal,
|
|
fileUri: element.source.uri)
|
|
..fileOffset = element.nameOffset;
|
|
|
|
default:
|
|
throw 'Unexpected member kind: $element';
|
|
}
|
|
}
|
|
|
|
ast.ProcedureKind _procedureKindOf(ExecutableElement element) {
|
|
if (element is PropertyAccessorElement) {
|
|
return element.isGetter
|
|
? ast.ProcedureKind.Getter
|
|
: ast.ProcedureKind.Setter;
|
|
}
|
|
if (element is MethodElement) {
|
|
if (element.isOperator) return ast.ProcedureKind.Operator;
|
|
return ast.ProcedureKind.Method;
|
|
}
|
|
if (element is FunctionElement) {
|
|
return ast.ProcedureKind.Method;
|
|
}
|
|
if (element is ConstructorElement) {
|
|
assert(element.isFactory);
|
|
return ast.ProcedureKind.Factory;
|
|
}
|
|
throw 'Unexpected procedure: $element';
|
|
}
|
|
|
|
ast.Name _nameOfMember(Element element) {
|
|
// Use 'displayName' to avoid a trailing '=' for setters and 'name' to
|
|
// ensure unary minus is called 'unary-'.
|
|
String name =
|
|
element is PropertyAccessorElement ? element.displayName : element.name;
|
|
return new ast.Name(name, getLibraryReference(element.library));
|
|
}
|
|
|
|
/// True if the two types have form `C<T1 ... Tm>` and `D<T1 ... Tn>`, and
|
|
/// `T1 ... TN` are distinct type variables with no upper bound, where
|
|
/// `N = max(m,n)`.
|
|
bool areDistinctUnboundTypeVariables(
|
|
ast.Supertype first, ast.Supertype second) {
|
|
var seen = new Set<ast.TypeParameter>();
|
|
if (first.typeArguments.length < second.typeArguments.length) {
|
|
var tmp = first;
|
|
first = second;
|
|
second = tmp;
|
|
}
|
|
for (int i = 0; i < first.typeArguments.length; ++i) {
|
|
var firstArg = first.typeArguments[i];
|
|
if (!(firstArg is ast.TypeParameterType &&
|
|
seen.add(firstArg.parameter) &&
|
|
firstArg.parameter.bound is ast.DynamicType)) {
|
|
return false;
|
|
}
|
|
if (i < second.typeArguments.length &&
|
|
firstArg != second.typeArguments[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns the canonical mixin application of two classes, instantiated with
|
|
/// the same list of unbound type variables.
|
|
///
|
|
/// Given two classes:
|
|
/// class C<C1 ... Cm>
|
|
/// class D<D1 ... Dn>
|
|
///
|
|
/// This creates or reuses a mixin application class in the library of form:
|
|
///
|
|
/// abstract class C&D<T1 ... TN> = C<T1 ... Tm> with D<T1 ... Tn>
|
|
///
|
|
/// where `N = max(m,n)`.
|
|
///
|
|
/// Such a class can in general contain type errors due to incompatible
|
|
/// inheritance from `C` and `D`. This method therefore should only be called
|
|
/// if a mixin application `C<S1 ... Sm> with D<S1 ... Sn>` is seen, where
|
|
/// `S1 ... SN` are distinct, unbound type variables.
|
|
ast.Class getSharedMixinApplicationClass(
|
|
ast.Library library, ast.Class superclass, ast.Class mixedInClass) {
|
|
// TODO(asgerf): Avoid potential name clash due to associativity.
|
|
// As it is, these mixins get the same name:
|
|
// (A with B) with C
|
|
// A with (B with C)
|
|
String name = '${superclass.name}&${mixedInClass.name}';
|
|
return _mixinApplications
|
|
.putIfAbsent(library, () => <String, ast.Class>{})
|
|
.putIfAbsent(name, () {
|
|
var fresh =
|
|
superclass.typeParameters.length >= mixedInClass.typeParameters.length
|
|
? getFreshTypeParameters(superclass.typeParameters)
|
|
: getFreshTypeParameters(mixedInClass.typeParameters);
|
|
var typeArguments =
|
|
fresh.freshTypeParameters.map(makeTypeParameterType).toList();
|
|
var superArgs = typeArguments.length != superclass.typeParameters.length
|
|
? typeArguments.sublist(0, superclass.typeParameters.length)
|
|
: typeArguments;
|
|
var mixinArgs = typeArguments.length != mixedInClass.typeParameters.length
|
|
? typeArguments.sublist(0, mixedInClass.typeParameters.length)
|
|
: typeArguments;
|
|
var result = new ast.Class(
|
|
name: name,
|
|
isAbstract: true,
|
|
typeParameters: fresh.freshTypeParameters,
|
|
supertype: new ast.Supertype(superclass, superArgs),
|
|
mixedInType: new ast.Supertype(mixedInClass, mixinArgs),
|
|
fileUri: library.fileUri);
|
|
result.level = ast.ClassLevel.Type;
|
|
library.addClass(result);
|
|
return result;
|
|
});
|
|
}
|
|
|
|
String formatErrorMessage(
|
|
AnalysisError error, String filename, LineInfo lines) {
|
|
var location = lines.getLocation(error.offset);
|
|
return '[error] ${error.message} ($filename, '
|
|
'line ${location.lineNumber}, '
|
|
'col ${location.columnNumber})';
|
|
}
|
|
|
|
void ensureLibraryIsLoaded(ast.Library node) {
|
|
_ensureLibraryIsLoaded(node);
|
|
_iterateMixinLibraryWorklist();
|
|
}
|
|
|
|
void _ensureLibraryIsLoaded(ast.Library node) {
|
|
if (!node.isExternal) return;
|
|
node.isExternal = false;
|
|
var source = context.sourceFactory
|
|
.forUri2(applicationRoot.absoluteUri(node.importUri));
|
|
assert(source != null);
|
|
var element = context.computeLibraryElement(source);
|
|
var units = <CompilationUnit>[];
|
|
bool reportErrors = node.importUri.scheme != 'dart';
|
|
var tree = context.resolveCompilationUnit(source, element);
|
|
units.add(tree);
|
|
if (reportErrors) _processErrors(source);
|
|
for (var part in element.parts) {
|
|
var source = part.source;
|
|
units.add(context.resolveCompilationUnit(source, element));
|
|
if (reportErrors) _processErrors(source);
|
|
}
|
|
for (var import in element.imports) {
|
|
if (import.isDeferred && import.prefix != null) {
|
|
node.addDependency(new ast.LibraryDependency.deferredImport(
|
|
getLibraryReference(import.importedLibrary), import.prefix.name));
|
|
} else {
|
|
node.addDependency(new ast.LibraryDependency.import(
|
|
getLibraryReference(import.importedLibrary),
|
|
name: import.prefix?.name));
|
|
}
|
|
}
|
|
for (var export in element.exports) {
|
|
node.addDependency(new ast.LibraryDependency.export(
|
|
getLibraryReference(export.exportedLibrary)));
|
|
}
|
|
_buildLibraryBody(element, node, units);
|
|
}
|
|
|
|
void _processErrors(Source source) {
|
|
LineInfo lines;
|
|
for (var error in context.computeErrors(source)) {
|
|
if (error.errorCode is CompileTimeErrorCode ||
|
|
error.errorCode is ParserErrorCode ||
|
|
error.errorCode is ScannerErrorCode ||
|
|
error.errorCode is StrongModeCode) {
|
|
lines ??= context.computeLineInfo(source);
|
|
errors.add(formatErrorMessage(error, source.shortName, lines));
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadSdkInterface(ast.Program program, Target target) {
|
|
var requiredSdkMembers = target.requiredSdkClasses;
|
|
for (var libraryUri in requiredSdkMembers.keys) {
|
|
var source = context.sourceFactory.forUri2(Uri.parse(libraryUri));
|
|
var libraryElement = context.computeLibraryElement(source);
|
|
for (var member in requiredSdkMembers[libraryUri]) {
|
|
var type = libraryElement.getType(member);
|
|
if (type == null) {
|
|
throw 'Could not find $member in $libraryUri';
|
|
}
|
|
promoteToTypeLevel(getClassReference(type));
|
|
}
|
|
}
|
|
_iterateTemporaryClassWorklist();
|
|
_iterateMixinLibraryWorklist();
|
|
}
|
|
|
|
void loadEverything({Target target, bool compileSdk}) {
|
|
compileSdk ??= true;
|
|
if (compileSdk) {
|
|
ensureLibraryIsLoaded(getLibraryReference(getDartCoreLibrary()));
|
|
if (target != null) {
|
|
for (var uri in target.extraRequiredLibraries) {
|
|
var library = _findLibraryElement(uri);
|
|
if (library == null) {
|
|
errors.add('Could not find required library $uri');
|
|
continue;
|
|
}
|
|
ensureLibraryIsLoaded(getLibraryReference(library));
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < program.libraries.length; ++i) {
|
|
var library = program.libraries[i];
|
|
if (compileSdk || library.importUri.scheme != 'dart') {
|
|
ensureLibraryIsLoaded(library);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Builds a list of sources that have been loaded.
|
|
///
|
|
/// This operation may be expensive and should only be used for diagnostics.
|
|
List<String> getLoadedFileNames() {
|
|
var list = <String>[];
|
|
for (var library in program.libraries) {
|
|
LibraryElement element = context.computeLibraryElement(context
|
|
.sourceFactory
|
|
.forUri2(applicationRoot.absoluteUri(library.importUri)));
|
|
for (var unit in element.units) {
|
|
list.add(unit.source.fullName);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
void _iterateTemporaryClassWorklist() {
|
|
while (temporaryClassWorklist.isNotEmpty) {
|
|
var element = temporaryClassWorklist.removeLast();
|
|
promoteToTypeLevel(element);
|
|
}
|
|
}
|
|
|
|
void _iterateMixinLibraryWorklist() {
|
|
// The worklist groups classes in the same library together so that we
|
|
// request resolved ASTs for each library only once.
|
|
while (mixinLibraryWorklist.isNotEmpty) {
|
|
LibraryElement library = mixinLibraryWorklist.keys.first;
|
|
_libraryBeingLoaded = library;
|
|
List<ClassElement> classes = mixinLibraryWorklist.remove(library);
|
|
for (var class_ in classes) {
|
|
var classNode = getClassReference(class_);
|
|
promoteToMixinLevel(classNode, class_, class_.computeNode());
|
|
}
|
|
_libraryBeingLoaded = null;
|
|
}
|
|
_iterateTemporaryClassWorklist();
|
|
}
|
|
|
|
ast.Procedure _getMainMethod(Uri uri) {
|
|
Source source = context.sourceFactory.forUri2(uri);
|
|
LibraryElement library = context.computeLibraryElement(source);
|
|
var mainElement = library.entryPoint;
|
|
if (mainElement == null) return null;
|
|
var mainMember = getMemberReference(mainElement);
|
|
if (mainMember is ast.Procedure && !mainMember.isAccessor) {
|
|
return mainMember;
|
|
}
|
|
// Top-level 'main' getters are not supported at the moment.
|
|
return null;
|
|
}
|
|
|
|
ast.Procedure _makeMissingMainMethod(ast.Library library) {
|
|
var main = new ast.Procedure(
|
|
new ast.Name('main'),
|
|
ast.ProcedureKind.Method,
|
|
new ast.FunctionNode(new ast.ExpressionStatement(new ast.Throw(
|
|
new ast.StringLiteral('Program has no main method')))),
|
|
isStatic: true)
|
|
..fileUri = library.fileUri;
|
|
library.addMember(main);
|
|
return main;
|
|
}
|
|
|
|
void loadProgram(Uri mainLibrary, {Target target, bool compileSdk}) {
|
|
ast.Library library = getLibraryReferenceFromUri(mainLibrary);
|
|
ensureLibraryIsLoaded(library);
|
|
var mainMethod = _getMainMethod(mainLibrary);
|
|
loadEverything(target: target, compileSdk: compileSdk);
|
|
if (mainMethod == null) {
|
|
mainMethod = _makeMissingMainMethod(library);
|
|
}
|
|
program.mainMethod = mainMethod;
|
|
for (LibraryElement libraryElement in libraryElements) {
|
|
for (CompilationUnitElement compilationUnitElement
|
|
in libraryElement.units) {
|
|
var source = compilationUnitElement.source;
|
|
LineInfo lineInfo = context.computeLineInfo(source);
|
|
List<int> sourceCode;
|
|
try {
|
|
sourceCode =
|
|
const Utf8Encoder().convert(context.getContents(source).data);
|
|
} catch (e) {
|
|
// The source's contents could not be accessed.
|
|
sourceCode = const <int>[];
|
|
}
|
|
program.uriToSource[source.uri] =
|
|
new ast.Source(lineInfo.lineStarts, sourceCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
ast.Library loadLibrary(Uri uri) {
|
|
ast.Library library = getLibraryReferenceFromUri(uri);
|
|
ensureLibraryIsLoaded(library);
|
|
return library;
|
|
}
|
|
}
|
|
|
|
class Bimap<K, V> {
|
|
final Map<K, V> nodeMap = <K, V>{};
|
|
final Map<V, K> inverse = <V, K>{};
|
|
|
|
bool containsKey(K key) => nodeMap.containsKey(key);
|
|
|
|
V operator [](K key) => nodeMap[key];
|
|
|
|
void operator []=(K key, V value) {
|
|
assert(!nodeMap.containsKey(key));
|
|
nodeMap[key] = value;
|
|
inverse[value] = key;
|
|
}
|
|
}
|
|
|
|
/// Creates [DartLoader]s for a given configuration, while reusing the
|
|
/// [DartSdk] and [Packages] object if possible.
|
|
class DartLoaderBatch {
|
|
Packages packages;
|
|
DartSdk dartSdk;
|
|
|
|
String lastSdk;
|
|
String lastPackagePath;
|
|
bool lastStrongMode;
|
|
|
|
Future<DartLoader> getLoader(ast.Program program, DartOptions options,
|
|
{String packageDiscoveryPath}) async {
|
|
if (dartSdk == null ||
|
|
lastSdk != options.sdk ||
|
|
lastStrongMode != options.strongMode) {
|
|
lastSdk = options.sdk;
|
|
lastStrongMode = options.strongMode;
|
|
dartSdk = createDartSdk(options.sdk, strongMode: options.strongModeSdk);
|
|
}
|
|
if (packages == null ||
|
|
lastPackagePath != options.packagePath ||
|
|
packageDiscoveryPath != null) {
|
|
lastPackagePath = options.packagePath;
|
|
packages = await createPackages(options.packagePath,
|
|
discoveryPath: packageDiscoveryPath);
|
|
}
|
|
return new DartLoader(program, options, packages, dartSdk: dartSdk);
|
|
}
|
|
}
|
|
|
|
Future<Packages> createPackages(String packagePath,
|
|
{String discoveryPath}) async {
|
|
if (packagePath != null) {
|
|
var absolutePath = new io.File(packagePath).absolute.path;
|
|
if (await new io.Directory(packagePath).exists()) {
|
|
return getPackagesDirectory(new Uri.file(absolutePath));
|
|
} else if (await new io.File(packagePath).exists()) {
|
|
return loadPackagesFile(new Uri.file(absolutePath));
|
|
} else {
|
|
throw 'Packages not found: $packagePath';
|
|
}
|
|
}
|
|
if (discoveryPath != null) {
|
|
return findPackagesFromFile(Uri.parse(discoveryPath));
|
|
}
|
|
return Packages.noPackages;
|
|
}
|
|
|
|
AnalysisOptions createAnalysisOptions(bool strongMode) {
|
|
return new AnalysisOptionsImpl()
|
|
..strongMode = strongMode
|
|
..generateImplicitErrors = false
|
|
..generateSdkErrors = false
|
|
..preserveComments = false
|
|
..hint = false
|
|
..enableSuperMixins = true;
|
|
}
|
|
|
|
DartSdk createDartSdk(String path, {bool strongMode, bool isSummary}) {
|
|
if (isSummary ?? false) {
|
|
return new SummaryBasedDartSdk(path, strongMode);
|
|
}
|
|
var resources = PhysicalResourceProvider.INSTANCE;
|
|
return new FolderBasedDartSdk(resources, resources.getFolder(path))
|
|
..context
|
|
.analysisOptions
|
|
.setCrossContextOptionsFrom(createAnalysisOptions(strongMode));
|
|
}
|
|
|
|
class CustomUriResolver extends UriResolver {
|
|
final ResourceUriResolver _resourceUriResolver;
|
|
final Map<Uri, Uri> _customUrlMappings;
|
|
|
|
CustomUriResolver(this._resourceUriResolver, this._customUrlMappings);
|
|
|
|
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
|
|
// TODO(kustermann): Once dartk supports configurable imports we should be
|
|
// able to get rid of this.
|
|
if (uri.toString() == 'package:mojo/src/internal_contract.dart') {
|
|
uri = actualUri = Uri.parse('dart:mojo.internal');
|
|
}
|
|
|
|
Uri baseUri = uri;
|
|
String relative;
|
|
String path = uri.path;
|
|
int index = path.indexOf('/');
|
|
if (index > 0) {
|
|
baseUri = uri.replace(path: path.substring(0, index));
|
|
relative = path.substring(index + 1);
|
|
}
|
|
Uri baseMapped = _customUrlMappings[baseUri];
|
|
if (baseMapped == null) return null;
|
|
|
|
Uri mapped = relative != null ? baseMapped.resolve(relative) : baseMapped;
|
|
return _resourceUriResolver.resolveAbsolute(mapped, actualUri);
|
|
}
|
|
|
|
Uri restoreAbsolute(Source source) {
|
|
return _resourceUriResolver.restoreAbsolute(source);
|
|
}
|
|
}
|
|
|
|
AnalysisContext createContext(DartOptions options, Packages packages,
|
|
{DartSdk dartSdk}) {
|
|
bool fromSummary = options.sdkSummary != null;
|
|
dartSdk ??= createDartSdk(fromSummary ? options.sdkSummary : options.sdk,
|
|
strongMode: options.strongModeSdk, isSummary: fromSummary);
|
|
|
|
var resourceProvider = PhysicalResourceProvider.INSTANCE;
|
|
var resourceUriResolver = new ResourceUriResolver(resourceProvider);
|
|
List<UriResolver> resolvers = [];
|
|
var customUriMappings = options.customUriMappings;
|
|
if (customUriMappings != null && customUriMappings.length > 0) {
|
|
resolvers
|
|
.add(new CustomUriResolver(resourceUriResolver, customUriMappings));
|
|
}
|
|
resolvers.add(new DartUriResolver(dartSdk));
|
|
resolvers.add(resourceUriResolver);
|
|
|
|
if (packages != null) {
|
|
var folderMap = <String, List<Folder>>{};
|
|
packages.asMap().forEach((String packagePath, Uri uri) {
|
|
String path = resourceProvider.pathContext.fromUri(uri);
|
|
folderMap[packagePath] = [resourceProvider.getFolder(path)];
|
|
});
|
|
resolvers.add(new PackageMapUriResolver(resourceProvider, folderMap));
|
|
}
|
|
|
|
AnalysisContext context = AnalysisEngine.instance.createAnalysisContext()
|
|
..sourceFactory = new SourceFactory(resolvers)
|
|
..analysisOptions = createAnalysisOptions(options.strongMode);
|
|
|
|
options.declaredVariables.forEach((String name, String value) {
|
|
context.declaredVariables.define(name, value);
|
|
});
|
|
|
|
return context;
|
|
}
|