| // 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/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/type.dart'; |
| import 'package:analyzer/dart/element/type_provider.dart'; |
| import 'package:analyzer/dart/element/type_system.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_system.dart' show TypeSystemImpl; |
| import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart'; |
| import 'package:analyzer/src/error/best_practices_verifier.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| 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/conditional_discard.dart'; |
| import 'package:nnbd_migration/src/decorated_class_hierarchy.dart'; |
| import 'package:nnbd_migration/src/decorated_type.dart'; |
| import 'package:nnbd_migration/src/edge_origin.dart'; |
| import 'package:nnbd_migration/src/expression_checks.dart'; |
| import 'package:nnbd_migration/src/nullability_node.dart'; |
| import 'package:nnbd_migration/src/nullability_node_target.dart'; |
| import 'package:nnbd_migration/src/utilities/built_value_transformer.dart'; |
| import 'package:nnbd_migration/src/utilities/completeness_tracker.dart'; |
| import 'package:nnbd_migration/src/utilities/hint_utils.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/scoped_set.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 'decorated_type_operations.dart'; |
| |
| /// A potentially reversible decision is that downcasts and sidecasts should |
| /// assume non-nullability. This could be changed such that we assume the |
| /// widest type, or the narrowest type. For now we assume non-nullability, but |
| /// have a flag to isolate that work. |
| const _assumeNonNullabilityInCasts = true; |
| |
| /// Test class mixing in _AssignmentChecker, to allow [checkAssignment] to be |
| /// more easily unit tested. |
| @visibleForTesting |
| class AssignmentCheckerForTesting extends Object with _AssignmentChecker { |
| @override |
| final TypeSystem _typeSystem; |
| |
| @override |
| final TypeProvider typeProvider; |
| |
| final NullabilityGraph _graph; |
| |
| /// Tests should fill in this map with the bounds of any type parameters being |
| /// tested. |
| final Map<TypeParameterElement, DecoratedType> bounds = {}; |
| |
| @override |
| final DecoratedClassHierarchy _decoratedClassHierarchy; |
| |
| AssignmentCheckerForTesting(this._typeSystem, this.typeProvider, this._graph, |
| this._decoratedClassHierarchy); |
| |
| void checkAssignment(EdgeOrigin origin, |
| {required DecoratedType source, |
| required DecoratedType destination, |
| required bool hard}) { |
| super._checkAssignment(origin, FixReasonTarget.root, |
| source: source, destination: destination, hard: hard); |
| } |
| |
| @override |
| void _connect(NullabilityNode? source, NullabilityNode? destination, |
| EdgeOrigin origin, FixReasonTarget edgeTarget, |
| {bool hard = false, bool checkable = true}) { |
| _graph.connect(source, destination!, origin, |
| hard: hard, checkable: checkable); |
| } |
| |
| @override |
| DecoratedType? _getCallMethodType(DecoratedType type) => null; |
| |
| @override |
| DecoratedType _getTypeParameterTypeBound(DecoratedType type) { |
| return bounds[(type.type as TypeParameterType).element] ?? |
| (throw StateError('Unknown bound for $type')); |
| } |
| } |
| |
| /// Visitor that builds nullability graph edges by examining code to be |
| /// migrated. |
| /// |
| /// The return type of each `visit...` method is a [DecoratedType] indicating |
| /// the static type of the visited expression, along with the constraint |
| /// variables that will determine its nullability. For `visit...` methods that |
| /// don't visit expressions, `null` will be returned. |
| class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> |
| with |
| _AssignmentChecker, |
| PermissiveModeVisitor<DecoratedType>, |
| CompletenessTracker<DecoratedType>, |
| ResolutionUtils { |
| final TypeSystem _typeSystem; |
| |
| final InheritanceManager3 _inheritanceManager; |
| |
| /// The repository of constraint variables and decorated types (from a |
| /// previous pass over the source code). |
| final Variables? _variables; |
| |
| final NullabilityMigrationListener? listener; |
| |
| final NullabilityMigrationInstrumentation? instrumentation; |
| |
| final NullabilityGraph _graph; |
| |
| TypeProvider typeProvider; |
| |
| @override |
| final Source? source; |
| |
| @override |
| final DecoratedClassHierarchy? _decoratedClassHierarchy; |
| |
| /// If we are visiting a function body or initializer, instance of flow |
| /// analysis. Otherwise `null`. |
| FlowAnalysis<AstNode, Statement, Expression, PromotableElement, |
| DecoratedType>? _flowAnalysis; |
| |
| /// If we are visiting a function body or initializer, assigned variable |
| /// information used in flow analysis. Otherwise `null`. |
| AssignedVariables<AstNode, PromotableElement>? _assignedVariables; |
| |
| /// The [DecoratedType] of the innermost function or method being visited, or |
| /// `null` if the visitor is not inside any function or method. |
| /// |
| /// This is needed to construct the appropriate nullability constraints for |
| /// return statements. |
| DecoratedType? _currentFunctionType; |
| |
| /// If the innermost enclosing executable is a constructor with field formal |
| /// parameters, a map from each field's getter to the corresponding field |
| /// formal parameter element. Otherwise, an empty map. |
| Map<PropertyAccessorElement, FieldFormalParameterElement> |
| _currentFieldFormals = const {}; |
| |
| FunctionExpression? _currentFunctionExpression; |
| |
| /// The [ClassElement] or [ExtensionElement] of the current class or extension |
| /// being visited, or null. |
| Element? _currentClassOrExtension; |
| |
| /// If an extension declaration is being visited, the decorated type of the |
| /// type appearing in the `on` clause (this is the type of `this` inside the |
| /// extension declaration). Null if an extension declaration is not being |
| /// visited. |
| DecoratedType? _currentExtendedType; |
| |
| /// The [DecoratedType] of the innermost list or set literal being visited, or |
| /// `null` if the visitor is not inside any list or set. |
| /// |
| /// This is needed to construct the appropriate nullability constraints for |
| /// ui as code elements. |
| DecoratedType? _currentLiteralElementType; |
| |
| /// The key [DecoratedType] of the innermost map literal being visited, or |
| /// `null` if the visitor is not inside any map. |
| /// |
| /// This is needed to construct the appropriate nullability constraints for |
| /// ui as code elements. |
| DecoratedType? _currentMapKeyType; |
| |
| /// The value [DecoratedType] of the innermost map literal being visited, or |
| /// `null` if the visitor is not inside any map. |
| /// |
| /// This is needed to construct the appropriate nullability constraints for |
| /// ui as code elements. |
| DecoratedType? _currentMapValueType; |
| |
| /// Information about the most recently visited binary expression whose |
| /// boolean value could possibly affect nullability analysis. |
| _ConditionInfo? _conditionInfo; |
| |
| /// The set of nullability nodes that would have to be `nullable` for the code |
| /// currently being visited to be reachable. |
| /// |
| /// Guard variables are attached to the left hand side of any generated |
| /// constraints, so that constraints do not take effect if they come from |
| /// code that can be proven unreachable by the migration tool. |
| final _guards = <NullabilityNode?>[]; |
| |
| /// The scope of locals (parameters, variables) that are post-dominated by the |
| /// current node as we walk the AST. We use a [_ScopedLocalSet] so that outer |
| /// scopes may track their post-dominators separately from inner scopes. |
| /// |
| /// Note that this is not guaranteed to be complete. It is used to make hard |
| /// edges on a best-effort basis. |
| var _postDominatedLocals = ScopedSet<Element>(); |
| |
| /// Map whose keys are expressions of the form `a?.b` on the LHS of |
| /// assignments, and whose values are the nullability nodes corresponding to |
| /// the expression preceding `?.`. These are needed in order to properly |
| /// analyze expressions like `a?.b += c`, since the type of the compound |
| /// assignment is nullable if the type of the expression preceding `?.` is |
| /// nullable. |
| final Map<Expression, NullabilityNode?> _conditionalNodes = {}; |
| |
| /// If we are visiting a cascade expression, the decorated type of the target |
| /// of the cascade. Otherwise `null`. |
| DecoratedType? _currentCascadeTargetType; |
| |
| /// While visiting a class declaration, set of class fields that lack |
| /// initializers at their declaration sites. |
| Set<FieldElement?>? _fieldsNotInitializedAtDeclaration; |
| |
| /// While visiting a constructor, set of class fields that lack initializers |
| /// at their declaration sites *and* for which we haven't yet found an |
| /// initializer in the constructor declaration. |
| Set<FieldElement?>? _fieldsNotInitializedByConstructor; |
| |
| /// Current nesting depth of [visitNamedType] |
| int _typeNameNesting = 0; |
| |
| final Set<PromotableElement> _lateHintedLocals = {}; |
| |
| final Map<Token, HintComment?> _nullCheckHints = {}; |
| |
| /// 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; |
| |
| /// Deferred processing that should be performed once we have finished |
| /// evaluating the decorated type of a method invocation. |
| final Map<MethodInvocation, DecoratedType Function(DecoratedType?)> |
| _deferredMethodInvocationProcessing = {}; |
| |
| /// If we are visiting a local function or closure, the set of local variables |
| /// assigned to so far inside it. Otherwise `null`. |
| Set<Element>? _elementsWrittenToInLocalFunction; |
| |
| final LibraryElement _library; |
| |
| EdgeBuilder(this.typeProvider, this._typeSystem, this._variables, this._graph, |
| this.source, this.listener, this._decoratedClassHierarchy, this._library, |
| {this.instrumentation}) |
| : _inheritanceManager = InheritanceManager3(), |
| _whereOrNullTransformer = |
| WhereOrNullTransformer(typeProvider, _typeSystem), |
| _whereNotNullTransformer = |
| WhereNotNullTransformer(typeProvider, _typeSystem); |
| |
| /// The synthetic element we use as a stand-in for `this` when analyzing |
| /// extension methods. |
| Element get _extensionThis => DynamicElementImpl.instance; |
| |
| /// Gets the decorated type of [element] from [_variables], performing any |
| /// necessary substitutions. |
| /// |
| /// [node] is used as the AST node for the edge origin if any graph edges need |
| /// to be created. [targetType], if provided, indicates the type of the |
| /// target (receiver) for a method, getter, or setter invocation. |
| /// [targetExpression], if provided, is the expression for the target |
| /// (receiver) for a method, getter, or setter invocation. |
| DecoratedType getOrComputeElementType(AstNode node, Element element, |
| {DecoratedType? targetType, Expression? targetExpression}) { |
| Map<TypeParameterElement, DecoratedType?>? substitution; |
| Element? baseElement = element.declaration; |
| if (targetType != null) { |
| var enclosingElement = baseElement!.enclosingElement; |
| if (enclosingElement is ClassElement) { |
| if (targetType.type!.resolveToBound(typeProvider.dynamicType) |
| is InterfaceType && |
| enclosingElement.typeParameters.isNotEmpty) { |
| substitution = _decoratedClassHierarchy! |
| .asInstanceOf(targetType, enclosingElement) |
| .asSubstitution; |
| } |
| } else { |
| assert(enclosingElement is ExtensionElement); |
| final extensionElement = enclosingElement as ExtensionElement; |
| // The semantics of calling an extension method or extension property |
| // are essentially the same as calling a static function where the |
| // receiver is passed as an invisible `this` parameter whose type is the |
| // extension's "on" type. If the extension declaration has type |
| // parameters, they behave like inferred type parameters of the static |
| // function. |
| // |
| // So what we need to do is (1) create a set of DecoratedTypes to |
| // represent the inferred types of the type parameters, (2) ensure that |
| // the receiver type is assignable to "on" type (with those decorated |
| // types substituted into it), and (3) substitute those decorated types |
| // into the declared type of the extension method or extension property, |
| // so that the caller will match up parameter types and the return type |
| // appropriately. |
| // |
| // Taking each step in turn: |
| // (1) create a set of decorated types to represent the inferred types |
| // of the type parameters. Note that we must make sure each of these |
| // types satisfies its associated bound. |
| var typeParameters = extensionElement.typeParameters; |
| if (typeParameters.isNotEmpty) { |
| var preMigrationSubstitution = (element as Member).substitution.map; |
| substitution = {}; |
| var target = NullabilityNodeTarget.text('extension'); |
| for (int i = 0; i < typeParameters.length; i++) { |
| var typeParameter = typeParameters[i]; |
| var decoratedTypeArgument = DecoratedType.forImplicitType( |
| typeProvider, |
| preMigrationSubstitution[typeParameter], |
| _graph, |
| target.typeArgument(i)); |
| substitution[typeParameter] = decoratedTypeArgument; |
| var edgeOrigin = |
| InferredTypeParameterInstantiationOrigin(source, node); |
| _checkAssignment(edgeOrigin, FixReasonTarget.root, |
| source: decoratedTypeArgument, |
| destination: |
| _variables!.decoratedTypeParameterBound(typeParameter)!, |
| hard: true); |
| } |
| } |
| // (2) ensure that the receiver type is assignable to "on" type (with |
| // those decorated types substituted into it) |
| var onType = _variables!.decoratedElementType(extensionElement); |
| if (substitution != null) { |
| onType = onType.substitute(substitution); |
| } |
| _checkAssignment(InferredTypeParameterInstantiationOrigin(source, node), |
| FixReasonTarget.root, |
| source: targetType, |
| destination: onType, |
| hard: targetExpression == null || |
| _isReferenceInScope(targetExpression)); |
| // (3) substitute those decorated types into the declared type of the |
| // extension method or extension property, so that the caller will match |
| // up parameter types and the return type appropriately. |
| // |
| // There's nothing more we need to do here. The substitution below |
| // will do the job. |
| } |
| } |
| DecoratedType decoratedBaseType; |
| if (baseElement is PropertyAccessorElement && |
| baseElement.isSynthetic && |
| !baseElement.variable.isSynthetic) { |
| var variable = baseElement.variable; |
| var decoratedElementType = _variables!.decoratedElementType(variable); |
| if (baseElement.isGetter) { |
| var target = NullabilityNodeTarget.text('getter function'); |
| decoratedBaseType = DecoratedType( |
| baseElement.type, NullabilityNode.forInferredType(target), |
| returnType: decoratedElementType); |
| } else { |
| assert(baseElement.isSetter); |
| var target = NullabilityNodeTarget.text('setter function'); |
| decoratedBaseType = DecoratedType( |
| baseElement.type, NullabilityNode.forInferredType(target), |
| positionalParameters: [decoratedElementType], |
| returnType: DecoratedType(VoidTypeImpl.instance, |
| NullabilityNode.forInferredType(target.returnType()))); |
| } |
| } else { |
| decoratedBaseType = _variables!.decoratedElementType(baseElement!); |
| } |
| if (substitution != null) { |
| return decoratedBaseType.substitute(substitution); |
| } else { |
| return decoratedBaseType; |
| } |
| } |
| |
| @override |
| // TODO(srawlins): Theoretically, edges should be connected between arguments |
| // and parameters, as in an instance creation. It is quite rare though, to |
| // declare a class and use it as an annotation in the same package. |
| DecoratedType? visitAnnotation(Annotation node) { |
| var previousFlowAnalysis = _flowAnalysis; |
| var previousAssignedVariables = _assignedVariables; |
| if (_flowAnalysis == null) { |
| _assignedVariables = AssignedVariables(); |
| // Note: we are using flow analysis to help us track true nullabilities; |
| // it's not necessary to replicate old bugs. So we pass `true` for |
| // `respectImplicitlyTypedVarInitializers`. |
| _flowAnalysis = FlowAnalysis<AstNode, Statement, Expression, |
| PromotableElement, DecoratedType>( |
| DecoratedTypeOperations(_typeSystem, _variables, _graph), |
| _assignedVariables!, |
| respectImplicitlyTypedVarInitializers: true); |
| } |
| try { |
| _dispatch(node.constructorName); |
| _dispatchList(node.arguments?.arguments); |
| } finally { |
| _flowAnalysis = previousFlowAnalysis; |
| _assignedVariables = previousAssignedVariables; |
| } |
| annotationVisited(node); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitAsExpression(AsExpression node) { |
| if (BestPracticesVerifier.isUnnecessaryCast( |
| node, _typeSystem as TypeSystemImpl)) { |
| _variables!.recordUnnecessaryCast(source, node); |
| } |
| _dispatch(node.type); |
| final typeNode = _variables!.decoratedTypeAnnotation(source, node.type); |
| _handleAssignment(node.expression, destinationType: typeNode); |
| _flowAnalysis!.asExpression_end(node.expression, typeNode); |
| return typeNode; |
| } |
| |
| @override |
| DecoratedType? visitAssertInitializer(AssertInitializer node) { |
| _flowAnalysis!.assert_begin(); |
| _checkExpressionNotNull(node.condition); |
| if (identical(_conditionInfo?.condition, node.condition)) { |
| var intentNode = _conditionInfo!.trueDemonstratesNonNullIntent; |
| if (intentNode != null && _conditionInfo!.postDominatingIntent!) { |
| _graph.makeNonNullable(_conditionInfo!.trueDemonstratesNonNullIntent, |
| NonNullAssertionOrigin(source, node)); |
| } |
| } |
| _flowAnalysis!.assert_afterCondition(node.condition); |
| _dispatch(node.message); |
| _flowAnalysis!.assert_end(); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitAssertStatement(AssertStatement node) { |
| _flowAnalysis!.assert_begin(); |
| _checkExpressionNotNull(node.condition); |
| if (identical(_conditionInfo?.condition, node.condition)) { |
| var intentNode = _conditionInfo!.trueDemonstratesNonNullIntent; |
| if (intentNode != null && _conditionInfo!.postDominatingIntent!) { |
| _graph.makeNonNullable(_conditionInfo!.trueDemonstratesNonNullIntent, |
| NonNullAssertionOrigin(source, node)); |
| } |
| } |
| _flowAnalysis!.assert_afterCondition(node.condition); |
| _dispatch(node.message); |
| _flowAnalysis!.assert_end(); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitAssignmentExpression(AssignmentExpression node) { |
| bool isQuestionAssign = false; |
| bool isCompound = false; |
| if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) { |
| isQuestionAssign = true; |
| } else if (node.operator.type != TokenType.EQ) { |
| isCompound = true; |
| } |
| |
| var sourceIsSetupCall = false; |
| if (node.leftHandSide is SimpleIdentifier && |
| _isCurrentFunctionExpressionFoundInTestSetUpCall()) { |
| var assignee = |
| getWriteOrReadElement(node.leftHandSide as SimpleIdentifier)!; |
| var enclosingElementOfCurrentFunction = |
| _currentFunctionExpression!.declaredElement!.enclosingElement; |
| if (enclosingElementOfCurrentFunction == assignee.enclosingElement) { |
| // [node]'s enclosing function is a function expression passed directly |
| // to a call to the test package's `setUp` function, and [node] is an |
| // assignment to a variable declared in the same scope as the call to |
| // `setUp`. |
| sourceIsSetupCall = true; |
| } |
| } |
| |
| var expressionType = _handleAssignment(node.rightHandSide, |
| assignmentExpression: node, |
| compoundOperatorInfo: isCompound ? node : null, |
| questionAssignNode: isQuestionAssign ? node : null, |
| sourceIsSetupCall: sourceIsSetupCall); |
| var conditionalNode = _conditionalNodes[node.leftHandSide]; |
| if (conditionalNode != null) { |
| expressionType = expressionType!.withNode( |
| NullabilityNode.forLUB(conditionalNode, expressionType.node)); |
| _variables!.recordDecoratedExpressionType(node, expressionType); |
| } |
| |
| return expressionType; |
| } |
| |
| @override |
| DecoratedType visitAwaitExpression(AwaitExpression node) { |
| var expressionType = _dispatch(node.expression)!; |
| var type = expressionType.type!; |
| if (type.isDartCoreNull) { |
| // Nothing to do; awaiting `null` produces `null`. |
| } else if (_typeSystem.isSubtypeOf(type, typeProvider.futureDynamicType)) { |
| expressionType = _decoratedClassHierarchy! |
| .asInstanceOf(expressionType, typeProvider.futureElement) |
| .typeArguments[0]!; |
| } else if (type.isDartAsyncFutureOr) { |
| expressionType = expressionType.typeArguments[0]!; |
| } |
| return expressionType; |
| } |
| |
| @override |
| DecoratedType visitBinaryExpression(BinaryExpression node) { |
| var operatorType = node.operator.type; |
| var leftOperand = node.leftOperand; |
| var rightOperand = node.rightOperand; |
| if (operatorType == TokenType.EQ_EQ || operatorType == TokenType.BANG_EQ) { |
| var leftType = _dispatch(leftOperand)!; |
| _graph.connectDummy(leftType.node, DummyOrigin(source, node)); |
| var equalityInfo = |
| _flowAnalysis!.equalityOperand_end(leftOperand, leftType); |
| var rightType = _dispatch(rightOperand)!; |
| _graph.connectDummy(rightType.node, DummyOrigin(source, node)); |
| bool notEqual = operatorType == TokenType.BANG_EQ; |
| _flowAnalysis!.equalityOperation_end(node, equalityInfo, |
| _flowAnalysis!.equalityOperand_end(rightOperand, rightType), |
| notEqual: notEqual); |
| |
| void buildNullConditionInfo(NullLiteral nullLiteral, |
| Expression otherOperand, NullabilityNode? otherNode) { |
| assert(nullLiteral != otherOperand); |
| // TODO(paulberry): only set falseChecksNonNull in unconditional |
| // control flow |
| // TODO(paulberry): figure out what the rules for isPure should be. |
| bool isPure = otherOperand is SimpleIdentifier; |
| var conditionInfo = _ConditionInfo(node, |
| isPure: isPure, |
| postDominatingIntent: _isReferenceInScope(otherOperand), |
| trueGuard: otherNode, |
| falseDemonstratesNonNullIntent: otherNode); |
| _conditionInfo = notEqual ? conditionInfo.not(node) : conditionInfo; |
| } |
| |
| if (rightOperand is NullLiteral) { |
| buildNullConditionInfo(rightOperand, leftOperand, leftType.node); |
| } else if (leftOperand is NullLiteral) { |
| buildNullConditionInfo(leftOperand, rightOperand, rightType.node); |
| } |
| |
| return _makeNonNullableBoolType(node); |
| } else if (operatorType == TokenType.AMPERSAND_AMPERSAND || |
| operatorType == TokenType.BAR_BAR) { |
| bool isAnd = operatorType == TokenType.AMPERSAND_AMPERSAND; |
| _flowAnalysis!.logicalBinaryOp_begin(); |
| _checkExpressionNotNull(leftOperand); |
| _flowAnalysis! |
| .logicalBinaryOp_rightBegin(node.leftOperand, node, isAnd: isAnd); |
| _postDominatedLocals.doScoped( |
| action: () => _checkExpressionNotNull(rightOperand)); |
| _flowAnalysis!.logicalBinaryOp_end(node, rightOperand, isAnd: isAnd); |
| return _makeNonNullableBoolType(node); |
| } else if (operatorType == TokenType.QUESTION_QUESTION) { |
| DecoratedType expressionType; |
| var leftType = _dispatch(leftOperand)!; |
| _flowAnalysis!.ifNullExpression_rightBegin(node.leftOperand, leftType); |
| try { |
| _guards.add(leftType.node); |
| DecoratedType? rightType; |
| _postDominatedLocals.doScoped(action: () { |
| rightType = _dispatch(rightOperand); |
| }); |
| var ifNullNode = NullabilityNode.forIfNotNull(node); |
| expressionType = _decorateUpperOrLowerBound( |
| node, node.staticType, leftType, rightType!, true, |
| node: ifNullNode); |
| _connect(rightType!.node, expressionType.node, |
| IfNullOrigin(source, node), null); |
| } finally { |
| _flowAnalysis!.ifNullExpression_end(); |
| _guards.removeLast(); |
| } |
| _variables!.recordDecoratedExpressionType(node, expressionType); |
| return expressionType; |
| } else if (operatorType.isUserDefinableOperator) { |
| var targetType = _checkExpressionNotNull(leftOperand); |
| var callee = node.staticElement; |
| if (callee == null) { |
| _dispatch(rightOperand); |
| return _makeNullableDynamicType(node); |
| } else { |
| var calleeType = getOrComputeElementType(node, callee, |
| targetType: targetType, targetExpression: leftOperand); |
| assert(calleeType.positionalParameters!.isNotEmpty); // TODO(paulberry) |
| _handleAssignment(rightOperand, |
| destinationType: calleeType.positionalParameters![0]); |
| return _fixNumericTypes(calleeType.returnType!, node.staticType); |
| } |
| } else { |
| // TODO(paulberry) |
| _dispatch(leftOperand); |
| _dispatch(rightOperand); |
| _unimplemented( |
| node, 'Binary expression with operator ${node.operator.lexeme}'); |
| } |
| } |
| |
| @override |
| DecoratedType visitBooleanLiteral(BooleanLiteral node) { |
| _flowAnalysis!.booleanLiteral(node, node.value); |
| return _makeNonNullLiteralType(node); |
| } |
| |
| @override |
| DecoratedType? visitBreakStatement(BreakStatement node) { |
| _flowAnalysis!.handleBreak(FlowAnalysisHelper.getLabelTarget( |
| node, node.label?.staticElement as LabelElement?)!); |
| // Later statements no longer post-dominate the declarations because we |
| // exited (or, in parent scopes, conditionally exited). |
| // TODO(mfairhurst): don't clear post-dominators beyond the current loop. |
| _postDominatedLocals.clearEachScope(); |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitCascadeExpression(CascadeExpression node) { |
| var oldCascadeTargetType = _currentCascadeTargetType; |
| try { |
| _currentCascadeTargetType = _checkExpressionNotNull(node.target); |
| _dispatchList(node.cascadeSections); |
| return _currentCascadeTargetType; |
| } finally { |
| _currentCascadeTargetType = oldCascadeTargetType; |
| } |
| } |
| |
| @override |
| DecoratedType? visitCatchClause(CatchClause node) { |
| _flowAnalysis!.tryCatchStatement_catchBegin( |
| node.exceptionParameter?.staticElement as PromotableElement?, |
| node.stackTraceParameter?.staticElement as PromotableElement?); |
| _dispatch(node.exceptionType); |
| // The catch clause may not execute, so create a new scope for |
| // post-dominators. |
| _postDominatedLocals.doScoped(action: () => _dispatch(node.body)); |
| _flowAnalysis!.tryCatchStatement_catchEnd(); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitClassDeclaration(ClassDeclaration node) { |
| visitClassOrMixinOrExtensionDeclaration(node); |
| _dispatch(node.extendsClause); |
| _dispatch(node.implementsClause); |
| _dispatch(node.withClause); |
| _dispatch(node.typeParameters); |
| return null; |
| } |
| |
| DecoratedType? visitClassOrMixinOrExtensionDeclaration( |
| CompilationUnitMember node) { |
| assert(node is ClassOrMixinDeclaration || node is ExtensionDeclaration); |
| try { |
| _currentClassOrExtension = node.declaredElement; |
| var members = node is ClassOrMixinDeclaration |
| ? node.members |
| : (node as ExtensionDeclaration).members; |
| |
| _fieldsNotInitializedAtDeclaration = { |
| for (var member in members) |
| if (member is FieldDeclaration && |
| _variables!.getLateHint(source, member.fields) == null) |
| for (var field in member.fields.variables) |
| if (!field.declaredElement!.isStatic && field.initializer == null) |
| (field.declaredElement as FieldElement?) |
| }; |
| if (_currentClassOrExtension is ClassElement && |
| (_currentClassOrExtension as ClassElement) |
| .unnamedConstructor |
| ?.isSynthetic == |
| true) { |
| _handleUninitializedFields(node, _fieldsNotInitializedAtDeclaration!); |
| } |
| _dispatchList(node.metadata); |
| _dispatchList(members); |
| _fieldsNotInitializedAtDeclaration = null; |
| } finally { |
| _currentClassOrExtension = null; |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitClassTypeAlias(ClassTypeAlias node) { |
| _dispatch(node.superclass); |
| _dispatch(node.implementsClause); |
| _dispatch(node.withClause); |
| var classElement = node.declaredElement!; |
| var supertype = classElement.supertype!; |
| var superElement = supertype.element; |
| for (var constructorElement in classElement.constructors) { |
| assert(constructorElement.isSynthetic); |
| var superConstructorElement = |
| superElement.getNamedConstructor(constructorElement.name)!; |
| var constructorDecoratedType = _variables! |
| .decoratedElementType(constructorElement) |
| .substitute(_decoratedClassHierarchy! |
| .getDecoratedSupertype(classElement, superElement) |
| .asSubstitution); |
| var superConstructorDecoratedType = |
| _variables!.decoratedElementType(superConstructorElement); |
| var origin = ImplicitMixinSuperCallOrigin(source, node); |
| _linkDecoratedTypeParameters( |
| constructorDecoratedType, superConstructorDecoratedType, origin, |
| isUnion: true); |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitComment(Comment node) { |
| // Ignore comments. |
| return null; |
| } |
| |
| @override |
| DecoratedType visitConditionalExpression(ConditionalExpression node) { |
| _flowAnalysis!.conditional_conditionBegin(); |
| _checkExpressionNotNull(node.condition); |
| NullabilityNode? trueGuard; |
| NullabilityNode? falseGuard; |
| if (identical(_conditionInfo?.condition, node.condition)) { |
| trueGuard = _conditionInfo!.trueGuard; |
| falseGuard = _conditionInfo!.falseGuard; |
| _variables!.recordConditionalDiscard(source, node, |
| ConditionalDiscard(trueGuard, falseGuard, _conditionInfo!.isPure)); |
| } |
| |
| DecoratedType? thenType; |
| DecoratedType? elseType; |
| |
| // Post-dominators diverge as we branch in the conditional. |
| // Note: we don't have to create a scope for each branch because they can't |
| // define variables. |
| _postDominatedLocals.doScoped(action: () { |
| _flowAnalysis!.conditional_thenBegin(node.condition, node); |
| if (trueGuard != null) { |
| _guards.add(trueGuard); |
| } |
| try { |
| thenType = _dispatch(node.thenExpression); |
| if (trueGuard != null) { |
| thenType = thenType!.withNode( |
| _nullabilityNodeForGLB(node, thenType!.node!, trueGuard)); |
| } |
| } finally { |
| if (trueGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| _flowAnalysis!.conditional_elseBegin(node.thenExpression); |
| if (falseGuard != null) { |
| _guards.add(falseGuard); |
| } |
| try { |
| elseType = _dispatch(node.elseExpression); |
| if (falseGuard != null) { |
| elseType = elseType!.withNode( |
| _nullabilityNodeForGLB(node, elseType!.node!, falseGuard)); |
| } |
| } finally { |
| if (falseGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| _flowAnalysis!.conditional_end(node, node.elseExpression); |
| }); |
| |
| var overallType = _decorateUpperOrLowerBound( |
| node, node.staticType, thenType!, elseType!, true); |
| _variables!.recordDecoratedExpressionType(node, overallType); |
| return overallType; |
| } |
| |
| @override |
| DecoratedType? visitConstructorDeclaration(ConstructorDeclaration node) { |
| _fieldsNotInitializedByConstructor = |
| _fieldsNotInitializedAtDeclaration!.toSet(); |
| _dispatch(node.redirectedConstructor?.type.typeArguments); |
| _handleExecutableDeclaration( |
| node, |
| node.declaredElement!, |
| node.metadata, |
| null, |
| node.parameters, |
| node.initializers, |
| node.body, |
| node.redirectedConstructor); |
| _fieldsNotInitializedByConstructor = null; |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitConstructorFieldInitializer( |
| ConstructorFieldInitializer node) { |
| _fieldsNotInitializedByConstructor!.remove(node.fieldName.staticElement); |
| _handleAssignment(node.expression, |
| destinationType: |
| getOrComputeElementType(node, node.fieldName.staticElement!)); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitContinueStatement(ContinueStatement node) { |
| _flowAnalysis!.handleContinue(FlowAnalysisHelper.getLabelTarget( |
| node, node.label?.staticElement as LabelElement?)!); |
| // Later statements no longer post-dominate the declarations because we |
| // exited (or, in parent scopes, conditionally exited). |
| // TODO(mfairhurst): don't clear post-dominators beyond the current loop. |
| _postDominatedLocals.clearEachScope(); |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitDefaultFormalParameter(DefaultFormalParameter node) { |
| _dispatch(node.parameter); |
| var defaultValue = node.defaultValue; |
| var declaredElement = node.declaredElement; |
| if (defaultValue == null) { |
| if (declaredElement!.hasRequired) { |
| // Nothing to do; the implicit default value of `null` will never be |
| // reached. |
| } else if (_variables!.getRequiredHint(source, node) != null) { |
| // Nothing to do; assume the implicit default value of `null` will never |
| // be reached. |
| } else { |
| var enclosingElement = declaredElement.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 { |
| _graph.makeNullable( |
| getOrComputeElementType(node, declaredElement).node!, |
| OptionalFormalParameterOrigin(source, node)); |
| } |
| } |
| } else { |
| _handleAssignment(defaultValue, |
| destinationType: getOrComputeElementType(node, declaredElement!), |
| fromDefaultValue: true); |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitDoStatement(DoStatement node) { |
| _flowAnalysis!.doStatement_bodyBegin(node); |
| _dispatch(node.body); |
| _flowAnalysis!.doStatement_conditionBegin(); |
| _checkExpressionNotNull(node.condition); |
| _flowAnalysis!.doStatement_end(node.condition); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitDoubleLiteral(DoubleLiteral node) { |
| return _makeNonNullLiteralType(node); |
| } |
| |
| @override |
| DecoratedType? visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| if (_currentFunctionType == null) { |
| _unimplemented( |
| node, |
| 'ExpressionFunctionBody with no current function ' |
| '(parent is ${node.parent.runtimeType})'); |
| } |
| _handleAssignment(node.expression, |
| destinationType: _currentFunctionType!.returnType, |
| wrapFuture: node.isAsynchronous); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitExpressionStatement(ExpressionStatement node) { |
| var decoratedType = _dispatch(node.expression)!; |
| _graph.connectDummy(decoratedType.node, DummyOrigin(source, node)); |
| return decoratedType; |
| } |
| |
| DecoratedType? visitExtensionDeclaration(ExtensionDeclaration node) { |
| _dispatch(node.typeParameters); |
| _dispatch(node.extendedType); |
| _currentExtendedType = |
| _variables!.decoratedTypeAnnotation(source, node.extendedType); |
| visitClassOrMixinOrExtensionDeclaration(node); |
| _currentExtendedType = null; |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitExtensionOverride(ExtensionOverride node) { |
| return _dispatch(node.argumentList.arguments.single); |
| } |
| |
| @override |
| DecoratedType? visitFieldFormalParameter(FieldFormalParameter node) { |
| _dispatchList(node.metadata); |
| _dispatch(node.parameters); |
| var parameterElement = node.declaredElement as FieldFormalParameterElement; |
| var parameterType = _variables!.decoratedElementType(parameterElement); |
| var field = parameterElement.field; |
| if (field != null) { |
| _fieldsNotInitializedByConstructor!.remove(field); |
| var fieldType = _variables!.decoratedElementType(field); |
| var origin = FieldFormalParameterOrigin(source, node); |
| if (node.type == null) { |
| _linkDecoratedTypes(parameterType, fieldType, origin, isUnion: false); |
| _checkAssignment(origin, FixReasonTarget.root, |
| source: fieldType, destination: parameterType, hard: false); |
| } else { |
| _dispatch(node.type); |
| _checkAssignment(origin, FixReasonTarget.root, |
| source: parameterType, destination: fieldType, hard: true); |
| } |
| } |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitForElement(ForElement node) { |
| _handleForLoopParts(node, node.forLoopParts, node.body, |
| (body) => _handleCollectionElement(body as CollectionElement)); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitForStatement(ForStatement node) { |
| _handleForLoopParts( |
| node, node.forLoopParts, node.body, (body) => _dispatch(body)); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitFunctionDeclaration(FunctionDeclaration node) { |
| _dispatchList(node.metadata); |
| _dispatch(node.returnType); |
| if (_flowAnalysis != null) { |
| // This is a local function. |
| var previousPostDominatedLocals = _postDominatedLocals; |
| var previousElementsWrittenToInLocalFunction = |
| _elementsWrittenToInLocalFunction; |
| try { |
| _elementsWrittenToInLocalFunction = {}; |
| _postDominatedLocals = ScopedSet<Element>(); |
| _flowAnalysis!.functionExpression_begin(node); |
| _dispatch(node.functionExpression); |
| _flowAnalysis!.functionExpression_end(); |
| } finally { |
| for (var element in _elementsWrittenToInLocalFunction!) { |
| previousElementsWrittenToInLocalFunction?.add(element); |
| previousPostDominatedLocals.removeFromAllScopes(element); |
| } |
| _elementsWrittenToInLocalFunction = |
| previousElementsWrittenToInLocalFunction; |
| _postDominatedLocals = previousPostDominatedLocals; |
| } |
| } else { |
| _createFlowAnalysis(node, node.functionExpression.parameters); |
| // Initialize a new postDominator scope that contains only the parameters. |
| try { |
| _dispatch(node.functionExpression); |
| _flowAnalysis!.finish(); |
| } finally { |
| _flowAnalysis = null; |
| _assignedVariables = null; |
| } |
| var declaredElement = node.declaredElement; |
| if (declaredElement is PropertyAccessorElement) { |
| if (declaredElement.isGetter) { |
| var setter = declaredElement.correspondingSetter; |
| if (setter != null) { |
| _handleGetterSetterCorrespondence( |
| node, null, declaredElement, setter.declaration); |
| } |
| } else { |
| assert(declaredElement.isSetter); |
| var getter = declaredElement.correspondingGetter; |
| if (getter != null) { |
| _handleGetterSetterCorrespondence( |
| node, null, getter.declaration, declaredElement); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitFunctionExpression(FunctionExpression node) { |
| // TODO(mfairhurst): enable edge builder "_insideFunction" hard edge tests. |
| _dispatch(node.parameters); |
| _dispatch(node.typeParameters); |
| if (node.parent is! FunctionDeclaration) { |
| _flowAnalysis!.functionExpression_begin(node); |
| } |
| _addParametersToFlowAnalysis(node.parameters); |
| var previousFunction = _currentFunctionExpression; |
| var previousFunctionType = _currentFunctionType; |
| var previousFieldFormals = _currentFieldFormals; |
| _currentFunctionExpression = node; |
| _currentFunctionType = |
| _variables!.decoratedElementType(node.declaredElement!); |
| _currentFieldFormals = const {}; |
| var previousPostDominatedLocals = _postDominatedLocals; |
| var previousElementsWrittenToInLocalFunction = |
| _elementsWrittenToInLocalFunction; |
| try { |
| if (node.parent is! FunctionDeclaration) { |
| _elementsWrittenToInLocalFunction = {}; |
| } |
| _postDominatedLocals = ScopedSet<Element>(); |
| _postDominatedLocals.doScoped( |
| elements: node.declaredElement!.parameters, |
| action: () => _dispatch(node.body)); |
| _variables!.recordDecoratedExpressionType(node, _currentFunctionType); |
| return _currentFunctionType; |
| } finally { |
| if (node.parent is! FunctionDeclaration) { |
| _flowAnalysis!.functionExpression_end(); |
| for (var element in _elementsWrittenToInLocalFunction!) { |
| previousElementsWrittenToInLocalFunction?.add(element); |
| previousPostDominatedLocals.removeFromAllScopes(element); |
| } |
| _elementsWrittenToInLocalFunction = |
| previousElementsWrittenToInLocalFunction; |
| } |
| _currentFunctionType = previousFunctionType; |
| _currentFieldFormals = previousFieldFormals; |
| _currentFunctionExpression = previousFunction; |
| _postDominatedLocals = previousPostDominatedLocals; |
| } |
| } |
| |
| @override |
| DecoratedType? visitFunctionExpressionInvocation( |
| FunctionExpressionInvocation node) { |
| final argumentList = node.argumentList; |
| final typeArguments = node.typeArguments; |
| _dispatch(typeArguments); |
| DecoratedType calleeType = _checkExpressionNotNull(node.function); |
| DecoratedType? result; |
| if (calleeType.type is FunctionType) { |
| result = _handleInvocationArguments(node, argumentList.arguments, |
| typeArguments, node.typeArgumentTypes, calleeType, null, |
| invokeType: node.staticInvokeType); |
| } else { |
| // Invocation of type `dynamic` or `Function`. |
| _dispatch(argumentList); |
| result = _makeNullableDynamicType(node); |
| } |
| return result; |
| } |
| |
| @override |
| DecoratedType? visitIfElement(IfElement node) { |
| _flowAnalysis!.ifStatement_conditionBegin(); |
| _checkExpressionNotNull(node.condition); |
| _flowAnalysis!.ifStatement_thenBegin(node.condition, node); |
| NullabilityNode? trueGuard; |
| NullabilityNode? falseGuard; |
| if (identical(_conditionInfo?.condition, node.condition)) { |
| trueGuard = _conditionInfo!.trueGuard; |
| falseGuard = _conditionInfo!.falseGuard; |
| _variables!.recordConditionalDiscard(source, node, |
| ConditionalDiscard(trueGuard, falseGuard, _conditionInfo!.isPure)); |
| } |
| if (trueGuard != null) { |
| _guards.add(trueGuard); |
| } |
| try { |
| _postDominatedLocals.doScoped( |
| action: () => _handleCollectionElement(node.thenElement)); |
| } finally { |
| if (trueGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| if (node.elseElement != null) { |
| _flowAnalysis!.ifStatement_elseBegin(); |
| if (falseGuard != null) { |
| _guards.add(falseGuard); |
| } |
| try { |
| _postDominatedLocals.doScoped( |
| action: () => _handleCollectionElement(node.elseElement)); |
| } finally { |
| if (falseGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| } |
| _flowAnalysis!.ifStatement_end(node.elseElement != null); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitIfStatement(IfStatement node) { |
| _flowAnalysis!.ifStatement_conditionBegin(); |
| _checkExpressionNotNull(node.condition); |
| NullabilityNode? trueGuard; |
| NullabilityNode? falseGuard; |
| if (identical(_conditionInfo?.condition, node.condition)) { |
| trueGuard = _conditionInfo!.trueGuard; |
| falseGuard = _conditionInfo!.falseGuard; |
| _variables!.recordConditionalDiscard(source, node, |
| ConditionalDiscard(trueGuard, falseGuard, _conditionInfo!.isPure)); |
| } |
| if (trueGuard != null) { |
| _guards.add(trueGuard); |
| } |
| try { |
| _flowAnalysis!.ifStatement_thenBegin(node.condition, node); |
| // We branched, so create a new scope for post-dominators. |
| _postDominatedLocals.doScoped( |
| action: () => _dispatch(node.thenStatement)); |
| } finally { |
| if (trueGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| if (falseGuard != null) { |
| _guards.add(falseGuard); |
| } |
| var elseStatement = node.elseStatement; |
| try { |
| if (elseStatement != null) { |
| _flowAnalysis!.ifStatement_elseBegin(); |
| // We branched, so create a new scope for post-dominators. |
| _postDominatedLocals.doScoped( |
| action: () => _dispatch(node.elseStatement)); |
| } |
| } finally { |
| _flowAnalysis!.ifStatement_end(elseStatement != null); |
| if (falseGuard != null) { |
| _guards.removeLast(); |
| } |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitImplicitCallReference(ImplicitCallReference node) { |
| return _handlePropertyAccessGeneralized( |
| node: node, |
| target: node.expression, |
| propertyName: 'call', |
| isNullAware: false, |
| isCascaded: false, |
| inSetterContext: false, |
| callee: node.staticElement); |
| } |
| |
| @override |
| DecoratedType? visitIndexExpression(IndexExpression node) { |
| DecoratedType? targetType; |
| var target = node.target; |
| if (node.isCascaded) { |
| targetType = _currentCascadeTargetType; |
| } else if (target != null) { |
| targetType = _checkExpressionNotNull(target); |
| } |
| var callee = getWriteOrReadElement(node); |
| DecoratedType? result; |
| if (callee == null) { |
| // Dynamic dispatch. The return type is `dynamic`. |
| // TODO(paulberry): would it be better to assume a return type of `Never` |
| // so that we don't unnecessarily propagate nullabilities everywhere? |
| result = _makeNullableDynamicType(node); |
| } else { |
| var calleeType = getOrComputeElementType(node, callee, |
| targetType: targetType, targetExpression: target); |
| // TODO(paulberry): substitute if necessary |
| _handleAssignment(node.index, |
| destinationType: calleeType.positionalParameters![0]); |
| if (node.inSetterContext()) { |
| result = calleeType.positionalParameters![1]; |
| } else { |
| result = calleeType.returnType; |
| } |
| } |
| return result; |
| } |
| |
| @override |
| DecoratedType visitInstanceCreationExpression( |
| InstanceCreationExpression node) { |
| var callee = node.constructorName.staticElement!; |
| var typeParameters = callee.enclosingElement.typeParameters; |
| Iterable<DartType?> typeArgumentTypes; |
| List<DecoratedType> decoratedTypeArguments; |
| var typeArguments = node.constructorName.type.typeArguments; |
| late List<EdgeOrigin> parameterEdgeOrigins; |
| var target = |
| NullabilityNodeTarget.text('constructed type').withCodeRef(node); |
| if (typeArguments != null) { |
| _dispatch(typeArguments); |
| typeArgumentTypes = typeArguments.arguments.map((t) => t.type); |
| decoratedTypeArguments = typeArguments.arguments |
| .map((t) => _variables!.decoratedTypeAnnotation(source, t)) |
| .toList(); |
| parameterEdgeOrigins = typeArguments.arguments |
| .map((typeAnn) => TypeParameterInstantiationOrigin(source, typeAnn)) |
| .toList(); |
| } else { |
| var staticType = node.staticType; |
| if (staticType is InterfaceType) { |
| typeArgumentTypes = staticType.typeArguments; |
| int index = 0; |
| decoratedTypeArguments = typeArgumentTypes.map((t) { |
| return DecoratedType.forImplicitType( |
| typeProvider, t, _graph, target.typeArgument(index++)); |
| }).toList(); |
| instrumentation?.implicitTypeArguments( |
| source, node, decoratedTypeArguments); |
| parameterEdgeOrigins = List.filled(typeArgumentTypes.length, |
| InferredTypeParameterInstantiationOrigin(source, node)); |
| } else { |
| // Note: this could happen if the code being migrated has errors. |
| typeArgumentTypes = const []; |
| decoratedTypeArguments = const []; |
| } |
| } |
| |
| if (node.staticType!.isDartCoreList && |
| callee.name == '' && |
| node.argumentList.arguments.length == 1) { |
| _graph.connect(_graph.always, decoratedTypeArguments[0].node!, |
| ListLengthConstructorOrigin(source, node)); |
| } |
| |
| var nullabilityNode = NullabilityNode.forInferredType(target); |
| _graph.makeNonNullable( |
| nullabilityNode, InstanceCreationOrigin(source, node)); |
| var createdType = DecoratedType(node.staticType, nullabilityNode, |
| typeArguments: decoratedTypeArguments); |
| var calleeType = |
| getOrComputeElementType(node, callee, targetType: createdType); |
| for (var i = 0; i < decoratedTypeArguments.length; ++i) { |
| _checkAssignment(parameterEdgeOrigins.elementAt(i), |
| FixReasonTarget.root.typeArgument(i), |
| source: decoratedTypeArguments[i], |
| destination: |
| _variables!.decoratedTypeParameterBound(typeParameters[i])!, |
| hard: true); |
| } |
| _handleInvocationArguments(node, node.argumentList.arguments, typeArguments, |
| typeArgumentTypes, calleeType, typeParameters); |
| return createdType; |
| } |
| |
| @override |
| DecoratedType visitIntegerLiteral(IntegerLiteral node) { |
| return _makeNonNullLiteralType(node); |
| } |
| |
| @override |
| DecoratedType visitIsExpression(IsExpression node) { |
| var expression = node.expression; |
| var expressionNode = _dispatch(expression)!.node; |
| var type = node.type; |
| _dispatch(type); |
| var decoratedType = _variables!.decoratedTypeAnnotation(source, type); |
| // The main type of the is check historically could not be nullable. |
| // Making it nullable could change runtime behavior. |
| _graph.makeNonNullable( |
| decoratedType.node, IsCheckMainTypeOrigin(source, type)); |
| _conditionInfo = _ConditionInfo(node, |
| isPure: expression is SimpleIdentifier, |
| postDominatingIntent: _isReferenceInScope(expression), |
| trueDemonstratesNonNullIntent: expressionNode); |
| if (node.notOperator != null) { |
| _conditionInfo = _conditionInfo!.not(node); |
| } |
| if (!_assumeNonNullabilityInCasts) { |
| // TODO(mfairhurst): wire this to handleDowncast if we do not assume |
| // nullability. |
| assert(false); |
| } |
| _flowAnalysis!.isExpression_end( |
| node, expression, node.notOperator != null, decoratedType); |
| return _makeNonNullableBoolType(node); |
| } |
| |
| @override |
| DecoratedType? visitLabel(Label node) { |
| // Labels are identifiers but they don't have types so we don't need to |
| // visit them directly. |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitLibraryDirective(LibraryDirective node) { |
| // skip directives, but not their metadata |
| _dispatchList(node.metadata); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitListLiteral(ListLiteral node) { |
| final previousLiteralType = _currentLiteralElementType; |
| try { |
| var listType = node.staticType as InterfaceType?; |
| if (node.typeArguments == null) { |
| var target = |
| NullabilityNodeTarget.text('list element type').withCodeRef(node); |
| var elementType = DecoratedType.forImplicitType( |
| typeProvider, listType!.typeArguments[0], _graph, target); |
| instrumentation?.implicitTypeArguments(source, node, [elementType]); |
| _currentLiteralElementType = elementType; |
| } else { |
| _dispatch(node.typeArguments); |
| _currentLiteralElementType = _variables! |
| .decoratedTypeAnnotation(source, node.typeArguments!.arguments[0]); |
| } |
| node.elements.forEach(_handleCollectionElement); |
| return _makeNonNullLiteralType(node, |
| typeArguments: [_currentLiteralElementType]); |
| } finally { |
| _currentLiteralElementType = previousLiteralType; |
| } |
| } |
| |
| @override |
| DecoratedType? visitMapLiteralEntry(MapLiteralEntry node) { |
| assert(_currentMapKeyType != null); |
| assert(_currentMapValueType != null); |
| _handleAssignment(node.key, destinationType: _currentMapKeyType); |
| _handleAssignment(node.value, destinationType: _currentMapValueType); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitMethodDeclaration(MethodDeclaration node) { |
| if (BuiltValueTransformer.findNullableAnnotation(node) != null) { |
| _graph.makeNullable( |
| _variables! |
| .decoratedElementType(node.declaredElement!.declaration) |
| .returnType! |
| .node!, |
| BuiltValueNullableOrigin(source, node)); |
| } |
| _handleExecutableDeclaration(node, node.declaredElement!, node.metadata, |
| node.returnType, node.parameters, null, node.body, null); |
| _dispatch(node.typeParameters); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitMethodInvocation(MethodInvocation node) { |
| DecoratedType? targetType; |
| var target = node.target; |
| bool isNullAware = node.isNullAware; |
| var callee = node.methodName.staticElement; |
| bool calleeIsStatic = callee is ExecutableElement && callee.isStatic; |
| _dispatch(node.typeArguments); |
| |
| if (node.isCascaded) { |
| targetType = _currentCascadeTargetType; |
| } else if (target != null) { |
| if (_isPrefix(target)) { |
| // Nothing to do. |
| } else if (calleeIsStatic) { |
| _dispatch(target); |
| } else if (isNullAware) { |
| targetType = _dispatch(target); |
| } else { |
| targetType = _handleTarget(target, node.methodName.name, callee); |
| } |
| } else if (target == null && callee!.enclosingElement is ClassElement) { |
| targetType = _thisOrSuper(node); |
| _checkThisNotNull(targetType, node); |
| } |
| DecoratedType? expressionType; |
| DecoratedType? calleeType; |
| if (targetType != null && |
| targetType.type is FunctionType && |
| node.methodName.name == 'call') { |
| // If `X` has a function type, then in the expression `X.call()`, the |
| // function being called is `X` itself, so the callee type is simply the |
| // type of `X`. |
| calleeType = targetType; |
| } else if (callee != null) { |
| calleeType = getOrComputeElementType(node, callee, |
| targetType: targetType, targetExpression: target); |
| if (callee is PropertyAccessorElement) { |
| calleeType = calleeType.returnType; |
| } |
| } |
| if (calleeType == null) { |
| // Dynamic dispatch. The return type is `dynamic`. |
| // TODO(paulberry): would it be better to assume a return type of `Never` |
| // so that we don't unnecessarily propagate nullabilities everywhere? |
| _dispatch(node.argumentList); |
| expressionType = _makeNullableDynamicType(node); |
| } else { |
| expressionType = _handleInvocationArguments( |
| node, |
| node.argumentList.arguments, |
| node.typeArguments, |
| node.typeArgumentTypes, |
| calleeType, |
| null, |
| invokeType: node.staticInvokeType); |
| // Do any deferred processing for this method invocation. |
| var deferredProcessing = _deferredMethodInvocationProcessing.remove(node); |
| if (deferredProcessing != null) { |
| expressionType = deferredProcessing(expressionType); |
| } |
| if (isNullAware) { |
| expressionType = expressionType!.withNode( |
| NullabilityNode.forLUB(targetType!.node, expressionType.node)); |
| } |
| _variables!.recordDecoratedExpressionType(node, expressionType); |
| } |
| _handleCustomCheckNotNull(node); |
| _handleQuiverCheckNotNull(node); |
| return expressionType; |
| } |
| |
| @override |
| DecoratedType? visitMixinDeclaration(MixinDeclaration node) { |
| visitClassOrMixinOrExtensionDeclaration(node); |
| _dispatch(node.implementsClause); |
| _dispatch(node.onClause); |
| _dispatch(node.typeParameters); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitNamedType(NamedType node) { |
| try { |
| _typeNameNesting++; |
| var typeArguments = node.typeArguments?.arguments; |
| var element = node.name.staticElement; |
| if (element is TypeAliasElement) { |
| var aliasedElement = |
| element.aliasedElement as GenericFunctionTypeElement; |
| final typedefType = _variables!.decoratedElementType(aliasedElement); |
| final typeNameType = _variables!.decoratedTypeAnnotation(source, node); |
| |
| Map<TypeParameterElement, DecoratedType> substitutions; |
| if (node.typeArguments == null) { |
| // TODO(mfairhurst): substitute instantiations to bounds |
| substitutions = {}; |
| } else { |
| substitutions = |
| Map<TypeParameterElement, DecoratedType>.fromIterables( |
| element.typeParameters, |
| node.typeArguments!.arguments.map( |
| (t) => _variables!.decoratedTypeAnnotation(source, t))); |
| } |
| |
| final decoratedType = typedefType.substitute(substitutions); |
| final origin = TypedefReferenceOrigin(source, node); |
| _linkDecoratedTypeParameters(decoratedType, typeNameType, origin, |
| isUnion: true); |
| _linkDecoratedTypes( |
| decoratedType.returnType!, typeNameType.returnType, origin, |
| isUnion: true); |
| } else if (element is TypeParameterizedElement) { |
| if (typeArguments == null) { |
| var instantiatedType = |
| _variables!.decoratedTypeAnnotation(source, node); |
| var origin = InstantiateToBoundsOrigin(source, node); |
| for (int i = 0; i < instantiatedType.typeArguments.length; i++) { |
| _linkDecoratedTypes( |
| instantiatedType.typeArguments[i]!, |
| _variables! |
| .decoratedTypeParameterBound(element.typeParameters[i]), |
| origin, |
| isUnion: false); |
| } |
| } else { |
| for (int i = 0; i < typeArguments.length; i++) { |
| DecoratedType? bound; |
| bound = _variables! |
| .decoratedTypeParameterBound(element.typeParameters[i]); |
| assert(bound != null); |
| var argumentType = |
| _variables!.decoratedTypeAnnotation(source, typeArguments[i]); |
| _checkAssignment( |
| TypeParameterInstantiationOrigin(source, typeArguments[i]), |
| FixReasonTarget.root, |
| source: argumentType, |
| destination: bound!, |
| hard: true); |
| } |
| } |
| } |
| node.visitChildren(this); |
| // If the type name is followed by a `/*!*/` comment, it is considered to |
| // apply to the type and not to the "as" expression. In order to prevent |
| // a future call to _handleNullCheck from interpreting it as applying to |
| // the "as" expression, we need to store the `/*!*/` comment in |
| // _nullCheckHints. |
| var token = node.endToken; |
| _nullCheckHints[token] = getPostfixHint(token); |
| namedTypeVisited(node); // Note this has been visited to TypeNameTracker. |
| return null; |
| } finally { |
| _typeNameNesting--; |
| } |
| } |
| |
| @override |
| DecoratedType? visitNamespaceDirective(NamespaceDirective node) { |
| // skip directives, but not their metadata |
| _dispatchList(node.metadata); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitNode(AstNode node) { |
| for (var child in node.childEntities) { |
| if (child is AstNode) { |
| _dispatch(child); |
| } |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType visitNullLiteral(NullLiteral node) { |
| _flowAnalysis!.nullLiteral(node); |
| var target = NullabilityNodeTarget.text('null literal').withCodeRef(node); |
| var decoratedType = DecoratedType.forImplicitType( |
| typeProvider, node.staticType, _graph, target); |
| _graph.makeNullable(decoratedType.node!, LiteralOrigin(source, node)); |
| return decoratedType; |
| } |
| |
| @override |
| DecoratedType? visitParenthesizedExpression(ParenthesizedExpression node) { |
| var result = _dispatch(node.expression); |
| _flowAnalysis!.parenthesizedExpression(node, node.expression); |
| return result; |
| } |
| |
| @override |
| DecoratedType? visitPartOfDirective(PartOfDirective node) { |
| // skip directives, but not their metadata |
| _dispatchList(node.metadata); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitPostfixExpression(PostfixExpression node) { |
| if (node.operator.type.isIncrementOperator) { |
| var operand = node.operand; |
| var targetType = _checkExpressionNotNull(operand); |
| var callee = node.staticElement; |
| DecoratedType writeType; |
| if (callee == null) { |
| // Dynamic dispatch. The return type is `dynamic`. |
| // TODO(paulberry): would it be better to assume a return type of `Never` |
| // so that we don't unnecessarily propagate nullabilities everywhere? |
| writeType = _makeNullableDynamicType(node); |
| } else { |
| var calleeType = getOrComputeElementType(node, callee, |
| targetType: targetType, targetExpression: operand); |
| writeType = _fixNumericTypes(calleeType.returnType!, node.staticType); |
| } |
| if (operand is SimpleIdentifier) { |
| var element = getWriteOrReadElement(operand); |
| if (element is PromotableElement) { |
| _flowAnalysis!.write(node, element, writeType, null); |
| } |
| } |
| return targetType; |
| } |
| _unimplemented( |
| node, 'Postfix expression with operator ${node.operator.lexeme}'); |
| } |
| |
| @override |
| DecoratedType? visitPrefixedIdentifier(PrefixedIdentifier node) { |
| if (node.prefix.staticElement is ImportElement) { |
| // TODO(paulberry) |
| _unimplemented(node, 'PrefixedIdentifier with a prefix'); |
| } else { |
| return _handlePropertyAccess( |
| node, node.prefix, node.identifier, false, false); |
| } |
| } |
| |
| @override |
| DecoratedType? visitPrefixExpression(PrefixExpression node) { |
| var operand = node.operand; |
| var targetType = _checkExpressionNotNull(operand); |
| var operatorType = node.operator.type; |
| if (operatorType == TokenType.BANG) { |
| _flowAnalysis!.logicalNot_end(node, operand); |
| return _makeNonNullableBoolType(node); |
| } else { |
| var callee = node.staticElement; |
| var isIncrementOrDecrement = operatorType.isIncrementOperator; |
| DecoratedType? staticType; |
| if (callee == null) { |
| // Dynamic dispatch. The return type is `dynamic`. |
| // TODO(paulberry): would it be better to assume a return type of `Never` |
| // so that we don't unnecessarily propagate nullabilities everywhere? |
| staticType = _makeNullableDynamicType(node); |
| } else { |
| var calleeType = getOrComputeElementType(node, callee, |
| targetType: targetType, targetExpression: operand); |
| if (isIncrementOrDecrement) { |
| staticType = |
| _fixNumericTypes(calleeType.returnType!, node.staticType); |
| } else { |
| staticType = _handleInvocationArguments( |
| node, [], null, null, calleeType, null); |
| } |
| } |
| if (isIncrementOrDecrement) { |
| if (operand is SimpleIdentifier) { |
| var element = getWriteOrReadElement(operand); |
| if (element is PromotableElement) { |
| _flowAnalysis!.write(node, element, staticType!, null); |
| } |
| } |
| } |
| return staticType; |
| } |
| } |
| |
| @override |
| DecoratedType? visitPropertyAccess(PropertyAccess node) { |
| return _handlePropertyAccess(node, node.target, node.propertyName, |
| node.isNullAware, node.isCascaded); |
| } |
| |
| @override |
| DecoratedType? visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| var callee = node.staticElement!; |
| var calleeType = _variables!.decoratedElementType(callee); |
| _handleInvocationArguments( |
| node, node.argumentList.arguments, null, null, calleeType, null); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitRethrowExpression(RethrowExpression node) { |
| _flowAnalysis!.handleExit(); |
| var target = |
| NullabilityNodeTarget.text('rethrow expression').withCodeRef(node); |
| var nullabilityNode = NullabilityNode.forInferredType(target); |
| _graph.makeNonNullable(nullabilityNode, ThrowOrigin(source, node)); |
| return DecoratedType(node.staticType, nullabilityNode); |
| } |
| |
| @override |
| DecoratedType? visitReturnStatement(ReturnStatement node) { |
| DecoratedType? returnType = _currentFunctionType!.returnType; |
| Expression? returnValue = node.expression; |
| var functionBody = node.thisOrAncestorOfType<FunctionBody>()!; |
| if (functionBody.isGenerator) { |
| // Do not connect the return value to the return type. |
| return _dispatch(returnValue); |
| } |
| final isAsync = functionBody.isAsynchronous; |
| if (returnValue == null) { |
| var target = |
| NullabilityNodeTarget.text('implicit null return').withCodeRef(node); |
| var implicitNullType = DecoratedType.forImplicitType( |
| typeProvider, typeProvider.nullType, _graph, target); |
| var origin = ImplicitNullReturnOrigin(source, node); |
| _graph.makeNullable(implicitNullType.node!, origin); |
| _checkAssignment(origin, FixReasonTarget.root, |
| source: |
| isAsync ? _futureOf(implicitNullType, node) : implicitNullType, |
| destination: returnType!, |
| hard: false); |
| } else { |
| _handleAssignment(returnValue, |
| destinationType: returnType, wrapFuture: isAsync); |
| } |
| |
| _flowAnalysis!.handleExit(); |
| // Later statements no longer post-dominate the declarations because we |
| // exited (or, in parent scopes, conditionally exited). |
| // TODO(mfairhurst): don't clear post-dominators beyond the current function. |
| _postDominatedLocals.clearEachScope(); |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType visitSetOrMapLiteral(SetOrMapLiteral node) { |
| var setOrMapType = node.staticType as InterfaceType?; |
| var typeArguments = node.typeArguments?.arguments; |
| |
| if (node.isSet) { |
| final previousLiteralType = _currentLiteralElementType; |
| try { |
| if (typeArguments == null) { |
| assert(setOrMapType!.typeArguments.length == 1); |
| var target = |
| NullabilityNodeTarget.text('set element type').withCodeRef(node); |
| var elementType = DecoratedType.forImplicitType( |
| typeProvider, setOrMapType!.typeArguments[0], _graph, target); |
| instrumentation?.implicitTypeArguments(source, node, [elementType]); |
| _currentLiteralElementType = elementType; |
| } else { |
| assert(typeArguments.length == 1); |
| _dispatch(node.typeArguments); |
| _currentLiteralElementType = |
| _variables!.decoratedTypeAnnotation(source, typeArguments[0]); |
| } |
| node.elements.forEach(_handleCollectionElement); |
| return _makeNonNullLiteralType(node, |
| typeArguments: [_currentLiteralElementType]); |
| } finally { |
| _currentLiteralElementType = previousLiteralType; |
| } |
| } else { |
| assert(node.isMap); |
| |
| final previousKeyType = _currentMapKeyType; |
| final previousValueType = _currentMapValueType; |
| try { |
| if (typeArguments == null) { |
| assert(setOrMapType!.typeArguments.length == 2); |
| var targetKey = |
| NullabilityNodeTarget.text('map key type').withCodeRef(node); |
| var keyType = DecoratedType.forImplicitType( |
| typeProvider, setOrMapType!.typeArguments[0], _graph, targetKey); |
| _currentMapKeyType = keyType; |
| var targetValue = |
| NullabilityNodeTarget.text('map value type').withCodeRef(node); |
| var valueType = DecoratedType.forImplicitType( |
| typeProvider, setOrMapType.typeArguments[1], _graph, targetValue); |
| _currentMapValueType = valueType; |
| instrumentation |
| ?.implicitTypeArguments(source, node, [keyType, valueType]); |
| } else { |
| assert(typeArguments.length == 2); |
| _dispatch(node.typeArguments); |
| _currentMapKeyType = |
| _variables!.decoratedTypeAnnotation(source, typeArguments[0]); |
| _currentMapValueType = |
| _variables!.decoratedTypeAnnotation(source, typeArguments[1]); |
| } |
| |
| node.elements.forEach(_handleCollectionElement); |
| return _makeNonNullLiteralType(node, |
| typeArguments: [_currentMapKeyType, _currentMapValueType]); |
| } finally { |
| _currentMapKeyType = previousKeyType; |
| _currentMapValueType = previousValueType; |
| } |
| } |
| } |
| |
| @override |
| DecoratedType? visitSimpleIdentifier(SimpleIdentifier node) { |
| DecoratedType? targetType; |
| DecoratedType? result; |
| var staticElement = _favorFieldFormalElements(getWriteOrReadElement(node)); |
| if (staticElement is PromotableElement) { |
| if (!node.inDeclarationContext()) { |
| var promotedType = _flowAnalysis!.variableRead(node, staticElement); |
| if (promotedType != null) return promotedType; |
| } |
| var type = getOrComputeElementType(node, staticElement); |
| if (!node.inDeclarationContext() && |
| node.inGetterContext() && |
| !_lateHintedLocals.contains(staticElement) && |
| !_flowAnalysis!.isAssigned(staticElement)) { |
| _graph.makeNullable(type.node!, UninitializedReadOrigin(source, node)); |
| } |
| result = type; |
| } else if (staticElement is FunctionElement || |
| staticElement is MethodElement || |
| staticElement is ConstructorElement) { |
| if (staticElement!.enclosingElement is ClassElement) { |
| targetType = _thisOrSuper(node); |
| } |
| result = |
| getOrComputeElementType(node, staticElement, targetType: targetType); |
| } else if (staticElement is PropertyAccessorElement) { |
| if (staticElement.enclosingElement is ClassElement) { |
| targetType = _thisOrSuper(node); |
| } |
| var elementType = |
| getOrComputeElementType(node, staticElement, targetType: targetType); |
| result = staticElement.isGetter |
| ? elementType.returnType |
| : elementType.positionalParameters![0]; |
| } else if (staticElement is TypeDefiningElement) { |
| result = _makeNonNullLiteralType(node); |
| } else if (staticElement is ExtensionElement) { |
| result = _makeNonNullLiteralType(node); |
| } else if (staticElement == null) { |
| assert(node.toString() == 'void', "${node.toString()} != 'void'"); |
| result = _makeNullableVoidType(node); |
| } else if (staticElement.enclosingElement is ClassElement && |
| (staticElement.enclosingElement as ClassElement).isEnum) { |
| result = getOrComputeElementType(node, staticElement); |
| } else { |
| // TODO(paulberry) |
| _unimplemented(node, |
| 'Simple identifier with a static element of type ${staticElement.runtimeType}'); |
| } |
| if (targetType != null) { |
| _checkThisNotNull(targetType, node); |
| } |
| return result; |
| } |
| |
| @override |
| DecoratedType? visitSpreadElement(SpreadElement node) { |
| final spreadType = node.expression.staticType!; |
| DecoratedType? spreadTypeDecorated; |
| var target = |
| NullabilityNodeTarget.text('spread element type').withCodeRef(node); |
| if (_typeSystem.isSubtypeOf(spreadType, typeProvider.mapObjectObjectType)) { |
| assert(_currentMapKeyType != null && _currentMapValueType != null); |
| final expectedType = typeProvider.mapType( |
| _currentMapKeyType!.type!, _currentMapValueType!.type!); |
| final expectedDecoratedType = DecoratedType.forImplicitType( |
| typeProvider, expectedType, _graph, target, |
| typeArguments: [_currentMapKeyType, _currentMapValueType]); |
| |
| spreadTypeDecorated = _handleAssignment(node.expression, |
| destinationType: expectedDecoratedType); |
| } else if (_typeSystem.isSubtypeOf( |
| spreadType, typeProvider.iterableDynamicType)) { |
| assert(_currentLiteralElementType != null); |
| final expectedType = |
| typeProvider.iterableType(_currentLiteralElementType!.type!); |
| final expectedDecoratedType = DecoratedType.forImplicitType( |
| typeProvider, expectedType, _graph, target, |
| typeArguments: [_currentLiteralElementType]); |
| |
| spreadTypeDecorated = _handleAssignment(node.expression, |
| destinationType: expectedDecoratedType); |
| } else { |
| // Downcast. We can't assume nullability here, so do nothing. |
| } |
| |
| if (!node.isNullAware) { |
| _checkExpressionNotNull(node.expression, sourceType: spreadTypeDecorated); |
| } |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType visitStringLiteral(StringLiteral node) { |
| node.visitChildren(this); |
| return _makeNonNullLiteralType(node); |
| } |
| |
| @override |
| DecoratedType? visitSuperConstructorInvocation( |
| SuperConstructorInvocation node) { |
| var callee = node.staticElement!; |
| var target = NullabilityNodeTarget.text('super constructor invocation') |
| .withCodeRef(node); |
| var nullabilityNode = NullabilityNode.forInferredType(target); |
| var class_ = node.thisOrAncestorOfType<ClassDeclaration>()!; |
| var decoratedSupertype = _decoratedClassHierarchy!.getDecoratedSupertype( |
| class_.declaredElement!, callee.enclosingElement); |
| var typeArguments = decoratedSupertype.typeArguments; |
| Iterable<DartType?> typeArgumentTypes; |
| typeArgumentTypes = typeArguments.map((t) => t!.type); |
| var createdType = DecoratedType(callee.returnType, nullabilityNode, |
| typeArguments: typeArguments); |
| var calleeType = |
| getOrComputeElementType(node, callee, targetType: createdType); |
| var constructorTypeParameters = callee.enclosingElement.typeParameters; |
| |
| _handleInvocationArguments( |
| node, |
| node.argumentList.arguments, |
| null /*typeArguments*/, |
| typeArgumentTypes, |
| calleeType, |
| constructorTypeParameters); |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitSuperExpression(SuperExpression node) { |
| return _thisOrSuper(node); |
| } |
| |
| @override |
| DecoratedType? visitSwitchStatement(SwitchStatement node) { |
| _dispatch(node.expression); |
| _flowAnalysis!.switchStatement_expressionEnd(node); |
| var hasDefault = false; |
| for (var member in node.members) { |
| _postDominatedLocals.doScoped(action: () { |
| var hasLabel = member.labels.isNotEmpty; |
| _flowAnalysis!.switchStatement_beginCase(hasLabel, node); |
| if (member is SwitchCase) { |
| _dispatch(member.expression); |
| } else { |
| hasDefault = true; |
| } |
| _dispatchList(member.statements); |
| }); |
| } |
| _flowAnalysis!.switchStatement_end(hasDefault); |
| return null; |
| } |
| |
| @override |
| DecoratedType visitSymbolLiteral(SymbolLiteral node) { |
| return _makeNonNullLiteralType(node); |
| } |
| |
| @override |
| DecoratedType? visitThisExpression(ThisExpression node) { |
| return _thisOrSuper(node); |
| } |
| |
| @override |
| DecoratedType visitThrowExpression(ThrowExpression node) { |
| _dispatch(node.expression); |
| // TODO(paulberry): do we need to check the expression type? I think not. |
| _flowAnalysis!.handleExit(); |
| var target = |
| NullabilityNodeTarget.text('throw expression').withCodeRef(node); |
| var nullabilityNode = NullabilityNode.forInferredType(target); |
| _graph.makeNonNullable(nullabilityNode, ThrowOrigin(source, node)); |
| return DecoratedType(node.staticType, nullabilityNode); |
| } |
| |
| @override |
| DecoratedType? visitTryStatement(TryStatement node) { |
| var finallyBlock = node.finallyBlock; |
| if (finallyBlock != null) { |
| _flowAnalysis!.tryFinallyStatement_bodyBegin(); |
| } |
| var catchClauses = node.catchClauses; |
| if (catchClauses.isNotEmpty) { |
| _flowAnalysis!.tryCatchStatement_bodyBegin(); |
| } |
| var body = node.body; |
| _dispatch(body); |
| if (catchClauses.isNotEmpty) { |
| _flowAnalysis!.tryCatchStatement_bodyEnd(body); |
| _dispatchList(catchClauses); |
| _flowAnalysis!.tryCatchStatement_end(); |
| } |
| if (finallyBlock != null) { |
| _flowAnalysis!.tryFinallyStatement_finallyBegin( |
| catchClauses.isNotEmpty ? node : body); |
| _dispatch(finallyBlock); |
| _flowAnalysis!.tryFinallyStatement_end(); |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitVariableDeclarationList(VariableDeclarationList node) { |
| var parent = node.parent; |
| bool isTopLevel = |
| parent is FieldDeclaration || parent is TopLevelVariableDeclaration; |
| _dispatchList(node.metadata); |
| _dispatch(node.type); |
| for (var variable in node.variables) { |
| _dispatchList(variable.metadata); |
| var initializer = variable.initializer; |
| var declaredElement = variable.declaredElement!; |
| if (isTopLevel) { |
| assert(_flowAnalysis == null); |
| _createFlowAnalysis(variable, null); |
| } else { |
| assert(_flowAnalysis != null); |
| if (declaredElement is PromotableElement && |
| _variables!.getLateHint(source, node) != null) { |
| _lateHintedLocals.add(declaredElement); |
| } |
| } |
| var type = _variables!.decoratedElementType(declaredElement); |
| var enclosingElement = declaredElement.enclosingElement; |
| if (!declaredElement.isStatic && enclosingElement is ClassElement) { |
| var overriddenElements = _inheritanceManager.getOverridden2( |
| enclosingElement, |
| Name(enclosingElement.library.source.uri, declaredElement.name)); |
| for (var overriddenElement |
| in overriddenElements ?? <ExecutableElement>[]) { |
| _handleFieldOverriddenDeclaration( |
| variable, type, enclosingElement, overriddenElement); |
| } |
| if (!declaredElement.isFinal) { |
| var overriddenElements = _inheritanceManager.getOverridden2( |
| enclosingElement, |
| Name(enclosingElement.library.source.uri, |
| '${declaredElement.name}=')); |
| for (var overriddenElement |
| in overriddenElements ?? <ExecutableElement>[]) { |
| _handleFieldOverriddenDeclaration( |
| variable, type, enclosingElement, overriddenElement); |
| } |
| } |
| } |
| try { |
| if (declaredElement is PromotableElement) { |
| _flowAnalysis!.declare(declaredElement, initializer != null); |
| } |
| if (initializer == null) { |
| // For top level variables and static fields, we have to generate an |
| // implicit assignment of `null`. For instance fields, this is done |
| // when processing constructors. For local variables, this is done |
| // when processing variable reads (only if flow analysis indicates |
| // the variable isn't definitely assigned). |
| if (isTopLevel && |
| _variables!.getLateHint(source, node) == null && |
| !(declaredElement is FieldElement && !declaredElement.isStatic)) { |
| _graph.makeNullable( |
| type.node!, ImplicitNullInitializerOrigin(source, node)); |
| } |
| } else { |
| _handleAssignment(initializer, destinationType: type); |
| } |
| if (isTopLevel) { |
| _flowAnalysis!.finish(); |
| } |
| } finally { |
| if (isTopLevel) { |
| _flowAnalysis = null; |
| _assignedVariables = null; |
| } |
| } |
| } |
| |
| // Track post-dominators, except we cannot make hard edges to multi |
| // declarations. Consider: |
| // |
| // int? x = null, y = 0; |
| // y.toDouble(); |
| // |
| // We cannot make a hard edge from y to never in this case. |
| if (node.variables.length == 1) { |
| _postDominatedLocals.add(node.variables.single.declaredElement!); |
| } |
| |
| return null; |
| } |
| |
| @override |
| DecoratedType? visitWhileStatement(WhileStatement node) { |
| // Note: we do not create guards. A null check here is *very* unlikely to be |
| // unnecessary after analysis. |
| _flowAnalysis!.whileStatement_conditionBegin(node); |
| _checkExpressionNotNull(node.condition); |
| _flowAnalysis!.whileStatement_bodyBegin(node, node.condition); |
| _postDominatedLocals.doScoped(action: () => _dispatch(node.body)); |
| _flowAnalysis!.whileStatement_end(); |
| return null; |
| } |
| |
| void _addParametersToFlowAnalysis(FormalParameterList? parameters) { |
| if (parameters != null) { |
| for (var parameter in parameters.parameters) { |
| _flowAnalysis!.declare(parameter.declaredElement!, true); |
| } |
| } |
| } |
| |
| /// Visits [expression] and generates the appropriate edge to assert that its |
| /// value is non-null. |
| /// |
| /// Returns the decorated type of [expression]. |
| DecoratedType _checkExpressionNotNull(Expression expression, |
| {DecoratedType? sourceType}) { |
| if (_isPrefix(expression)) { |
| throw ArgumentError('cannot check non-nullability of a prefix'); |
| } |
| sourceType ??= _dispatch(expression); |
| if (sourceType == null) { |
| throw StateError('No type computed for ${expression.runtimeType} ' |
| '(${expression.toSource()}) offset=${expression.offset}'); |
| } |
| var origin = _makeEdgeOrigin(sourceType, expression); |
| var hard = _shouldUseHardEdge(expression); |
| var edge = _graph.makeNonNullable(sourceType.node, origin, |
| hard: hard, guards: _guards); |
| if (origin is ExpressionChecksOrigin) { |
| origin.checks.edges[FixReasonTarget.root] = edge; |
| } |
| return sourceType; |
| } |
| |
| /// Generates the appropriate edge to assert that the value of `this` is |
| /// non-null. |
| void _checkThisNotNull(DecoratedType? thisType, AstNode node) { |
| // `this` can only be `null` in extensions, so if we're not in an extension, |
| // there's nothing to do. |
| if (_currentExtendedType == null) return; |
| var origin = ImplicitThisOrigin(source, node); |
| var hard = _postDominatedLocals.isInScope(_extensionThis); |
| _graph.makeNonNullable(thisType!.node, origin, hard: hard, guards: _guards); |
| } |
| |
| /// Computes the map to be stored in [_currentFieldFormals] while visiting the |
| /// constructor having the given [constructorElement]. |
| Map<PropertyAccessorElement, FieldFormalParameterElement> |
| _computeFieldFormalMap(ConstructorElement constructorElement) { |
| var result = <PropertyAccessorElement, FieldFormalParameterElement>{}; |
| for (var parameter in constructorElement.parameters) { |
| if (parameter is FieldFormalParameterElement) { |
| var getter = parameter.field?.getter; |
| if (getter != null) { |
| result[getter] = parameter; |
| } |
| } |
| } |
| return result; |
| } |
| |
| @override |
| void _connect(NullabilityNode? source, NullabilityNode? destination, |
| EdgeOrigin origin, FixReasonTarget? edgeTarget, |
| {bool hard = false, bool checkable = true}) { |
| var edge = _graph.connect(source, destination!, origin, |
| hard: hard, checkable: checkable, guards: _guards); |
| if (origin is ExpressionChecksOrigin) { |
| origin.checks.edges[edgeTarget] = edge; |
| } |
| } |
| |
| void _createFlowAnalysis(Declaration node, FormalParameterList? parameters) { |
| assert(_flowAnalysis == null); |
| assert(_assignedVariables == null); |
| _assignedVariables = |
| FlowAnalysisHelper.computeAssignedVariables(node, parameters); |
| // Note: we are using flow analysis to help us track true nullabilities; |
| // it's not necessary to replicate old bugs. So we pass `true` for |
| // `respectImplicitlyTypedVarInitializers`. |
| _flowAnalysis = FlowAnalysis<AstNode, Statement, Expression, |
| PromotableElement, DecoratedType>( |
| DecoratedTypeOperations(_typeSystem, _variables, _graph), |
| _assignedVariables!, |
| respectImplicitlyTypedVarInitializers: true); |
| if (parameters != null) { |
| for (var parameter in parameters.parameters) { |
| _flowAnalysis!.declare(parameter.declaredElement!, true); |
| } |
| } |
| } |
| |
| /// Creates a type that can be used to check that an expression's value is |
| /// non-nullable. |
| DecoratedType _createNonNullableType(Expression expression) { |
| var target = |
| NullabilityNodeTarget.text('expression type').withCodeRef(expression); |
| // Note: it's not necessary for the type to precisely match the type of the |
| // expression, since all we are going to do is cause a single graph edge to |
| // be built; it is sufficient to pass in any decorated type whose node is |
| // non-nullable. So we use `Object`. |
| var nullabilityNode = NullabilityNode.forInferredType(target); |
| _graph.makeNonNullableUnion( |
| nullabilityNode, NonNullableUsageOrigin(source, expression)); |
| return DecoratedType(typeProvider.objectType, nullabilityNode); |
| } |
| |
| DecoratedType _decorateUpperOrLowerBound(AstNode astNode, DartType? type, |
| DecoratedType left, DecoratedType right, bool isLUB, |
| {NullabilityNode? node}) { |
| var leftType = left.type; |
| var rightType = right.type; |
| if (leftType is TypeParameterType && leftType != type) { |
| // We are "unwrapping" a type parameter type to its bound. |
| final typeParam = leftType.element; |
| return _decorateUpperOrLowerBound( |
| astNode, |
| type, |
| left.substitute( |
| {typeParam: _variables!.decoratedTypeParameterBound(typeParam)}), |
| right, |
| isLUB, |
| node: node); |
| } |
| if (rightType is TypeParameterType && rightType != type) { |
| // We are "unwrapping" a type parameter type to its bound. |
| final typeParam = rightType.element; |
| return _decorateUpperOrLowerBound( |
| astNode, |
| type, |
| left, |
| right.substitute( |
| {typeParam: _variables!.decoratedTypeParameterBound(typeParam)}), |
| isLUB, |
| node: node); |
| } |
| |
| node ??= isLUB |
| ? NullabilityNode.forLUB(left.node, right.node) |
| : _nullabilityNodeForGLB(astNode, left.node!, right.node!); |
| |
| if (type!.isDynamic || type.isVoid) { |
| return DecoratedType(type, node); |
| } else if (leftType!.isBottom) { |
| return right.withNode(node); |
| } else if (rightType!.isBottom) { |
| return left.withNode(node); |
| } else if (type is InterfaceType) { |
| if (type.typeArguments.isEmpty) { |
| return DecoratedType(type, node); |
| } else { |
| if (leftType.isDartCoreNull) { |
| assert(isLUB, "shouldn't be possible to get C<T> from GLB(null, S)"); |
| return DecoratedType(type, node, typeArguments: right.typeArguments); |
| } else if (rightType.isDartCoreNull) { |
| assert(isLUB, "shouldn't be possible to get C<T> from GLB(S, null)"); |
| return DecoratedType(type, node, typeArguments: left.typeArguments); |
| } else if (leftType is InterfaceType && rightType is InterfaceType) { |
| List<DecoratedType?> leftTypeArguments; |
| List<DecoratedType?> rightTypeArguments; |
| if (isLUB) { |
| leftTypeArguments = _decoratedClassHierarchy! |
| .asInstanceOf(left, type.element) |
| .typeArguments; |
| rightTypeArguments = _decoratedClassHierarchy! |
| .asInstanceOf(right, type.element) |
| .typeArguments; |
| } else { |
| if (leftType.element != type.element || |
| rightType.element != type.element) { |
| _unimplemented(astNode, 'GLB with substitution'); |
| } |
| leftTypeArguments = left.typeArguments; |
| rightTypeArguments = right.typeArguments; |
| } |
| List<DecoratedType> newTypeArguments = []; |
| for (int i = 0; i < type.typeArguments.length; i++) { |
| newTypeArguments.add(_decorateUpperOrLowerBound( |
| astNode, |
| type.typeArguments[i], |
| leftTypeArguments[i]!, |
| rightTypeArguments[i]!, |
| isLUB)); |
| } |
| return DecoratedType(type, node, typeArguments: newTypeArguments); |
| } else { |
| _unimplemented( |
| astNode, |
| 'LUB/GLB with unexpected types: ${leftType.runtimeType}/' |
| '${rightType.runtimeType}'); |
| } |
| } |
| } else if (type is FunctionType) { |
| var leftType = left.type!; |
| var rightType = right.type; |
| if (leftType.isDartCoreNull) { |
| assert( |
| isLUB, "shouldn't be possible to get a function from GLB(null, S)"); |
| return DecoratedType(type, node, |
| returnType: right.returnType, |
| positionalParameters: right.positionalParameters, |
| namedParameters: right.namedParameters); |
| } else if (rightType!.isDartCoreNull) { |
| assert( |
| isLUB, "shouldn't be possible to get a function from GLB(S, null)"); |
| return DecoratedType(type, node, |
| returnType: left.returnType, |
| positionalParameters: left.positionalParameters, |
| namedParameters: left.namedParameters); |
| } |
| if (leftType is FunctionType && rightType is FunctionType) { |
| var returnType = _decorateUpperOrLowerBound(astNode, type.returnType, |
| left.returnType!, right.returnType!, isLUB); |
| List<DecoratedType> positionalParameters = []; |
| Map<String, DecoratedType> namedParameters = {}; |
| int positionalParameterCount = 0; |
| for (var parameter in type.parameters) { |
| DecoratedType? leftParameterType; |
| DecoratedType? rightParameterType; |
| if (parameter.isNamed) { |
| leftParameterType = left.namedParameters![parameter.name]; |
| rightParameterType = right.namedParameters![parameter.name]; |
| } else { |
| leftParameterType = |
| left.positionalParameters![positionalParameterCount]; |
| rightParameterType = |
| right.positionalParameters![positionalParameterCount]; |
| positionalParameterCount++; |
| } |
| var decoratedParameterType = _decorateUpperOrLowerBound(astNode, |
| parameter.type, leftParameterType!, rightParameterType!, !isLUB); |
| if (parameter.isNamed) { |
| namedParameters[parameter.name] = decoratedParameterType; |
| } else { |
| positionalParameters.add(decoratedParameterType); |
| } |
| } |
| return DecoratedType(type, node, |
| returnType: returnType, |
| positionalParameters: positionalParameters, |
| namedParameters: namedParameters); |
| } else { |
| _unimplemented( |
| astNode, |
| 'LUB/GLB with unexpected types: ${leftType.runtimeType}/' |
| '${rightType.runtimeType}'); |
| } |
| } else if (type is TypeParameterType) { |
| var leftType = left.type!; |
| var rightType = right.type; |
| if (leftType.isDartCoreNull || rightType!.isDartCoreNull) { |
| assert(isLUB, "shouldn't be possible to get T from GLB(null, S)"); |
| return DecoratedType(type, node); |
| } |
| |
| assert(leftType.element == type.element && |
| rightType.element == type.element); |
| return DecoratedType(type, node); |
| } |
| _unimplemented(astNode, '_decorateUpperOrLowerBound'); |
| } |
| |
| DecoratedType? _dispatch(AstNode? node, {bool skipNullCheckHint = false}) { |
| try { |
| var type = node?.accept(this); |
| if (!skipNullCheckHint && node is Expression) { |
| type = _handleNullCheckHint(node, type); |
| } |
| return type; |
| } catch (exception, stackTrace) { |
| if (listener != null) { |
| listener!.reportException(source, node, exception, stackTrace); |
| return null; |
| } else { |
| rethrow; |
| } |
| } |
| } |
| |
| void _dispatchList(NodeList? nodeList) { |
| if (nodeList == null) return; |
| for (var node in nodeList) { |
| _dispatch(node); |
| } |
| } |
| |
| /// If the innermost enclosing executable is a constructor with field formal |
| /// parameters, and [staticElement] refers to the getter associated with one |
| /// of those fields, returns the corresponding field formal parameter element. |
| /// Otherwise returns [staticElement] unchanged. |
| /// |
| /// This allows us to treat null checks on the field as though they were null |
| /// checks on the field formal parameter, which is not strictly correct, but |
| /// tends to produce migrations that are more in line with user intent. |
| Element? _favorFieldFormalElements(Element? staticElement) { |
| if (staticElement is PropertyAccessorElement) { |
| var fieldFormal = _currentFieldFormals[staticElement]; |
| if (fieldFormal != null) { |
| return fieldFormal; |
| } |
| } |
| return staticElement; |
| } |
| |
| DecoratedType _fixNumericTypes( |
| DecoratedType decoratedType, DartType? undecoratedType) { |
| if (decoratedType.type!.isDartCoreNum && undecoratedType!.isDartCoreInt) { |
| // In a few cases the type computed by normal method lookup is `num`, |
| // but special rules kick in to cause the type to be `int` instead. If |
| // that is the case, we need to fix up the decorated type. |
| return DecoratedType(undecoratedType, decoratedType.node); |
| } else { |
| return decoratedType; |
| } |
| } |
| |
| DecoratedType _futureOf(DecoratedType type, AstNode node) => |
| DecoratedType.forImplicitType( |
| typeProvider, |
| typeProvider.futureType(type.type!), |
| _graph, |
| NullabilityNodeTarget.text('implicit future').withCodeRef(node), |
| typeArguments: [type]); |
| |
| @override |
| DecoratedType? _getCallMethodType(DecoratedType type) { |
| var typeType = type.type; |
| if (typeType is InterfaceType) { |
| var callMethod = typeType.lookUpMethod2('call', _library); |
| if (callMethod != null) { |
| return _variables! |
| .decoratedElementType(callMethod.declaration) |
| .substitute(type.asSubstitution); |
| } |
| } |
| return null; |
| } |
| |
| @override |
| DecoratedType? _getTypeParameterTypeBound(DecoratedType type) { |
| // TODO(paulberry): once we've wired up flow analysis, return promoted |
| // bounds if applicable. |
| return _variables! |
| .decoratedTypeParameterBound((type.type as TypeParameterType).element); |
| } |
| |
| /// Creates the necessary constraint(s) for an assignment of the given |
| /// [expression] to a destination whose type is [destinationType]. |
| /// |
| /// Optionally, the caller may supply an [assignmentExpression] instead of |
| /// [destinationType]. In this case, then the type comes from visiting the |
| /// LHS of the assignment expression. If the LHS of the assignment expression |
| /// refers to a local variable, we mark it as assigned in flow analysis at the |
| /// proper time. |
| /// |
| /// Set [wrapFuture] to true to handle assigning Future<flatten(T)> to R. |
| DecoratedType? _handleAssignment(Expression? expression, |
| {DecoratedType? destinationType, |
| AssignmentExpression? assignmentExpression, |
| AssignmentExpression? compoundOperatorInfo, |
| AssignmentExpression? questionAssignNode, |
| bool fromDefaultValue = false, |
| bool wrapFuture = false, |
| bool sourceIsSetupCall = false}) { |
| assert( |
| (assignmentExpression == null) != (destinationType == null), |
| 'Either assignmentExpression or destinationType should be supplied, ' |
| 'but not both'); |
| PromotableElement? destinationLocalVariable; |
| if (destinationType == null) { |
| var destinationExpression = assignmentExpression!.leftHandSide; |
| if (destinationExpression is SimpleIdentifier) { |
| var element = getWriteOrReadElement(destinationExpression); |
| if (element is PromotableElement) { |
| destinationLocalVariable = element; |
| } |
| } |
| if (destinationLocalVariable != null) { |
| _dispatch(destinationExpression); |
| destinationType = getOrComputeElementType( |
| destinationExpression, destinationLocalVariable); |
| } else { |
| destinationType = _dispatch(destinationExpression); |
| } |
| } |
| |
| if (questionAssignNode != null) { |
| _guards.add(destinationType!.node); |
| _flowAnalysis!.ifNullExpression_rightBegin( |
| questionAssignNode.leftHandSide, destinationType); |
| } |
| DecoratedType? sourceType; |
| try { |
| sourceType = _dispatch(expression); |
| if (wrapFuture) { |
| sourceType = _wrapFuture(sourceType!, expression); |
| } |
| if (sourceType == null) { |
| throw StateError('No type computed for ${expression.runtimeType} ' |
| '(${expression!.toSource()}) offset=${expression.offset}'); |
| } |
| EdgeOrigin edgeOrigin = _makeEdgeOrigin(sourceType, expression, |
| isSetupAssignment: sourceIsSetupCall); |
| if (compoundOperatorInfo != null) { |
| var compoundOperatorMethod = compoundOperatorInfo.staticElement; |
| if (compoundOperatorMethod != null) { |
| _checkAssignment( |
| CompoundAssignmentOrigin(source, compoundOperatorInfo), |
| FixReasonTarget.root, |
| source: destinationType!, |
| destination: _createNonNullableType(compoundOperatorInfo), |
| hard: _shouldUseHardEdge(assignmentExpression!.leftHandSide)); |
| DecoratedType compoundOperatorType = getOrComputeElementType( |
| compoundOperatorInfo, compoundOperatorMethod, |
| targetType: destinationType, |
| targetExpression: compoundOperatorInfo.leftHandSide); |
| assert(compoundOperatorType.positionalParameters!.isNotEmpty); |
| _checkAssignment(edgeOrigin, FixReasonTarget.root, |
| source: sourceType, |
| destination: compoundOperatorType.positionalParameters![0]!, |
| hard: _shouldUseHardEdge(expression!), |
| sourceIsFunctionLiteral: expression is FunctionExpression); |
| sourceType = _fixNumericTypes(compoundOperatorType.returnType!, |
| compoundOperatorInfo.staticType); |
| _checkAssignment( |
| CompoundAssignmentOrigin(source, compoundOperatorInfo), |
| FixReasonTarget.root, |
| source: sourceType, |
| destination: destinationType, |
| hard: false); |
| } else { |
| sourceType = _makeNullableDynamicType(compoundOperatorInfo); |
| } |
| } else { |
| if (_tryTransformOrElse(expression, sourceType) || |
| _tryTransformWhere( |
| expression, edgeOrigin, sourceType, destinationType!)) { |
| // Nothing further to do. |
| } else { |
| var hard = _shouldUseHardEdge(expression!, |
| isConditionallyExecuted: questionAssignNode != null); |
| _checkAssignment(edgeOrigin, FixReasonTarget.root, |
| source: sourceType, |
| destination: destinationType, |
| hard: hard, |
| sourceIsFunctionLiteral: expression is FunctionExpression); |
| } |
| } |
| if (destinationLocalVariable != null) { |
| _flowAnalysis!.write(assignmentExpression!, destinationLocalVariable, |
| sourceType, compoundOperatorInfo == null ? expression : null); |
| } |
| if (questionAssignNode != null) { |
| _flowAnalysis!.ifNullExpression_end(); |
| // a ??= b is only nullable if both a and b are nullable. |
| sourceType = destinationType!.withNode(_nullabilityNodeForGLB( |
| questionAssignNode, sourceType.node!, destinationType.node!)); |
| _variables! |
| .recordDecoratedExpressionType(questionAssignNode, sourceType); |
| } |
| } finally { |
| if (questionAssignNode != null) { |
| _guards.removeLast(); |
| } |
| } |
| if (assignmentExpression != null) { |
| var element = _referencedElement(assignmentExpression.leftHandSide); |
| if (element != null) { |
| _postDominatedLocals.removeFromAllScopes(element); |
| _elementsWrittenToInLocalFunction?.add(element); |
| } |
| } |
| return sourceType; |
| } |
| |
| DecoratedType? _handleCollectionElement(CollectionElement? element) { |
| if (element is Expression) { |
| assert(_currentLiteralElementType != null); |
| return _handleAssignment(element, |
| destinationType: _currentLiteralElementType); |
| } else { |
| return _dispatch(element); |
| } |
| } |
| |
| void _handleConstructorRedirection( |
| FormalParameterList parameters, ConstructorName redirectedConstructor) { |
| var callee = redirectedConstructor.staticElement!.declaration; |
| var redirectedClass = callee.enclosingElement; |
| var calleeType = _variables!.decoratedElementType(callee); |
| var typeArguments = redirectedConstructor.type.typeArguments; |
| var typeArgumentTypes = |
| typeArguments?.arguments.map((t) => t.type).toList(); |
| _handleInvocationArguments( |
| redirectedConstructor, |
| parameters.parameters, |
| typeArguments, |
| typeArgumentTypes, |
| calleeType, |
| redirectedClass.typeParameters); |
| } |
| |
| void _handleCustomCheckNotNull(MethodInvocation node) { |
| var callee = node.methodName.staticElement; |
| if (node.argumentList.arguments.isNotEmpty && |
| callee is ExecutableElement && |
| callee.isStatic) { |
| var enclosingElement = callee.enclosingElement; |
| if (enclosingElement is ClassElement) { |
| if (callee.name == 'checkNotNull' && |
| enclosingElement.name == 'ArgumentError' && |
| callee.library.isDartCore || |
| callee.name == 'checkNotNull' && |
| enclosingElement.name == 'BuiltValueNullFieldError' && |
| callee.library.source.uri.toString() == |
| 'package:built_value/built_value.dart') { |
| var argument = node.argumentList.arguments.first; |
| if (argument is SimpleIdentifier && _isReferenceInScope(argument)) { |
| var argumentType = _variables!.decoratedElementType( |
| _favorFieldFormalElements(getWriteOrReadElement(argument))!); |
| _graph.makeNonNullable(argumentType.node, |
| ArgumentErrorCheckNotNullOrigin(source, argument)); |
| } |
| } |
| } |
| } |
| } |
| |
| void _handleExecutableDeclaration( |
| Declaration node, |
| ExecutableElement declaredElement, |
| NodeList<Annotation> metadata, |
| TypeAnnotation? returnType, |
| FormalParameterList? parameters, |
| NodeList<ConstructorInitializer>? initializers, |
| FunctionBody body, |
| ConstructorName? redirectedConstructor) { |
| assert(_currentFunctionType == null); |
| assert(_currentFieldFormals.isEmpty); |
| _dispatchList(metadata); |
| _dispatch(returnType); |
| _createFlowAnalysis(node, parameters); |
| _dispatch(parameters); |
| _currentFunctionType = _variables!.decoratedElementType(declaredElement); |
| _currentFieldFormals = declaredElement is ConstructorElement |
| ? _computeFieldFormalMap(declaredElement) |
| : const {}; |
| _addParametersToFlowAnalysis(parameters); |
| // Push a scope of post-dominated declarations on the stack. |
| _postDominatedLocals.pushScope(elements: declaredElement.parameters); |
| if (declaredElement.enclosingElement is ExtensionElement) { |
| _postDominatedLocals.add(_extensionThis); |
| } |
| try { |
| _dispatchList(initializers); |
| if (declaredElement is ConstructorElement && |
| !declaredElement.isFactory && |
| declaredElement.redirectedConstructor == null) { |
| _handleUninitializedFields(node, _fieldsNotInitializedByConstructor!); |
| } |
| _dispatch(body); |
| if (redirectedConstructor != null) { |
| _handleConstructorRedirection(parameters!, redirectedConstructor); |
| } |
| if (declaredElement is! ConstructorElement) { |
| var enclosingElement = declaredElement.enclosingElement; |
| if (enclosingElement is ClassElement) { |
| var overriddenElements = _inheritanceManager.getOverridden2( |
| enclosingElement, |
| Name(enclosingElement.library.source.uri, declaredElement.name)); |
| for (var overriddenElement |
| in overriddenElements ?? <ExecutableElement>[]) { |
| _handleExecutableOverriddenDeclaration(node, returnType, parameters, |
| enclosingElement, overriddenElement); |
| } |
| if (declaredElement is PropertyAccessorElement) { |
| if (declaredElement.isGetter) { |
| var setters = [declaredElement.correspondingSetter]; |
| if (setters[0] == null && !declaredElement.isStatic) { |
| // No corresponding setter in this class; look for inherited |
| // setters. |
| var getterName = declaredElement.name; |
| var setterName = '$getterName='; |
| var inheritedMembers = _inheritanceManager.getOverridden2( |
| enclosingElement, |
| Name(enclosingElement.library.source.uri, setterName)); |
| if (inheritedMembers != null) { |
| setters = [ |
| for (var setter in inheritedMembers) |
| if (setter is PropertyAccessorElement) setter |
| ]; |
| } |
| } |
| for (var setter in setters) { |
| if (setter != null) { |
| _handleGetterSetterCorrespondence( |
| node, |
| declaredElement.isStatic ? null : enclosingElement, |
| declaredElement, |
| setter.declaration); |
| } |
| } |
| } else { |
| assert(declaredElement.isSetter); |
| assert(declaredElement.name.endsWith('=')); |
| var getters = [declaredElement.correspondingGetter]; |
| if (getters[0] == null && !declaredElement.isStatic) { |
| // No corresponding getter in this class; look for inherited |
| // getters. |
| var setterName = declaredElement.name; |
| var getterName = setterName.substring(0, setterName.length - 1); |
| var inheritedMembers = _inheritanceManager.getOverridden2( |
| enclosingElement, |
| Name(enclosingElement.library.source.uri, getterName)); |
| if (inheritedMembers != null) { |
| getters = [ |
| for (var getter in inheritedMembers) |
| if (getter is PropertyAccessorElement) getter |
| ]; |
| } |
| } |
| for (var getter in getters) { |
| if (getter != null) { |
| _handleGetterSetterCorrespondence( |
| node, |
| declaredElement.isStatic ? null : enclosingElement, |
| getter.declaration, |
| declaredElement); |
| } |
| } |
| } |
| } |
| } |
| } |
| _flowAnalysis!.finish(); |
| } finally { |
| _flowAnalysis = null; |
| _assignedVariables = null; |
| _currentFunctionType = null; |
| _currentFieldFormals = const {}; |
| _postDominatedLocals.popScope(); |
| } |
| } |
| |
| void _handleExecutableOverriddenDeclaration( |
| Declaration node, |
| TypeAnnotation? returnType, |
| FormalParameterList? parameters, |
| ClassElement classElement, |
| Element overriddenElement) { |
| overriddenElement = overriddenElement.declaration!; |
| var overriddenClass = overriddenElement.enclosingElement as ClassElement; |
| var decoratedSupertype = _decoratedClassHierarchy! |
| .getDecoratedSupertype(classElement, overriddenClass); |
| var substitution = decoratedSupertype.asSubstitution; |
| if (overriddenElement is PropertyAccessorElement && |
| overriddenElement.isSynthetic) { |
| assert(node is MethodDeclaration); |
| var method = node as MethodDeclaration; |
| var decoratedOverriddenField = |
| _variables!.decoratedElementType(overriddenElement.variable); |
| var overriddenFieldType = |
| decoratedOverriddenField.substitute(substitution); |
| if (method.isGetter) { |
| _checkAssignment( |
| ReturnTypeInheritanceOrigin(source, node), FixReasonTarget.root, |
| source: _currentFunctionType!.returnType!, |
| destination: overriddenFieldType, |
| hard: true); |
| } else { |
| assert(method.isSetter); |
| DecoratedType currentParameterType = |
| _currentFunctionType!.positionalParameters!.single!; |
| DecoratedType overriddenParameterType = overriddenFieldType; |
| _checkAssignment( |
| ParameterInheritanceOrigin(source, node), FixReasonTarget.root, |
| source: overriddenParameterType, |
| destination: currentParameterType, |
| hard: true); |
| } |
| } else { |
| var decoratedOverriddenFunctionType = |
| _variables!.decoratedElementType(overriddenElement); |
| var overriddenFunctionType = |
| decoratedOverriddenFunctionType.substitute(substitution); |
| if (returnType == null) { |
| _linkDecoratedTypes( |
| _currentFunctionType!.returnType!, |
| overriddenFunctionType.returnType, |
| ReturnTypeInheritanceOrigin(source, node), |
| isUnion: false); |
| } else { |
| _checkAssignment( |
| ReturnTypeInheritanceOrigin(source, node), FixReasonTarget.root, |
| source: _currentFunctionType!.returnType!, |
| destination: overriddenFunctionType.returnType!, |
| hard: true); |
| } |
| if (parameters != null) { |
| int positionalParameterCount = 0; |
| for (var parameter in parameters.parameters) { |
| NormalFormalParameter normalParameter; |
| if (parameter is NormalFormalParameter) { |
| normalParameter = parameter; |
| } else { |
| normalParameter = (parameter as DefaultFormalParameter).parameter; |
| } |
| DecoratedType? currentParameterType; |
| DecoratedType? overriddenParameterType; |
| if (parameter.isNamed) { |
| var name = normalParameter.identifier!.name; |
| currentParameterType = _currentFunctionType!.namedParameters![name]; |
| overriddenParameterType = |
| overriddenFunctionType.namedParameters![name]; |
| } else { |
| if (positionalParameterCount < |
| _currentFunctionType!.positionalParameters!.length) { |
| currentParameterType = _currentFunctionType! |
| .positionalParameters![positionalParameterCount]; |
| } |
| if (positionalParameterCount < |
| overriddenFunctionType.positionalParameters!.length) { |
| overriddenParameterType = overriddenFunctionType |
| .positionalParameters![positionalParameterCount]; |
| } |
| positionalParameterCount++; |
| } |
| if (overriddenParameterType != null) { |
| var origin = ParameterInheritanceOrigin(source, node); |
| if (_isUntypedParameter(normalParameter)) { |
| _linkDecoratedTypes( |
| overriddenParameterType, currentParameterType, origin, |
| isUnion: false); |
| } else { |
| _checkAssignment(origin, FixReasonTarget.root, |
| source: overriddenParameterType, |
| destination: currentParameterType!, |
| hard: false, |
| checkable: false); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void _handleFieldOverriddenDeclaration( |
| VariableDeclaration node, |
| DecoratedType type, |
| ClassElement classElement, |
| Element overriddenElement) { |
| overriddenElement = overriddenElement.declaration!; |
| var overriddenClass = overriddenElement.enclosingElement as ClassElement; |
| var decoratedSupertype = _decoratedClassHierarchy! |
| .getDecoratedSupertype(classElement, overriddenClass); |
| var substitution = decoratedSupertype.asSubstitution; |
| if (overriddenElement is PropertyAccessorElement) { |
| DecoratedType? unsubstitutedOverriddenType; |
| if (overriddenElement.isSynthetic) { |
| unsubstitutedOverriddenType = |
| _variables!.decoratedElementType(overriddenElement.variable); |
| } else { |
| if (overriddenElement.isGetter) { |
| unsubstitutedOverriddenType = |
| _variables!.decoratedElementType(overriddenElement).returnType; |
| } else { |
| unsubstitutedOverriddenType = _variables! |
| .decoratedElementType(overriddenElement) |
| .positionalParameters![0]; |
| } |
| } |
| var overriddenType = |
| unsubstitutedOverriddenType!.substitute(substitution); |
| if (overriddenElement.isGetter) { |
| _checkAssignment( |
| ReturnTypeInheritanceOrigin(source, node), FixReasonTarget.root, |
| source: type, destination: overriddenType, hard: true); |
| } else { |
| assert(overriddenElement.isSetter); |
| _checkAssignment( |
| ParameterInheritanceOrigin(source, node), FixReasonTarget.root, |
| source: overriddenType, destination: type, hard: true); |
| } |
| } else { |
| assert(false, 'Field overrides non-property-accessor'); |
| } |
| } |
| |
| void _handleForLoopParts(AstNode node, ForLoopParts parts, AstNode body, |
| DecoratedType? Function(AstNode) bodyHandler) { |
| if (parts is ForParts) { |
| if (parts is ForPartsWithDeclarations) { |
| _dispatch(parts.variables); |
| } else if (parts is ForPartsWithExpression) { |
| var initializationType = _dispatch(parts.initialization); |
| if (initializationType != null) { |
| |