Add a new interceptor class JsArray, and support intercepting some list methods with the new interceptor scheme.
Review URL: https://codereview.chromium.org//11275316

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15009 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/_internal/compiler/implementation/compile_time_constants.dart b/sdk/lib/_internal/compiler/implementation/compile_time_constants.dart
index 3505466..1f16080 100644
--- a/sdk/lib/_internal/compiler/implementation/compile_time_constants.dart
+++ b/sdk/lib/_internal/compiler/implementation/compile_time_constants.dart
@@ -38,6 +38,8 @@
   String get name => 'ConstantHandler';
 
   void registerCompileTimeConstant(Constant constant) {
+    compiler.enqueuer.codegen.registerInstantiatedClass(
+        constant.computeType(compiler).element);
     compiledConstants.add(constant);
   }
 
diff --git a/sdk/lib/_internal/compiler/implementation/enqueue.dart b/sdk/lib/_internal/compiler/implementation/enqueue.dart
index 2c51c32..8ed7bdb 100644
--- a/sdk/lib/_internal/compiler/implementation/enqueue.dart
+++ b/sdk/lib/_internal/compiler/implementation/enqueue.dart
@@ -138,12 +138,6 @@
   }
 
   void registerInstantiatedClass(ClassElement cls) {
-    if (cls.isInterface()) {
-      compiler.internalErrorOnElement(
-          // Use the current element, as this is where cls is referenced from.
-          compiler.currentElement,
-          'Expected a class, but $cls is an interface.');
-    }
     universe.instantiatedClasses.add(cls);
     onRegisterInstantiatedClass(cls);
     compiler.backend.registerInstantiatedClass(cls, this);
@@ -239,9 +233,7 @@
         if (seenClasses.contains(cls)) continue;
         seenClasses.add(cls);
         cls.ensureResolved(compiler);
-        if (!cls.isInterface()) {
-          cls.implementation.forEachMember(processInstantiatedClassMember);
-        }
+        cls.implementation.forEachMember(processInstantiatedClassMember);
         if (isResolutionQueue) {
           compiler.resolver.checkMembers(cls);
         }
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
index 74c7d41..5cc96d6 100644
--- a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
@@ -647,6 +647,7 @@
   CodeEmitterTask emitter;
 
   ClassElement jsStringClass;
+  ClassElement jsArrayClass;
   ClassElement objectInterceptorClass;
   Element getInterceptorMethod;
 
@@ -716,7 +717,7 @@
 
   bool isInterceptorClass(Element element) {
     if (element == null) return false;
-    return element == jsStringClass;
+    return element == jsStringClass || element == jsArrayClass;
   }
 
   void addInterceptedSelector(Selector selector) {
@@ -749,6 +750,13 @@
         initializeInterceptorElements();
       }
       result = jsStringClass;
+    } else if (cls == compiler.listClass) {
+      if (jsArrayClass == null) {
+        jsArrayClass =
+            compiler.findInterceptor(const SourceString('JSArray'));
+        initializeInterceptorElements();
+      }
+      result = jsArrayClass;
     }
 
     if (result == null) return;
diff --git a/sdk/lib/_internal/compiler/implementation/lib/interceptors.dart b/sdk/lib/_internal/compiler/implementation/lib/interceptors.dart
index 1f00319..ca685dc 100644
--- a/sdk/lib/_internal/compiler/implementation/lib/interceptors.dart
+++ b/sdk/lib/_internal/compiler/implementation/lib/interceptors.dart
@@ -6,6 +6,7 @@
 
 import 'dart:collection';
 
+part 'js_array.dart';
 part 'js_string.dart';
 
 /**
@@ -23,47 +24,10 @@
  */
 getInterceptor(object) {
   if (object is String) return const JSString();
+  if (isJsArray(object)) return const JSArray();
   return const ObjectInterceptor();
 }
 
