diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart index 04deb4c482a..b2491402c8d 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart @@ -32,6 +32,7 @@ import '../js_ast/js_ast.dart' show ModuleItem, js; import '../js_ast/source_map_printer.dart' show NodeEnd, NodeSpan, HoverComment, continueSourceMap; import 'constants.dart'; +import 'future_or_normalizer.dart'; import 'js_interop.dart'; import 'js_typerep.dart'; import 'kernel_helpers.dart'; @@ -939,7 +940,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor } return _emitInterfaceType(t, emitNullability: emitNullability); } else if (t is FutureOrType) { - var normalizedType = _normalizeFutureOr(t); + var normalizedType = normalizeFutureOr(t, _coreTypes); if (normalizedType is FutureOrType) { _declareBeforeUse(_coreTypes.deprecatedFutureOrClass); var typeRep = _emitFutureOrTypeWithArgument( @@ -2911,64 +2912,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor ? visitNullType(const NullType()) : _emitNullabilityWrapper(runtimeCall('Never'), type.nullability); - /// Normalizes `FutureOr` types. - /// - /// Any changes to the normalization logic here should be mirrored in the - /// classes.dart runtime library method named `normalizeFutureOr`. - DartType _normalizeFutureOr(FutureOrType futureOr) { - var typeArgument = futureOr.typeArgument; - if (typeArgument is DynamicType) { - // FutureOr --> dynamic - return typeArgument; - } - if (typeArgument is VoidType) { - // FutureOr --> void - return typeArgument; - } - - if (typeArgument is InterfaceType && - typeArgument.classNode == _coreTypes.objectClass) { - // Normalize FutureOr of Object, Object?, Object*. - var nullable = futureOr.nullability == Nullability.nullable || - typeArgument.nullability == Nullability.nullable; - var legacy = futureOr.nullability == Nullability.legacy || - typeArgument.nullability == Nullability.legacy; - var nullability = nullable - ? Nullability.nullable - : legacy - ? Nullability.legacy - : Nullability.nonNullable; - return typeArgument.withDeclaredNullability(nullability); - } else if (typeArgument is NeverType) { - // FutureOr --> Future - return InterfaceType( - _coreTypes.futureClass, futureOr.nullability, [typeArgument]); - } else if (typeArgument is NullType) { - // FutureOr --> Future? - return InterfaceType( - _coreTypes.futureClass, Nullability.nullable, [typeArgument]); - } else if (futureOr.declaredNullability == Nullability.nullable && - typeArgument.nullability == Nullability.nullable) { - // FutureOr? --> FutureOr - return futureOr.withDeclaredNullability(Nullability.nonNullable); - } - // The following is not part of the normalization spec but this is a - // convenient place to perform this change of nullability consistently. This - // only applies at compile-time and is not needed in the runtime version of - // the FutureOr normalization. - // FutureOr% --> FutureOr - // - // If the type argument has undetermined nullability the CFE propagates - // it to the FutureOr type as well. In this case we can represent the - // FutureOr type without any nullability wrappers and rely on the runtime to - // handle the nullability of the instantiated type appropriately. - if (futureOr.nullability == Nullability.undetermined && - typeArgument.nullability == Nullability.undetermined) { - return futureOr.withDeclaredNullability(Nullability.nonNullable); - } - return futureOr; - } - @override js_ast.Expression visitInterfaceType(InterfaceType type) => _emitInterfaceType(type); @@ -2979,7 +2922,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor @override js_ast.Expression visitFutureOrType(FutureOrType type) { - var normalizedType = _normalizeFutureOr(type); + var normalizedType = normalizeFutureOr(type, _coreTypes); return normalizedType is FutureOrType ? _emitFutureOrType(normalizedType) : normalizedType.accept(this); diff --git a/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart b/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart new file mode 100644 index 00000000000..492167bf4e6 --- /dev/null +++ b/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart @@ -0,0 +1,91 @@ +// Copyright (c) 2022, 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. + +import 'package:kernel/ast.dart'; +import 'package:kernel/core_types.dart'; +import 'package:kernel/kernel.dart'; +import 'package:kernel/src/replacement_visitor.dart'; + +/// Normalizes all `FutureOr` types found in [type]. +DartType normalizeFutureOr(DartType type, CoreTypes coreTypes) => + type.accept1(_FutureOrNormalizer.instance(coreTypes), Variance.unrelated) ?? + type; + +/// Visit methods returns a normalized version of `FutureOr` types or `null` if +/// no normalization was applied. +/// +/// The `variance` parameters in the visit methods are unused in this type +/// replacement. +/// +/// `FutureOr` types are normalized per the spec: +/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md +/// +/// Any changes to the normalization logic here should be mirrored in the +/// classes.dart runtime library method named `normalizeFutureOr`. +class _FutureOrNormalizer extends ReplacementVisitor { + final CoreTypes _coreTypes; + + static _FutureOrNormalizer? _instance; + + _FutureOrNormalizer._(this._coreTypes); + + factory _FutureOrNormalizer.instance(CoreTypes coreTypes) => + _instance ?? (_instance = _FutureOrNormalizer._(coreTypes)); + + @override + DartType? visitFutureOrType(FutureOrType futureOr, int variance) { + var normalizedTypeArg = futureOr.typeArgument.accept1(this, variance); + var typeArgument = normalizedTypeArg ?? futureOr.typeArgument; + if (typeArgument is DynamicType) { + // FutureOr --> dynamic + return typeArgument; + } + if (typeArgument is VoidType) { + // FutureOr --> void + return typeArgument; + } + + if (typeArgument is InterfaceType && + typeArgument.classNode == _coreTypes.objectClass) { + // Normalize FutureOr of Object, Object?, Object*. + var nullable = futureOr.nullability == Nullability.nullable || + typeArgument.nullability == Nullability.nullable; + var legacy = futureOr.nullability == Nullability.legacy || + typeArgument.nullability == Nullability.legacy; + var nullability = nullable + ? Nullability.nullable + : legacy + ? Nullability.legacy + : Nullability.nonNullable; + return typeArgument.withDeclaredNullability(nullability); + } else if (typeArgument is NeverType) { + // FutureOr --> Future + return InterfaceType( + _coreTypes.futureClass, futureOr.nullability, [typeArgument]); + } else if (typeArgument is NullType) { + // FutureOr --> Future? + return InterfaceType( + _coreTypes.futureClass, Nullability.nullable, [typeArgument]); + } else if (futureOr.declaredNullability == Nullability.nullable && + typeArgument.nullability == Nullability.nullable) { + // FutureOr? --> FutureOr + return futureOr.withDeclaredNullability(Nullability.nonNullable); + } + // The following is not part of the normalization spec but this is a + // convenient place to perform this change of nullability consistently. This + // only applies at compile-time and is not needed in the runtime version of + // the FutureOr normalization. + // FutureOr% --> FutureOr + // + // If the type argument has undetermined nullability the CFE propagates + // it to the FutureOr type as well. In this case we can represent the + // FutureOr type without any nullability wrappers and rely on the runtime to + // handle the nullability of the instantiated type appropriately. + if (futureOr.declaredNullability == Nullability.undetermined && + typeArgument.declaredNullability == Nullability.undetermined) { + return futureOr.withDeclaredNullability(Nullability.nonNullable); + } + return null; + } +}