Skip to content

Commit ab30452

Browse files
nshahancommit-bot@chromium.org
authored andcommitted
[dartdevc] Add normalization of FutureOr types
These changes are also being back ported into the existing SDK with minimal differences that will be gone when we unfork the dart:_runtime library. In the current back port: * FutureOr<Null>? is normalized to Future<Null>* instead of Future<Null>?. * There is no runtime normalization of FutureOr<T?>?. This should have no effect since nullable types can not yet appear. Fixes: #40611, #40720 Change-Id: Ib3ddffbf3778f0f571cf95f58b6142d3ee0bf59b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136160 Reviewed-by: Sigmund Cherem <[email protected]> Reviewed-by: Mark Zhou <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]>
1 parent 2864742 commit ab30452

File tree

10 files changed

+214
-80
lines changed

10 files changed

+214
-80
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
649649
js.call('(#) => { #; }', [jsFormals, deferredBaseClass]),
650650
];
651651

652-
var genericCall = runtimeCall('generic(#)', [genericArgs]);
652+
// FutureOr types have a runtime normalization step that will call
653+
// generic() as needed.
654+
var genericCall = c == _coreTypes.futureOrClass
655+
? runtimeCall('normalizeFutureOr(#)', [genericArgs])
656+
: runtimeCall('generic(#)', [genericArgs]);
653657

