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