Version 1.22.0-dev.10.3
Cherry-pick 3fa3964a70024787e8fbde9208815aeb0658608f to dev
Merge CHANGELOG.md updates to dev
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45e8aee..3fce2e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,8 @@
* Breaking change: ['Generalized tear-offs'](https://github.com/gbracha/generalizedTearOffs/blob/master/proposal.md)
are no longer supported, and will cause errors. We updated the language spec
and added warnings in 1.21, and are now taking the last step to fully
- de-support them. They were previously supported in the VM only.
+ de-support them. They were previously only supported in the VM, and there
+ are almost no known uses of them in the wild.
* The `assert()` statement has been expanded to support an optional second
`message` argument (SDK issue [27342](https://github.com/dart-lang/sdk/issues/27342)).
@@ -30,19 +31,187 @@
```
* The `Null` type has been moved to the bottom of the type hierarchy. As such,
- it is considered a subtype of every other type.
+ it is considered a subtype of every other type. The `null` *literal* was
+ always treated as a bottom type. Now the named class `Null` is too:
- Examples:
- ```
- Null foo() => null;
- int x = foo();
- String x = foo();
+ ```dart
+ const empty = <Null>[];
- List<Null> bar() => <Null>[];
- List<int> = bar();
- List<String> = bar();
+ String concatenate(List<String> parts) => parts.join();
+ int sum(List<int> numbers) => numbers.fold(0, (sum, n) => sum + n);
+
+ concatenate(empty); // OK.
+ sum(empty); // OK.
```
+ * Introduce `covariant` modifier on parameters. It indicates that the
+ parameter (and the corresponding parameter in any method that overrides it)
+ has looser override rules. In strong mode, these require a runtime type
+ check to maintain soundness, but enable an architectural pattern that is
+ useful in some code.
+
+ It lets you specialize a family of classes together, like so:
+
+ ```dart
+ abstract class Predator {
+ void chaseAndEat(covariant Prey p);
+ }
+
+ abstract class Prey {}
+
+ class Mouse extends Prey {}
+
+ class Seal extends Prey {}
+
+ class Cat extends Predator {
+ void chaseAndEat(Mouse m) => ...
+ }
+
+ class Orca extends Predator {
+ void chaseAndEat(Seal s) => ...
+ }
+ ```
+
+ This isn't statically safe, because you could do:
+
+ ```dart
+ Predator predator = new Cat(); // Upcast.
+ predator(new Seal()); // Cats can't eat seals!
+ ```
+
+ To preserve soundness in strong mode, in the body of a method that uses a
+ covariant override (here, `Cat.chaseAndEat()`), the compiler automatically
+ inserts a check that the parameter is of the expected type. So the compiler
+ gives you something like:
+
+ ```dart
+ class Cat extends Predator {
+ void chaseAndEat(o) {
+ var m = o as Mouse;
+ ...
+ }
+ }
+ ```
+
+ Spec mode allows this unsound behavior on all parameters, even though users
+ rarely rely on it. Strong mode disallowed it initially. Now, strong mode
+ lets you opt into this behavior in the places where you do want it by using
+ this modifier. Outside of strong mode, the modifier is ignored.
+
+ * Change instantiate-to-bounds rules for generic type parameters when running
+ in strong mode. If you leave off the type parameters from a generic type, we
+ need to decide what to fill them in with. Dart 1.0 says just use `dynamic`,
+ but that isn't sound:
+
+ ```dart
+ class Abser<T extends num> {
+ void absThis(T n) { n.abs(); }
+ }
+
+ var a = new Abser(); // Abser<dynamic>.
+ a.absThis("not a num");
+ ```
+
+ We want the body of `absThis()` to be able to safely assume `n` is at
+ least a `num` -- that's why there's a constraint on T, after all. Implicitly
+ using `dynamic` as the type parameter in this example breaks that.
+
+ Instead, strong mode uses the bound. In the above example, it fills it in
+ with `num`, and then the second line where a string is passed becomes a
+ static error.
+
+ However, there are some cases where it is hard to figure out what that
+ default bound should be:
+
+ ```dart
+ class RuhRoh<T extends Comparable<T>> {}
+ ```
+
+ Strong mode's initial behavior sometimes produced surprising, unintended
+ results. For 1.22, we take a simpler approach and then report an error if
+ a good default type argument can't be found.
+
+### Core libraries
+
+ * Define `FutureOr<T>` for code that works with either a future or an
+ immediate value of some type. For example, say you do a lot of text
+ manipulation, and you want a handy function to chain a bunch of them:
+
+ ```dart
+ typedef String StringSwizzler(String input);
+
+ String swizzle(String input, List<StringSwizzler> swizzlers) {
+ var result = input;
+ for (var swizzler in swizzlers) {
+ result = swizzler(result);
+ }
+
+ return result;
+ }
+ ```
+
+ This works fine:
+
+ ```dart
+ main() {
+ var result = swizzle("input", [
+ (s) => s.toUpperCase(),
+ (s) => () => s * 2)
+ ]);
+ print(result); // "INPUTINPUT".
+ }
+ ```
+
+ Later, you realize you'd also like to support swizzlers that are
+ asynchronous (maybe they look up synonyms for words online). You could make
+ your API strictly asynchronous, but then users of simple synchronous
+ swizzlers have to manually wrap the return value in a `Future.value()`.
+ Ideally, your `swizzle()` function would be "polymorphic over asynchrony".
+ It would allow both synchronous and asynchronous swizzlers. Because `await`
+ accepts immediate values, it is easy to implement this dynamically:
+
+ ```dart
+ Future<String> swizzle(String input, List<StringSwizzler> swizzlers) async {
+ var result = input;
+ for (var swizzler in swizzlers) {
+ result = await swizzler(result);
+ }
+
+ return result;
+ }
+
+ main() async {
+ var result = swizzle("input", [
+ (s) => s.toUpperCase(),
+ (s) => new Future.delayed(new Duration(milliseconds: 40), () => s * 2)
+ ]);
+ print(await result);
+ }
+ ```
+
+ What should the declared return type on StringSwizzler be? In the past, you
+ had to use `dynamic` or `Object`, but that doesn't tell the user much. Now,
+ you can do:
+
+ ```dart
+ typedef FutureOr<String> StringSwizzler(String input);
+ ```
+
+ Like the name implies, `FutureOr<String>` is a union type. It can be a
+ `String` or a `Future<String>`, but not anything else. In this case, that's
+ not super useful beyond just stating a more precise type for readers of the
+ code. It does give you a little better error checking in code that uses the
+ result of that.
+
+ `FutureOr<T>` becomes really important in *generic* methods like
+ `Future.then()`. In those cases, having the type system understand this
+ magical union type helps type inference figure out the type argument of
+ `then()` based on the closure you pass it.
+
+ Previously, strong mode had hard-coded rules for handling `Future.then()`
+ specifically. `FutureOr<T>` exposes that functionality so third-party APIs
+ can take advantage of it too.
+
### Tool changes
* Dart2Js
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 32d24bf..973ef70 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -8370,12 +8370,7 @@
}
type = typeSystem.instantiateType(type, typeArguments);
} else {
- List<bool> hasError = [false];
- type = typeSystem.instantiateToBounds(type, hasError: hasError);
- if (hasError[0]) {
- errorListener.onError(new AnalysisError(source, node.offset,
- node.length, StrongModeCode.NO_DEFAULT_BOUNDS));
- }
+ type = typeSystem.instantiateToBounds(type);
}
typeName.staticType = type;
node.type = type;
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index b951f4f..bcf8ad5 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -58,14 +58,14 @@
{this.implicitCasts: true,
this.nonnullableTypes: AnalysisOptionsImpl.NONNULLABLE_TYPES});
+ @override
+ bool get isStrong => true;
+
bool anyParameterType(FunctionType ft, bool predicate(DartType t)) {
return ft.parameters.any((p) => predicate(p.type));
}
@override
- bool get isStrong => true;
-
- @override
FunctionType functionTypeToConcreteType(FunctionType t) {
// TODO(jmesserly): should we use a real "fuzzyArrow" bit on the function
// type? That would allow us to implement this in the subtype relation.
@@ -371,13 +371,26 @@
}
// If we stopped making progress, and not all types are ground,
- // then the whole type is malbounded and an error should be reported.
+ // then the whole type is malbounded and an error should be reported
+ // if errors are requested, and a partially completed type should
+ // be returned.
if (partials.isNotEmpty) {
if (hasError != null) {
hasError[0] = true;
}
- return instantiateType(
- type, new List<DartType>.filled(count, DynamicTypeImpl.instance));
+ var domain = defaults.keys.toList();
+ var range = defaults.values.toList();
+ // Build a substitution Phi mapping each uncompleted type variable to
+ // dynamic, and each completed type variable to its default.
+ for (TypeParameterType parameter in partials.keys) {
+ domain.add(parameter);
+ range.add(DynamicTypeImpl.instance);
+ }
+ // Set the default for an uncompleted type variable (T extends B)
+ // to be Phi(B)
+ for (TypeParameterType parameter in partials.keys) {
+ defaults[parameter] = partials[parameter].substitute2(range, domain);
+ }
}
List<DartType> orderedArguments =
diff --git a/pkg/analyzer/test/generated/strong_mode_test.dart b/pkg/analyzer/test/generated/strong_mode_test.dart
index 364bcf5..bc5bd88 100644
--- a/pkg/analyzer/test/generated/strong_mode_test.dart
+++ b/pkg/analyzer/test/generated/strong_mode_test.dart
@@ -955,6 +955,51 @@
expect(functionReturnValue(4).staticType, typeProvider.stringType);
}
+ test_futureOr_assignFromFuture() async {
+ // Test a Future<T> can be assigned to FutureOr<T>.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ FutureOr<T> mk<T>(Future<T> x) => x;
+ test() => mk(new Future<int>.value(42));
+ ''');
+ _isFutureOrOfInt(invoke.staticType);
+ }
+
+ test_futureOr_assignFromValue() async {
+ // Test a T can be assigned to FutureOr<T>.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ FutureOr<T> mk<T>(T x) => x;
+ test() => mk(42);
+ ''');
+ _isFutureOrOfInt(invoke.staticType);
+ }
+
+ test_futureOr_asyncExpressionBody() async {
+ // A FutureOr<T> can be used as the expression body for an async function
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async => x;
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
+ test_futureOr_asyncReturn() async {
+ // A FutureOr<T> can be used as the return value for an async function
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async { return x; }
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
+ test_futureOr_await() async {
+ // Test a FutureOr<T> can be awaited.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async => await x;
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
test_futureOr_downwards1() async {
// Test that downwards inference interacts correctly with FutureOr
// parameters.
@@ -1090,51 +1135,6 @@
_isFutureOf([_isObject])(invoke.staticType);
}
- test_futureOr_assignFromValue() async {
- // Test a T can be assigned to FutureOr<T>.
- MethodInvocation invoke = await _testFutureOr(r'''
- FutureOr<T> mk<T>(T x) => x;
- test() => mk(42);
- ''');
- _isFutureOrOfInt(invoke.staticType);
- }
-
- test_futureOr_assignFromFuture() async {
- // Test a Future<T> can be assigned to FutureOr<T>.
- MethodInvocation invoke = await _testFutureOr(r'''
- FutureOr<T> mk<T>(Future<T> x) => x;
- test() => mk(new Future<int>.value(42));
- ''');
- _isFutureOrOfInt(invoke.staticType);
- }
-
- test_futureOr_await() async {
- // Test a FutureOr<T> can be awaited.
- MethodInvocation invoke = await _testFutureOr(r'''
- Future<T> mk<T>(FutureOr<T> x) async => await x;
- test() => mk(42);
- ''');
- _isFutureOfInt(invoke.staticType);
- }
-
- test_futureOr_asyncExpressionBody() async {
- // A FutureOr<T> can be used as the expression body for an async function
- MethodInvocation invoke = await _testFutureOr(r'''
- Future<T> mk<T>(FutureOr<T> x) async => x;
- test() => mk(42);
- ''');
- _isFutureOfInt(invoke.staticType);
- }
-
- test_futureOr_asyncReturn() async {
- // A FutureOr<T> can be used as the return value for an async function
- MethodInvocation invoke = await _testFutureOr(r'''
- Future<T> mk<T>(FutureOr<T> x) async { return x; }
- test() => mk(42);
- ''');
- _isFutureOfInt(invoke.staticType);
- }
-
test_inference_hints() async {
Source source = addSource(r'''
void main () {
@@ -2738,14 +2738,41 @@
expectIdentifierType('cc', "C<int, B<int>, A<dynamic>>");
}
+ @failingTest
+ test_instantiateToBounds_class_error_extension_malbounded() async {
+ // Test that superclasses are strictly checked for malbounded default
+ // types
+ String code = r'''
+class C<T0 extends List<T1>, T1 extends List<T0>> {}
+class D extends C {}
+''';
+ await resolveTestUnit(code, noErrors: false);
+ assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
+ }
+
+ @failingTest
+ test_instantiateToBounds_class_error_instantiation_malbounded() async {
+ // Test that instance creations are strictly checked for malbounded default
+ // types
+ String code = r'''
+class C<T0 extends List<T1>, T1 extends List<T0>> {}
+void test() {
+ var c = new C();
+}
+''';
+ await resolveTestUnit(code, noErrors: false);
+ assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
+ expectIdentifierType('c;', 'C<List<dynamic>, List<dynamic>>');
+ }
+
test_instantiateToBounds_class_error_recursion() async {
String code = r'''
class C<T0 extends List<T1>, T1 extends List<T0>> {}
C c;
''';
await resolveTestUnit(code, noErrors: false);
- assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
- expectIdentifierType('c;', 'C<dynamic, dynamic>');
+ assertNoErrors(testSource);
+ expectIdentifierType('c;', 'C<List<dynamic>, List<dynamic>>');
}
test_instantiateToBounds_class_error_recursion_self() async {
@@ -2754,8 +2781,8 @@
C c;
''';
await resolveTestUnit(code, noErrors: false);
- assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
- expectIdentifierType('c;', 'C<dynamic>');
+ assertNoErrors(testSource);
+ expectIdentifierType('c;', 'C<C<dynamic>>');
}
test_instantiateToBounds_class_error_recursion_self2() async {
@@ -2765,8 +2792,8 @@
C c;
''';
await resolveTestUnit(code, noErrors: false);
- assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
- expectIdentifierType('c;', 'C<dynamic>');
+ assertNoErrors(testSource);
+ expectIdentifierType('c;', 'C<A<dynamic>>');
}
test_instantiateToBounds_class_error_typedef() async {
@@ -2776,8 +2803,8 @@
C c;
''';
await resolveTestUnit(code, noErrors: false);
- assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
- expectIdentifierType('c;', 'C<dynamic>');
+ assertNoErrors(testSource);
+ expectIdentifierType('c;', 'C<(dynamic) → dynamic>');
}
test_instantiateToBounds_class_ok_implicitDynamic_multi() async {
@@ -2852,6 +2879,22 @@
expectIdentifierType('d;', 'D<A<dynamic>>');
}
+ @failingTest
+ test_instantiateToBounds_generic_function_error_malbounded() async {
+ // Test that generic methods are strictly checked for malbounded default
+ // types
+ String code = r'''
+T0 f<T0 extends List<T1>, T1 extends List<T0>>() {}
+void g() {
+ var c = f();
+ return;
+}
+''';
+ await resolveTestUnit(code, noErrors: false);
+ assertErrors(testSource, [StrongModeCode.NO_DEFAULT_BOUNDS]);
+ expectIdentifierType('c;', 'List<dynamic>');
+ }
+
test_instantiateToBounds_method_ok_referenceOther_before() async {
String code = r'''
class C<T> {
diff --git a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
index be79415..e185566 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
@@ -720,7 +720,6 @@
super.createOptions()..strongMode = true;
@override
- @failingTest
test_instantiateToBounds_boundRefersToItself() {
super.test_instantiateToBounds_boundRefersToItself();
}
diff --git a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
index 541e75f..6077b7c 100644
--- a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
+++ b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
@@ -644,7 +644,6 @@
''');
}
- @failingTest
void test_constructors_inferenceFBounded() {
// Regression for https://github.com/dart-lang/sdk/issues/26990
var unit = checkFile('''
diff --git a/pkg/dev_compiler/test/not_yet_strong_tests.dart b/pkg/dev_compiler/test/not_yet_strong_tests.dart
index 0d67109..9ca06f3 100644
--- a/pkg/dev_compiler/test/not_yet_strong_tests.dart
+++ b/pkg/dev_compiler/test/not_yet_strong_tests.dart
@@ -930,7 +930,6 @@
'language/is_not_class2_test',
'language/is_object_test',
'language/isnot_malformed_type_test',
- 'language/issue_23914_test', // issue 28478
'language/issue11724_test',
'language/issue11793_test',
'language/issue13474_test',
@@ -1448,8 +1447,6 @@
'language/proxy_test_02_multi',
'language/proxy_test_05_multi',
'language/proxy_test_06_multi',
- 'language/recursive_generic_test', // issue 28478
- 'language/recursive_inheritance_test', // issue 28478
'language/redirecting_constructor_initializer_test',
'language/redirecting_factory_default_values_test_01_multi',
'language/redirecting_factory_default_values_test_02_multi',
@@ -2093,7 +2090,6 @@
'corelib/symbol_test_none_multi',
'corelib/uri_path_test',
'corelib/uri_query_test',
- 'lib/collection/linked_list_test', // issue 28478
'lib/convert/chunked_conversion1_test',
'lib/math/min_max_test',
'lib/typed_data/float32x4_test',
diff --git a/tools/VERSION b/tools/VERSION
index 4eb30ea..e45ee0b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
MINOR 22
PATCH 0
PRERELEASE 10
-PRERELEASE_PATCH 2
+PRERELEASE_PATCH 3