Fix receiver type for js-interop access

This includes the unknown potential targets of access to js-interop
members. Since we don't know actual classes implementing the js-interop
classes we just assume it could be any of them.

Change-Id: I4d91ab673fa8221eb701b34e9c32fd16e5a1c381
Reviewed-on: https://dart-review.googlesource.com/74980
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ssa/kernel_impact.dart b/pkg/compiler/lib/src/ssa/kernel_impact.dart
index a1d6c47..c88565b 100644
--- a/pkg/compiler/lib/src/ssa/kernel_impact.dart
+++ b/pkg/compiler/lib/src/ssa/kernel_impact.dart
@@ -14,6 +14,7 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../ir/util.dart';
+import '../js_backend/native_data.dart';
 import '../kernel/element_map.dart';
 import '../kernel/runtime_type_analysis.dart';
 import '../options.dart';
@@ -58,6 +59,8 @@
 
   CommonElements get commonElements => elementMap.commonElements;
 
+  NativeBasicData get _nativeBasicData => elementMap.nativeBasicData;
+
   /// Add a checked-mode type use of [type] if it is not `dynamic`.
   DartType checkType(ir.DartType irType, TypeUseKind kind) {
     DartType type = elementMap.getDartType(irType);
@@ -117,7 +120,7 @@
     if (field.isInstanceMember &&
         elementMap.isNativeClass(field.enclosingClass)) {
       MemberEntity member = elementMap.getMember(field);
-      bool isJsInterop = elementMap.nativeBasicData.isJsInteropMember(member);
+      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
       impactBuilder.registerNativeData(elementMap
           .getNativeBehaviorForFieldLoad(field, isJsInterop: isJsInterop));
       impactBuilder
@@ -132,7 +135,7 @@
     visitNode(constructor.function.body);
     MemberEntity member = elementMap.getMember(constructor);
     if (constructor.isExternal && !commonElements.isForeignHelper(member)) {
-      bool isJsInterop = elementMap.nativeBasicData.isJsInteropMember(member);
+      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
       impactBuilder.registerNativeData(elementMap
           .getNativeBehaviorForMethod(constructor, isJsInterop: isJsInterop));
     }
@@ -187,7 +190,7 @@
     handleAsyncMarker(procedure.function);
     MemberEntity member = elementMap.getMember(procedure);
     if (procedure.isExternal && !commonElements.isForeignHelper(member)) {
-      bool isJsInterop = elementMap.nativeBasicData.isJsInteropMember(member);
+      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
       impactBuilder.registerNativeData(elementMap
           .getNativeBehaviorForMethod(procedure, isJsInterop: isJsInterop));
     }
@@ -493,7 +496,8 @@
     // TODO(johnniwinther): Restrict the dynamic use to only match the known
     // target.
     // TODO(johnniwinther): Restrict this to subclasses?
-    Object constraint = new StrongModeConstraint(member.enclosingClass);
+    Object constraint = new StrongModeConstraint(
+        commonElements, _nativeBasicData, member.enclosingClass);
     impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
         new Selector.call(
             member.memberName, elementMap.getCallStructure(node.arguments)),
@@ -606,7 +610,8 @@
         Object constraint;
         DartType receiverType = elementMap.getStaticType(node.receiver);
         if (receiverType is InterfaceType) {
-          constraint = new StrongModeConstraint(receiverType.element);
+          constraint = new StrongModeConstraint(
+              commonElements, _nativeBasicData, receiverType.element);
         }
 
         if (interfaceTarget is ir.Field ||
@@ -621,7 +626,8 @@
             DartType receiverType =
                 elementMap.getDartType(interfaceTarget.getterType);
             if (receiverType is InterfaceType) {
-              getterConstraint = new StrongModeConstraint(receiverType.element);
+              getterConstraint = new StrongModeConstraint(
+                  commonElements, _nativeBasicData, receiverType.element);
             }
           }
 
@@ -641,7 +647,8 @@
     Object constraint;
     DartType receiverType = elementMap.getStaticType(node.receiver);
     if (receiverType is InterfaceType) {
-      constraint = new StrongModeConstraint(receiverType.element);
+      constraint = new StrongModeConstraint(
+          commonElements, _nativeBasicData, receiverType.element);
     }
     impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
         new Selector.getter(elementMap.getName(node.name)),
@@ -680,7 +687,8 @@
     Object constraint;
     DartType receiverType = elementMap.getStaticType(node.receiver);
     if (receiverType is InterfaceType) {
-      constraint = new StrongModeConstraint(receiverType.element);
+      constraint = new StrongModeConstraint(
+          commonElements, _nativeBasicData, receiverType.element);
     }
     impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
         new Selector.setter(elementMap.getName(node.name)),
diff --git a/pkg/compiler/lib/src/universe/world_builder.dart b/pkg/compiler/lib/src/universe/world_builder.dart
index 03f3fcb..5107b28 100644
--- a/pkg/compiler/lib/src/universe/world_builder.dart
+++ b/pkg/compiler/lib/src/universe/world_builder.dart
@@ -192,7 +192,17 @@
 class StrongModeConstraint {
   final ClassEntity cls;
 
-  const StrongModeConstraint(this.cls);
+  factory StrongModeConstraint(CommonElements commonElements,
+      NativeBasicData nativeBasicData, ClassEntity cls) {
+    if (nativeBasicData.isJsInteropClass(cls)) {
+      // We can not tell js-interop classes apart, so we just assume the
+      // receiver could be any js-interop class.
+      cls = commonElements.jsJavaScriptObjectClass;
+    }
+    return new StrongModeConstraint.internal(cls);
+  }
+
+  const StrongModeConstraint.internal(this.cls);
 
   bool needsNoSuchMethodHandling(Selector selector, World world) => true;
 
diff --git a/tests/compiler/dart2js/impact/data/jsinterop.dart b/tests/compiler/dart2js/impact/data/jsinterop.dart
index a2db31e..b36d583 100644
--- a/tests/compiler/dart2js/impact/data/jsinterop.dart
+++ b/tests/compiler/dart2js/impact/data/jsinterop.dart
@@ -44,7 +44,7 @@
 }
 
 /*strong.element: testJsInteropClass:
- dynamic=[JsInteropClass.method(0)],
+ dynamic=[JavaScriptObject.method(0)],
  static=[JsInteropClass.(0)]
 */
 testJsInteropClass() => new JsInteropClass().method();
@@ -76,7 +76,7 @@
 }
 
 /*strong.element: testOptionalGenericFunctionTypeArgument:
- dynamic=[GenericClass.method(0)],
+ dynamic=[JavaScriptObject.method(0)],
  static=[GenericClass.(0)]
 */
 testOptionalGenericFunctionTypeArgument() => new GenericClass().method();
diff --git a/tests/compiler/dart2js/model/enqueuer_test.dart b/tests/compiler/dart2js/model/enqueuer_test.dart
index 56d652d..60c3fe6 100644
--- a/tests/compiler/dart2js/model/enqueuer_test.dart
+++ b/tests/compiler/dart2js/model/enqueuer_test.dart
@@ -250,7 +250,8 @@
     checkInvariant(enqueuer, elementEnvironment);
 
     Object createConstraint(ClassEntity cls) {
-      return new StrongModeConstraint(cls);
+      return new StrongModeConstraint(compiler.frontendStrategy.commonElements,
+          compiler.frontendStrategy.nativeBasicData, cls);
     }
 
     for (Impact impact in impacts) {
diff --git a/tests/compiler/dart2js_extra/js_interop_implements_test.dart b/tests/compiler/dart2js_extra/js_interop_implements_test.dart
new file mode 100644
index 0000000..a4c8a72
--- /dev/null
+++ b/tests/compiler/dart2js_extra/js_interop_implements_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, 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.
+
+// Test that methods implemented (not extended) in js-interop classes are still
+// considered live.
+
+@JS()
+library anonymous_js_test;
+
+import 'package:js/js.dart';
+
+@JS()
+@anonymous
+abstract class A {
+  external factory A();
+  external String get a;
+  external set a(String a);
+}
+
+@JS()
+@anonymous
+abstract class B implements A {
+  external factory B();
+  external String get b;
+  external set b(String b);
+}
+
+void main() {
+  final b = B();
+  // This setter is missing if we don't assume the receiver could be an
+  // unknown but concrete implementation of A.
+  b.a = 'Hi';
+  b.b = 'Hello';
+  if (b.a != 'Hi') throw 'b.a';
+  if (b.b != 'Hello') throw 'b.b';
+}