dart-sdk/tests/dartdevc_2/no_such_method_errors_test.dart
Nicholas Shahan f7e8677ff6 [ddc] Add DDC specific NSM error test cases
Ensure there are tests for all the different code paths that
throw `NoSuchMethodError` when making some kind of dynamic invocation.

Add tests to cover the name descriptors that appear as well. Some are
confusing and in need of improvement. 

Null values in particular will cause a crash before correctly throwing
a `NoSuchMethodError`.

Change-Id: I54bbb91214e0d333b1a99abe455c8ff1a4df6cb9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/297182
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
2023-05-01 18:39:35 +00:00

314 lines
12 KiB
Dart

// Copyright (c) 2019, 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.
// @dart = 2.6
import 'package:expect/expect.dart';
import 'package:expect/minitest.dart';
/// This test is currently written as a white box test to ensure we don't
/// regress on any of the existing code paths that lead to NoSuchMethod errors
/// from dynamic calls. The test cases written with the knowledge of what the
/// generated code will look like.
// TODO(52190): Improve the NSM errors to make them more helpful.
expectThrowsNSMWithExactError(
void Function() computation, String expectedErrorMessage) =>
Expect.throws<NoSuchMethodError>(
computation, (error) => error.toString() == expectedErrorMessage);
class A {
String arity1(int val) {
val += 10;
return val.toString();
}
void genericArity2<T, S>(T t, S s) => print('$T, $S');
static String staticArity1(int val) {
val += 10;
return val.toString();
}
static void staticGenericArity2<T, S>(T t, S s) => print('$T, $S');
Function nullField;
Function fieldArity1 = (int val) {
val += 10;
return val.toString();
};
}
String arity1(int val) {
val += 10;
return val.toString();
}
void genericArity2<T, S>(T t, S s) => print('$T, $S');
Function fieldArity1 = (int val) {
val += 10;
return val.toString();
};
String namedArity1({bool fosse}) {
return fosse.toString();
}
int x;
void main() {
group('Dynamic call of', () {
dynamic instanceOfA = A();
test('instance of a class with no `call()` method', () {
// Compiled as `dcall()`.
expectThrowsNSMWithExactError(
() => instanceOfA(),
"NoSuchMethodError: 'call'\n"
"Dynamic call of object has no instance method 'call'.\n"
"Receiver: ${Error.safeToString(instanceOfA)}\n"
"Arguments: []");
});
group('null', () {
// TODO(49628): These should actually throw NoSuchMethodError with a
// helpful error message.
group('value', () {
dynamic nullVal = null;
test('without type arguments', () {
// Compiled as `dcall()`.
Expect.throws(() => nullVal());
});
test('passing type arguments', () {
// Compiled as `dgcall()`.
Expect.throws(() => nullVal<String, bool>());
});
});
group('instance field', () {
test('without type arguments', () {
// Compiled as `dsend()`.
Expect.throws(() => instanceOfA.nullField());
});
test('passing type arguments', () {
// Compiled as `dgsend()`.
Expect.throws(() => instanceOfA.nullField<String, bool>());
});
});
});
group('class instance members that do not exist', () {
group('method', () {
test('without passing type arguments', () {
// Compiled as `dsend()`.
expectThrowsNSMWithExactError(
() => instanceOfA.doesNotExist(),
"NoSuchMethodError: 'doesNotExist'\n"
"Dynamic call of null.\n"
"Receiver: ${Error.safeToString(instanceOfA)}\n"
"Arguments: []");
});
test('passing type arguments', () {
// Compiled as `dgsend()`.
expectThrowsNSMWithExactError(
() => instanceOfA.doesNotExist<String, bool>(),
"NoSuchMethodError: 'doesNotExist'\n"
"Dynamic call of null.\n"
"Receiver: ${Error.safeToString(instanceOfA)}\n"
"Arguments: []");
});
});
test('setter', () {
// Compiled as `dput()`.
expectThrowsNSMWithExactError(
() => instanceOfA.doesNotExist = 10,
"NoSuchMethodError: 'doesNotExist='\n"
"method not found\n"
"Receiver: ${Error.safeToString(instanceOfA)}\n"
"Arguments: [10]");
});
test('getter', () {
// Compiled as `dload()`.
expectThrowsNSMWithExactError(
() => x = instanceOfA.doesNotExist,
"NoSuchMethodError: 'doesNotExist'\n"
"method not found\n"
"Receiver: ${Error.safeToString(instanceOfA)}\n"
"Arguments: []");
});
});
group('tearoff', () {
// The code path for throwing NoSuchMethodErrors because of the incorrect
// type arguments is shared by all forms of dynamic invocations. Simply
// using tearoffs to trigger each form of the error.
dynamic arity1Tearoff = arity1;
dynamic genericArity2Tearoff = genericArity2;
void Function(int, String) instantiatedFn = genericArity2;
dynamic instantiatedTearoff = instantiatedFn;
test('passing unexpected type arguments', () {
// Compiled as `dgcall()` and throws from `checkAndCall()`.
expectThrowsNSMWithExactError(
() => arity1Tearoff<bool>(42),
"NoSuchMethodError: 'arity1'\n"
"Dynamic call with unexpected type arguments. "
"Expected: 0 Actual: 1\n"
"Receiver: ${Error.safeToString(arity1Tearoff)}\n"
"Arguments: [42]");
});
test('passing too many type arguments', () {
// Compiled as `dgcall()` and throws from `checkAndCall()`.
expectThrowsNSMWithExactError(
() => genericArity2Tearoff<int, double, String>(),
"NoSuchMethodError: 'genericArity2'\n"
"Dynamic call with incorrect number of type arguments. "
"Expected: 2 Actual: 3\n"
"Receiver: ${Error.safeToString(genericArity2Tearoff)}\n"
"Arguments: []");
});
test('passing too few type arguments', () {
// Compiled as `dgcall()` and throws from `checkAndCall()`.
expectThrowsNSMWithExactError(
() => genericArity2Tearoff<int>(),
"NoSuchMethodError: 'genericArity2'\n"
"Dynamic call with incorrect number of type arguments. "
"Expected: 2 Actual: 1\n"
"Receiver: ${Error.safeToString(genericArity2Tearoff)}\n"
"Arguments: []");
});
test('already instantiated and passing type arguments ', () {
// Compiled as `dgcall()` and throws from `checkAndCall()`.
expectThrowsNSMWithExactError(
() => instantiatedTearoff<int, double>(),
"NoSuchMethodError: 'result'\n"
"Dynamic call with unexpected type arguments. "
"Expected: 0 Actual: 2\n"
"Receiver: ${Error.safeToString(instantiatedTearoff)}\n"
"Arguments: []");
});
});
group('`Function.apply()`', () {
Function arity1Tearoff = arity1;
Function namedArity1Tearoff = namedArity1;
// The code path for throwing NoSuchMethodErrors because of the wrong
// number of arguments or incorrect named arguments is shared by all
// forms of dynamic invocations. Function.apply used here for simplicity.
// Argument errors generated from `_argumentErrors()`.
test('passing too many arguments', () {
expectThrowsNSMWithExactError(
() => Function.apply(arity1Tearoff, [42, false]),
"NoSuchMethodError: 'arity1'\n"
"Dynamic call with too many arguments. Expected: 1 Actual: 2\n"
"Receiver: ${Error.safeToString(arity1Tearoff)}\n"
"Arguments: [42, false]");
});
test('passing too few arguments', () {
expectThrowsNSMWithExactError(
() => Function.apply(arity1Tearoff, []),
"NoSuchMethodError: 'arity1'\n"
"Dynamic call with too few arguments. Expected: 1 Actual: 0\n"
"Receiver: ${Error.safeToString(arity1Tearoff)}\n"
"Arguments: []");
});
test('passing unexpected named argument', () {
expectThrowsNSMWithExactError(
() => Function.apply(
namedArity1Tearoff, null, {#fosse: true, #cello: true}),
"NoSuchMethodError: 'namedArity1'\n"
"Dynamic call with unexpected named argument 'cello'.\n"
"Receiver: ${Error.safeToString(namedArity1Tearoff)}\n"
"Arguments: [fosse: true, cello: true]");
});
});
});
group('Descriptors appearing in `NoSuchMethodError` message for ', () {
// Some extra tests for the names that appear in all the forms of dynamic
// calls. All of these tests pass wrong number of arguments just to trigger
// the error message.
test('class instance method', () {
dynamic instanceOfA = A();
Expect.throws<NoSuchMethodError>(() => instanceOfA.arity1(),
(error) => error.toString().contains("NoSuchMethodError: 'arity1'"));
});
test('class instance method tearoff', () {
dynamic tearoff = A().arity1;
Expect.throws<NoSuchMethodError>(
() => tearoff(),
(error) =>
error.toString().contains("NoSuchMethodError: 'bound arity1'"));
});
test('class instance generic method', () {
dynamic instanceOfA = A();
Expect.throws<NoSuchMethodError>(
() => instanceOfA.genericArity2(),
(error) =>
error.toString().contains("NoSuchMethodError: 'genericArity2'"));
});
test('class instance generic method tearoff', () {
dynamic tearoff = A().genericArity2;
Expect.throws<NoSuchMethodError>(
() => tearoff(10),
(error) => error
.toString()
.contains("NoSuchMethodError: 'bound genericArity2'"));
});
test('class instance generic method tearoff instantiated', () {
void Function(int, String) instantiatedFn = A().genericArity2;
dynamic tearoff = instantiatedFn;
Expect.throws<NoSuchMethodError>(() => tearoff(),
(error) => error.toString().contains("NoSuchMethodError: 'result'"));
});
test('class instance field', () {
dynamic instanceOfA = A();
Expect.throws<NoSuchMethodError>(
() => instanceOfA.fieldArity1(),
(error) =>
error.toString().contains("NoSuchMethodError: 'fieldArity1'"));
});
test('class static method tearoff', () {
dynamic tearoff = A.staticArity1;
Expect.throws<NoSuchMethodError>(
() => tearoff(),
(error) =>
error.toString().contains("NoSuchMethodError: 'staticArity1'"));
});
test('class static generic method tearoff', () {
dynamic tearoff = A.staticGenericArity2;
Expect.throws<NoSuchMethodError>(
() => tearoff(),
(error) => error
.toString()
.contains("NoSuchMethodError: 'staticGenericArity2'"));
});
test('class static generic method tearoff instantiated', () {
void Function(int, String) instantiatedFn = A.staticGenericArity2;
dynamic tearoff = instantiatedFn;
Expect.throws<NoSuchMethodError>(() => tearoff(),
(error) => error.toString().contains("NoSuchMethodError: 'result'"));
});
test('top level method tearoff', () {
dynamic tearoff = A.staticArity1;
Expect.throws<NoSuchMethodError>(
() => tearoff(),
(error) =>
error.toString().contains("NoSuchMethodError: 'staticArity1'"));
});
test('top level generic method tearoff', () {
dynamic tearoff = genericArity2;
Expect.throws<NoSuchMethodError>(
() => tearoff(),
(error) =>
error.toString().contains("NoSuchMethodError: 'genericArity2'"));
});
test('top level generic method tearoff instantiated', () {
void Function(int, String) instantiatedFn = genericArity2;
dynamic tearoff = instantiatedFn;
Expect.throws<NoSuchMethodError>(() => tearoff(),
(error) => error.toString().contains("NoSuchMethodError: 'result'"));
});
test('top level field', () {
Expect.throws<NoSuchMethodError>(() => fieldArity1(),
(error) => error.toString().contains("NoSuchMethodError: ''"));
});
});
}