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 930a3227e2a..ebb64ec39e4 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 @@ abstract class KernelClassBuilder 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: [], + positionalParameters: [], + namedParameters: [], + 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: [], + positionalParameters: [ + new VariableDeclaration("value", + type: substitution.substituteType(field.type)) + ], + namedParameters: [], + 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 @@ abstract class KernelClassBuilder 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 concreteSetters = @@ -514,6 +565,15 @@ abstract class KernelClassBuilder 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 bdcce31cb25..c0b01c00fbd 100644 --- a/pkg/front_end/testcases/compile.status +++ b/pkg/front_end/testcases/compile.status @@ -130,3 +130,8 @@ incomplete_field_formal_parameter: Fail # Fasta doesn't recover well 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 00000000000..fdaad6424ce --- /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 00000000000..cebe1ac9a31 --- /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 00000000000..74d8deeb4ca --- /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 00000000000..8b3cd2cb2e9 --- /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 { + List foo; +} + +class B implements A { + dynamic noSuchMethod(i) => []; + + // The noSuchMethod forwarder for the getter should return `List`. + + // The noSuchMethod forwarder for the setter should take `List`. +} + +main() { + var b = new B(); + expectTypeError(() => b.foo); + expectTypeError(() => (b as dynamic).foo = []); +} diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status index 1159e1b6f38..1d24e3fd731 100644 --- a/pkg/front_end/testcases/outline.status +++ b/pkg/front_end/testcases/outline.status @@ -255,3 +255,8 @@ rasta/type_with_parse_error: Fail 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 d0d42f80e1f..638fc7d01a7 100644 --- a/pkg/front_end/testcases/strong.status +++ b/pkg/front_end/testcases/strong.status @@ -223,3 +223,8 @@ incomplete_field_formal_parameter: Fail # Fasta doesn't recover well 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 bd1548b113a..65b15640d20 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/02: MissingCompileTimeError 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 f2ea23c415d..c456c9180ce 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/27: MissingCompileTimeError 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_override6_test/03: MissingCompileTimeError 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_override6_test/03: MissingCompileTimeError 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