Allow extensions with no signature in registerExtension
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index edcc079..29162c1 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -41,6 +41,7 @@
import 'nullability_inferrer.dart';
import 'side_effect_analysis.dart';
import 'usage.dart';
+import 'dart:io';
// Various dynamic helpers we call.
// If renaming these, make sure to check other places like the
@@ -110,7 +111,7 @@
bool _isDartRuntime;
JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary,
- this._extensionTypes, this._fieldsNeedingStorage, this._isReachable)
+ this._extensionTypes, this._fieldsNeedingStorage, this._isReachable, this._treeShakingData)
: compiler = compiler,
options = compiler.options.codegenOptions,
_types = compiler.context.typeProvider {
@@ -127,6 +128,7 @@
NullableExpressionPredicate _isNullable;
ReachabilityPredicate _isReachable;
+ Function _treeShakingData;
JS.Program emitLibrary(LibraryUnit library) {
// Modify the AST to make coercions explicit.
@@ -441,6 +443,7 @@
staticFields, methods, node.metadata, jsPeerName);
var result = _finishClassDef(type, body);
+ result = _statement([new JS.Comment('/* ${_treeShakingData(classElem)} */'), result]);
if (jsPeerName != null) {
// This class isn't allowed to be lazy, because we need to set up
@@ -1589,6 +1592,14 @@
_loader.declareBeforeUse(element);
+ // if (node is! FormalParameter && !_isReachable(element)) {
+ // var enclosingDecl = node.getAncestor((n) => n is Declaration);
+ // // if (enclosin)
+ // stderr.writeln('enclosingDecl($node) = $enclosingDecl');
+ // if (enclosingDecl?.element != null) _isReachable(enclosingDecl.element, throwIfNot: true);
+ // _isReachable(element, throwIfNot: true);
+ // }
+
// type literal
if (element is ClassElement ||
element is DynamicElementImpl ||
@@ -3547,6 +3558,8 @@
final _roots = new Set<Element>();
UsageVisitor _usageVisitor;
final TreeShakingMode _treeShakingMode;
+
+ ReachabilityPredicate _isReachable;
JSGenerator(AbstractCompiler compiler)
: _types = compiler.context.typeProvider,
_treeShakingMode = compiler.options.codegenOptions.treeShakingMode,
@@ -3568,6 +3581,10 @@
_addExtensionType(_types.doubleType);
_usageVisitor = new UsageVisitor(_extensionTypes);
+
+ _isReachable = _treeShakingMode == TreeShakingMode.none
+ ? (_) => true
+ : _usageVisitor.buildReachabilityPredicate(_roots);
}
void _addExtensionType(InterfaceType t) {
@@ -3635,11 +3652,8 @@
var library = unit.library.element.library;
var fields = findFieldsNeedingStorage(unit, _extensionTypes);
var rules = new StrongTypeSystemImpl();
- var isReachable = _treeShakingMode == TreeShakingMode.none
- ? (_) => true
- : _usageVisitor.buildReachabilityPredicate(_roots);
var codegen =
- new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields, isReachable);
+ new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields, _isReachable, _usageVisitor.getTreeShakingData);
var module = codegen.emitLibrary(unit);
var out = compiler.getOutputPath(library.source.uri);
var flags = compiler.options;
diff --git a/lib/src/codegen/usage.dart b/lib/src/codegen/usage.dart
index f7fafd4..3234200 100644
--- a/lib/src/codegen/usage.dart
+++ b/lib/src/codegen/usage.dart
@@ -9,29 +9,104 @@
import '../utils.dart';
-typedef bool ReachabilityPredicate(Element e);
+typedef bool ReachabilityPredicate(Element e, {bool throwIfNot});
class UsageVisitor extends GeneralizingAstVisitor {
- final _extensionMappings = <ClassElement, Set<ClassElement>>{};
+ final _extensionMembers = <ClassElement, Map<String, ClassMemberElement>>{};
+ // final _extensionMappings = <ClassElement, Set<ClassElement>>{};
// TODO(ochafik): Detect reflect & reflectType.
bool followReflection;
bool debug;
- final _specialRoots = new Set<Element>();
+ final _specialRoots = new Set<Element>.identity();
final _graph = new DirectedGraph<Object>();
UsageVisitor(Set<ClassElement> extensionTypes, {this.followReflection : true, this.debug : true}) {
+ // var extensionMappings = <ClassElement, Set<ClassElement>>{};
for (var t in extensionTypes) {
- for (var a in _collectHierarchy(t, useExtensions: false)) {
- if (t == a) continue;
- _extensionMappings.putIfAbsent(a, () => new Set<ClassElement>()).add(t);
+ visit(InterfaceType a) {
+ if (a.isObject || a == t) return;
+ // stderr.writeln('Extension: $a');
+ var members = _extensionMembers.putIfAbsent(a.element, () => <String, ClassMemberElement>{});
+ for (var m in a.methods) {
+ members[m.name] = m;
+ }
+ for (var m in a.accessors) {
+ if (m.isGetter) {
+ members[m.name] = m.variable;
+ }
+ }
+ // extensionMappings.putIfAbsent(a.element, () => new Set<ClassElement>()).add(t);
+ a.element.allSupertypes.forEach(visit);
}
+ t.interfaces.forEach(visit);
+ // for (var a in _collectHierarchy(t, useExtensions: false)) {
+ // if (t == a /*|| a.type.isObject*/) continue;
+ // _extensionMappings.putIfAbsent(a, () => new Set<ClassElement>()).add(t);
+ // }
}
// stderr.writeln('ExtensionMappings:');
- // _extensionMappings.forEach((from, tos) {
- // stderr.writeln('\t${from.name} -> ${tos.map((t) => t.name).join(", ")}');
+ // _extensionMembers.forEach((from, tos) {
+ // stderr.writeln('\t${from.name}: ${tos.keys.join(", ")}');
// });
}
+ bool _isSpecialRoot(Element e) {
+ e = _normalize(e);
+ var uri = e.source.uri.toString();
+ var res =
+ uri.startsWith('dart:_runtime') ||
+ uri == 'dart:_debugger' && (debug || e.name == 'registerDevtoolsFormatter') ||
+ uri == 'dart:_isolate_helper' && e.name == 'startRootIsolate' ||
+ // // e.name == 'iterator' ||
+ // e is ClassMemberElement && (
+ // e.name == 'values'
+ // // e.enclosingElement.name == 'Map' && e.name == 'values'
+ // ) ||
+ uri.startsWith('dart:core') && (
+ e.name == 'List' ||
+ e.name == 'String' ||
+ e.name == 'Iterator' ||
+ e.name == 'Object'
+ ) ||
+ uri.startsWith('dart:_interceptors') && (
+ e.name == 'Interceptor' ||
+ e.name == 'JSNumber' ||
+ e.name == 'JSArray' ||
+ e.name == 'JSBool' ||
+ e is ClassMemberElement && (
+ e.enclosingElement.name == 'JSNumber' && e.name == 'truncate' ||
+ e.enclosingElement.name == 'JSArray' && e.name == 'checkGrowable'
+ )
+ ) ||
+ uri == 'dart:_internal/iterable.dart' && (
+ e.name == 'MappedIterator'
+ ) ||
+ uri.startsWith('dart:collection') && (
+ e.name == 'ListBase' ||
+ e.name == 'ListMixin' ||
+ e.name == 'LinkedHashSetCell' ||
+ e.name == 'LinkedHashMapCell' ||
+ e.name == 'IterableBase' ||
+ e.name == 'LinkedHashMapKeyIterable' ||
+ e.name == 'LinkedHashMapKeyIterator' ||
+ e.name == 'ListQueue' ||
+ // e.name == '_LinkedHashSet' ||
+ // e.name == '_LinkedHashMap' ||
+ e is ClassMemberElement && (
+ // e.enclosingElement.name == 'MappedIterator' ||
+ // e.enclosingElement.name == 'LinkedHashMapKeyIterable' ||
+ // e.enclosingElement.name == 'ListQueue' ||
+ e.enclosingElement.name == '_LinkedHashSet' ||
+ e.enclosingElement.name == '_LinkedHashMap' ||
+ e.enclosingElement.name == 'Map' && e.name == 'values'
+ )
+ );
+ // if (e.name == 'dynamicR') {//e.name == 'MappedIterator') { //e.enclosingElement.name == 'JSNumber') {
+ // stderr.writeln("_isSpecialRoot(${_str(e)} @ $uri) = $res");
+ // }
+ return res;
+ }
+
AstNode getSameOrAncestor(AstNode node, bool predicate(AstNode node)) =>
predicate(node) ? node : node.getAncestor(predicate);
@@ -48,7 +123,7 @@
}
Element _normalize(Element e) {
- // if (e is PropertyAccessorElement) return _normalize(e.variable);
+ if (e is PropertyAccessorElement) return _normalize(e.variable);
if (e is ConstructorElement) {
// Normalize generic constructors (Map<dynamic, dynamic> -> Map<K, V>).
var cls = e.enclosingElement;
@@ -98,17 +173,13 @@
Iterable<ClassElement> _collectHierarchy(ClassElement e, {bool useExtensions: true}) sync* {
yield e;
+ // if (!e.type.isObject) {
yield* e.allSupertypes.expand((InterfaceType t) => _collectHierarchy(t.element));
- if (useExtensions) {
- yield* _extensionMappings[e] ?? [];
- }
- // if (_extensionTypes.contains(e)) {
- // stderr.writeln("EXT: $e");
+ // if (useExtensions) {
+ // yield* _extensionMappings[e] ?? [];
+ // }
// }
- // if (e.supertype != null) yield e.supertype.element;
- // if (e.interfaces != null) yield* e.interfaces.map(_getElement);
- // if (e.mixins != null) yield* e.mixins.map(_getElement);
}
@override
@@ -299,31 +370,35 @@
if (memberElement != null) {
_declareDep(node, memberElement);
}
+
if (target is! ClassElement) {
- if (memberElement == null) _declareDep(node, new _UnresolvedElement(null, propertyName));
+ if (memberElement == null) {
+ _declareDep(node, new _UnresolvedElement(null, propertyName));
+ }
} else {
+ var members = _extensionMembers[target];
+ memberElement ??= members == null ? null : members[propertyName];
+
if (memberElement != null) {
- for (var ancestor in _collectHierarchy(target)) {
- var e;
- if (ancestor == target) e = memberElement;
- e ??= ancestor.getField(propertyName);
- e ??= ancestor.getGetter(propertyName);
- e ??= ancestor.getSetter(propertyName);
- e ??= ancestor.getMethod(propertyName);
- // if ((propertyName == 'floor' || propertyName == 'truncate')) {
- // if (e != null) {
- // stderr.writeln('RESOLVED ON ANCESTOR ${ancestor.name} of ${target.name}: ${e.name}');
- // } else {
- // stderr.writeln('NOT RESOLVED ON ANCESTOR ${ancestor.name} of ${target.name}: $propertyName');
- // }
- // }
- e ??= new _UnresolvedElement(ancestor, propertyName);
- _declareDep(node, e);
- }
- } else {
- for (var ancestor in _collectHierarchy(target)) {
- _declareDep(node, new _UnresolvedElement(ancestor, propertyName));
- }
+ _declareDep(node, memberElement);
+ }
+
+ for (var ancestor in _collectHierarchy(target)) {
+ if (ancestor == target) continue;
+
+ var e = ancestor.getField(propertyName);
+ e ??= ancestor.getGetter(propertyName);
+ e ??= ancestor.getSetter(propertyName);
+ e ??= ancestor.getMethod(propertyName);
+ // if ((propertyName == 'floor' || propertyName == 'truncate')) {
+ // if (e != null) {
+ // stderr.writeln('RESOLVED ON ANCESTOR ${ancestor.name} of ${target.name}: ${e.name}');
+ // } else {
+ // stderr.writeln('NOT RESOLVED ON ANCESTOR ${ancestor.name} of ${target.name}: $propertyName');
+ // }
+ // }
+ e ??= new _UnresolvedElement(ancestor, propertyName);
+ _declareDep(node, e);
}
}
}
@@ -359,6 +434,8 @@
@override
visitFormalParameterList(FormalParameterList node) {
for (var param in node.parameters) {
+ _declareDep(node, param.element);
+ if (param is DefaultFormalParameter) param.defaultValue?.accept(this);
var e = param.element?.type?.element;
if (e != null) _declareDep(node, e);
}
@@ -382,12 +459,26 @@
}
@override
+ visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
+ visitFunctionDeclaration(node.functionDeclaration);
+ // super.visitFunctionDeclarationStatement(node);
+ }
+
+ @override
+ visitFunctionExpression(FunctionExpression node) {
+ _withEnclosingElement(node.element, () {
+ if (node.parameters != null) {
+ visitFormalParameterList(node.parameters);
+ }
+ super.visitFunctionExpression(node);
+ });
+ }
+
+ @override
visitFunctionDeclaration(FunctionDeclaration node) {
_withEnclosingElement(node.element, () {
if (node.returnType != null) visitTypeName(node.returnType);
- if (node.functionExpression.parameters != null) {
- visitFormalParameterList(node.functionExpression.parameters);
- }
+ visitFunctionExpression(node.functionExpression);
super.visitFunctionDeclaration(node);
});
}
@@ -422,46 +513,29 @@
super.visitIdentifier(node);
}
- bool _isSpecialRoot(Element e) {
- var uri = e.source.uri.toString();
- // if (e.enclosingElement.name == 'JSNumber') {
- // stderr.writeln("URI for $e: $uri");
- // }
- return
- uri == 'dart:_debugger' && (debug || e.name == 'registerDevtoolsFormatter') ||
- uri == 'dart:_isolate_helper' && e.name == 'startRootIsolate' ||
- e.name == 'iterator' ||
- e.name == 'values' ||
- uri.startsWith('dart:_interceptors/') && (
- e.name == 'JSNumber' ||
- e.name == 'JSArray' ||
- e.name == 'JSBool' ||
- e is ClassMemberElement && (
- e.name == 'truncate'
- )
- // e.enclosingElement.name == 'JSNumber' ||
- // e.enclosingElement.name == 'JSArray' ||
- // e.enclosingElement.name == 'JSBool'
- ) ||
- uri == 'dart:collection' && (
- e.name == 'LinkedHashSetCell' ||
- e.name == 'LinkedHashMapCell' ||
- e is ClassMemberElement && (
- e.enclosingElement.name == 'MappedIterator' ||
- e.enclosingElement.name == 'LinkedHashMapKeyIterable' ||
- e.enclosingElement.name == 'ListQueue' ||
- e.enclosingElement.name == '_LinkedHashSet' ||
- e.enclosingElement.name == '_LinkedHashMap'
- )
- ) ||
- false;
- }
ReachabilityPredicate buildReachabilityPredicate(Set<Element> roots) {
- var accessible = _graph.getTransitiveClosure(
- new Set<Element>()..addAll(_specialRoots)..addAll(roots));
- bool isReachable(Element e) {
+ // stderr.writeln("#\n# BUILDING PREDICATE\n#");
+
+ var allRoots = new Set<Element>.identity()..addAll(_specialRoots)..addAll(roots);
+ var accessible = _graph.getTransitiveClosure(allRoots);
+ bool isReachable(Element e, {bool throwIfNot: false}) {
+ if (e == null) throw new ArgumentError.notNull('e');
e = _normalize(e);
+ printDiagnostic() {
+ stderr.writeln("$e: ${e.runtimeType}");
+ stderr.writeln("SPECIAL ROOTS:\n\t${(_specialRoots.map(_str).toSet().toList()..sort()).join("\n\t")}");
+ var path = _graph.getSomePath(allRoots, e);
+ if (path != null) {
+ stderr.writeln('FOUND PATH to $e:\n\t' + path.map(_str).join(' -> '));
+ } else {
+ stderr.writeln('FOUND NO PATH ${_str(e)}');
+ stderr.writeln('INCOMING(${_str(e)}): ${_graph.getIncoming(e)?.map(_str)}');
+ }
+ }
+ // if (e.name == 'generic') //e.name.contains('_ChildNodeListLazy') || e.name.contains('ListBase'))
+ // printDiagnostic();
+
// isInExtensionType() {
// if (e is ClassMemberElement) {
// for (var p in _collectHierarchy(e.enclosingElement)) {
@@ -470,11 +544,21 @@
// }
// return false;
// }
- if (e == null) throw new ArgumentError.notNull('e');
- if (accessible.contains(e)) {
+ if (accessible.contains(e) || _specialRoots.contains(e)) {
// if (isInExtensionType() && (e.name == 'floor' || e.name == 'truncate')) stderr.writeln("EXT reachable: ${e.enclosingElement.name}.${e.name}");
return true;
}
+ var uri = e.source.uri.toString();
+ if (e.name == '_Manager') {//uri.startsWith('dart:_runtime') && e.name == 'dynamicR') {
+ printDiagnostic();
+ stderr.writeln('SPECIAL TREATMENT: ${_str(e)} (_specialRoots.contains: ${_specialRoots.contains(e)})');
+ return true;
+ }
+
+ if (throwIfNot) {
+ printDiagnostic();
+ throw new StateError('Not reachable: ${_str(e)}');
+ }
// if (isInExtensionType() && (e.name == 'floor' || e.name == 'truncate')) stderr.writeln("EXT not reachable: ${e.enclosingElement.name}.${e.name}");
// if (_isSpecialRoot(e) || _isSpecialRoot(e.enclosingElement)) {
@@ -487,52 +571,49 @@
// if (_isSpecialRoot(e)) {
// stderr.writeln("SPECIAL: $e");
// stderr.writeln('INCOMING($e): ${_graph.getIncoming(e)}');
- // // if (e.name.contains('_Manager')) {
- // var path;
- // for (var root in roots) {
- // path = _graph.getSomePath(roots, e);
- // if (path != null) {
- // stderr.writeln('FOUND PATH from $root to $e:\n\t' + path.join('\n\t'));
- // break;
- // }
- // }
- // if (path == null) stderr.writeln('FOUND NO PATH $e');
- // // }
// return true;
// }
- bool containsUnresolved() {
- if (e is FunctionElement) {
+ if (e is FunctionElement) {
+ return accessible.contains(new _UnresolvedElement(null, e.name));
+ }
+ if (e is ClassMemberElement || e is PropertyAccessorElement) {
+ var enclosingClass = e.getAncestor((a) => a is ClassElement);
+ if (enclosingClass != null) {
+ return _collectHierarchy(enclosingClass)
+ .any((ClassElement parent) {
+ var parentMemberElement;
+ if (e is PropertyAccessorElement) {
+ parentMemberElement = parent.getField(e.variable.name);
+ } else if (e is PropertyInducingElement) {
+ parentMemberElement = parent.getField(e.name);
+ } else if (e is MethodElement) {
+ parentMemberElement = parent.getMethod(e.name);
+ }
+ return parentMemberElement != null && accessible.contains(parentMemberElement) ||
+ accessible.contains(new _UnresolvedElement(parent, e.name));
+ });
+ } else {
return accessible.contains(new _UnresolvedElement(null, e.name));
}
- if (e is ClassMemberElement || e is PropertyAccessorElement) {
- var enclosingClass = e.getAncestor((a) => a is ClassElement);
- if (enclosingClass != null) {
- return _collectHierarchy(enclosingClass)
- .any((ClassElement parent) {
- var parentMemberElement;
- if (e is PropertyAccessorElement) {
- parentMemberElement = parent.getField(e.variable.name);
- } else if (e is PropertyInducingElement) {
- parentMemberElement = parent.getField(e.name);
- } else if (e is MethodElement) {
- parentMemberElement = parent.getMethod(e.name);
- }
- return parentMemberElement != null && accessible.contains(parentMemberElement) ||
- accessible.contains(new _UnresolvedElement(parent, e.name));
- });
- } else {
- return accessible.contains(new _UnresolvedElement(null, e.name));
- }
- }
- // stderr.writeln('Unreachable by default: $e: ${e.runtimeType}');
- return false;
}
- var res = containsUnresolved();
- // if (e.name.contains('Map') && res) stderr.writeln("CONTAINS UNRESOLVED: $e");
- return res;
+ // stderr.writeln('Unreachable by default: $e: ${e.runtimeType}');
+ return false;
}
return isReachable;
+ // return (e) {
+ // var res = isReachable(e);
+ // if (e.name.contains('_ChildNodeListLazy')) stderr.writeln("REACHABLE(${e.name} = $res");
+ // return res;
+ // };
+ }
+
+ getTreeShakingData(Element e) =>
+ 'Incoming: ${_graph.getIncoming(e).map(_str).join(', ')}';
+ String _str(Element e) {
+ // if (e is PropertyAccessorElement) e = e.variable;
+ var suffix = '${e.name} (${e.runtimeType} @ ${e.source.uri})';
+ return e is ClassMemberElement ? '${e.enclosingElement.name}.$suffix' : suffix;
}
}
diff --git a/tool/input_sdk/private/classes.dart b/tool/input_sdk/private/classes.dart
index 7a784a4..cc957e7 100644
--- a/tool/input_sdk/private/classes.dart
+++ b/tool/input_sdk/private/classes.dart
@@ -287,9 +287,12 @@
$copyTheseProperties(jsProto, extProto, $getOwnPropertySymbols(extProto));
extProto = extProto.__proto__;
}
- let originalSigFn = $getOwnPropertyDescriptor($dartExtType, $_methodSig).get;
- $assert_(originalSigFn);
- $defineMemoizedGetter($jsType, $_methodSig, originalSigFn);
+ let desc = $getOwnPropertyDescriptor($dartExtType, $_methodSig);
+ if (desc) {
+ let originalSigFn = desc.get;
+ $assert_(originalSigFn);
+ $defineMemoizedGetter($jsType, $_methodSig, originalSigFn);
+ }
})()''');
///
diff --git a/tool/tree_shaking_test.sh b/tool/tree_shaking_test.sh
index 3acf253..fad2158 100755
--- a/tool/tree_shaking_test.sh
+++ b/tool/tree_shaking_test.sh
@@ -3,22 +3,23 @@
# switch to the root directory of dev_compiler
cd $( dirname "${BASH_SOURCE[0]}" )/..
-[[ -d example/tree_shaking ]] || mkdir -p example/tree_shaking
-
function compile_and_run() {
local file=$1
local name=`basename $file | sed 's/\.dart$//' | sed 's/\.html$//'`
- ./tool/build_sdk.sh \
+ [[ -d example/tree_shaking/$name ]] || mkdir -p example/tree_shaking/$name
+
+ time \
+ ./tool/build_sdk.sh \
--modules=node \
--tree-shaking=all \
--destructure-named-params \
- -o example/$name \
+ -o example/tree_shaking/$name \
$file
# cp lib/runtime/{dart_library,harmony_feature_check}.js example/$name
- NODE_PATH=example/$name \
+ NODE_PATH=example/tree_shaking/$name \
node \
--harmony \
--harmony_destructuring \
@@ -26,6 +27,6 @@
-e "require('dart/_isolate_helper').startRootIsolate(require('$name').main, []);"
}
-compile_and_run test/codegen/language/hello_dart_test.dart
+# compile_and_run test/codegen/language/hello_dart_test.dart
compile_and_run test/codegen/DeltaBlue.dart
# compile_and_run test/codegen/DeltaBlue.html