d791aa29fb
Two changes landed that touched this test and they didn't merge cleanly. Change-Id: Ie199dcb30b63627164ce2213fe4d61e65d664371 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426285 Reviewed-by: Bob Nystrom <rnystrom@google.com> Commit-Queue: Nicholas Shahan <nshahan@google.com>
464 lines
16 KiB
Dart
464 lines
16 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.
|
|
|
|
import 'package:expect/expect.dart';
|
|
import 'package:expect/legacy/minitest.dart'; // ignore: deprecated_member_use_from_same_package
|
|
|
|
/// 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) {
|
|
Expect.equals(expectedErrorMessage, error.toString());
|
|
return true;
|
|
});
|
|
|
|
class A {
|
|
String arity1(int val) {
|
|
val += 10;
|
|
return val.toString();
|
|
}
|
|
|
|
void genericArity2<T, S>() => print('$T, $S');
|
|
|
|
static String staticArity1(int val) {
|
|
val += 10;
|
|
return val.toString();
|
|
}
|
|
|
|
static void staticGenericArity2<T, S>() => print('$T, $S');
|
|
|
|
Function? nullField;
|
|
|
|
Function fieldArity1 = (int val) {
|
|
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) {
|
|
val += 10;
|
|
return val.toString();
|
|
}
|
|
|
|
void genericArity2<T, S>() => print('$T, $S');
|
|
|
|
Function fieldArity1 = (int val) {
|
|
val += 10;
|
|
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();
|
|
}
|
|
|
|
int? x;
|
|
|
|
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 failed.\n"
|
|
"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()`.
|
|
expectThrowsNSMWithExactError(
|
|
() => instanceOfA(),
|
|
"NoSuchMethodError: 'call'\n"
|
|
"Dynamic call failed.\n"
|
|
"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 failed.\n"
|
|
"Tried to invoke `null` like a method.\n"
|
|
"Receiver: ${Error.safeToString(instanceOfA)}\n"
|
|
"Arguments: []",
|
|
);
|
|
});
|
|
test('passing type arguments', () {
|
|
// Compiled as `dgsend()`.
|
|
expectThrowsNSMWithExactError(
|
|
() => instanceOfA.doesNotExist<String, bool>(),
|
|
"NoSuchMethodError: 'doesNotExist'\n"
|
|
"Dynamic call failed.\n"
|
|
"Tried to invoke `null` like a method.\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;
|
|
dynamic instantiatedTearoff = genericArity2<int, String>;
|
|
test('passing unexpected type arguments', () {
|
|
// Compiled as `dgcall()` and throws from `checkAndCall()`.
|
|
expectThrowsNSMWithExactError(
|
|
() => arity1Tearoff<bool>(42),
|
|
"NoSuchMethodError: 'arity1'\n"
|
|
"Dynamic call failed.\n"
|
|
"Incorrect number of 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 failed.\n"
|
|
"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 failed.\n"
|
|
"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>(),
|
|
// 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. "
|
|
"Expected: 0 Actual: 2\n"
|
|
"Receiver: ${Error.safeToString(instantiatedTearoff)}\n"
|
|
"Arguments: []",
|
|
);
|
|
});
|
|
});
|
|
group('`Function.apply()`', () {
|
|
Function arity1Tearoff = arity1;
|
|
Function requiredNamedArity1Tearoff = requiredNamedArity1;
|
|
// 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 failed.\n"
|
|
"Too many positional 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 failed.\n"
|
|
"Missing positional arguments. "
|
|
"Expected: 1 Actual: 0\n"
|
|
"Receiver: ${Error.safeToString(arity1Tearoff)}\n"
|
|
"Arguments: []",
|
|
);
|
|
});
|
|
test('passing unexpected named argument', () {
|
|
expectThrowsNSMWithExactError(
|
|
() => Function.apply(requiredNamedArity1Tearoff, null, {
|
|
#fosse: true,
|
|
#cello: true,
|
|
}),
|
|
"NoSuchMethodError: 'requiredNamedArity1'\n"
|
|
"Dynamic call failed.\n"
|
|
"Unexpected named argument 'cello'.\n"
|
|
"Receiver: ${Error.safeToString(requiredNamedArity1Tearoff)}\n"
|
|
"Arguments: [fosse: true, cello: true]",
|
|
);
|
|
});
|
|
test('missing required named argument', () {
|
|
// Missing required named arguments are not an error when running
|
|
// without sound null safety.
|
|
if (hasUnsoundNullSafety) return;
|
|
expectThrowsNSMWithExactError(
|
|
() => Function.apply(requiredNamedArity1Tearoff, null),
|
|
"NoSuchMethodError: 'requiredNamedArity1'\n"
|
|
"Dynamic call failed.\n"
|
|
"Missing required named arguments: fosse.\n"
|
|
"Receiver: ${Error.safeToString(requiredNamedArity1Tearoff)}\n"
|
|
"Arguments: []",
|
|
);
|
|
});
|
|
});
|
|
});
|
|
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: '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>(
|
|
() => instanceOfA.genericArity2(10),
|
|
(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: 'genericArity2'"),
|
|
);
|
|
});
|
|
test('class instance generic method tearoff instantiated', () {
|
|
dynamic tearoff = A().genericArity2<int, String>;
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => tearoff(10),
|
|
(error) => error.toString().contains(
|
|
// TODO(60654): Improve error message to include the actual name of
|
|
// the method.
|
|
"NoSuchMethodError: 'result'",
|
|
),
|
|
);
|
|
});
|
|
test('class instance field that stores a closure', () {
|
|
dynamic instanceOfA = A();
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => instanceOfA.fieldArity1(),
|
|
(error) =>
|
|
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>(
|
|
() => tearoff(),
|
|
(error) =>
|
|
error.toString().contains("NoSuchMethodError: 'staticArity1'"),
|
|
);
|
|
});
|
|
test('class static generic method tearoff', () {
|
|
dynamic tearoff = A.staticGenericArity2;
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => tearoff(10),
|
|
(error) => error.toString().contains(
|
|
"NoSuchMethodError: 'staticGenericArity2'",
|
|
),
|
|
);
|
|
});
|
|
test('class static generic method tearoff instantiated', () {
|
|
dynamic tearoff = A.staticGenericArity2<int, double>;
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => tearoff(10),
|
|
(error) => error.toString().contains(
|
|
// TODO(60654): Improve error message to include the actual name of
|
|
// the method.
|
|
"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(10),
|
|
(error) =>
|
|
error.toString().contains("NoSuchMethodError: 'genericArity2'"),
|
|
);
|
|
});
|
|
test('top level generic method tearoff instantiated', () {
|
|
dynamic tearoff = genericArity2<int, String>;
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => tearoff(10),
|
|
(error) => error.toString().contains(
|
|
// TODO(60654): Improve error message to include the actual name of
|
|
// the method.
|
|
"NoSuchMethodError: 'result'",
|
|
),
|
|
);
|
|
});
|
|
test('top level field storing a closure', () {
|
|
Expect.throws<NoSuchMethodError>(
|
|
() => fieldArity1(),
|
|
(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'"),
|
|
);
|
|
});
|
|
});
|
|
}
|