[fasta] Generate noSuchMethod forwarders for abstract accessors

Fixes #32722

Bug: http://dartbug.com/32722
Change-Id: I5a2b29c766a82e06949028dccb69680c685e1ba4
Reviewed-on: https://dart-review.googlesource.com/62151
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Samir Jindel <sjindel@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_class_builder.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_class_builder.dart
index 930a322..ebb64ec 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_class_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_class_builder.dart
@@ -432,6 +432,49 @@
     cloned.parent = cls;
   }
 
+  void addNoSuchMethodForwarderGetterForField(Member noSuchMethod,
+      KernelTarget target, Field field, ClassHierarchy hierarchy) {
+    Substitution substitution = Substitution.fromSupertype(
+        hierarchy.getClassAsInstanceOf(cls, field.enclosingClass));
+    Procedure getter = new Procedure(
+        field.name,
+        ProcedureKind.Getter,
+        new FunctionNode(null,
+            typeParameters: <TypeParameter>[],
+            positionalParameters: <VariableDeclaration>[],
+            namedParameters: <VariableDeclaration>[],
+            requiredParameterCount: 0,
+            returnType: substitution.substituteType(field.type)),
+        fileUri: field.fileUri)
+      ..fileOffset = field.fileOffset;
+    transformProcedureToNoSuchMethodForwarder(noSuchMethod, target, getter);
+    cls.procedures.add(getter);
+    getter.parent = cls;
+  }
+
+  void addNoSuchMethodForwarderSetterForField(Member noSuchMethod,
+      KernelTarget target, Field field, ClassHierarchy hierarchy) {
+    Substitution substitution = Substitution.fromSupertype(
+        hierarchy.getClassAsInstanceOf(cls, field.enclosingClass));
+    Procedure setter = new Procedure(
+        field.name,
+        ProcedureKind.Setter,
+        new FunctionNode(null,
+            typeParameters: <TypeParameter>[],
+            positionalParameters: <VariableDeclaration>[
+              new VariableDeclaration("value",
+                  type: substitution.substituteType(field.type))
+            ],
+            namedParameters: <VariableDeclaration>[],
+            requiredParameterCount: 1,
+            returnType: const VoidType()),
+        fileUri: field.fileUri)
+      ..fileOffset = field.fileOffset;
+    transformProcedureToNoSuchMethodForwarder(noSuchMethod, target, setter);
+    cls.procedures.add(setter);
+    setter.parent = cls;
+  }
+
   /// Adds noSuchMethod forwarding stubs to this class. Returns `true` if the
   /// class was modified.
   bool addNoSuchMethodForwarders(
@@ -492,6 +535,14 @@
         existingForwardersNames.add(member.name);
         changed = true;
       }
+      if (member is Field &&
+          ClassHierarchy.findMemberByName(concrete, member.name) == null &&
+          !existingForwardersNames.contains(member.name)) {
+        addNoSuchMethodForwarderGetterForField(
+            noSuchMethod, target, member, hierarchy);
+        existingForwardersNames.add(member.name);
+        changed = true;
+      }
     }
 
     List<Member> concreteSetters =
@@ -514,6 +565,15 @@
         existingSetterForwardersNames.add(member.name);
         changed = true;
       }
+      if (member is Field &&
+          ClassHierarchy.findMemberByName(concreteSetters, member.name) ==
+              null &&
+          !existingSetterForwardersNames.contains(member.name)) {
+        addNoSuchMethodForwarderSetterForField(
+            noSuchMethod, target, member, hierarchy);
+        existingSetterForwardersNames.add(member.name);
+        changed = true;
+      }
     }
 
     return changed;
diff --git a/pkg/front_end/testcases/compile.status b/pkg/front_end/testcases/compile.status
index bdcce31..c0b01c00 100644
--- a/pkg/front_end/testcases/compile.status
+++ b/pkg/front_end/testcases/compile.status
@@ -130,3 +130,8 @@
 
 co19_language_metadata_syntax_t04: RuntimeError # Fasta doesn't recover well
 external_import: RuntimeError # Expected -- test uses import which doesn't exist.
