| // 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 file. |
| |
| import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'; |
| import 'package:analyzer/dart/analysis/features.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_provider.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type_provider.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:analyzer/src/error/best_practices_verifier.dart'; |
| import 'package:analyzer/src/generated/element_type_provider.dart'; |
| import 'package:analyzer/src/generated/migration.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/utilities_dart.dart'; |
| import 'package:collection/collection.dart' show IterableExtension; |
| import 'package:meta/meta.dart'; |
| import 'package:nnbd_migration/fix_reason_target.dart'; |
| import 'package:nnbd_migration/instrumentation.dart'; |
| import 'package:nnbd_migration/nnbd_migration.dart'; |
| import 'package:nnbd_migration/src/decorated_class_hierarchy.dart'; |
| import 'package:nnbd_migration/src/decorated_type.dart'; |
| import 'package:nnbd_migration/src/edit_plan.dart'; |
| import 'package:nnbd_migration/src/fix_aggregator.dart'; |
| import 'package:nnbd_migration/src/nullability_node.dart'; |
| import 'package:nnbd_migration/src/utilities/built_value_transformer.dart'; |
| import 'package:nnbd_migration/src/utilities/permissive_mode.dart'; |
| import 'package:nnbd_migration/src/utilities/resolution_utils.dart'; |
| import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart'; |
| import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart'; |
| import 'package:nnbd_migration/src/variables.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| |
| bool _isIncrementOrDecrementOperator(TokenType tokenType) { |
| switch (tokenType) { |
| case TokenType.PLUS_PLUS: |
| case TokenType.MINUS_MINUS: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /// Problem reported by [FixBuilder] when encountering a compound assignment |
| /// for which the combination result is nullable. This occurs if the compound |
| /// assignment resolves to a user-defined operator that returns a nullable type, |
| /// but the target of the assignment expects a non-nullable type. We need to |
| /// add a null check but it's nontrivial to do so because we would have to |
| /// rewrite the assignment as an ordinary assignment (e.g. change `x += y` to |
| /// `x = (x + y)!`), but that might change semantics by causing subexpressions |
| /// of the target to be evaluated twice. |
| /// |
| /// TODO(paulberry): consider alternatives. |
| /// See https://github.com/dart-lang/sdk/issues/38675. |
| class CompoundAssignmentCombinedNullable implements Problem { |
| const CompoundAssignmentCombinedNullable(); |
| } |
| |
| /// Problem reported by [FixBuilder] when encountering a compound assignment |
| /// for which the value read from the target of the assignment has a nullable |
| /// type. We need to add a null check but it's nontrivial to do so because we |
| /// would have to rewrite the assignment as an ordinary assignment (e.g. change |
| /// `x += y` to `x = x! + y`), but that might change semantics by causing |
| /// subexpressions of the target to be evaluated twice. |
| /// |
| /// TODO(paulberry): consider alternatives. |
| /// See https://github.com/dart-lang/sdk/issues/38676. |
| class CompoundAssignmentReadNullable implements Problem { |
| const CompoundAssignmentReadNullable(); |
| } |
| |
| /// This class runs the analyzer's resolver over the code being migrated, after |
| /// graph propagation, to figure out what changes need to be made. It doesn't |
| /// actually make the changes; it simply reports what changes are necessary |
| /// through abstract methods. |
| class FixBuilder { |
| final DecoratedClassHierarchy? _decoratedClassHierarchy; |
| |
| /// The type provider providing non-nullable types. |
| final TypeProvider typeProvider; |
| |
| final Map<AstNode, NodeChange> changes = {}; |
| |
| final Map<AstNode, Set<Problem>> problems = {}; |
| |
| /// The NNBD type system. |
| final TypeSystemImpl _typeSystem; |
| |
| /// Variables for this migration run. |
| final Variables? _variables; |
| |
| /// The file being analyzed. |
| final Source? source; |
| |
| late ResolverVisitor _resolver; |
| |
| /// The listener to which exceptions should be reported. |
| final NullabilityMigrationListener? listener; |
| |
| /// The compilation unit for which fixes are being built. |
| final CompilationUnit? unit; |
| |
| final MigrationResolutionHooksImpl migrationResolutionHooks; |
| |
| /// Parameter elements for which an explicit type should be added, and what |
| /// that type should be. |
| final Map<ParameterElement, DartType> _addedParameterTypes = {}; |
| |
| final bool? warnOnWeakCode; |
| |
| final NullabilityGraph _graph; |
| |
| /// Helper that assists us in transforming Iterable methods to their "OrNull" |
| /// equivalents. |
| final WhereOrNullTransformer _whereOrNullTransformer; |
| |
| /// Helper that assists us in transforming calls to `Iterable.where` to |
| /// `Iterable.whereNotNull`. |
| final WhereNotNullTransformer _whereNotNullTransformer; |
| |
| /// The set of extensions that need to be imported from package:collection. |
| @visibleForTesting |
| final Set<String> neededCollectionPackageExtensions = {}; |
| |
| /// Map of additional package dependencies that will be required by the |
| /// migrated code. Keys are package names; values indicate the minimum |
| /// required version of each package. |
| final Map<String, Version> _neededPackages; |
| |
| factory FixBuilder( |
| Source? source, |
| DecoratedClassHierarchy? decoratedClassHierarchy, |
| TypeProvider typeProvider, |
| TypeSystemImpl typeSystem, |
| Variables? variables, |
| LibraryElementImpl definingLibrary, |
| NullabilityMigrationListener? listener, |
| CompilationUnit? unit, |
| bool? warnOnWeakCode, |
| NullabilityGraph graph, |
| Map<String, Version> neededPackages) { |
| var migrationResolutionHooks = MigrationResolutionHooksImpl(); |
| return FixBuilder._( |
| decoratedClassHierarchy, |
| _makeNnbdTypeSystem( |
| (typeProvider as TypeProviderImpl).asNonNullableByDefault, |
| typeSystem, |
| migrationResolutionHooks), |
| variables, |
| source, |
| definingLibrary, |
| listener, |
| unit, |
| migrationResolutionHooks, |
| warnOnWeakCode, |
| graph, |
| neededPackages); |
| } |
| |
| FixBuilder._( |
| this._decoratedClassHierarchy, |
| this._typeSystem, |
| this._variables, |
| this.source, |
| LibraryElementImpl definingLibrary, |
| this.listener, |
| this.unit, |
| this.migrationResolutionHooks, |
| this.warnOnWeakCode, |
| this._graph, |
| this._neededPackages) |
| : typeProvider = _typeSystem.typeProvider, |
| _whereOrNullTransformer = |
| WhereOrNullTransformer(_typeSystem.typeProvider, _typeSystem), |
| _whereNotNullTransformer = |
| WhereNotNullTransformer(_typeSystem.typeProvider, _typeSystem) { |
| migrationResolutionHooks._fixBuilder = this; |
| assert(_typeSystem.isNonNullableByDefault); |
| assert((typeProvider as TypeProviderImpl).isNonNullableByDefault); |
| var inheritanceManager = InheritanceManager3(); |
| // TODO(paulberry): is it a bad idea to throw away errors? |
| var errorListener = AnalysisErrorListener.NULL_LISTENER; |
| _resolver = ResolverVisitorForMigration( |
| inheritanceManager, |
| definingLibrary, |
| source!, |
| typeProvider, |
| errorListener, |
| _typeSystem, |
| FeatureSet.fromEnableFlags2( |
| sdkLanguageVersion: Feature.non_nullable.releaseVersion!, |
| flags: [], |
| ), |
| migrationResolutionHooks); |
| } |
| |
| /// Visits the entire compilation [unit] using the analyzer's resolver and |
| /// makes note of changes that need to be made. |
| void visitAll() { |
| try { |
| ElementTypeProvider.current = migrationResolutionHooks; |
| unit!.accept(_FixBuilderPreVisitor(this)); |
| unit!.accept(_resolver); |
| unit!.accept(_FixBuilderPostVisitor(this)); |
| } catch (exception, stackTrace) { |
| if (listener != null) { |
| listener!.reportException(source, unit, exception, stackTrace); |
| } else { |
| rethrow; |
| } |
| } finally { |
| ElementTypeProvider.current = const ElementTypeProvider(); |
| } |
| } |
| |
| /// Called whenever an AST node is found that can't be automatically fixed. |
| void _addProblem(AstNode node, Problem problem) { |
| var newlyAdded = (problems[node] ??= {}).add(problem); |
| assert(newlyAdded); |
| } |
| |
| /// Computes the type that [element] will have after migration. |
| /// |
| /// If [targetType] is present, and [element] is a class member, it is the |
| /// type of the class within which [element] is being accessed; this is used |
| /// to perform the correct substitutions. |
| DartType _computeMigratedType(Element element) { |
| element = element.declaration!; |
| if (element is ClassElement || element is TypeParameterElement) { |
| return typeProvider.typeType; |
| } else if (element is PropertyAccessorElement && |
| element.isSynthetic && |
| !element.variable.isSynthetic) { |
| var variableType = _variables! |
| .toFinalType(_variables!.decoratedElementType(element.variable)); |
| if (element.isSetter) { |
| return FunctionTypeImpl( |
| returnType: typeProvider.voidType, |
| typeFormals: [], |
| parameters: [ |
| ParameterElementImpl.synthetic( |
| 'value', variableType, ParameterKind.REQUIRED) |
| ], |
| nullabilitySuffix: NullabilitySuffix.none); |
| } else { |
| return FunctionTypeImpl( |
| returnType: variableType, |
| typeFormals: [], |
| parameters: [], |
| nullabilitySuffix: NullabilitySuffix.none); |
| } |
| } else { |
| return _variables!.toFinalType(_variables!.decoratedElementType(element)); |
| } |
| } |
| |
| /// Returns the [NodeChange] object accumulating changes for the given [node], |
| /// creating it if necessary. |
| NodeChange _getChange(AstNode node) => |
| changes[node] ??= NodeChange.create(node)!; |
| |
| /// Determines whether the given [node], which is a null-aware method |
| /// invocation, property access, or index expression, should remain null-aware |
| /// after migration. |
| bool _shouldStayNullAware(Expression node) { |
| Expression? target; |
| if (node is PropertyAccess) { |
| target = node.target; |
| } else if (node is MethodInvocation) { |
| target = node.target; |
| } else { |
| throw StateError('Unexpected expression type: ${node.runtimeType}'); |
| } |
| if (!_typeSystem.isPotentiallyNullable(target!.staticType!)) { |
| (_getChange(node) as NodeChangeForNullAware).removeNullAwareness = true; |
| return false; |
| } |
| return true; |
| } |
| |
| static TypeSystemImpl _makeNnbdTypeSystem( |
| TypeProvider nnbdTypeProvider, |
| TypeSystemImpl typeSystem, |
| MigrationResolutionHooksImpl migrationResolutionHooks) { |
| // TODO(paulberry): do we need to test both possible values of |
| // strictInference? |
| return TypeSystemImpl( |
| implicitCasts: typeSystem.implicitCasts, |
| isNonNullableByDefault: true, |
| strictCasts: typeSystem.strictCasts, |
| strictInference: typeSystem.strictInference, |
| typeProvider: nnbdTypeProvider); |
| } |
| } |
| |
| /// Fix reason object when adding a null check because of an explicit hint. |
| class FixReason_NullCheckHint implements SimpleFixReasonInfo { |
| @override |
| final CodeReference codeReference; |
| |
| FixReason_NullCheckHint(this.codeReference); |
| |
| @override |
| String get description => 'Null check hint'; |
| } |
| |
| /// Implementation of [MigrationResolutionHooks] that interfaces with |
| /// [FixBuilder]. |
| class MigrationResolutionHooksImpl |
| with ResolutionUtils |
| implements MigrationResolutionHooks { |
| FixBuilder? _fixBuilder; |
| |
| final Expando<List<CollectionElement>> _collectionElements = Expando(); |
| |
| final Expando<bool> _shouldStayNullAware = Expando(); |
| |
| final Map<Expression, _AssignmentLikeExpressionHandler> |
| _assignmentLikeExpressionHandlers = {}; |
| |
| FlowAnalysis<AstNode, Statement, Expression, PromotableElement, DartType>? |
| _flowAnalysis; |
| |
| /// Deferred processing that should be performed once we have finished |
| /// evaluating the type of a method invocation. |
| final Map<MethodInvocation, DartType Function(DartType)> |
| _deferredMethodInvocationProcessing = {}; |
| |
| final Map<Expression, DartType?> _contextTypes = {}; |
| |
| TypeProvider get typeProvider => _fixBuilder!.typeProvider; |
| |
| @override |
| void freshTypeParameterCreated(TypeParameterElement newTypeParameter, |
| TypeParameterElement oldTypeParameter) { |
| DecoratedTypeParameterBounds.current!.put(newTypeParameter, |
| DecoratedTypeParameterBounds.current!.get(oldTypeParameter)); |
| } |
| |
| @override |
| List<InterfaceType> getClassInterfaces(AbstractClassElementImpl element) { |
| return _wrapExceptions( |
| _fixBuilder!.unit, |
| () => element.interfacesInternal, |
| () => [ |
| for (var interface in element.interfacesInternal) |
| _getClassInterface(element, interface.element) |
| ]); |
| } |
| |
| @override |
| bool? getConditionalKnownValue(AstNode node) => |
| _wrapExceptions(node, () => null, () { |
| // TODO(paulberry): handle conditional expressions. |
| var conditionalDiscard = _fixBuilder!._variables! |
| .getConditionalDiscard(_fixBuilder!.source, node); |
| if (conditionalDiscard == null) { |
| return null; |
| } else { |
| if (conditionalDiscard.keepTrue && conditionalDiscard.keepFalse) { |
| return null; |
| } |
| var conditionValue = conditionalDiscard.keepTrue; |
| (_fixBuilder!._getChange(node) as NodeChangeForConditional) |
| ..conditionValue = conditionValue |
| ..conditionReason = conditionalDiscard.reason; |
| // If we're just issuing warnings, instruct the resolver to go ahead |
| // and visit both branches of the conditional. |
| return _fixBuilder!.warnOnWeakCode! ? null : conditionValue; |
| } |
| }); |
| |
| @override |
| List<ParameterElement> getExecutableParameters( |
| ExecutableElementImpl element) { |
| // Note: even if the element is part of a library that's being migrated, |
| // there's no guarantee that the parameter elements have been appropriately |
| // updated by the migration process, because they might be synthetic |
| // parameters. (This happens when the code being migrated contains a mixin |
| // application and there's a synthetic constructor). So we can't safely get |
| // the parameters out of the element. But it is always safe to defer to |
| // `getExecutableType` and get the parameter list from the function type. |
| return getExecutableType(element).parameters; |
| } |
| |
| @override |
| DartType getExecutableReturnType(Element element) => |
| getExecutableType(element as ElementImplWithFunctionType).returnType; |
| |
| @override |
| FunctionType getExecutableType(ElementImplWithFunctionType element) => |
| _wrapExceptions(_fixBuilder!.unit, () => element.typeInternal, () { |
| var type = _fixBuilder!._computeMigratedType(element); |
| Element baseElement = element; |
| if (baseElement is Member) { |
| type = baseElement.substitution.substituteType(type); |
| } |
| return type as FunctionType; |
| }); |
| |
| @override |
| DartType getExtendedType(ExtensionElementImpl element) { |
| return _wrapExceptions( |
| _fixBuilder!.unit, |
| () => element.extendedTypeInternal, |
| () => _fixBuilder!._variables!.toFinalType( |
| _fixBuilder!._variables!.decoratedElementType(element))); |
| } |
| |
| @override |
| DartType getFieldType(PropertyInducingElementImpl element) => |
| _wrapExceptions(_fixBuilder!.unit, () => element.typeInternal, () { |
| assert(!element.isSynthetic); |
| return _fixBuilder!._computeMigratedType(element); |
| }); |
| |
| @override |
| List<CollectionElement> getListElements(ListLiteral node) => _wrapExceptions( |
| node, |
| () => node.elements, |
| () => _collectionElements[node] ??= |
| _transformCollectionElements(node.elements)); |
| |
| @override |
| List<CollectionElement> getSetOrMapElements(SetOrMapLiteral node) => |
| _wrapExceptions( |
| node, |
| () => node.elements, |
| () => _collectionElements[node] ??= |
| _transformCollectionElements(node.elements)); |
| |
| @override |
| DartType? getTypeParameterBound(TypeParameterElementImpl element) { |
| var decoratedBound = _fixBuilder!._variables! |
| .decoratedTypeParameterBound(element, allowNullUnparentedBounds: true); |
| if (decoratedBound == null) return element.boundInternal; |
| var bound = _fixBuilder!._variables!.toFinalType(decoratedBound); |
| if (bound.isDynamic) { |
| return null; |
| } else if (bound.isDartCoreObject && |
| bound.nullabilitySuffix == NullabilitySuffix.question) { |
| return null; |
| } else { |
| return bound; |
| } |
| } |
| |
| @override |
| DartType getVariableType(VariableElementImpl variable) => |
| _wrapExceptions(_fixBuilder!.unit, () => variable.typeInternal, () { |
| if (variable.library == null) { |
| // This is a synthetic variable created during resolution (e.g. a |
| // parameter of a function type), so the type it currently has is the |
| // correct post-migration type. |
| return variable.typeInternal; |
| } |
| if (variable is ParameterElement) { |
| var enclosingElement = variable.enclosingElement; |
| if (enclosingElement is PropertyAccessorElement && |
| enclosingElement.isSynthetic) { |
| // This is the parameter of a synthetic getter, so it has the same |
| // type as the corresponding variable. |
| return _fixBuilder!._computeMigratedType(enclosingElement.variable); |
| } |
| } |
| return _fixBuilder!._computeMigratedType(variable); |
| }); |
| |
| @override |
| bool isIndexExpressionNullAware(IndexExpression node) { |
| // Null-aware index expressions weren't supported prior to NNBD. |
| assert(!node.isNullAware); |
| return false; |
| } |
| |
| @override |
| bool isLibraryNonNullableByDefault(LibraryElementImpl element) { |
| return _fixBuilder!._graph.isBeingMigrated(element.source) || |
| element.isNonNullableByDefaultInternal; |
| } |
| |
| @override |
| bool isMethodInvocationNullAware(MethodInvocation node) { |
| return node.isNullAware && |
| (_shouldStayNullAware[node] ??= |
| _fixBuilder!._shouldStayNullAware(node)); |
| } |
| |
| /// Indicates whether the given [element] is a member of an extension on a |
| /// potentially nullable type (and hence the extension member can be invoked |
| /// on a nullable type without introducing a null check). |
| bool isNullableExtensionMember(Element? element) { |
| if (element != null) { |
| var enclosingElement = element.enclosingElement; |
| if (enclosingElement is ExtensionElement) { |
| return _fixBuilder!._typeSystem |
| .isPotentiallyNullable(enclosingElement.extendedType); |
| } |
| } |
| return false; |
| } |
| |
| @override |
| bool isPropertyAccessNullAware(PropertyAccess node) { |
| return node.isNullAware && |
| (_shouldStayNullAware[node] ??= |
| _fixBuilder!._shouldStayNullAware(node)); |
| } |
| |
| @override |
| DartType modifyExpressionType( |
| Expression node, DartType type, DartType? contextType) => |
| _wrapExceptions(node, () => type, () { |
| _contextTypes[node] = contextType; |
| if (node is NamedExpression) { |
| // Do not attempt to modify named expressions. We should already have |
| // been called for [node.expression], and we should have made the |
| // necessary modifications then. |
| return type; |
| } |
| var parent = node.parent; |
| if (parent is AssignmentExpression) { |
| if (parent.leftHandSide == node) { |
| return type; |
| } |
| return _assignmentLikeExpressionHandlers[parent]! |
| .modifyAssignmentRhs(this, node, type); |
| } else if (parent is PrefixExpression) { |
| if (_isIncrementOrDecrementOperator(parent.operator.type)) { |
| return type; |
| } |
| } else if (parent is PostfixExpression) { |
| if (_isIncrementOrDecrementOperator(parent.operator.type)) { |
| return type; |
| } |
| } |
| return _modifyRValueType(node, type); |
| }); |
| |
| @override |
| DartType modifyInferredParameterType( |
| ParameterElement parameter, DartType type) { |
| var postMigrationType = parameter.type; |
| if (postMigrationType != type) { |
| // TODO(paulberry): test field formal parameters. |
| _fixBuilder!._addedParameterTypes[parameter] = postMigrationType; |
| return postMigrationType; |
| } |
| return type; |
| } |
| |
| @override |
| void reportBinaryExpressionContext( |
| BinaryExpression node, DartType? contextType) { |
| _contextTypes[node] = contextType; |
| } |
| |
| @override |
| void setCompoundAssignmentExpressionTypes(CompoundAssignmentExpression node) { |
| assert(_assignmentLikeExpressionHandlers[node] == null); |
| if (node is AssignmentExpression) { |
| var handler = _AssignmentExpressionHandler(node); |
| _assignmentLikeExpressionHandlers[node] = handler; |
| handler.handleLValueType(this, node.readType, node.writeType!); |
| } else if (node is PrefixExpression) { |
| assert(_isIncrementOrDecrementOperator(node.operator.type)); |
| var handler = _PrefixExpressionHandler(node); |
| _assignmentLikeExpressionHandlers[node] = handler; |
| handler.handleLValueType(this, node.readType, node.writeType!); |
| handler.handleAssignmentRhs(this, _fixBuilder!.typeProvider.intType); |
| } else if (node is PostfixExpression) { |
| assert(_isIncrementOrDecrementOperator(node.operator.type)); |
| var handler = _PostfixExpressionHandler(node); |
| _assignmentLikeExpressionHandlers[node] = handler; |
| handler.handleLValueType(this, node.readType, node.writeType!); |
| handler.handleAssignmentRhs(this, _fixBuilder!.typeProvider.intType); |
| } else { |
| throw StateError('(${node.runtimeType}) $node'); |
| } |
| } |
| |
| @override |
| void setFlowAnalysis( |
| FlowAnalysis<AstNode, Statement, Expression, PromotableElement, DartType>? |
| flowAnalysis) { |
| _flowAnalysis = flowAnalysis; |
| } |
| |
| DartType _addCastOrNullCheck( |
| Expression node, DartType expressionType, DartType contextType) { |
| var checks = |
| _fixBuilder!._variables!.expressionChecks(_fixBuilder!.source, node); |
| var change = _createExpressionChange(node, expressionType, contextType); |
| var info = AtomicEditInfo(change.description, checks?.edges ?? {}); |
| (_fixBuilder!._getChange(node) as NodeChangeForExpression) |
| .addExpressionChange(change, info); |
| return change.resultType; |
| } |
| |
| /// Ensures that the migrated file will contain an import of the extension |
| /// called [extensionName] from `package:collection/collection.dart`, and that |
| /// the pubspec will be appropriately updated (if necessary). |
| void _addCollectionPackageExtension(String extensionName) { |
| _fixBuilder!.neededCollectionPackageExtensions.add(extensionName); |
| _fixBuilder!._neededPackages['collection'] = |
| Version.parse('1.15.0-nullsafety.4'); |
| } |
| |
| DartType _addNullCheck(Expression node, DartType type, |
| {AtomicEditInfo? info, HintComment? hint}) { |
| var change = _createNullCheckChange(node, type, hint: hint); |
| var checks = |
| _fixBuilder!._variables!.expressionChecks(_fixBuilder!.source, node); |
| info ??= AtomicEditInfo(change.description, checks?.edges ?? {}); |
| var nodeChangeForExpression = |
| _fixBuilder!._getChange(node) as NodeChangeForExpression; |
| nodeChangeForExpression.addExpressionChange(change, info); |
| return change.resultType; |
| } |
| |
| ExpressionChange _createExpressionChange( |
| Expression node, DartType expressionType, DartType contextType) { |
| var expressionFutureTypeArgument = _getFutureTypeArgument(expressionType); |
| var contextFutureTypeArgument = _getFutureTypeArgument(contextType); |
| if (expressionFutureTypeArgument != null && |
| contextFutureTypeArgument != null) { |
| return IntroduceThenChange( |
| contextType, |
| _createExpressionChange( |
| node, expressionFutureTypeArgument, contextFutureTypeArgument)); |
| } |
| // Either a cast or a null check is needed. We prefer to do a null |
| // check if we can. |
| var nonNullType = _fixBuilder!._typeSystem.promoteToNonNull(expressionType); |
| if (_fixBuilder!._typeSystem.isSubtypeOf(nonNullType, contextType)) { |
| return _createNullCheckChange(node, expressionType); |
| } else { |
| _flowAnalysis!.asExpression_end(node, contextType); |
| return IntroduceAsChange(contextType, |
| isDowncast: _fixBuilder!._typeSystem |
| .isSubtypeOf(contextType, expressionType)); |
| } |
| } |
| |
| ExpressionChange _createNullCheckChange(Expression node, DartType type, |
| {HintComment? hint}) { |
| var resultType = |
| _fixBuilder!._typeSystem.promoteToNonNull(type as TypeImpl); |
| _flowAnalysis!.nonNullAssert_end(node); |
| return type.isDartCoreNull && hint == null |
| ? NoValidMigrationChange(resultType) |
| : NullCheckChange(resultType, hint: hint); |
| } |
| |
| Expression _findNullabilityContextAncestor(Expression node) { |
| while (true) { |
| var parent = node.parent; |
| if (parent is BinaryExpression && |
| parent.operator.type == TokenType.QUESTION_QUESTION && |
| identical(node, parent.rightOperand)) { |
| node = parent; |
| continue; |
| } |
| return node; |
| } |
| } |
| |
| InterfaceType _getClassInterface( |
| ClassElement class_, ClassElement superclass) { |
| var decoratedSupertype = _fixBuilder!._decoratedClassHierarchy! |
| .getDecoratedSupertype(class_, superclass); |
| var finalType = _fixBuilder!._variables!.toFinalType(decoratedSupertype); |
| return finalType as InterfaceType; |
| } |
| |
| DartType? _getFutureTypeArgument(DartType type) { |
| if (type is InterfaceType && type.isDartAsyncFuture) { |
| var typeArguments = type.typeArguments; |
| if (typeArguments.isNotEmpty) { |
| return typeArguments.first; |
| } |
| } |
| return null; |
| } |
| |
| bool _isSubtypeOrCoercible(DartType type, DartType context) { |
| var fixBuilder = _fixBuilder!; |
| var typeSystem = fixBuilder._typeSystem; |
| if (typeSystem.isSubtypeOf(type, context)) { |
| return true; |
| } |
| if (context is FunctionType && type is InterfaceType) { |
| var callMethod = |
| type.lookUpMethod2('call', fixBuilder.unit!.declaredElement!.library); |
| if (callMethod != null) { |
| var variables = fixBuilder._variables!; |
| var callMethodType = variables.toFinalType( |
| variables.decoratedElementType(callMethod.declaration)); |
| if (callMethod is MethodMember) { |
| callMethodType = |
| callMethod.substitution.substituteType(callMethodType); |
| } |
| if (typeSystem.isSubtypeOf(callMethodType, context)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| DartType _modifyRValueType(Expression node, DartType type, |
| {DartType? context}) { |
| if (node is MethodInvocation) { |
| var deferredProcessing = _deferredMethodInvocationProcessing.remove(node); |
| if (deferredProcessing != null) { |
| type = deferredProcessing(type); |
| } |
| } |
| var hint = |
| _fixBuilder!._variables!.getNullCheckHint(_fixBuilder!.source, node); |
| if (hint != null) { |
| type = _addNullCheck(node, type, |
| info: AtomicEditInfo( |
| NullabilityFixDescription.checkExpressionDueToHint, |
| { |
| FixReasonTarget.root: |
| FixReason_NullCheckHint(CodeReference.fromAstNode(node)) |
| }, |
| hintComment: hint), |
| hint: hint); |
| } |
| if (type.isDynamic) return type; |
| var ancestor = _findNullabilityContextAncestor(node); |
| context ??= _contextTypes[ancestor] ?? DynamicTypeImpl.instance; |
| if (!_isSubtypeOrCoercible(type, context)) { |
| return _tryTransformOrElse(node, type) ?? |
| _tryTransformWhere(node, type) ?? |
| _addCastOrNullCheck(node, type, context); |
| } else { |
| // Even if the type is a subtype of its context, we still want to |
| // transform `.where`. |
| type = _tryTransformWhere(node, type) ?? type; |
| } |
| if (!_fixBuilder!._typeSystem.isNullable(type)) return type; |
| if (_needsNullCheckDueToStructure(ancestor)) { |
| return _addNullCheck(node, type); |
| } |
| return type; |
| } |
| |
| bool _needsNullCheckDueToStructure(Expression node) { |
| var parent = node.parent; |
| |
| if (parent is BinaryExpression) { |
| if (identical(node, parent.leftOperand)) { |
| var operatorType = parent.operator.type; |
| if (operatorType == TokenType.QUESTION_QUESTION || |
| operatorType == TokenType.EQ_EQ || |
| operatorType == TokenType.BANG_EQ) { |
| return false; |
| } else { |
| return !isNullableExtensionMember(parent.staticElement); |
| } |
| } |
| } else if (parent is PrefixedIdentifier) { |
| if (isDeclaredOnObject(parent.identifier.name) || |
| isNullableExtensionMember(parent.identifier.staticElement)) { |
| return false; |
| } |
| return identical(node, parent.prefix); |
| } else if (parent is PropertyAccess) { |
| if (isDeclaredOnObject(parent.propertyName.name) || |
| isNullableExtensionMember(parent.propertyName.staticElement)) { |
| return false; |
| } |
| // TODO(paulberry): what about cascaded? |
| return parent.operator.type == TokenType.PERIOD && |
| identical(node, parent.target); |
| } else if (parent is MethodInvocation) { |
| if (isDeclaredOnObject(parent.methodName.name) || |
| isNullableExtensionMember(parent.methodName.staticElement)) { |
| return false; |
| } |
| // TODO(paulberry): what about cascaded? |
| return parent.operator!.type == TokenType.PERIOD && |
| identical(node, parent.target); |
| } else if (parent is IndexExpression) { |
| if (identical(node, parent.target)) { |
| return !isNullableExtensionMember(parent.staticElement); |
| } else { |
| return false; |
| } |
| } else if (parent is ConditionalExpression) { |
| return identical(node, parent.condition); |
| } else if (parent is FunctionExpressionInvocation) { |
| if (identical(node, parent.function)) { |
| return !isNullableExtensionMember(parent.staticElement); |
| } else { |
| return false; |
| } |
| } else if (parent is PrefixExpression) { |
| // TODO(paulberry): for prefix increment/decrement, inserting a null check |
| // isn't sufficient. |
| return !isNullableExtensionMember(parent.staticElement); |
| } else if (parent is ThrowExpression) { |
| return true; |
| } |
| return false; |
| } |
| |
| CollectionElement? _transformCollectionElement(CollectionElement? node) { |
| while (node is IfElement) { |
| var conditionalDiscard = _fixBuilder!._variables! |
| .getConditionalDiscard(_fixBuilder!.source, node); |
| if (conditionalDiscard == null || |
| conditionalDiscard.keepTrue && conditionalDiscard.keepFalse) { |
| return node; |
| } |
| var conditionValue = conditionalDiscard.keepTrue; |
| var ifElement = node; |
| node = conditionValue ? ifElement.thenElement : ifElement.elseElement; |
| } |
| return node; |
| } |
| |
| List<CollectionElement> _transformCollectionElements( |
| NodeList<CollectionElement> elements) { |
| return elements |
| .map(_transformCollectionElement) |
| .whereType<CollectionElement>() |
| .toList(); |
| } |
| |
| /// If [node] is an `orElse` argument to an iterable method that should be |
| /// transformed, transforms it and returns [type] (this indicates to the |
| /// caller that no further transformations to this expression need to be |
| /// considered); otherwise returns `null`. |
| DartType? _tryTransformOrElse(Expression node, DartType type) { |
| var transformationInfo = |
| _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node); |
| if (transformationInfo != null) { |
| // We can fix this by dropping the node and changing the method call. |
| _addCollectionPackageExtension('IterableExtension'); |
| var info = AtomicEditInfo( |
| NullabilityFixDescription.changeMethodName( |
| transformationInfo.originalName, |
| transformationInfo.replacementName), |
| {}); |
| (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName) |
| as NodeChangeForMethodName) |
| .replaceWith(transformationInfo.replacementName, info); |
| (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList) |
| as NodeChangeForArgumentList) |
| .dropArgument(transformationInfo.orElseArgument, info); |
| _deferredMethodInvocationProcessing[transformationInfo.methodInvocation] = |
| (methodInvocationType) => _fixBuilder!._typeSystem |
| .makeNullable(methodInvocationType as TypeImpl); |
| return type; |
| } |
| return null; |
| } |
| |
| /// If [node] is a call to `Iterable.where` that should be transformed, |
| /// transforms it and returns the type of the transformed method call (this |
| /// indicates to the caller that no further transformations to this expression |
| /// need to be considered); otherwise returns `null`. |
| DartType? _tryTransformWhere(Expression node, DartType type) { |
| var transformationInfo = _fixBuilder!._whereNotNullTransformer |
| .tryTransformMethodInvocation(node); |
| if (transformationInfo != null) { |
| // We can fix this by dropping the method call's argument and changing the |
| // call. |
| _addCollectionPackageExtension('IterableNullableExtension'); |
| var info = AtomicEditInfo( |
| NullabilityFixDescription.changeMethodName( |
| transformationInfo.originalName, |
| transformationInfo.replacementName), |
| {}); |
| (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName) |
| as NodeChangeForMethodName) |
| .replaceWith(transformationInfo.replacementName, info); |
| (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList) |
| as NodeChangeForArgumentList) |
| .dropArgument(transformationInfo.argument, info); |
| return _fixBuilder!._whereNotNullTransformer |
| .transformPostMigrationInvocationType(type); |
| } |
| return null; |
| } |
| |
| /// Runs the computation in [compute]. If an exception occurs and |
| /// [_fixBuilder.listener] is non-null, the exception is reported to the |
| /// listener and [fallback] is called to produce a result. Otherwise the |
| /// exception is propagated normally. |
| T _wrapExceptions<T>( |
| AstNode? node, T Function() fallback, T Function() compute) { |
| if (_fixBuilder!.listener == null) return compute(); |
| try { |
| return compute(); |
| } catch (exception, stackTrace) { |
| _fixBuilder!.listener! |
| .reportException(_fixBuilder!.source, node, exception, stackTrace); |
| return fallback(); |
| } |
| } |
| } |
| |
| /// Problem reported by [FixBuilder] when encountering a non-nullable unnamed |
| /// optional parameter that lacks a default value. |
| class NonNullableUnnamedOptionalParameter implements Problem { |
| const NonNullableUnnamedOptionalParameter(); |
| } |
| |
| /// Common supertype for problems reported by [FixBuilder._addProblem]. |
| abstract class Problem {} |
| |
| /// Specialization of [_AssignmentLikeExpressionHandler] for |
| /// [AssignmentExpression]. |
| class _AssignmentExpressionHandler extends _AssignmentLikeExpressionHandler { |
| @override |
| final AssignmentExpression node; |
| |
| _AssignmentExpressionHandler(this.node); |
| |
| @override |
| MethodElement? get combiner => node.staticElement; |
| |
| @override |
| TokenType get combinerType => node.operator.type; |
| |
| @override |
| Expression get target => node.leftHandSide; |
| } |
| |
| /// Data structure keeping track of intermediate results when the fix builder |
| /// is handling an assignment expression, or an expression that desugars to an |
| /// assignment. |
| abstract class _AssignmentLikeExpressionHandler { |
| /// For compound and null-aware assignments, the type read from the LHS. |
| late final DartType? readType; |
| |
| /// The type that may be written to the LHS. |
| late final DartType writeType; |
| |
| /// The type that should be used as a context type when inferring the RHS. |
| DartType? rhsContextType; |
| |
| /// Gets the static element representing the combiner. |
| MethodElement? get combiner; |
| |
| /// Gets the operator type representing the combiner. |
| TokenType get combinerType; |
| |
| /// Gets the expression in question. |
| Expression get node; |
| |
| /// Gets the target of the assignment. |
| Expression get target; |
| |
| /// Called after visiting the RHS of the assignment, to verify that for |
| /// compound assignments, the return value of the assignment is assignable to |
| /// [writeType]. |
| void handleAssignmentRhs( |
| MigrationResolutionHooksImpl hooks, DartType rhsType) { |
| MethodElement? combiner = this.combiner; |
| if (combiner != null) { |
| var fixBuilder = hooks._fixBuilder!; |
| var combinerReturnType = |
| fixBuilder._typeSystem.refineBinaryExpressionType( |
| readType!, |
| combinerType, |
| rhsType, |
| combiner.returnType, |
| combiner, |
| ); |
| if (!fixBuilder._typeSystem.isSubtypeOf(combinerReturnType, writeType)) { |
| (fixBuilder._getChange(node) as NodeChangeForAssignmentLike) |
| .hasBadCombinedType = true; |
| } |
| } |
| } |
| |
| /// Called after visiting the LHS of the assignment. Records the [readType], |
| /// [writeType], and [rhsContextType]. Also verifies that for compound |
| /// assignments, the [readType] is non-nullable, and that for null-aware |
| /// assignments, the [readType] is nullable. |
| void handleLValueType(MigrationResolutionHooksImpl hooks, |
| DartType? readTypeToSet, DartType writeTypeToSet) { |
| assert(writeTypeToSet.nullabilitySuffix != NullabilitySuffix.star); |
| writeType = writeTypeToSet; |
| // TODO(scheglov) Remove this after the analyzer breaking change that |
| // will top setting types for LHS. |
| (target as ExpressionImpl).staticType = writeTypeToSet; |
| var fixBuilder = hooks._fixBuilder; |
| if (combinerType == TokenType.EQ) { |
| rhsContextType = writeTypeToSet; |
| } else { |
| readType = readTypeToSet; |
| assert(readType!.nullabilitySuffix != NullabilitySuffix.star); |
| if (combinerType == TokenType.QUESTION_QUESTION_EQ) { |
| rhsContextType = writeTypeToSet; |
| if (fixBuilder!._typeSystem.isNonNullable(readType!)) { |
| (fixBuilder._getChange(node) as NodeChangeForAssignment) |
| .isWeakNullAware = true; |
| } |
| } else { |
| if (!readType!.isDynamic && |
| fixBuilder!._typeSystem.isPotentiallyNullable(readType!)) { |
| (fixBuilder._getChange(node) as NodeChangeForAssignmentLike) |
| .hasNullableSource = true; |
| } |
| } |
| } |
| } |
| |
| /// Called after visiting the RHS of the assignment. |
| DartType modifyAssignmentRhs(MigrationResolutionHooksImpl hooks, |
| Expression subexpression, DartType type) { |
| type = |
| hooks._modifyRValueType(subexpression, type, context: rhsContextType); |
| handleAssignmentRhs(hooks, type); |
| return type; |
| } |
| } |
| |
| /// Visitor that computes additional migrations on behalf of [FixBuilder] that |
| /// should be run after resolution |
| class _FixBuilderPostVisitor extends GeneralizingAstVisitor<void> |
| with PermissiveModeVisitor<void> { |
| final FixBuilder _fixBuilder; |
| |
| _FixBuilderPostVisitor(this._fixBuilder); |
| |
| @override |
| NullabilityMigrationListener? get listener => _fixBuilder.listener; |
| |
| @override |
| Source? get source => _fixBuilder.source; |
| |
| @override |
| void visitAsExpression(AsExpression node) { |
| if (!_fixBuilder._variables!.wasUnnecessaryCast(_fixBuilder.source, node) && |
| BestPracticesVerifier.isUnnecessaryCast( |
| node, _fixBuilder._typeSystem)) { |
| (_fixBuilder._getChange(node) as NodeChangeForAsExpression).removeAs = |
| true; |
| } |
| } |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| if ((node as CompilationUnitImpl).languageVersionToken != null) { |
| (_fixBuilder._getChange(node) as NodeChangeForCompilationUnit) |
| .removeLanguageVersionComment = true; |
| } |
| if (_fixBuilder.neededCollectionPackageExtensions.isNotEmpty) { |
| var packageCollectionImport = |
| _findImportDirective(node, 'package:collection/collection.dart'); |
| if (packageCollectionImport != null) { |
| for (var combinator in packageCollectionImport.combinators) { |
| if (combinator is ShowCombinator) { |
| for (var extensionName |
| in _fixBuilder.neededCollectionPackageExtensions) { |
| _ensureShows(combinator, extensionName); |
| } |
| } |
| } |
| } else { |
| var change = |
| _fixBuilder._getChange(node) as NodeChangeForCompilationUnit; |
| for (var extensionName |
| in _fixBuilder.neededCollectionPackageExtensions) { |
| change.addImport('package:collection/collection.dart', extensionName); |
| } |
| } |
| } |
| super.visitCompilationUnit(node); |
| } |
| |
| @override |
| void visitSimpleFormalParameter(SimpleFormalParameter node) { |
| if (node.type == null) { |
| var typeToAdd = _fixBuilder._addedParameterTypes[node.declaredElement!]; |
| if (typeToAdd != null) { |
| (_fixBuilder._getChange(node) as NodeChangeForSimpleFormalParameter) |
| .addExplicitType = typeToAdd; |
| } |
| } |
| } |
| |
| @override |
| void visitVariableDeclarationList(VariableDeclarationList node) { |
| if (node.type == null) { |
| // TODO(paulberry): for fields, handle inference via override as well. |
| List<DartType> neededTypes = []; |
| List<DartType> inferredTypes = []; |
| bool explicitTypeNeeded = false; |
| for (var variableDeclaration in node.variables) { |
| var neededType = _fixBuilder |
| ._computeMigratedType(variableDeclaration.declaredElement!); |
| neededTypes.add(neededType); |
| var inferredType = variableDeclaration.initializer?.staticType ?? |
| _fixBuilder.typeProvider.dynamicType; |
| inferredTypes.add(inferredType); |
| if (neededType != inferredType) { |
| explicitTypeNeeded = true; |
| } |
| } |
| if (explicitTypeNeeded) { |
| var firstNeededType = neededTypes[0]; |
| if (neededTypes.any((t) => t != firstNeededType)) { |
| // Different variables need different types. We handle this by |
| // introducing casts, which is not great but gets the job done. |
| for (int i = 0; i < node.variables.length; i++) { |
| if (neededTypes[i] != inferredTypes[i]) { |
| // We only have to worry about variables with initializers because |
| // variables without initializers will get the type `dynamic`. |
| var initializer = node.variables[i].initializer; |
| if (initializer != null) { |
| (_fixBuilder._getChange(initializer) as NodeChangeForExpression) |
| .addExpressionChange( |
| IntroduceAsChange(neededTypes[i], isDowncast: false), |
| AtomicEditInfo( |
| NullabilityFixDescription.otherCastExpression, {})); |
| } |
| } |
| } |
| } else { |
| (_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList) |
| .addExplicitType = firstNeededType; |
| } |
| } |
| } |
| |
| // Check if the nullability node for a single variable declaration has been |
| // declared to be late. |
| if (node.variables.length == 1) { |
| var variableElement = node.variables.single.declaredElement!; |
| var lateCondition = _fixBuilder._variables! |
| .decoratedElementType(variableElement) |
| .node! |
| .lateCondition; |
| switch (lateCondition) { |
| case LateCondition.possiblyLate: |
| (_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList) |
| .lateAdditionReason = LateAdditionReason.inference; |
| break; |
| case LateCondition.possiblyLateDueToTestSetup: |
| (_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList) |
| .lateAdditionReason = LateAdditionReason.testVariableInference; |
| break; |
| case LateCondition.lateDueToHint: |
| // Handled below. |
| case LateCondition.notLate: |
| // Nothing to do. |
| } |
| } |
| |
| var lateHint = _fixBuilder._variables!.getLateHint(source, node); |
| if (lateHint != null) { |
| (_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList) |
| .lateHint = lateHint; |
| } |
| super.visitVariableDeclarationList(node); |
| } |
| |
| /// Creates the necessary changes to ensure that [combinator] shows [name]. |
| void _ensureShows(ShowCombinator combinator, String name) { |
| if (combinator.shownNames.any((shownName) => shownName.name == name)) { |
| return; |
| } |
| (_fixBuilder._getChange(combinator) as NodeChangeForShowCombinator) |
| .addName(name); |
| } |
| |
| /// Searches [unit] for an unprefixed import directive whose URI matches |
| /// [uri], returning it if found, or `null` if not found. |
| ImportDirective? _findImportDirective(CompilationUnit unit, String uri) { |
| for (var directive in unit.directives) { |
| if (directive is ImportDirective && |
| directive.prefix == null && |
| directive.uriContent == uri) { |
| return directive; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /// Visitor that computes additional migrations on behalf of [FixBuilder] that |
| /// don't need to be integrated into the resolver itself, and should be run |
| /// prior to resolution |
| class _FixBuilderPreVisitor extends GeneralizingAstVisitor<void> |
| with PermissiveModeVisitor<void> { |
| final FixBuilder _fixBuilder; |
| |
| _FixBuilderPreVisitor(this._fixBuilder); |
| |
| @override |
| NullabilityMigrationListener? get listener => _fixBuilder.listener; |
| |
| @override |
| Source? get source => _fixBuilder.source; |
| |
| @override |
| void visitDefaultFormalParameter(DefaultFormalParameter node) { |
| var element = node.declaredElement; |
| if (node.defaultValue == null) { |
| var requiredHint = |
| _fixBuilder._variables!.getRequiredHint(_fixBuilder.source, node); |
| var nullabilityNode = |
| _fixBuilder._variables!.decoratedElementType(element!).node!; |
| if (!nullabilityNode.isNullable) { |
| var enclosingElement = element.enclosingElement; |
| if (enclosingElement is ConstructorElement && |
| enclosingElement.isFactory && |
| enclosingElement.redirectedConstructor != null) { |
| // Redirecting factory constructors inherit their parameters' default |
| // values from the constructors they redirect to, so the lack of a |
| // default value doesn't mean the parameter has to be nullable. |
| } else if (element.isNamed) { |
| _addRequiredKeyword(node, nullabilityNode, requiredHint); |
| } else { |
| _fixBuilder._addProblem( |
| node, const NonNullableUnnamedOptionalParameter()); |
| } |
| } else if (requiredHint != null || |
| element.metadata.any((m) => m.isRequired)) { |
| _addRequiredKeyword(node, nullabilityNode, requiredHint); |
| } |
| } |
| super.visitDefaultFormalParameter(node); |
| } |
| |
| @override |
| void visitFieldFormalParameter(FieldFormalParameter node) { |
| if (node.type == null) { |
| // Potentially add an explicit type to a field formal parameter. |
| var decl = node.declaredElement as FieldFormalParameterElement; |
| var decoratedType = _fixBuilder._variables!.decoratedElementType(decl); |
| var field = decl.field; |
| if (field != null) { |
| var decoratedFieldType = |
| _fixBuilder._variables!.decoratedElementType(field); |
| var typeToAdd = _fixBuilder._variables!.toFinalType(decoratedType); |
| var fieldFinalType = |
| _fixBuilder._variables!.toFinalType(decoratedFieldType); |
| if (typeToAdd is InterfaceType && |
| !_fixBuilder._typeSystem.isSubtypeOf(fieldFinalType, typeToAdd)) { |
| (_fixBuilder._getChange(node) as NodeChangeForFieldFormalParameter) |
| .addExplicitType = typeToAdd; |
| } |
| } |
| } else if (node.parameters != null) { |
| // Handle function-typed field formal parameters. |
| var decoratedType = |
| _fixBuilder._variables!.decoratedElementType(node.declaredElement!); |
| if (decoratedType.node!.isNullable) { |
| (_fixBuilder._getChange(node) as NodeChangeForFieldFormalParameter) |
| .recordNullability(decoratedType, decoratedType.node!.isNullable, |
| nullabilityHint: |
| _fixBuilder._variables!.getNullabilityHint(source, node)); |
| } |
| } |
| super.visitFieldFormalParameter(node); |
| } |
| |
| @override |
| void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| var decoratedType = |
| _fixBuilder._variables!.decoratedElementType(node.declaredElement!); |
| if (decoratedType.node!.isNullable) { |
| (_fixBuilder._getChange(node) |
| as NodeChangeForFunctionTypedFormalParameter) |
| .recordNullability(decoratedType, decoratedType.node!.isNullable, |
| nullabilityHint: |
| _fixBuilder._variables!.getNullabilityHint(source, node)); |
| } |
| super.visitFunctionTypedFormalParameter(node); |
| } |
| |
| @override |
| void visitGenericFunctionType(GenericFunctionType node) { |
| var decoratedType = _fixBuilder._variables! |
| .decoratedTypeAnnotation(_fixBuilder.source, node); |
| if (!typeIsNonNullableByContext(node)) { |
| _makeTypeNameNullable(node, decoratedType); |
| } |
| (node as GenericFunctionTypeImpl).type = |
| _fixBuilder._variables!.toFinalType(decoratedType); |
| super.visitGenericFunctionType(node); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| var nullableAnnotation = BuiltValueTransformer.findNullableAnnotation(node); |
| if (nullableAnnotation != null) { |
| var info = AtomicEditInfo( |
| NullabilityFixDescription.removeNullableAnnotation, {}); |
| (_fixBuilder._getChange(node) as NodeChangeForMethodDeclaration) |
| ..annotationToRemove = nullableAnnotation |
| ..removeAnnotationInfo = info; |
| } |
| super.visitMethodDeclaration(node); |
| } |
| |
| @override |
| void visitNamedType(NamedType node) { |
| var decoratedType = _fixBuilder._variables! |
| .decoratedTypeAnnotation(_fixBuilder.source, node); |
| if (!typeIsNonNullableByContext(node)) { |
| if (!_typeIsNaturallyNullable(decoratedType.type!)) { |
| _makeTypeNameNullable(node, decoratedType); |
| } |
| } |
| (node as NamedTypeImpl).type = |
| _fixBuilder._variables!.toFinalType(decoratedType); |
| super.visitNamedType(node); |
| } |
| |
| void _addRequiredKeyword(DefaultFormalParameter parameter, |
| NullabilityNode node, HintComment? requiredHint) { |
| // Change an existing `@required` annotation into a `required` keyword if |
| // possible. |
| final element = parameter.declaredElement!; |
| final method = element.enclosingElement!; |
| final cls = method.enclosingElement!; |
| var info = AtomicEditInfo( |
| NullabilityFixDescription.addRequired( |
| cls.name, method.name, element.name), |
| {FixReasonTarget.root: node}); |
| var metadata = parameter.metadata; |
| if (metadata.isNotEmpty) { |
| // Only the last annotation can be changed into a `required` keyword; |
| // changing an earlier annotation into a keyword would be illegal. |
| var lastAnnotation = metadata.last; |
| if (lastAnnotation.elementAnnotation!.isRequired) { |
| (_fixBuilder._getChange(lastAnnotation) as NodeChangeForAnnotation) |
| ..changeToRequiredKeyword = true |
| ..changeToRequiredKeywordInfo = info; |
| return; |
| } |
| } |
| // Otherwise create a new `required` keyword. |
| var nodeChange = (_fixBuilder._getChange(parameter) |
| as NodeChangeForDefaultFormalParameter) |
| ..addRequiredKeyword = true |
| ..addRequiredKeywordInfo = info |
| ..requiredHint = requiredHint; |
| var requiredAnnotation = metadata.firstWhereOrNull( |
| (annotation) => annotation.elementAnnotation!.isRequired); |
| if (requiredAnnotation != null) { |
| // If the parameter was annotated with `@required`, but it was not the |
| // last annotation, we remove the annotation in addition to adding the |
| // `required` keyword. |
| nodeChange |
| ..annotationToRemove = requiredAnnotation |
| ..removeAnnotationInfo = info; |
| } |
| } |
| |
| void _makeTypeNameNullable(TypeAnnotation node, DecoratedType decoratedType) { |
| bool makeNullable = decoratedType.node!.isNullable; |
| if (decoratedType.type!.isDartAsyncFutureOr) { |
| var typeArguments = decoratedType.typeArguments; |
| if (typeArguments.length == 1) { |
| var typeArgument = typeArguments[0]!; |
| if ((_typeIsNaturallyNullable(typeArgument.type!) || |
| typeArgument.node!.isNullable)) { |
| // FutureOr<T?>? is equivalent to FutureOr<T?>, so there is no need to |
| // make this type nullable. |
| makeNullable = false; |
| } |
| } |
| } |
| (_fixBuilder._getChange(node) as NodeChangeForTypeAnnotation) |
| .recordNullability( |
| decoratedType, makeNullable, |
| nullabilityHint: |
| _fixBuilder._variables!.getNullabilityHint(source, node)); |
| } |
| |
| bool _typeIsNaturallyNullable(DartType type) => |
| type.isDynamic || type.isVoid || type.isDartCoreNull; |
| } |
| |
| /// Specialization of [_AssignmentLikeExpressionHandler] for |
| /// [PostfixExpression]. |
| class _PostfixExpressionHandler extends _AssignmentLikeExpressionHandler { |
| @override |
| final PostfixExpression node; |
| |
| _PostfixExpressionHandler(this.node) |
| : assert(_isIncrementOrDecrementOperator(node.operator.type)); |
| |
| @override |
| MethodElement? get combiner => node.staticElement; |
| |
| @override |
| TokenType get combinerType => node.operator.type; |
| |
| @override |
| Expression get target => node.operand; |
| } |
| |
| /// Specialization of [_AssignmentLikeExpressionHandler] for |
| /// [PrefixExpression]. |
| class _PrefixExpressionHandler extends _AssignmentLikeExpressionHandler { |
| @override |
| final PrefixExpression node; |
| |
| _PrefixExpressionHandler(this.node) |
| : assert(_isIncrementOrDecrementOperator(node.operator.type)); |
| |
| @override |
| MethodElement? get combiner => node.staticElement; |
| |
| @override |
| TokenType get combinerType => node.operator.type; |
| |
| @override |
| Expression get target => node.operand; |
| } |