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:
Lasse Reichstein Holst Nielsen 2020-06-11 17:13:21 +00:00 committed by commit-bot@chromium.org
parent 67da8cf7f5
commit 6428bab7ed
2 changed files with 692 additions and 0 deletions

View 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() {}

View 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();
}