+
+no_such_method_forwarders/abstract_accessors_from_field: Fail
+no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in: Fail
+no_such_method_forwarders/abstract_accessors_from_field_one_defined: Fail
+no_such_method_forwarders/abstract_accessors_from_field_with_substitution: Fail
diff --git a/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field.dart b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field.dart
new file mode 100644
index 0000000..fdaad64
--- /dev/null
+++ b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field.dart
@@ -0,0 +1,35 @@
+// 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.
+
+// This test checks that noSuchMethod forwarders are generated for abstract
+// accessors implicitly declared via fields of abstract classes.  The type
+// checks should be performed for the return values of getters and for the r.h.s
+// of assignments for setters.
+
+void expectTypeError(callback()) {
+  try {
+    callback();
+    throw 'Expected TypeError, did not occur';
+  } on TypeError {}
+}
+
+abstract class I {
+  int foo;
+}
+
+class A implements I {
+  dynamic noSuchMethod(i) => "bar";
+
+  // Should have noSuchMethod forwarders for the 'foo' getter and setter.
+}
+
+class B extends A {
+  // Should not have noSuchMethod forwarders for the 'foo' getter and setter.
+}
+
+main() {
+  var a = new A();
+  expectTypeError(() => a.foo);
+  expectTypeError(() => (a as dynamic).foo = "bar");
+}
diff --git a/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in.dart b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in.dart
new file mode 100644
index 0000000..cebe1ac
--- /dev/null
+++ b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in.dart
@@ -0,0 +1,40 @@
+// 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.
+
+// This test checks that noSuchMethod forwarders that were generated for
+// abstract accessors declared via field in an interface don't override concrete
+// getters and setters in the mixin application.
+
+int count = 0;
+
+abstract class A {
+  int foo;
+}
+
+class B implements A {
+  noSuchMethod(i) {
+    ++count;
+    return null;
+  }
+
+  // Should receive noSuchMethod forwarders for the 'foo' getter and setter.
+}
+
+class C extends Object with B {
+  // The getter and the setter below shouldn't be overridden with noSuchMethod
+  // forwarders.
+  int get foo => 42;
+  void set foo(int value) {}
+}
+
+main() {
+  var c = new C();
+  if (c.foo != 42) {
+    throw "Value mismatch: c.foo != 42.";
+  }
+  c.foo = 43;
+  if (count != 0) {
+    throw "Value mismatch: count != 0";
+  }
+}
diff --git a/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_one_defined.dart b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_one_defined.dart
new file mode 100644
index 0000000..74d8dee
--- /dev/null
+++ b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_one_defined.dart
@@ -0,0 +1,48 @@
+// 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.
+
+// This test checks that noSuchMethod forwarders are generated for abstract
+// accessors implicitly declared via fields of abstract classes in case when one
+// of the accessors is defined in a superclass.
+
+void expectTypeError(callback()) {
+  try {
+    callback();
+    throw 'Expected TypeError, did not occur';
+  } on TypeError {}
+}
+
+abstract class A {
+  int foo;
+}
+
+abstract class B implements A {
+  int get foo => 42;
+
+  noSuchMethod(i) => "bar";
+}
+
+class C extends B {
+  // Should receive a noSuchMethod forwarder for the 'foo' setter, but not for
+  // the 'foo' getter.
+}
+
+abstract class D implements A {
+  void set foo(int value) {}
+
+  noSuchMethod(i) => "bar";
+}
+
+class E extends D {
+  // Should receive a noSuchMethod forwarder for the 'foo' getter, but not for
+  // the 'foo' setter.
+}
+
+main() {
+  var c = new C();
+  expectTypeError(() => (c as dynamic).foo = "bar");
+
+  var e = new E();
+  expectTypeError(() => e.foo);
+}
diff --git a/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_with_substitution.dart b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_with_substitution.dart
new file mode 100644
index 0000000..8b3cd2c
--- /dev/null
+++ b/pkg/front_end/testcases/no_such_method_forwarders/abstract_accessors_from_field_with_substitution.dart
@@ -0,0 +1,33 @@
+// 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.
+
+// This test checks that noSuchMethod forwarders that are generated for abstract
+// accessors synthesized from a field of an interface have the proper type
+// substitution performed on the types of their parameters and on the return
+// type.
+
+void expectTypeError(callback()) {
+  try {
+    callback();
+    throw 'Expected TypeError, did not occur';
+  } on TypeError {}
+}
+
+abstract class A<X> {
+  List<X> foo;
+}
+
+class B implements A<int> {
+  dynamic noSuchMethod(i) => <dynamic>[];
+
+  // The noSuchMethod forwarder for the getter should return `List<int>`.
+
+  // The noSuchMethod forwarder for the setter should take `List<int>`.
+}
+
+main() {
+  var b = new B();
+  expectTypeError(() => b.foo);
+  expectTypeError(() => (b as dynamic).foo = <dynamic>[]);
+}
diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status
index 1159e1b..1d24e3f 100644
--- a/pkg/front_end/testcases/outline.status
+++ b/pkg/front_end/testcases/outline.status
@@ -255,3 +255,8 @@
 
 instantiate_to_bound/body_typedef_super_bounded_type: Fail # Issue 33444
 instantiate_to_bound/typedef_super_bounded_type: Fail # Issue 33444
