mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
Add tests for override inherited inference.
Change-Id: I636682c38e2ba97826420f6f6bbb8e54aa29e21f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148761 Reviewed-by: Lasse R.H. Nielsen <lrn@google.com> Reviewed-by: Erik Ernst <eernst@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Leaf Petersen <leafp@google.com> Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
This commit is contained in:
parent
67da8cf7f5
commit
6428bab7ed
248
tests/language/class/override_inference_error_test.dart
Normal file
248
tests/language/class/override_inference_error_test.dart
Normal file
|
@ -0,0 +1,248 @@
|
|||
// Copyright (c) 2020, 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.
|
||||
|
||||
// ignore_for_file: unused_local_variable
|
||||
|
||||
// Static tests for inheriting types on overriding members.
|
||||
|
||||
// If a member `m` omits any parameter type, or the return type, and
|
||||
// one or more of the immediate superinterfaces have a member named
|
||||
// `m`: Find the combined member signature `s` for `m` in the immediate
|
||||
// superinterfaces. A compile-time error occurs if it does not exist.
|
||||
// Otherwise, each missing type annotation of a parameter is obtained
|
||||
// from the corresponding parameter in `s`, and the return type, if
|
||||
// missing, is obtained from `s`. If there is no corresponding
|
||||
// parameter in `s`, the inferred type annotation is `dynamic`.
|
||||
//
|
||||
// Only types are inherited. Other modifiers and annotations are not.
|
||||
// This includes `final`, `required` and any annotations
|
||||
// or default values.
|
||||
// (The `covariant` keyword is not inherited, but its semantics
|
||||
// are so it's impossible to tell the difference).
|
||||
//
|
||||
// For getters and setters, if both are present, subclasses inherit the type of
|
||||
// the corresponding superclass member.
|
||||
// If the superclass has only a setter or a getter, subclasses inherit that type
|
||||
// for both getters and setters.
|
||||
|
||||
// Incompatible `foo` signatures.
|
||||
abstract class IIntInt {
|
||||
int foo(int x);
|
||||
}
|
||||
|
||||
abstract class IIntDouble {
|
||||
double foo(int x);
|
||||
}
|
||||
|
||||
abstract class IDoubleInt {
|
||||
int foo(double x);
|
||||
}
|
||||
|
||||
abstract class IDoubleDouble {
|
||||
double foo(double x);
|
||||
}
|
||||
|
||||
// If the superinterfaces do not have a most specific member signature,
|
||||
// then omitting any parameter or return type is an error.
|
||||
|
||||
abstract class CInvalid1 implements IIntInt, IIntDouble {
|
||||
/*indent*/ foo(x);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
abstract class CInvalid2 implements IIntInt, IDoubleInt {
|
||||
/*indent*/ foo(x);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
abstract class CInvalid3 implements IIntInt, IDoubleDouble {
|
||||
/*indent*/ foo(x);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
// Even if the conflicting super-parameter/return type is given a type.
|
||||
abstract class CInvalid4 implements IIntInt, IIntDouble {
|
||||
Never foo(x);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
abstract class CInvalid5 implements IIntInt, IDoubleInt {
|
||||
/*indent*/ foo(num x);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
// Even if the omitted parameter doesn't exist in the super-interfaces.
|
||||
abstract class CInvalid6 implements IIntInt, IDoubleInt {
|
||||
Never foo(num x, [y]);
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
// And even if there is no real conflict.
|
||||
abstract class IOptx {
|
||||
int foo({int x});
|
||||
}
|
||||
|
||||
abstract class IOpty {
|
||||
int foo({int y});
|
||||
}
|
||||
|
||||
abstract class CInvalid7 implements IOptx, IOpty {
|
||||
/*indent*/ foo({int x, int y});
|
||||
// ^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
// The type of unconstrained omitted types is `dynamic`.
|
||||
class CInherit1 implements IOptx {
|
||||
foo({x = 0, y = 0}) {
|
||||
// Type of `y` is `dynamic`.
|
||||
Object? tmp;
|
||||
y = tmp; // Top type.
|
||||
Null tmp2 = y; // And implicit downcast.
|
||||
y.arglebargle(); // And unsound member invocations.
|
||||
|
||||
// x is exactly int.
|
||||
// Assignable to int and usable as int.
|
||||
int intVar = x;
|
||||
x = x.toRadixString(16).length;
|
||||
// And not dynamic.
|
||||
/*indent*/ x.arglebargle();
|
||||
// ^^^^^^^^^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
|
||||
// Return type is exactly int.
|
||||
if (x == 0) {
|
||||
num tmp3 = x;
|
||||
return tmp3; // Does not allow returning a supertype of int.
|
||||
// ^^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
}
|
||||
// Allows returning int.
|
||||
return intVar;
|
||||
}
|
||||
|
||||
// No supertype signature, infer `dynamic` for every type.
|
||||
bar(x) {
|
||||
// x is Object?.
|
||||
Object? tmp;
|
||||
x = tmp; // A top type since Object? is assignable to it.
|
||||
Null tmp2 = x; // Implicit downcast.
|
||||
x.arglebargle(); // Unsafe invocations.
|
||||
|
||||
// Return type is `dynamic` when calling `bar`.
|
||||
var ret = bar(x);
|
||||
ret = tmp;
|
||||
tmp2 = ret;
|
||||
ret.arglebargle();
|
||||
|
||||
// And definitely a top type when returning.
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not inherit `required`.
|
||||
class IReq {
|
||||
void foo({required int x}) {}
|
||||
}
|
||||
|
||||
class CInvalid8 implements IReq {
|
||||
// Do not inherit `required` if there is a type.
|
||||
foo({num x}) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.MISSING_DEFAULT_VALUE_FOR_PARAMETER
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
class CInvalid9 implements IReq {
|
||||
// Do not inherit `required` if there is no type.
|
||||
void foo({x}) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.MISSING_DEFAULT_VALUE_FOR_PARAMETER
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
abstract class INonNullable {
|
||||
foo({num x});
|
||||
}
|
||||
|
||||
class CInvalid10 implements INonNullable {
|
||||
// Inherit type even when it would be invalid in the supertype, if it had been
|
||||
// non-abstract.
|
||||
foo({x}) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.MISSING_DEFAULT_VALUE_FOR_PARAMETER
|
||||
// [cfe] unspecified
|
||||
}
|
||||
|
||||
/// Do not inherit default value implicitly.
|
||||
class IDefault {
|
||||
int foo({int x = 0}) => x;
|
||||
}
|
||||
|
||||
class CInvalid11 implements IDefault {
|
||||
foo({x}) => x;
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.MISSING_DEFAULT_VALUE_FOR_PARAMETER
|
||||
// [cfe] unspecified
|
||||
// ^
|
||||
// [analyzer] STATIC_WARNING.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_NAMED
|
||||
}
|
||||
|
||||
// Inherits type variables, even with different names.
|
||||
class CGeneric<T> {
|
||||
T foo(T x) => x;
|
||||
|
||||
R bar<R>(R x) => x;
|
||||
}
|
||||
|
||||
class CInheritGeneric<S> implements CGeneric<S> {
|
||||
foo(x) {
|
||||
// x has type exactly S.
|
||||
// Assignable both ways.
|
||||
S tmp = x;
|
||||
x = tmp;
|
||||
// And not dynamic.
|
||||
/*indent*/ x.arglebargle();
|
||||
// ^^^^^^^^^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
|
||||
// Return type is S.
|
||||
tmp = foo(x);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bar<Q>(x) {
|
||||
// x has type exactly Q.
|
||||
// Assignable both ways.
|
||||
Q tmp = x;
|
||||
x = tmp;
|
||||
// And not dynamic.
|
||||
/*indent*/ x.arglebargle();
|
||||
// ^^^^^^^^^^^
|
||||
// [analyzer] unspecified
|
||||
// [cfe] unspecified
|
||||
|
||||
// Return type is Q.
|
||||
tmp = bar<Q>(x);
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
main() {}
|
444
tests/language/class/override_inference_test.dart
Normal file
444
tests/language/class/override_inference_test.dart
Normal file
|
@ -0,0 +1,444 @@
|
|||
// Copyright (c) 2020, 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.
|
||||
|
||||
// Test that inheriting types on overriding members work as specified.
|
||||
|
||||
// ignore_for_file: unused_local_variable
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
// Helper variables.
|
||||
int intVar = 0;
|
||||
List<int> listIntVar = <int>[];
|
||||
int? intQVar;
|
||||
List<int?> listIntQVar = <int?>[];
|
||||
num numVar = 0;
|
||||
List<num> listNumVar = <num>[];
|
||||
|
||||
/// Override can work with incompatible superinterfaces,
|
||||
/// as long as override is more specific than all of them,
|
||||
/// and it specifies all types.
|
||||
class IIntOpts {
|
||||
int foo(int x, {int v = 0, required int z}) => x;
|
||||
}
|
||||
|
||||
class IDoubleOpts {
|
||||
double foo(double x, {double w = 0.0, int z = 1}) => x;
|
||||
}
|
||||
|
||||
class CNumOpts implements IIntOpts, IDoubleOpts {
|
||||
// Override is more specific than all supertype members.
|
||||
Never foo(num x, {int v = 0, double w = 0.0, int z = 1}) => throw "Never";
|
||||
}
|
||||
|
||||
// When a type is omitted, the most specific immediate superinterface member
|
||||
// signature is used. One such must exist.
|
||||
|
||||
class IIntInt {
|
||||
int foo(int x) => x;
|
||||
}
|
||||
|
||||
class INumInt {
|
||||
int foo(num x) => x.toInt();
|
||||
}
|
||||
|
||||
class IIntNum {
|
||||
num foo(int x) => x.toInt();
|
||||
}
|
||||
|
||||
class IInt {
|
||||
void foo(int x) {}
|
||||
}
|
||||
|
||||
class IIntQ {
|
||||
void foo(int? x) {}
|
||||
}
|
||||
|
||||
class IOpt1 {
|
||||
void foo([int x = 0]) {}
|
||||
}
|
||||
|
||||
class IOpt2 {
|
||||
void foo([int x = 0, int y = 0]) {}
|
||||
}
|
||||
|
||||
class IOptX {
|
||||
void foo({int x = 0}) {}
|
||||
}
|
||||
|
||||
class IOptXY {
|
||||
void foo({int x = 0, int y = 0}) {}
|
||||
}
|
||||
|
||||
abstract class IOptA1 {
|
||||
void foo([int x]);
|
||||
}
|
||||
|
||||
abstract class IOptAX {
|
||||
void foo({int x});
|
||||
}
|
||||
|
||||
class IReq {
|
||||
void foo({required int x}) {}
|
||||
}
|
||||
|
||||
// Type is inherited as long as no other type is written.
|
||||
// Even if prefixed by `var`, `final`, `required` or `covariant`,
|
||||
// or if made optional with or without a default value.
|
||||
class CVar implements IIntInt {
|
||||
foo(var x) {
|
||||
// Checks that x is exactly int.
|
||||
// It is assignable in both directions, and it's not dynamic.
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var xList = [x];
|
||||
listIntVar = xList;
|
||||
return itVar;
|
||||
}
|
||||
}
|
||||
|
||||
class CFinal implements IInt {
|
||||
foo(final x) {
|
||||
var sameTypeAsX = x;
|
||||
intVar = x;
|
||||
sameTypeAsX = intVar;
|
||||
var xList = [x];
|
||||
listIntVar = xList;
|
||||
}
|
||||
}
|
||||
|
||||
class COptDefault implements IInt {
|
||||
foo([x = 0]) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var xList = [x];
|
||||
listIntVar = xList;
|
||||
}
|
||||
}
|
||||
|
||||
// Must use the nullable type when not having a default.
|
||||
class COptNoDefault implements IIntQ {
|
||||
foo([x]) {
|
||||
int? tmp = x;
|
||||
x = tmp;
|
||||
var xList = [x];
|
||||
listIntQVar = xList;
|
||||
}
|
||||
}
|
||||
|
||||
class CReq implements IReq {
|
||||
foo({required x}) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var xList = [x];
|
||||
listIntQVar = xList;
|
||||
}
|
||||
}
|
||||
|
||||
// Do inherit when specifying `covariant`.
|
||||
class CCovar implements IInt {
|
||||
foo(covariant x) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var xList = [x];
|
||||
listIntVar = xList;
|
||||
}
|
||||
}
|
||||
|
||||
class CCovar2 implements CCovar {
|
||||
// Method was covariant in CCovar.
|
||||
foo(Never x) => 0;
|
||||
}
|
||||
|
||||
/// A more specific `foo` than [IInt.foo].
|
||||
/// Subclass inherits types from most specific superclass member.
|
||||
class CInherit1 implements INumInt, IIntNum {
|
||||
foo(x) {
|
||||
// x is num.
|
||||
numVar = x;
|
||||
x = numVar;
|
||||
var xList = [x];
|
||||
listNumVar = xList;
|
||||
|
||||
// return type is int.
|
||||
var ret = foo(x);
|
||||
intVar = ret;
|
||||
ret = intVar;
|
||||
var retList = [ret];
|
||||
listIntVar = retList;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class CInherit2 extends INumInt implements IIntNum {
|
||||
foo(x) {
|
||||
numVar = x;
|
||||
x = numVar;
|
||||
var xList = [x];
|
||||
listNumVar = xList;
|
||||
|
||||
var ret = foo(x);
|
||||
intVar = ret;
|
||||
ret = intVar;
|
||||
var retList = [ret];
|
||||
listIntVar = retList;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class CInherit3 extends IIntNum implements INumInt {
|
||||
foo(x) {
|
||||
numVar = x;
|
||||
x = numVar;
|
||||
var xList = [x];
|
||||
listNumVar = xList;
|
||||
|
||||
var ret = foo(x);
|
||||
intVar = ret;
|
||||
ret = intVar;
|
||||
var retList = [ret];
|
||||
listIntVar = retList;
|
||||
return intVar;
|
||||
}
|
||||
}
|
||||
|
||||
class CInherit4 with IIntNum implements INumInt {
|
||||
foo(x) {
|
||||
numVar = x;
|
||||
x = numVar;
|
||||
var xList = [x];
|
||||
listNumVar = xList;
|
||||
|
||||
var ret = foo(x);
|
||||
intVar = ret;
|
||||
ret = intVar;
|
||||
var retList = [ret];
|
||||
listIntVar = retList;
|
||||
return intVar;
|
||||
}
|
||||
}
|
||||
|
||||
void testInheritFull() {
|
||||
Expect.type<int Function(num)>(CInherit1().foo);
|
||||
Expect.type<int Function(num)>(CInherit2().foo);
|
||||
Expect.type<int Function(num)>(CInherit3().foo);
|
||||
Expect.type<int Function(num)>(CInherit4().foo);
|
||||
}
|
||||
|
||||
/// Works for optional parameters too.
|
||||
|
||||
class COptInherit1 implements IOpt1, IOpt2 {
|
||||
foo([x = 0, y = 0]) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var listX = [x];
|
||||
listIntVar = listX;
|
||||
|
||||
intVar = y;
|
||||
y = intVar;
|
||||
var listY = [y];
|
||||
listIntVar = listY;
|
||||
}
|
||||
}
|
||||
|
||||
class COptInherit2 implements IOptX, IOptXY {
|
||||
foo({x = 0, y = 0}) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var listX = [x];
|
||||
listIntVar = listX;
|
||||
|
||||
intVar = y;
|
||||
y = intVar;
|
||||
var listY = [y];
|
||||
listIntVar = listY;
|
||||
}
|
||||
}
|
||||
|
||||
class COptInherit3 implements IIntInt, INumInt {
|
||||
foo(x, [y]) {
|
||||
// Ensure that type is: int Function(num, [dynamic]) .
|
||||
// For static checks only, do not call the method!
|
||||
|
||||
// x is exactly num.
|
||||
numVar = x;
|
||||
x = numVar;
|
||||
var listX = [x];
|
||||
listNumVar = listX;
|
||||
|
||||
// y is dynamic.
|
||||
Object? tmpObject;
|
||||
y = tmpObject; // A top type.
|
||||
Null tmpNull = y; // Implicit downcast.
|
||||
y.arglebargle(); // Unsound member invocations.
|
||||
|
||||
// return type is exactly int.
|
||||
var ret = foo(x, y);
|
||||
intVar = ret;
|
||||
ret = intVar;
|
||||
var retList = [ret];
|
||||
listIntVar = retList;
|
||||
return intVar;
|
||||
}
|
||||
}
|
||||
|
||||
class COptInherit4 implements IOptA1 {
|
||||
foo([x = 1]) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var listX = [x];
|
||||
listIntVar = listX;
|
||||
}
|
||||
}
|
||||
|
||||
class COptInherit5 implements IOptAX {
|
||||
foo({x = 1}) {
|
||||
intVar = x;
|
||||
x = intVar;
|
||||
var listX = [x];
|
||||
listIntVar = listX;
|
||||
}
|
||||
}
|
||||
|
||||
void testInheritOpt() {
|
||||
Expect.type<void Function([int, int])>(COptInherit1().foo);
|
||||
Expect.type<void Function({int x, int y})>(COptInherit2().foo);
|
||||
Expect.type<int Function(num, [dynamic])>(COptInherit3().foo);
|
||||
Expect.type<void Function([int])>(COptInherit4().foo);
|
||||
Expect.type<void Function({int x})>(COptInherit5().foo);
|
||||
}
|
||||
|
||||
// Do not inherit `final` with the type.
|
||||
class IFinal {
|
||||
void foo(final int x) {}
|
||||
}
|
||||
|
||||
class CInheritFinal implements IFinal {
|
||||
void foo(x) {
|
||||
x = 42;
|
||||
x.toRadixString(16);
|
||||
}
|
||||
}
|
||||
|
||||
// Also applies to getters and setters.
|
||||
class IGetSetInt {
|
||||
int get foo => 0;
|
||||
set foo(int _) {}
|
||||
}
|
||||
|
||||
class IFieldInt {
|
||||
int foo = 0;
|
||||
}
|
||||
|
||||
class ILateFieldInt {
|
||||
late int foo;
|
||||
}
|
||||
|
||||
class IFinalFieldInt {
|
||||
final int foo = 0;
|
||||
}
|
||||
|
||||
class CInheritGetSet implements IGetSetInt {
|
||||
get foo => throw "whatever";
|
||||
set foo(set) {
|
||||
// For static checking only, do not call.
|
||||
// `set` is assignable both ways to int.
|
||||
intVar = set;
|
||||
set = intVar;
|
||||
var listSet = [set];
|
||||
listIntVar = listSet;
|
||||
|
||||
var get = foo;
|
||||
// get is assignable both ways to int.
|
||||
intVar = get;
|
||||
get = intVar;
|
||||
var listGet = [get];
|
||||
listIntVar = listGet;
|
||||
}
|
||||
}
|
||||
|
||||
class CInheritField implements IFieldInt {
|
||||
get foo => throw "whatever";
|
||||
set foo(set) {
|
||||
// For static checking only, do not call.
|
||||
// `set` is assignable both ways to int.
|
||||
intVar = set;
|
||||
set = intVar;
|
||||
var listSet = [set];
|
||||
listIntVar = listSet;
|
||||
|
||||
var get = foo;
|
||||
// get is assignable both ways to int.
|
||||
intVar = get;
|
||||
get = intVar;
|
||||
var listGet = [get];
|
||||
listIntVar = listGet;
|
||||
}
|
||||
}
|
||||
|
||||
class CInheritLateField implements ILateFieldInt {
|
||||
get foo => throw "whatever";
|
||||
set foo(set) {
|
||||
// For static checking only, do not call.
|
||||
// `set` is assignable both ways to int.
|
||||
intVar = set;
|
||||
set = intVar;
|
||||
var listSet = [set];
|
||||
listIntVar = listSet;
|
||||
|
||||
var get = foo;
|
||||
// get is assignable both ways to int.
|
||||
intVar = get;
|
||||
get = intVar;
|
||||
var listGet = [get];
|
||||
listIntVar = listGet;
|
||||
}
|
||||
}
|
||||
|
||||
class CInheritFinalField implements IFinalFieldInt {
|
||||
get foo => throw "whatever";
|
||||
set foo(set) {
|
||||
// For static checking only, do not call.
|
||||
|
||||
// `set` is assignable both ways to int.
|
||||
intVar = set;
|
||||
set = intVar;
|
||||
var listSet = [set];
|
||||
listIntVar = listSet;
|
||||
|
||||
var get = foo; // Is int.
|
||||
// get is assignable both ways to int.
|
||||
intVar = get;
|
||||
get = intVar;
|
||||
var listGet = [get];
|
||||
listIntVar = listGet;
|
||||
}
|
||||
}
|
||||
|
||||
class ISetterOnly {
|
||||
set foo(int value) {}
|
||||
}
|
||||
|
||||
class IInheritSetter implements ISetterOnly {
|
||||
// Infers `int` as return type.
|
||||
get foo => throw "whatever";
|
||||
|
||||
set foo(value) {
|
||||
int tmp = value;
|
||||
value = tmp;
|
||||
var valueList = [value];
|
||||
List<int> list = valueList;
|
||||
|
||||
var get = foo;
|
||||
intVar = get;
|
||||
get = intVar;
|
||||
var getList = [get];
|
||||
list = getList;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testInheritFull();
|
||||
testInheritOpt();
|
||||
}
|
Loading…
Reference in a new issue