[ddc] Improve NoSuchMethod error for closures
Changes the text from "NoSuchMethodError: ''" to
"NoSuchMethodError: '<anonymous closure>'" for some failed invocations.
Adds more test cases involving getter and field invocations.
Change-Id: I685772fc69c7216ae67ea2535de38c8b925c0809
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425940
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index 21bc20d..6a11674 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -553,10 +553,14 @@
var originalTarget = JS<bool>('!', '# === void 0', obj) ? f : obj;
callNSM(@notNull String errorMessage) {
+ var name = displayName;
+ if (name is String && name.isEmpty) {
+ name = '<anonymous closure>';
+ }
return noSuchMethod(
originalTarget,
InvocationImpl(
- displayName,
+ name,
JS<List<Object?>>('!', '#', args),
namedArguments: named,
// Repeated the default value here in JS to preserve the historic
diff --git a/tests/dartdevc/no_such_method_errors_test.dart b/tests/dartdevc/no_such_method_errors_test.dart
index a7e7174..1dc87b6 100644
--- a/tests/dartdevc/no_such_method_errors_test.dart
+++ b/tests/dartdevc/no_such_method_errors_test.dart
@@ -41,6 +41,22 @@
val += 10;
return val.toString();
};
+
+ Function get getterArity1 => (int val) {
+ val += 10;
+ return val.toString();
+ };
+
+ Function fieldArity1Tearoff = A.staticArity1;
+
+ Function get getterArity1Tearoff => A.staticArity1;
+}
+
+class B {
+ String call(int val) {
+ val += 10;
+ return val.toString();
+ }
}
String arity1(int val) {
@@ -55,6 +71,15 @@
return val.toString();
};
+Function get getterArity1 => (int val) {
+ val += 10;
+ return val.toString();
+};
+
+Function fieldArity1Tearoff = arity1;
+
+Function get getterArity1Tearoff => arity1;
+
String requiredNamedArity1({required bool fosse}) {
return fosse.toString();
}
@@ -63,6 +88,19 @@
void main() {
group('Dynamic call of', () {
+ test('instance of class with a `call()` method', () {
+ dynamic d = B();
+ Expect.throws<NoSuchMethodError>(
+ () => d(),
+ (error) => error.toString().contains(
+ "NoSuchMethodError: 'call'\n"
+ "Dynamic call with missing positional arguments. "
+ "Expected: 1 Actual: 0\n"
+ "Receiver: Instance of 'B'\n"
+ "Arguments: []",
+ ),
+ );
+ });
dynamic instanceOfA = A();
test('instance of a class with no `call()` method', () {
// Compiled as `dcall()`.
@@ -193,6 +231,8 @@
// Compiled as `dgcall()` and throws from `checkAndCall()`.
expectThrowsNSMWithExactError(
() => instantiatedTearoff<int, double>(),
+ // TODO(60654): Improve error message to include the actual name of
+ // the method.
"NoSuchMethodError: 'result'\n"
"Dynamic call failed.\n"
"Incorrect number of type arguments. "
@@ -277,6 +317,22 @@
(error) => error.toString().contains("NoSuchMethodError: 'arity1'"),
);
});
+ test('class instance getter that returns tearoff', () {
+ Expect.throws<NoSuchMethodError>(
+ () => A().getterArity1Tearoff(),
+ (error) => error.toString().contains(
+ "NoSuchMethodError: 'getterArity1Tearoff'",
+ ),
+ );
+ });
+ test('class instance field that stores a tearoff', () {
+ Expect.throws<NoSuchMethodError>(
+ () => A().fieldArity1Tearoff(),
+ (error) => error.toString().contains(
+ "NoSuchMethodError: 'fieldArity1Tearoff'",
+ ),
+ );
+ });
test('class instance generic method', () {
dynamic instanceOfA = A();
Expect.throws<NoSuchMethodError>(
@@ -297,10 +353,14 @@
dynamic tearoff = A().genericArity2<int, String>;
Expect.throws<NoSuchMethodError>(
() => tearoff(10),
- (error) => error.toString().contains("NoSuchMethodError: 'result'"),
+ (error) => error.toString().contains(
+ // TODO(60654): Improve error message to include the actual name of
+ // the method.
+ "NoSuchMethodError: 'result'",
+ ),
);
});
- test('class instance field', () {
+ test('class instance field that stores a closure', () {
dynamic instanceOfA = A();
Expect.throws<NoSuchMethodError>(
() => instanceOfA.fieldArity1(),
@@ -308,6 +368,14 @@
error.toString().contains("NoSuchMethodError: 'fieldArity1'"),
);
});
+ test('class instance getter that stores a closure', () {
+ dynamic instanceOfA = A();
+ Expect.throws<NoSuchMethodError>(
+ () => instanceOfA.getterArity1(),
+ (error) =>
+ error.toString().contains("NoSuchMethodError: 'getterArity1'"),
+ );
+ });
test('class static method tearoff', () {
dynamic tearoff = A.staticArity1;
Expect.throws<NoSuchMethodError>(
@@ -329,7 +397,11 @@
dynamic tearoff = A.staticGenericArity2<int, double>;
Expect.throws<NoSuchMethodError>(
() => tearoff(10),
- (error) => error.toString().contains("NoSuchMethodError: 'result'"),
+ (error) => error.toString().contains(
+ // TODO(60654): Improve error message to include the actual name of
+ // the method.
+ "NoSuchMethodError: 'result'",
+ ),
);
});
test('top level method tearoff', () {
@@ -352,13 +424,39 @@
dynamic tearoff = genericArity2<int, String>;
Expect.throws<NoSuchMethodError>(
() => tearoff(10),
- (error) => error.toString().contains("NoSuchMethodError: 'result'"),
+ (error) => error.toString().contains(
+ // TODO(60654): Improve error message to include the actual name of
+ // the method.
+ "NoSuchMethodError: 'result'",
+ ),
);
});
- test('top level field', () {
+ test('top level field storing a closure', () {
Expect.throws<NoSuchMethodError>(
() => fieldArity1(),
- (error) => error.toString().contains("NoSuchMethodError: ''"),
+ (error) => error.toString().contains(
+ "NoSuchMethodError: '<anonymous closure>'",
+ ),
+ );
+ });
+ test('top level getter that returns a closure', () {
+ Expect.throws<NoSuchMethodError>(
+ () => getterArity1(),
+ (error) => error.toString().contains(
+ "NoSuchMethodError: '<anonymous closure>'",
+ ),
+ );
+ });
+ test('top level field storing a tearoff', () {
+ Expect.throws<NoSuchMethodError>(
+ () => fieldArity1Tearoff(),
+ (error) => error.toString().contains("NoSuchMethodError: 'arity1'"),
+ );
+ });
+ test('top level getter that returns a tearoff', () {
+ Expect.throws<NoSuchMethodError>(
+ () => getterArity1Tearoff(),
+ (error) => error.toString().contains("NoSuchMethodError: 'arity1'"),
);
});
});