-add$1(var receiver, var value) {
-  if (isJsArray(receiver)) {
-    checkGrowable(receiver, 'add');
-    JS('void', r'#.push(#)', receiver, value);
-    return;
-  }
-  return UNINTERCEPTED(receiver.add(value));
-}
-
-removeAt$1(var receiver, int index) {
-  if (isJsArray(receiver)) {
-    if (index is !int) throw new ArgumentError(index);
-    if (index < 0 || index >= receiver.length) {
-      throw new RangeError.value(index);
-    }
-    checkGrowable(receiver, 'removeAt');
-    return JS('var', r'#.splice(#, 1)[0]', receiver, index);
-  }
-  return UNINTERCEPTED(receiver.removeAt(index));
-}
-
-removeLast(var receiver) {
-  if (isJsArray(receiver)) {
-    checkGrowable(receiver, 'removeLast');
-    if (receiver.length == 0) throw new RangeError.value(-1);
-    return JS('var', r'#.pop()', receiver);
-  }
-  return UNINTERCEPTED(receiver.removeLast());
-}
-
-filter(var receiver, var predicate) {
-  if (!isJsArray(receiver)) {
-    return UNINTERCEPTED(receiver.filter(predicate));
-  } else {
-    return Collections.filter(receiver, [], predicate);
-  }
-}
-
 get$length(var receiver) {
   if (receiver is String || isJsArray(receiver)) {
     return JS('num', r'#.length', receiver);  // TODO(sra): Use 'int'?
@@ -103,13 +67,6 @@
   return JS('String', r'String(#)', value);
 }
 
-iterator(receiver) {
-  if (isJsArray(receiver)) {
-    return new ListIterator(receiver);
-  }
-  return UNINTERCEPTED(receiver.iterator());
-}
-
 get$isEmpty(receiver) {
   if (receiver is String || isJsArray(receiver)) {
     return JS('bool', r'#.length === 0', receiver);
@@ -149,70 +106,11 @@
   }
 }
 
-addAll(receiver, collection) {
-  if (!isJsArray(receiver)) return UNINTERCEPTED(receiver.addAll(collection));
-
-  // TODO(ahe): Use for-in when it is implemented correctly.
-  var iterator = collection.iterator();
-  while (iterator.hasNext) {
-    receiver.add(iterator.next());
+iterator(receiver) {
+  if (isJsArray(receiver)) {
+    return new ListIterator(receiver);
   }
-}
-
-addLast(receiver, value) {
-  if (!isJsArray(receiver)) return UNINTERCEPTED(receiver.addLast(value));
-
-  checkGrowable(receiver, 'addLast');
-  JS('void', r'#.push(#)', receiver, value);
-}
-
-clear(receiver) {
-  if (!isJsArray(receiver)) return UNINTERCEPTED(receiver.clear());
-  receiver.length = 0;
-}
-
-forEach(receiver, f) {
-  if (!isJsArray(receiver)) {
-    return UNINTERCEPTED(receiver.forEach(f));
-  } else {
-    return Collections.forEach(receiver, f);
-  }
-}
-
-map(receiver, f) {
-  if (!isJsArray(receiver)) {
-    return UNINTERCEPTED(receiver.map(f));
-  } else {
-    return Collections.map(receiver, [], f);
-  }
-}
-
-reduce(receiver, initialValue, f) {
-  if (!isJsArray(receiver)) {
-    return UNINTERCEPTED(receiver.reduce(initialValue, f));
-  } else {
-    return Collections.reduce(receiver, initialValue, f);
-  }
-}
-
-getRange(receiver, start, length) {
-  if (!isJsArray(receiver)) {
-    return UNINTERCEPTED(receiver.getRange(start, length));
-  }
-  if (0 == length) return [];
-  checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
-  checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
-  if (start is !int) throw new ArgumentError(start);
-  if (length is !int) throw new ArgumentError(length);
-  if (length < 0) throw new ArgumentError(length);
-  if (start < 0) throw new RangeError.value(start);
-  var end = start + length;
-  if (end > receiver.length) {
-    throw new RangeError.value(length);
-  }
-  if (length < 0) throw new ArgumentError(length);
-  // TODO(sra): We need a type that is exactly the JavaScript Array type.
-  return JS('=List', r'#.slice(#, #)', receiver, start, end);
+  return UNINTERCEPTED(receiver.iterator());
 }
 
 indexOf$1(receiver, element) {
diff --git a/sdk/lib/_internal/compiler/implementation/lib/js_array.dart b/sdk/lib/_internal/compiler/implementation/lib/js_array.dart
new file mode 100644
index 0000000..9f5db9b
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/lib/js_array.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2012, 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.
+
+part of _interceptors;
+
+/**
+ * The interceptor class for [List]. The compiler recognizes this
+ * class as an interceptor, and changes references to [:this:] to
+ * actually use the receiver of the method, which is generated as an extra
+ * argument added to each member.
+ */
+class JSArray<E> implements List<E> {
+  const JSArray();
+
+  void add(E value) {
+    checkGrowable(this, 'add');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  E removeAt(int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index < 0 || index >= length) {
+      throw new RangeError.value(index);
+    }
+    checkGrowable(this, 'removeAt');
+    return JS('var', r'#.splice(#, 1)[0]', this, index);
+  }
+
+  E removeLast() {
+    checkGrowable(this, 'removeLast');
+    if (length == 0) throw new RangeError.value(-1);
+    return JS('var', r'#.pop()', this);
+  }
+
+  List<E> filter(bool f(E element)) {
+    return Collections.filter(this, <E>[], f);
+  }
+
+  void addAll(Collection<E> collection) {
+    for (Element e in collection) {
+      this.add(e);
+    }
+  }
+
+  void addLast(E value) {
+    checkGrowable(this, 'addLast');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  void clear() {
+    length = 0;
+  }
+
+  void forEach(void f(E element)) {
+    return Collections.forEach(this, f);
+  }
+
+  Collection map(f(E element)) {
+    return Collections.map(this, [], f);
+  }
+
+  reduce(initialValue, combine(previousValue, E element)) {
+    return Collections.reduce(this, initialValue, combine);
+  }
+
+  List<E> getRange(int start, int length) {
+    // TODO(ngeoffray): Parameterize the return value.
+    if (0 == length) return [];
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (length < 0) throw new ArgumentError(length);
+    if (start < 0) throw new RangeError.value(start);
+    int end = start + length;
+    if (end > this.length) {
+      throw new RangeError.value(length);
+    }
+    if (length < 0) throw new ArgumentError(length);
+    return JS('List', r'#.slice(#, #)', this, start, end);
+  }
+}
diff --git a/sdk/lib/_internal/compiler/implementation/resolution/members.dart b/sdk/lib/_internal/compiler/implementation/resolution/members.dart
index 8dc19bf..d2c8c11 100644
--- a/sdk/lib/_internal/compiler/implementation/resolution/members.dart
+++ b/sdk/lib/_internal/compiler/implementation/resolution/members.dart
@@ -2098,6 +2098,7 @@
   }
 
   visitLiteralList(LiteralList node) {
+    world.registerInstantiatedClass(compiler.listClass);
     NodeList arguments = node.typeArguments;
     if (arguments != null) {
       Link<Node> nodes = arguments.nodes;
diff --git a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
index f47e5da..b6137f2 100644
--- a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
+++ b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
@@ -2291,6 +2291,17 @@
     return result;
   }
 
+  Element getInterceptor(Send send, Selector selector) {
+    if (!methodInterceptionEnabled) return null;
+    if (!backend.isInterceptorClass(currentElement.getEnclosingClass())
+        && send.receiver == null) {
+      // The call applies to [:this:] which can not be an interceptor
+      // object.
+      return null;
+    }
+    return interceptors.getStaticInterceptor(selector);
+  }
+
   void generateInstanceGetterWithCompiledReceiver(Send send,
                                                   HInstruction receiver) {
     assert(Elements.isInstanceSend(send, elements));
@@ -2302,10 +2313,8 @@
         : elements.getSelector(send.selector);
     assert(selector.isGetter());
     SourceString getterName = selector.name;
-    Element interceptor = null;
-    if (methodInterceptionEnabled) {
-      interceptor = interceptors.getStaticInterceptor(selector);
-    }
+    Element interceptor = getInterceptor(send, selector);
+
     bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
     if (interceptor == backend.getInterceptorMethod && interceptor != null) {
       // If we're using an interceptor class, emit a call to the
@@ -2386,10 +2395,7 @@
     Selector selector = elements.getSelector(send);
     assert(selector.isSetter());
     SourceString setterName = selector.name;
-    Element interceptor = null;
-    if (methodInterceptionEnabled) {
-      interceptor = interceptors.getStaticInterceptor(selector);
-    }
+    Element interceptor = getInterceptor(send, selector);
     bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector);
     if (interceptor != null && interceptor == backend.getInterceptorMethod) {
       compiler.internalError(
@@ -2659,10 +2665,7 @@
       dartMethodName = node.selector.asIdentifier().source;
     }
 
-    Element interceptor = null;
-    if (methodInterceptionEnabled) {
-      interceptor = interceptors.getStaticInterceptor(selector);
-    }
+    Element interceptor = getInterceptor(node, selector);
 
     if (interceptor != null) {
       if (interceptor == backend.getInterceptorMethod) {