| // Copyright (c) 2019, 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.md file. |
| |
| import '../ast.dart'; |
| import 'replacement_visitor.dart'; |
| |
| /// Returns legacy erasure of [type], that is, the type in which all nnbd |
| /// nullabilities have been replaced with legacy nullability, and all required |
| /// named parameters are not required. |
| DartType legacyErasure(DartType type) { |
| return rawLegacyErasure(type) ?? type; |
| } |
| |
| /// Returns legacy erasure of [type], that is, the type in which all nnbd |
| /// nullabilities have been replaced with legacy nullability, and all required |
| /// named parameters are not required. |
| /// |
| /// Returns `null` if the type wasn't changed. |
| DartType? rawLegacyErasure(DartType type) { |
| return type.accept1(const _LegacyErasure(), Variance.covariant); |
| } |
| |
| /// Returns legacy erasure of [supertype], that is, the type in which all nnbd |
| /// nullabilities have been replaced with legacy nullability, and all required |
| /// named parameters are not required. |
| Supertype legacyErasureSupertype(Supertype supertype) { |
| if (supertype.typeArguments.isEmpty) { |
| return supertype; |
| } |
| List<DartType>? newTypeArguments; |
| for (int i = 0; i < supertype.typeArguments.length; i++) { |
| DartType typeArgument = supertype.typeArguments[i]; |
| DartType? newTypeArgument = |
| typeArgument.accept1(const _LegacyErasure(), Variance.covariant); |
| if (newTypeArgument != null) { |
| newTypeArguments ??= supertype.typeArguments.toList(growable: false); |
| newTypeArguments[i] = newTypeArgument; |
| } |
| } |
| if (newTypeArguments != null) { |
| return new Supertype(supertype.classNode, newTypeArguments); |
| } |
| return supertype; |
| } |
| |
| /// Visitor that replaces all nnbd nullabilities with legacy nullabilities and |
| /// all required named parameters with optional named parameters. |
| /// |
| /// The visitor returns `null` if the type wasn't changed. |
| class _LegacyErasure extends ReplacementVisitor { |
| const _LegacyErasure(); |
| |
| @override |
| Nullability? visitNullability(DartType node) { |
| if (node.declaredNullability != Nullability.legacy) { |
| return Nullability.legacy; |
| } |
| return null; |
| } |
| |
| @override |
| NamedType? createNamedType(NamedType node, DartType? newType) { |
| if (node.isRequired || newType != null) { |
| return new NamedType(node.name, newType ?? node.type, isRequired: false); |
| } |
| return null; |
| } |
| |
| @override |
| DartType visitNeverType(NeverType node, int variance) => const NullType(); |
| } |
| |
| /// Returns `true` if a member declared in [declaringClass] inherited or |
| /// mixed into [enclosingClass] needs legacy erasure to compute its inherited |
| /// type. |
| /// |
| /// For instance: |
| /// |
| /// // Opt in: |
| /// class Super { |
| /// int extendedMethod(int i, {required int j}) => i; |
| /// } |
| /// class Mixin { |
| /// int mixedInMethod(int i, {required int j}) => i; |
| /// } |
| /// // Opt out: |
| /// class Legacy extends Super with Mixin {} |
| /// // Opt in: |
| /// class Class extends Legacy { |
| /// test() { |
| /// // Ok to call `Legacy.extendedMethod` since its type is |
| /// // `int* Function(int*, {int* j})`. |
| /// super.extendedMethod(null); |
| /// // Ok to call `Legacy.mixedInMethod` since its type is |
| /// // `int* Function(int*, {int* j})`. |
| /// super.mixedInMethod(null); |
| /// } |
| /// } |
| /// |
| bool needsLegacyErasure(Class enclosingClass, Class declaringClass) { |
| Class? cls = enclosingClass; |
| while (cls != null) { |
| if (!cls.enclosingLibrary.isNonNullableByDefault) { |
| return true; |
| } |
| if (cls == declaringClass) { |
| return false; |
| } |
| if (cls.mixedInClass == declaringClass) { |
| return false; |
| } |
| cls = cls.superclass; |
| } |
| return false; |
| } |