blob: b43cdbfbef63f62e521dbdf44fea8d8917a0610b [file] [log] [blame]
// Copyright (c) 2023, 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.
// Tests the `toString` of a `Type` object for a type that is,
// or that contains, a `FutureOr` type, including whether its trailing `? s
// are correct.
//
// The `toString` is performed on the **NORM**-normalized type,
// and **NORM**(`FutureOr<T>?`), with `N` bein **NORM**(`T`),
// may be either `FutureOr<N>` or `FutureOr<N>?`, depending on
// whether `T`/`N` is nullable or not.
//
// This test checks that the `toString` result always matches
// the correctly normalized type format.
// Also checks for types `T` where normalization removes or changes
// the `FutureOr`.
// Requirements=nnbd-strong
import "dart:async" show FutureOr;
import "package:expect/expect.dart";
void main() {
// Generally assumes that `Type.toString()` mathches the canonical
// format of the type, as used in the language specification,
// with a leading name, `<...>` around type arguments,
// and trailing `?` for the nullable union types,
// and that it uses the same name for the same type every time,
// even if the names have been minified.
//
// That is the case for all current implementations, in all modes,
// even if `Type.toString()` doesn't document it.
Match match = RegExp(r"^(.+)<(.+)<(.+)>>$")
.matchAsPrefix((A<FutureOr<String>>).toString())!;
String a = match[1]!;
String fo = match[2]!;
Expect.equals("$String", match[3]); // Sanity check.
void testType<T>(String expected) {
Expect.equals(expected, "$T");
}
void testRuntimeType(Object o, String expected) {
Expect.equals(expected, "${o.runtimeType}");
}
void testNullable<B>() {
assert(null is B);
var b = "$B";
// Equal to `B` for top types and `Object`.
var fob = "${FutureOr<B>}";
if (topOrObject<B>()) {
Expect.equals(b, fob);
} else if (isNull<B>()) {
Expect.equals("${Future<B>}?", fob);
} else {
Expect.equals("$fo<$b>", fob);
}
testType<FutureOr<B>>(fob);
testType<FutureOr<B>?>(fob);
testType<FutureOr<B?>>(fob);
testType<FutureOr<B?>?>(fob);
testType<FutureOr<N<B?>>>(fob);
testType<N<FutureOr<B>?>>(fob);
var afob = "$a<$fob>";
testType<A<FutureOr<B>?>>(afob);
testType<A<FutureOr<B?>>>(afob);
testType<A<FutureOr<B?>?>>(afob);
testType<A<FutureOr<N<B?>>>>(afob);
testType<A<N<FutureOr<B>?>>>(afob);
testRuntimeType(A<FutureOr<B>?>(), afob);
testRuntimeType(A<FutureOr<B?>>(), afob);
testRuntimeType(A<FutureOr<B?>?>(), afob);
testRuntimeType(A<FutureOr<N<B?>>>(), afob);
testRuntimeType(A<N<FutureOr<B>?>>(), afob);
}
void testNotNullable<B>({bool alsoTestNullable = true}) {
assert(null is! B);
assert(!top<B>());
var b = "$B";
var fob = "${FutureOr<B>}";
if (never<B>()) {
Expect.equals("${Future<B>}", fob);
} else if (topOrObject<B>()) {
// Object.
Expect.equals(b, fob);
} else {
Expect.equals("$fo<$b>", fob);
}
testType<FutureOr<B>>(fob);
testType<FutureOr<B>?>("$fob?");
testType<N<FutureOr<B>?>>("$fob?");
testType<A<FutureOr<B>?>>("$a<$fob?>");
testType<A<N<FutureOr<B>?>>>("$a<$fob?>");
testRuntimeType(A<FutureOr<B>?>(), "$a<$fob?>");
testRuntimeType(A<N<FutureOr<B>?>>(), "$a<$fob?>");
if (alsoTestNullable) testNullable<B?>();
}
testNotNullable<String>();
testNullable<dynamic>();
testNullable<void>();
testNotNullable<Object>();
testNullable<Null>();
testNotNullable<Never>();
// Some more cases that could be non-trivial, but really aren't.
testNotNullable<Future<Object>>();
testNotNullable<Future<Object?>>();
// Test nested `FutureOr`, since normalization of that is somewhat
// non-trivial.
testNotNullable<FutureOr<String>>(alsoTestNullable: false);
testNullable<FutureOr<String>?>();
testNullable<FutureOr<String?>>();
testNullable<FutureOr<String?>?>();
// A singleton record with nullable field, for good measure.
testNotNullable<(String?,)>();
}
// Supports nesting nullables, like `N<N<N<int>>>`, which cannot
// syntactically be written as `int???`.
typedef N<T> = T?;
bool top<T>() => <Object?>[] is List<T>;
bool topOrObject<T>() => <Object>[] is List<T>;
bool never<T>() => <T>[] is List<Never>;
bool isNull<T>() => <T>[] is List<Null> && null is T;
class A<T> {}