[cfe] Support noSuchMethod forwarder for invalid implementation through inheritance
This adds support for adding a noSuchMethod forwarder when an inherited private method from a different library doesn't implement the inherited interface.
Closes #61717
Change-Id: I932b47cd007219cce1b31e05406f2d08b28e588e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454601
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/builder/library_builder.dart b/pkg/front_end/lib/src/builder/library_builder.dart
index 8f16fde..3024326 100644
--- a/pkg/front_end/lib/src/builder/library_builder.dart
+++ b/pkg/front_end/lib/src/builder/library_builder.dart
@@ -107,7 +107,7 @@
/// Lookups the required member [name] declared in this library.
///
/// If no member is found an internal problem is reported.
- NamedBuilder? lookupRequiredLocalMember(String name);
+ NamedBuilder lookupRequiredLocalMember(String name);
void recordAccess(
CompilationUnit accessor,
@@ -257,7 +257,7 @@
}
@override
- NamedBuilder? lookupRequiredLocalMember(String name) {
+ NamedBuilder lookupRequiredLocalMember(String name) {
NamedBuilder? builder = libraryNameSpace.lookup(name)?.getable;
if (builder == null) {
internalProblem(
diff --git a/pkg/front_end/lib/src/kernel/hierarchy/class_member.dart b/pkg/front_end/lib/src/kernel/hierarchy/class_member.dart
index a64ab9f..8c887a2 100644
--- a/pkg/front_end/lib/src/kernel/hierarchy/class_member.dart
+++ b/pkg/front_end/lib/src/kernel/hierarchy/class_member.dart
@@ -2,6 +2,7 @@
// 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.
+import 'package:front_end/src/kernel/hierarchy/members_node.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/type_algebra.dart';
@@ -871,15 +872,18 @@
@override
bool get isNoSuchMethodForwarder {
- // [implementedInterfaceMember] can only be a noSuchMethod forwarder if
- // [inheritedClassMember] also is, since we don't allow overriding a regular
- // member with a noSuchMethodForwarder.
+ // [implementedInterfaceMember] can be a noSuchMethod forwarder in two
+ // scenarios:
//
- // If the current class is abstract, [inheritedClassMember] can be a
- // a noSuchMethod forwarder while [implementedInterfaceMember] is not,
- // because we only insert noSuchMethod forwarders into non-abstract classes.
+ // * If [inheritedClassMember] also is, since we don't allow overriding a
+ // regular member with a noSuchMethodForwarder.
//
- // For instance
+ // If the current class is abstract, [inheritedClassMember] can be a
+ // a noSuchMethod forwarder while [implementedInterfaceMember] is not,
+ // because we only insert noSuchMethod forwarders into non-abstract
+ // classes.
+ //
+ // For instance
//
// class Super {
// noSuchMethod(_) => null;
@@ -899,15 +903,43 @@
// // and this will be an error.
// }
//
+ // * If [name] is not visible in the library of [classBuilder].
+ //
+ // If the [inheritedClassMember] is private wrt. a different library,
+ // there is no requirement that it implements the
+ // [implementedInterfaceMember], and we will create a noSuchMethod
+ // forwarder if it doesn't.
+ //
+ // For instance
+ //
+ // // library a.dart
+ // class A {
+ // void _m(int i) {}
+ // }
+ // abstract class B extends A {
+ // void _m(num i);
+ // }
+ //
+ // // library b.dart
+ // import 'a.dart';
+ // class C extends B {
+ // /* _m(num i) */ // A._m doesn't implement B._m so a noSuchMethod
+ // // forwarder is created.
+ // }
+ //
assert(
- !(implementedInterfaceMember.isNoSuchMethodForwarder &&
- !inheritedClassMember.isNoSuchMethodForwarder),
+ !implementedInterfaceMember.isNoSuchMethodForwarder ||
+ inheritedClassMember.isNoSuchMethodForwarder ||
+ !isNameVisibleIn(name, classBuilder.libraryBuilder),
"The inherited $inheritedClassMember has "
"isNoSuchMethodForwarder="
"${inheritedClassMember.isNoSuchMethodForwarder} but "
"the implemented $implementedInterfaceMember has "
"isNoSuchMethodForwarder="
- "${implementedInterfaceMember.isNoSuchMethodForwarder}.",
+ "${implementedInterfaceMember.isNoSuchMethodForwarder} "
+ " and the name $name is "
+ "${!isNameVisibleIn(name, classBuilder.libraryBuilder) ? " not " : ""} "
+ "visible.",
);
return inheritedClassMember.isNoSuchMethodForwarder;
}
diff --git a/pkg/front_end/lib/src/kernel/hierarchy/members_node.dart b/pkg/front_end/lib/src/kernel/hierarchy/members_node.dart
index 0a4a5b6..bb003b2 100644
--- a/pkg/front_end/lib/src/kernel/hierarchy/members_node.dart
+++ b/pkg/front_end/lib/src/kernel/hierarchy/members_node.dart
@@ -2505,11 +2505,58 @@
interfaceMembers.addAll(_implementedMembers);
ClassMember? noSuchMethodTarget;
- if (_extendedMember.isNoSuchMethodForwarder &&
- !classBuilder.isAbstract &&
- (userNoSuchMethodMember != null ||
- !isNameVisibleIn(name, classBuilder.libraryBuilder))) {
- noSuchMethodTarget = noSuchMethodMember;
+ if (!classBuilder.isAbstract) {
+ if (_extendedMember.isNoSuchMethodForwarder &&
+ userNoSuchMethodMember != null) {
+ // If we inherit a noSuchMethod forwarder we might need to create
+ // a new one in this class.
+ //
+ // For instance
+ //
+ // class Super {
+ // noSuchMethod(_) => null;
+ // method1(); // noSuchMethod forwarder created for this.
+ // method2(int i); // noSuchMethod forwarder created for this.
+ // method3(int i); // noSuchMethod forwarder created for this.
+ // method4(int i) {}
+ // }
+ // abstract class Abstract extends Super {
+ // method2(num i); // No noSuchMethod forwarder will be
+ // // inserted here.
+ // }
+ // class Class extends Abstract {
+ // method1(); // noSuchMethod forwarder from Super is valid.
+ // /* method2(num i) */ // A new noSuchMethod forwarder is
+ // // created.
+ // method3(num i); // A new noSuchMethod forwarder is created.
+ // method4(num i); // No noSuchMethod forwarder will be
+ // // inserted and this will be an error.
+ // }
+ noSuchMethodTarget = noSuchMethodMember;
+ }
+ if (!isNameVisibleIn(name, classBuilder.libraryBuilder)) {
+ // [name] is not visible, so there is no requirement that
+ // [_extendedMember] implements the [interfaceMembers]. We will
+ // create a noSuchMethod forwarder if it doesn't.
+ //
+ // For instance
+ //
+ // // library a.dart
+ // class A {
+ // void _m(int i) {}
+ // }
+ // abstract class B extends A {
+ // void _m(num i);
+ // }
+ //
+ // // library b.dart
+ // import 'a.dart';
+ // class C extends B {
+ // /* _m(num i) */ // A._m doesn't implement B._m so a
+ // // noSuchMethod forwarder is created.
+ // }
+ noSuchMethodTarget = noSuchMethodMember;
+ }
}
/// Normally, if only one member defines the interface member there
diff --git a/pkg/front_end/testcases/general/issue61717.dart b/pkg/front_end/testcases/general/issue61717.dart
new file mode 100644
index 0000000..0404c48
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2025, 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.
+
+import 'issue61717_lib.dart';
+
+base class C extends B {}
+
+void main() {
+ try {
+ C().public();
+ } catch (_) {
+ print('C.public throws');
+ }
+}
diff --git a/pkg/front_end/testcases/general/issue61717.dart.strong.expect b/pkg/front_end/testcases/general/issue61717.dart.strong.expect
new file mode 100644
index 0000000..add930e
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.strong.expect
@@ -0,0 +1,47 @@
+library;
+import self as self;
+import "issue61717_lib.dart" as iss;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///issue61717_lib.dart";
+
+base class C extends iss::B {
+ synthetic constructor •() → self::C
+ : super iss::B::•()
+ ;
+ synthetic no-such-method-forwarder method iss::_m(core::num _#wc1#formal) → void
+ return throw{for-error-handling} core::NoSuchMethodError::withInvocation(this, new core::_InvocationMirror::_withType(#C1, 0, #C2, core::List::unmodifiable<dynamic>(<dynamic>[_#wc1#formal]), core::Map::unmodifiable<core::Symbol, dynamic>(#C3)));
+}
+static method main() → void {
+ try {
+ new self::C::•().{iss::A::public}(){() → void};
+ }
+ on core::Object catch(final wildcard core::Object _#wc0#formal) {
+ core::print("C.public throws");
+ }
+}
+
+library;
+import self as iss;
+import "dart:core" as core;
+
+base class A extends core::Object {
+ synthetic constructor •() → iss::A
+ : super core::Object::•()
+ ;
+ method _m(wildcard core::int _#wc0#formal) → void {}
+ method public() → void
+ return this.{iss::A::_m}(0){(core::int) → void};
+}
+abstract base class B extends iss::A {
+ synthetic constructor •() → iss::B
+ : super iss::A::•()
+ ;
+ abstract method _m(wildcard core::num _#wc1#formal) → void;
+}
+
+constants {
+ #C1 = #org-dartlang-testcase:///issue61717.dart::_m
+ #C2 = <core::Type>[]
+ #C3 = <core::Symbol, dynamic>{}
+}
diff --git a/pkg/front_end/testcases/general/issue61717.dart.strong.modular.expect b/pkg/front_end/testcases/general/issue61717.dart.strong.modular.expect
new file mode 100644
index 0000000..add930e
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.strong.modular.expect
@@ -0,0 +1,47 @@
+library;
+import self as self;
+import "issue61717_lib.dart" as iss;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///issue61717_lib.dart";
+
+base class C extends iss::B {
+ synthetic constructor •() → self::C
+ : super iss::B::•()
+ ;
+ synthetic no-such-method-forwarder method iss::_m(core::num _#wc1#formal) → void
+ return throw{for-error-handling} core::NoSuchMethodError::withInvocation(this, new core::_InvocationMirror::_withType(#C1, 0, #C2, core::List::unmodifiable<dynamic>(<dynamic>[_#wc1#formal]), core::Map::unmodifiable<core::Symbol, dynamic>(#C3)));
+}
+static method main() → void {
+ try {
+ new self::C::•().{iss::A::public}(){() → void};
+ }
+ on core::Object catch(final wildcard core::Object _#wc0#formal) {
+ core::print("C.public throws");
+ }
+}
+
+library;
+import self as iss;
+import "dart:core" as core;
+
+base class A extends core::Object {
+ synthetic constructor •() → iss::A
+ : super core::Object::•()
+ ;
+ method _m(wildcard core::int _#wc0#formal) → void {}
+ method public() → void
+ return this.{iss::A::_m}(0){(core::int) → void};
+}
+abstract base class B extends iss::A {
+ synthetic constructor •() → iss::B
+ : super iss::A::•()
+ ;
+ abstract method _m(wildcard core::num _#wc1#formal) → void;
+}
+
+constants {
+ #C1 = #org-dartlang-testcase:///issue61717.dart::_m
+ #C2 = <core::Type>[]
+ #C3 = <core::Symbol, dynamic>{}
+}
diff --git a/pkg/front_end/testcases/general/issue61717.dart.strong.outline.expect b/pkg/front_end/testcases/general/issue61717.dart.strong.outline.expect
new file mode 100644
index 0000000..5355dde
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.strong.outline.expect
@@ -0,0 +1,40 @@
+library;
+import self as self;
+import "issue61717_lib.dart" as iss;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///issue61717_lib.dart";
+
+base class C extends iss::B {
+ synthetic constructor •() → self::C
+ ;
+ synthetic no-such-method-forwarder method iss::_m(core::num _#wc1#formal) → void
+ return throw{for-error-handling} core::NoSuchMethodError::withInvocation(this, new core::_InvocationMirror::_withType(#_m, 0, const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>[_#wc1#formal]), core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})));
+}
+static method main() → void
+ ;
+
+library;
+import self as iss;
+import "dart:core" as core;
+
+base class A extends core::Object {
+ synthetic constructor •() → iss::A
+ ;
+ method _m(wildcard core::int _#wc0#formal) → void
+ ;
+ method public() → void
+ ;
+}
+abstract base class B extends iss::A {
+ synthetic constructor •() → iss::B
+ ;
+ abstract method _m(wildcard core::num _#wc1#formal) → void;
+}
+
+
+Extra constant evaluation status:
+Evaluated: SymbolLiteral @ org-dartlang-testcase:///issue61717.dart:7:12 -> SymbolConstant(#_m)
+Evaluated: ListLiteral @ org-dartlang-testcase:///issue61717.dart:7:12 -> ListConstant(const <Type>[])
+Evaluated: MapLiteral @ org-dartlang-testcase:///issue61717.dart:7:12 -> MapConstant(const <Symbol, dynamic>{})
+Extra constant evaluation: evaluated: 11, effectively constant: 3
diff --git a/pkg/front_end/testcases/general/issue61717.dart.strong.transformed.expect b/pkg/front_end/testcases/general/issue61717.dart.strong.transformed.expect
new file mode 100644
index 0000000..de52a8c
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.strong.transformed.expect
@@ -0,0 +1,47 @@
+library;
+import self as self;
+import "issue61717_lib.dart" as iss;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///issue61717_lib.dart";
+
+base class C extends iss::B {
+ synthetic constructor •() → self::C
+ : super iss::B::•()
+ ;
+ synthetic no-such-method-forwarder method iss::_m(core::num _#wc1#formal) → void
+ return throw{for-error-handling} core::NoSuchMethodError::withInvocation(this, new core::_InvocationMirror::_withType(#C1, 0, #C2, core::List::unmodifiable<dynamic>(core::_GrowableList::_literal1<dynamic>(_#wc1#formal)), core::Map::unmodifiable<core::Symbol, dynamic>(#C3)));
+}
+static method main() → void {
+ try {
+ new self::C::•().{iss::A::public}(){() → void};
+ }
+ on core::Object catch(final wildcard core::Object _#wc0#formal) {
+ core::print("C.public throws");
+ }
+}
+
+library;
+import self as iss;
+import "dart:core" as core;
+
+base class A extends core::Object {
+ synthetic constructor •() → iss::A
+ : super core::Object::•()
+ ;
+ method _m(wildcard core::int _#wc0#formal) → void {}
+ method public() → void
+ return this.{iss::A::_m}(0){(core::int) → void};
+}
+abstract base class B extends iss::A {
+ synthetic constructor •() → iss::B
+ : super iss::A::•()
+ ;
+ abstract method _m(wildcard core::num _#wc1#formal) → void;
+}
+
+constants {
+ #C1 = #org-dartlang-testcase:///issue61717.dart::_m
+ #C2 = <core::Type>[]
+ #C3 = <core::Symbol, dynamic>{}
+}
diff --git a/pkg/front_end/testcases/general/issue61717.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue61717.dart.textual_outline.expect
new file mode 100644
index 0000000..3131f03
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+import 'issue61717_lib.dart';
+
+base class C extends B {}
+
+void main() {}
diff --git a/pkg/front_end/testcases/general/issue61717.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue61717.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..3131f03
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717.dart.textual_outline_modelled.expect
@@ -0,0 +1,5 @@
+import 'issue61717_lib.dart';
+
+base class C extends B {}
+
+void main() {}
diff --git a/pkg/front_end/testcases/general/issue61717_lib.dart b/pkg/front_end/testcases/general/issue61717_lib.dart
new file mode 100644
index 0000000..88a086d
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue61717_lib.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, 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.
+
+base class A {
+ void _m(int _) {}
+ void public() => _m(0);
+}
+
+abstract base class B extends A {
+ void _m(num _);
+}