+
+no_such_method_forwarders/abstract_accessors_from_field: Fail
+no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in: Fail
+no_such_method_forwarders/abstract_accessors_from_field_one_defined: Fail
+no_such_method_forwarders/abstract_accessors_from_field_with_substitution: Fail
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index d0d42f8..638fc7d 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -223,3 +223,8 @@
 co19_language_metadata_syntax_t04: RuntimeError # Fasta doesn't recover well
 
 external_import: RuntimeError # The native extension to import doesn't exist. This is ok.
+
+no_such_method_forwarders/abstract_accessors_from_field: Fail
+no_such_method_forwarders/abstract_accessors_from_field_arent_mixed_in: Fail
+no_such_method_forwarders/abstract_accessors_from_field_one_defined: Fail
+no_such_method_forwarders/abstract_accessors_from_field_with_substitution: Fail
diff --git a/tests/language_2/language_2_dartdevc.status b/tests/language_2/language_2_dartdevc.status
index bd1548b..65b1564 100644
--- a/tests/language_2/language_2_dartdevc.status
+++ b/tests/language_2/language_2_dartdevc.status
@@ -530,7 +530,6 @@
 mixin_supertype_subclass_test/05: MissingCompileTimeError
 mixin_type_parameters_errors_test/03: MissingCompileTimeError
 mixin_type_parameters_errors_test/04: MissingCompileTimeError
-mock_writable_final_field_test: RuntimeError # Issue 30847
 mock_writable_final_private_field_test: RuntimeError
 multiline_newline_test/06: MissingCompileTimeError
 multiline_newline_test/06r: MissingCompileTimeError
diff --git a/tests/language_2/language_2_kernel.status b/tests/language_2/language_2_kernel.status
index f2ea23c..c456c91 100644
--- a/tests/language_2/language_2_kernel.status
+++ b/tests/language_2/language_2_kernel.status
@@ -124,7 +124,6 @@
 mixin_illegal_superclass_test/28: MissingCompileTimeError
 mixin_illegal_superclass_test/29: MissingCompileTimeError
 mixin_illegal_superclass_test/30: MissingCompileTimeError
-mock_writable_final_private_field_test: RuntimeError
 named_parameters_default_eq_test/none: RuntimeError
 nested_generic_closure_test: RuntimeError
 no_main_test/01: Crash
@@ -792,7 +791,6 @@
 method_override_test: CompileTimeError # Issue 31616
 mixin_illegal_super_use_test: Skip # Issues 24478 and 23773
 mixin_illegal_superclass_test: Skip # Issues 24478 and 23773
-mock_writable_final_private_field_test: RuntimeError # Issue 30849
 named_constructor_test/01: MissingRuntimeError # Fasta bug: Bad compilation of constructor reference.
 named_parameters_default_eq_test/none: RuntimeError
 nested_generic_closure_test: RuntimeError
@@ -1119,7 +1117,6 @@
 method_override_test: CompileTimeError # Issue 31616
 mixin_illegal_super_use_test: Skip # Issues 24478 and 23773
 mixin_illegal_superclass_test: Skip # Issues 24478 and 23773
-mock_writable_final_private_field_test: RuntimeError # Issue 30849
 named_parameters_default_eq_test/none: RuntimeError
 nested_generic_closure_test: RuntimeError
 no_main_test/01: Skip