Support for modular mixin resolution.
When a class from a different build unit is mixed in, the instance
members of the mixed-in class are retained in the external library
definition, so the mixin resolution pass can clone them.
BUG=
R=kmillikin@google.com
Review-Url: https://codereview.chromium.org/2669303002 .
diff --git a/pkg/kernel/bin/dartk.dart b/pkg/kernel/bin/dartk.dart
index 00ef986..84fbf77 100755
--- a/pkg/kernel/bin/dartk.dart
+++ b/pkg/kernel/bin/dartk.dart
@@ -340,6 +340,7 @@
program = loader.loadProgram(fileUri, target: target);
} else {
var library = loader.loadLibrary(fileUri);
+ loader.loadSdkInterface(program, target);
assert(library ==
repository.getLibraryReference(applicationRoot.relativeUri(fileUri)));
program = new Program(repository.libraries);
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index a287dd6..dfeb53e 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -184,6 +184,8 @@
Byte tag;
}
+enum ClassLevel { Type = 0, Hierarchy = 1, Mixin = 2, Body = 3, }
+
// A class can be represented at one of three levels: type, hierarchy, or body.
//
// If the enclosing library is external, a class is either at type or
@@ -197,7 +199,7 @@
type NormalClass extends Class {
Byte tag = 2;
FileOffset fileOffset;
- Byte flags (isAbstract, isTypeLevel);
+ Byte flags (isAbstract, xx); // Where xx is index into ClassLevel
StringReference name;
// An absolute path URI to the .dart file from which the class was created.
UriReference fileUri;
diff --git a/pkg/kernel/lib/analyzer/loader.dart b/pkg/kernel/lib/analyzer/loader.dart
index 664bf79..d9dbf13 100644
--- a/pkg/kernel/lib/analyzer/loader.dart
+++ b/pkg/kernel/lib/analyzer/loader.dart
@@ -106,6 +106,8 @@
/// 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;
@@ -113,8 +115,9 @@
bool get strongMode => context.analysisOptions.strongMode;
DartLoader(this.repository, DartOptions options, Packages packages,
- {DartSdk dartSdk, AnalysisContext context,
- this.ignoreRedirectingFactories: true})
+ {DartSdk dartSdk,
+ AnalysisContext context,
+ this.ignoreRedirectingFactories: true})
: this.context =
context ?? createContext(options, packages, dartSdk: dartSdk),
this.applicationRoot = options.applicationRoot;
@@ -199,7 +202,7 @@
}
}
libraryElements.add(element);
- _iterateWorklist();
+ _iterateTemporaryClassWorklist();
// Ensure everything is stored in the original declaration order.
library.classes
..clear()
@@ -388,15 +391,43 @@
}
}
- void promoteToBodyLevel(ast.Class classNode, ClassElement element,
+ void promoteToMixinLevel(ast.Class classNode, ClassElement element,
NamedCompilationUnitMember astNode) {
- if (classNode.level == ast.ClassLevel.Body) return;
+ if (classNode.level.index >= ast.ClassLevel.Mixin.index) return;
promoteToHierarchyLevel(classNode);
- classNode.level = ast.ClassLevel.Body;
+ 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);
+
+ // 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 (isLibraryBeingLoaded(element.library)) {
+ 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) {
@@ -617,6 +648,11 @@
}
void ensureLibraryIsLoaded(ast.Library node) {
+ _ensureLibraryIsLoaded(node);
+ _iterateMixinLibraryWorklist();
+ }
+
+ void _ensureLibraryIsLoaded(ast.Library node) {
if (!node.isExternal) return;
node.isExternal = false;
var source = context.sourceFactory
@@ -649,6 +685,23 @@
}
}
+ 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) {
@@ -688,13 +741,29 @@
return list;
}
- void _iterateWorklist() {
+ 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);
@@ -714,8 +783,7 @@
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;
+ isStatic: true)..fileUri = library.fileUri;
library.addMember(main);
return main;
}
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 80a62f04..30998cd 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -275,6 +275,18 @@
/// members needed to detect override constraints.
Hierarchy,
+ /// All instance members of the class have their body loaded, and their
+ /// annotations are present.
+ ///
+ /// All supertypes of this class are at [Hierarchy] level or higher.
+ ///
+ /// If this class is a mixin application, then its mixin is loaded at [Mixin]
+ /// level or higher.
+ ///
+ /// This level exists so the contents of a mixin can be cloned into a
+ /// mixin application.
+ Mixin,
+
/// All members of the class are fully loaded and are in the correct order.
///
/// Annotations are present on classes and members.
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 8c940f6..0ca2f607 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -324,9 +324,8 @@
node.fileOffset = readOffset();
int flags = readByte();
node.isAbstract = flags & 0x1 != 0;
- node.level = _currentLibrary.isExternal
- ? (flags & 0x2 != 0) ? ClassLevel.Type : ClassLevel.Hierarchy
- : ClassLevel.Body;
+ int levelIndex = (flags >> 1) & 0x3;
+ node.level = ClassLevel.values[levelIndex + 1];
node.name = readStringOrNullIfEmpty();
node.fileUri = readUriReference();
node.annotations = readAnnotationList(node);
@@ -351,9 +350,8 @@
node.fileOffset = readOffset();
int flags = readByte();
node.isAbstract = flags & 0x1 != 0;
- node.level = _currentLibrary.isExternal
- ? (flags & 0x2 != 0) ? ClassLevel.Type : ClassLevel.Hierarchy
- : ClassLevel.Body;
+ int levelIndex = (flags >> 1) & 0x3;
+ node.level = ClassLevel.values[levelIndex];
node.name = readStringOrNullIfEmpty();
node.fileUri = readUriReference();
node.annotations = readAnnotationList(node);
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 6d3dabd..b838d5e 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -283,11 +283,14 @@
writeList(annotations, writeAnnotation);
}
+ int _encodeClassFlags(bool isAbstract, ClassLevel level) {
+ int abstactFlag = isAbstract ? 1 : 0;
+ int levelFlags = (level.index - 1) << 1;
+ return abstactFlag | levelFlags;
+ }
+
visitClass(Class node) {
- int flags = node.isAbstract ? 1 : 0;
- if (node.level == ClassLevel.Type) {
- flags |= 0x2;
- }
+ int flags = _encodeClassFlags(node.isAbstract, node.level);
if (node.isMixinApplication) {
writeByte(Tag.MixinClass);
writeOffset(node, node.fileOffset);
diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart
index 28ce858e..72bf93d9 100644
--- a/pkg/kernel/lib/core_types.dart
+++ b/pkg/kernel/lib/core_types.dart
@@ -27,6 +27,33 @@
Class functionClass;
Class invocationClass;
+ static final Map<String, List<String>> requiredClasses = {
+ 'dart:core': [
+ 'Object',
+ 'Null',
+ 'bool',
+ 'int',
+ 'num',
+ 'double',
+ 'String',
+ 'List',
+ 'Map',
+ 'Iterable',
+ 'Iterator',
+ 'Symbol',
+ 'Type',
+ 'Function',
+ 'Invocation',
+ ],
+ 'dart:_internal': [
+ 'Symbol',
+ ],
+ 'dart:async': [
+ 'Future',
+ 'Stream',
+ ]
+ };
+
Library getCoreLibrary(String uri) => _dartLibraries[uri]?.library;
Class getCoreClass(String libraryUri, String className) {
diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart
index e1d6a21..ec89ffb 100644
--- a/pkg/kernel/lib/target/targets.dart
+++ b/pkg/kernel/lib/target/targets.dart
@@ -4,11 +4,11 @@
library kernel.target.targets;
import '../ast.dart';
-
+import '../core_types.dart';
+import '../transformations/treeshaker.dart' show ProgramRoot;
+import 'flutter.dart';
import 'vm.dart';
import 'vmcc.dart';
-import 'flutter.dart';
-import '../transformations/treeshaker.dart' show ProgramRoot;
final List<String> targetNames = targets.keys.toList();
@@ -51,6 +51,10 @@
/// by the target.
Map<String, String> get extraDeclaredVariables => const <String, String>{};
+ /// Classes from the SDK whose interface is required for the modular
+ /// transformations.
+ Map<String, List<String>> get requiredSdkClasses => CoreTypes.requiredClasses;
+
bool get strongMode;
/// If true, the SDK should be loaded in strong mode.
diff --git a/pkg/kernel/lib/transformations/mixin_full_resolution.dart b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
index 92ed3b7..8d84347 100644
--- a/pkg/kernel/lib/transformations/mixin_full_resolution.dart
+++ b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
@@ -78,7 +78,8 @@
if (!processedClasses.add(class_)) return;
// Ensure super classes have been transformed before this class.
- if (class_.superclass != null) {
+ if (class_.superclass != null &&
+ class_.superclass.level.index >= ClassLevel.Mixin.index) {
transformClass(processedClasses, transformedClasses, class_.superclass);
}
@@ -86,7 +87,7 @@
// constructors in this class.
if (!class_.isMixinApplication) return;
- if (class_.mixedInClass.level != ClassLevel.Body) {
+ if (class_.mixedInClass.level.index < ClassLevel.Mixin.index) {
throw new Exception(
'Class "${class_.name}" mixes in "${class_.mixedInClass.name}" from'
' an external library. Did you forget --link?');