654658
var genericName = _emitTopLevelNameNoInterop(c, suffix: '\$');
655659
return js.statement('{ # = #; # = #(); }',
@@ -2617,10 +2621,53 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
26172621

26182622
@override
26192623
js_ast.Expression visitNeverType(NeverType type) =>
2620-
_emitNullabilityWrapper(runtimeCall('Never'), type.nullability);
2624+
type.nullability == Nullability.nullable
2625+
? visitInterfaceType(_coreTypes.nullType)
2626+
: _emitNullabilityWrapper(runtimeCall('Never'), type.nullability);
2627+
2628+
/// Normalizes `FutureOr` types and emits the normalized version.
2629+
js_ast.Expression _normalizeFutureOr(InterfaceType futureOr) {
2630+
var typeArgument = futureOr.typeArguments.single;
2631+
if (typeArgument is DynamicType) {
2632+
// FutureOr<dynamic> --> dynamic
2633+
return visitDynamicType(typeArgument);
2634+
}
2635+
if (typeArgument is VoidType) {
2636+
// FutureOr<void> --> void
2637+
return visitVoidType(typeArgument);
2638+
}
2639+
2640+
var normalizedType = futureOr;
2641+
if (typeArgument is InterfaceType &&
2642+
typeArgument.classNode == _coreTypes.objectClass) {
2643+
// Normalize FutureOr of Object, Object?, Object*.
2644+
normalizedType = typeArgument;
2645+
} else if (typeArgument is NeverType) {
2646+
// FutureOr<Never> --> Future<Never>
2647+
normalizedType = InterfaceType(
2648+
_coreTypes.futureClass, futureOr.nullability, [typeArgument]);
2649+
} else if (typeArgument is InterfaceType &&
2650+
typeArgument.classNode == _coreTypes.nullClass) {
2651+
// TODO(40266) Remove this workaround when we unfork dart:_runtime
2652+
var nullability = _options.enableNullSafety
2653+
? Nullability.nullable
2654+
: currentLibrary.nullable;
2655+
// FutureOr<Null> --> Future<Null>?
2656+
normalizedType =
2657+
InterfaceType(_coreTypes.futureClass, nullability, [typeArgument]);
2658+
} else if (futureOr.nullability == Nullability.nullable &&
2659+
typeArgument.nullability == Nullability.nullable) {
2660+
// FutureOr<T?>? --> FutureOr<T?>
2661+
normalizedType = futureOr.withNullability(Nullability.nonNullable);
2662+
}
2663+
return _emitInterfaceType(normalizedType);
2664+
}
2665+
26212666
@override
26222667
js_ast.Expression visitInterfaceType(InterfaceType type) =>
2623-
_emitInterfaceType(type);
2668+
type.classNode == _coreTypes.futureOrClass
2669+
? _normalizeFutureOr(type)
2670+
: _emitInterfaceType(type);
26242671

26252672
/// Emits the representation of [type].
26262673
///

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,39 @@ final _originalDeclaration = JS('', 'Symbol("originalDeclaration")');
124124

125125
final mixinNew = JS('', 'Symbol("dart.mixinNew")');
126126

127+
/// Normalizes `FutureOr` types when they are constructed at runtime.
128+
///
129+
/// This normalization should mirror the normalization performed at compile time
130+
/// in the method named `_normalizeFutureOr()`.
131+
normalizeFutureOr(typeConstructor, setBaseClass) {
132+
// The canonical version of the generic FutureOr type constructor.
133+
var genericFutureOrType =
134+
JS('!', '#', generic(typeConstructor, setBaseClass));
135+
136+
normalize(typeArg) {
137+
// Normalize raw FutureOr --> dynamic
138+
if (JS<bool>('!', '# == void 0', typeArg)) return _dynamic;
139+
140+
// FutureOr<dynamic|void|Object> --> dynamic|void|Object
141+
if (_isTop(typeArg)) return typeArg;
142+
143+
// FutureOr<Null> --> Future<Null>
144+
// NOTE: These are a legacy FutureOr and Future that can be null already.
145+
if (typeArg == unwrapType(Null)) {
146+
return JS('!', '#(#)', getGenericClass(Future), typeArg);
147+
}
148+
// Otherwise, create the FutureOr<T> type as a normal generic type.
149+
var genericType = JS('!', '#(#)', genericFutureOrType, typeArg);
150+
// Overwrite the original declaration so that it correctly points back to
151+
// this method. This ensures that the we can test a type value returned here
152+
// as a FutureOr because it is equal to 'async.FutureOr` (in the JS).
153+
JS('!', '#[#] = #', genericType, _originalDeclaration, normalize);
154+
return genericType;
155+
}
156+
157+
return normalize;
158+
}
159+
127160
/// Memoize a generic type constructor function.
128161
generic(typeConstructor, setBaseClass) => JS('', '''(() => {
129162
let length = $typeConstructor.length;

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -858,9 +858,6 @@ bool _isBottom(type) => JS('!', '# == # || # == #', type, bottom, type, Null);
858858

859859
@notNull
860860
bool _isTop(type) {
861-
if (_isFutureOr(type)) {
862-
return _isTop(JS('', '#[0]', getGenericArgs(type)));
863-
}
864861
return JS('!', '# == # || # == # || # == #', type, Object, type, dynamic,
865862
type, void_);
866863
}

sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,49 @@ final _originalDeclaration = JS('', 'Symbol("originalDeclaration")');
124124

125125
final mixinNew = JS('', 'Symbol("dart.mixinNew")');
126126

127+
/// Normalizes `FutureOr` types when they are constructed at runtime.
128+
///
129+
/// This normalization should mirror the normalization performed at compile time
130+
/// in the method named `_normalizeFutureOr()`.
131+
///
132+
/// **NOTE** Normalization of FutureOr<T?>? --> FutureOr<T?> is handled in
133+
/// [nullable].
134+
normalizeFutureOr(typeConstructor, setBaseClass) {
135+
// The canonical version of the generic FutureOr type constructor.
136+
var genericFutureOrType =
137+
JS('!', '#', generic(typeConstructor, setBaseClass));
138+
139+
normalize(typeArg) {
140+
// Normalize raw FutureOr --> dynamic
141+
if (JS<bool>('!', '# == void 0', typeArg)) return _dynamic;
142+
143+
// FutureOr<dynamic|void|Object?|Object*> --> dynamic|void|Object?|Object*
144+
if (_isTop(typeArg) ||
145+
(_isLegacy(typeArg) &&
146+
JS<bool>('!', '#.type === #', typeArg, Object))) {
147+
return typeArg;
148+
}
149+
150+
// FutureOr<Never> --> Future<Never>
151+
if (typeArg == never_) {
152+
return JS('!', '#(#)', getGenericClass(Future), typeArg);
153+
}
154+
// FutureOr<Null> --> Future<Null>?
155+
if (typeArg == unwrapType(Null)) {
156+
return nullable(JS('!', '#(#)', getGenericClass(Future), typeArg));
157+
}
158+
// Otherwise, create the FutureOr<T> type as a normal generic type.
159+
var genericType = JS('!', '#(#)', genericFutureOrType, typeArg);
160+
// Overwrite the original declaration so that it correctly points back to
161+
// this method. This ensures that the we can test a type value returned here
162+
// as a FutureOr because it is equal to 'async.FutureOr` (in the JS).
163+
JS('!', '#[#] = #', genericType, _originalDeclaration, normalize);
164+
return genericType;
165+
}
166+
167+
return normalize;
168+
}
169+
127170
/// Memoize a generic type constructor function.
128171
generic(typeConstructor, setBaseClass) => JS('', '''(() => {
129172
let length = $typeConstructor.length;

sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,19 @@ final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
269269
/// Returns a nullable (question, ?) version of [type].
270270
///
271271
/// The resulting type returned in a normalized form based on the rules from the
272-
/// normalization doc: https://github.com/dart-lang/language/pull/456
273-
// TODO(nshahan): Update after the normalization doc PR lands.
272+
/// normalization doc:
273+
/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
274274
@notNull
275275
Object nullable(type) {
276-
if (_isNullable(type) || _isTop(type) || _isNullType(type)) return type;
276+
if (_isNullable(type) ||
277+
_isTop(type) ||
278+
_isNullType(type) ||
279+
// Normalize FutureOr<T?>? --> FutureOr<T?>
280+
// All other runtime FutureOr normalization is in `normalizeFutureOr()`.
281+
(_isFutureOr(type)) &&
282+
_isNullable(JS<Object>('!', '#[0]', getGenericArgs(type)))) {
283+
return type;
284+
}
277285
if (type == never_) return unwrapType(Null);
278286
if (_isLegacy(type)) type = JS<Object>('', '#.type', type);
279287

@@ -290,8 +298,8 @@ Object nullable(type) {
290298
/// Returns a legacy (star, *) version of [type].
291299
///
292300
/// The resulting type returned in a normalized form based on the rules from the
293-
/// normalization doc: https://github.com/dart-lang/language/pull/456
294-
// TODO(nshahan): Update after the normalization doc PR lands.
301+
/// normalization doc:
302+
/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
295303
@notNull
296304
Object legacy(type) {
297305
if (_isLegacy(type) || _isNullable(type) || _isTop(type) || _isNullType(type))
@@ -1223,15 +1231,11 @@ final _subtypeCache = JS('', 'Symbol("_subtypeCache")');
12231231
bool _isBottom(type, strictMode) => JS(
12241232
'!', '# === # || (!# && # === #)', type, never_, strictMode, type, bottom);
12251233

1226-
// TODO(nshahan): Add support for strict/weak mode.
12271234
@notNull
12281235
bool _isTop(type) {
1229-
// TODO(nshahan): Handle Object* in a way that ensures
1230-
// instanceOf(null, Object*) returns true.
1231-
if (_isFutureOr(type)) return _isTop(JS('', '#[0]', getGenericArgs(type)));
1232-
if (_isNullable(type)) return (JS('!', '# == #', type.type, Object));
1236+
if (_isNullable(type)) return JS('!', '# === #', type.type, Object);
12331237

1234-
return JS('!', '# == # || # == #', type, dynamic, type, void_);
1238+
return JS('!', '# === # || # === #', type, dynamic, type, void_);
12351239
}
12361240

12371241
/// Returns `true` if [type] represents a nullable (question, ?) type.

tests/compiler/dartdevc_native/nnbd_strong_subtype_test.dart

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import 'dart:async';
88

9-
import 'runtime_utils.dart' show voidType;
9+
import 'runtime_utils.dart' show futureOrOf, voidType;
1010
import 'runtime_utils_nnbd.dart';
1111

1212
class A {}
@@ -103,31 +103,30 @@ void main() {
103103

104104
// Futures.
105105
// Null <: FutureOr<Object?>
106-
checkProperSubtype(Null, generic1(FutureOr, nullable(Object)));
106+
checkProperSubtype(Null, futureOrOf(nullable(Object)));
107107
// Object <: FutureOr<Object?>
108-
checkProperSubtype(Object, generic1(FutureOr, nullable(Object)));
108+
checkProperSubtype(Object, futureOrOf(nullable(Object)));
109109
// Object? <: FutureOr<Object?>
110-
checkSubtype(nullable(Object), generic1(FutureOr, nullable(Object)));
110+
checkSubtype(nullable(Object), futureOrOf(nullable(Object)));
111111
// Object <: FutureOr<Object>
112-
checkSubtype(Object, generic1(FutureOr, Object));
112+
checkSubtype(Object, futureOrOf(Object));
113113
// FutureOr<Object> <: Object
114-
checkSubtype(generic1(FutureOr, Object), Object);
114+
checkSubtype(futureOrOf(Object), Object);
115115
// Object <: FutureOr<dynamic>
116-
checkProperSubtype(Object, generic1(FutureOr, dynamic));
116+
checkProperSubtype(Object, futureOrOf(dynamic));
117117
// Object <: FutureOr<void>
118-
checkProperSubtype(Object, generic1(FutureOr, voidType));
118+
checkProperSubtype(Object, futureOrOf(voidType));
119119
// Future<Object> <: FutureOr<Object?>
120-
checkProperSubtype(
121-
generic1(Future, Object), generic1(FutureOr, nullable(Object)));
120+
checkProperSubtype(generic1(Future, Object), futureOrOf(nullable(Object)));
122121
// Future<Object?> <: FutureOr<Object?>
123122
checkProperSubtype(
124-
generic1(Future, nullable(Object)), generic1(FutureOr, nullable(Object)));
123+
generic1(Future, nullable(Object)), futureOrOf(nullable(Object)));
125124
// FutureOr<Never> <: Future<Never>
126-
checkSubtype(generic1(FutureOr, neverType), generic1(Future, neverType));
125+
checkSubtype(futureOrOf(neverType), generic1(Future, neverType));
127126
// Future<B> <: FutureOr<A>
128-
checkProperSubtype(generic1(Future, B), generic1(FutureOr, A));
127+
checkProperSubtype(generic1(Future, B), futureOrOf(A));
129128
// B <: <: FutureOr<A>
130-
checkProperSubtype(B, generic1(FutureOr, A));
129+
checkProperSubtype(B, futureOrOf(A));
131130
// Future<B> <: Future<A>
132131
checkProperSubtype(generic1(Future, B), generic1(Future, A));
133132

@@ -207,16 +206,15 @@ void main() {
207206
// Bound is a FutureOr.
208207
// <T extends FutureOr<B>> void -> void <:
209208
// <T extends FutureOr<B>> void -> void
210-
checkSubtype(genericFunction(generic1(FutureOr, B)),
211-
genericFunction(generic1(FutureOr, B)));
209+
checkSubtype(genericFunction(futureOrOf(B)), genericFunction(futureOrOf(B)));
212210

213211
// <T extends FutureOr<B>> A -> T <: <T extends FutureOr<B>> B -> T
214-
checkProperSubtype(functionGenericReturn(generic1(FutureOr, B), A),
215-
functionGenericReturn(generic1(FutureOr, B), B));
212+
checkProperSubtype(functionGenericReturn(futureOrOf(B), A),
213+
functionGenericReturn(futureOrOf(B), B));
216214

217215
// <T extends FutureOr<B>> T -> B <: <T extends FutureOr<B>> T -> A
218-
checkProperSubtype(functionGenericArg(generic1(FutureOr, B), B),
219-
functionGenericArg(generic1(FutureOr, B), A));
216+
checkProperSubtype(functionGenericArg(futureOrOf(B), B),
217+
functionGenericArg(futureOrOf(B), A));
220218

221219
// Generics.
222220
// D <: D<B>
@@ -279,17 +277,17 @@ void main() {
279277
// Null <\: A
280278
checkSubtypeFailure(Null, A);
281279
// Null <\: FutureOr<A>
282-
checkSubtypeFailure(Null, generic1(FutureOr, A));
280+
checkSubtypeFailure(Null, futureOrOf(A));
283281
// Null <\: Future<A>
284282
checkSubtypeFailure(Null, generic1(Future, A));
285283
// FutureOr<Null> <\: Future<Null>
286-
checkSubtypeFailure(generic1(FutureOr, Null), generic1(Future, Null));
284+
checkSubtypeFailure(futureOrOf(Null), generic1(Future, Null));
287285
// Null <\: Future<A?>
288286
checkSubtypeFailure(Null, generic1(Future, nullable(A)));
289287
// FutureOr<Object?> <\: Object
290-
checkSubtypeFailure(generic1(FutureOr, nullable(Object)), Object);
288+
checkSubtypeFailure(futureOrOf(nullable(Object)), Object);
291289
// FutureOr<dynamic> <\: Object
292-
checkSubtypeFailure(generic1(FutureOr, dynamic), Object);
290+
checkSubtypeFailure(futureOrOf(dynamic), Object);
293291
// FutureOr<void> <\: Object
294-
checkSubtypeFailure(generic1(FutureOr, voidType), Object);
292+
checkSubtypeFailure(futureOrOf(voidType), Object);
295293
}

0 commit comments

Comments
 (0)