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