blob: ed5ca6e0350969f9f690419df227c3767c35a71a [file] [log] [blame]
// 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_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;
/// 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);
/// 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));
_flowAnalysis!.equalityOp_rightBegin(leftOperand, leftType);
var rightType = _dispatch(rightOperand)!;
_graph.connectDummy(rightType.node, DummyOrigin(source, node));
bool notEqual = operatorType == TokenType.BANG_EQ;
_flowAnalysis!
.equalityOp_end(node, 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 {
var transformationInfo =
_whereOrNullTransformer.tryTransformOrElseArgument(expression);
if (transformationInfo != null) {
// Don't build any edges for this argument; if necessary we'll transform
// it rather than make things nullable. But do save the nullability of
// the return value of the `orElse` method, so that we can later connect
// it to the nullability of the value returned from the method
// invocation.
var extraNullability = sourceType.returnType!.node;
_deferredMethodInvocationProcessing[
transformationInfo.methodInvocation] = (methodInvocationType) {
var newNode = NullabilityNode.forInferredType(
NullabilityNodeTarget.text(
'return value from ${transformationInfo.originalName}'));
var origin = IteratorMethodReturnOrigin(
source, transformationInfo.methodInvocation);
_graph.connect(methodInvocationType!.node, newNode, origin);
_graph.connect(extraNullability, newNode, origin);
return methodInvocationType.withNode(newNode);
};
} 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) {
_graph.connectDummy(
initializationType.node, DummyOrigin(source, parts));
}
}
_flowAnalysis!.for_conditionBegin(node);
if (parts.condition != null) {
_checkExpressionNotNull(parts.condition!);
}
_flowAnalysis!
.for_bodyBegin(node is Statement ? node : null, parts.condition);
} else if (parts is ForEachParts) {
Element? lhsElement;
DecoratedType? lhsType;
if (parts is ForEachPartsWithDeclaration) {
var variableElement = parts.loopVariable.declaredElement!;
_flowAnalysis!.declare(variableElement, true);
lhsElement = variableElement;
_dispatch(parts.loopVariable.type);
lhsType = _variables!.decoratedElementType(lhsElement);
} else if (parts is ForEachPartsWithIdentifier) {
lhsElement = parts.identifier.staticElement;
lhsType = _dispatch(parts.identifier);
} else {
throw StateError(
'Unexpected ForEachParts subtype: ${parts.runtimeType}');
}
var iterableType = _checkExpressionNotNull(parts.iterable);
DecoratedType? elementType;
if (lhsType != null) {
var iterableTypeType = iterableType.type!;
if (_typeSystem.isSubtypeOf(
iterableTypeType, typeProvider.iterableDynamicType)) {
elementType = _decoratedClassHierarchy!
.asInstanceOf(
iterableType, typeProvider.iterableDynamicType.element)
.typeArguments[0];
_checkAssignment(
ForEachVariableOrigin(source, parts), FixReasonTarget.root,
source: elementType!, destination: lhsType, hard: false);
}
}
_flowAnalysis!.forEach_bodyBegin(node);
if (lhsElement is PromotableElement) {
_flowAnalysis!.write(node, lhsElement,
elementType ?? _makeNullableDynamicType(node), null);
}
}
// The condition may fail/iterable may be empty, so the body gets a new
// post-dominator scope.
_postDominatedLocals.doScoped(action: () {
bodyHandler(body);
if (parts is ForParts) {
_flowAnalysis!.for_updaterBegin();
for (var updater in parts.updaters) {
var updaterType = _dispatch(updater)!;
_graph.connectDummy(updaterType.node, DummyOrigin(source, updater));
}
_flowAnalysis!.for_end();
} else {
_flowAnalysis!.forEach_end();
}
});
}
void _handleGetterSetterCorrespondence(Declaration node, ClassElement? class_,
PropertyAccessorElement getter, PropertyAccessorElement setter) {
DecoratedType? getType;
if (getter.isSynthetic) {
var field = getter.variable;
if (field.isSynthetic) return;
getType = _variables!.decoratedElementType(field);
} else {
getType = _variables!.decoratedElementType(getter).returnType;
}
DecoratedType? setType;
if (setter.isSynthetic) {
var field = setter.variable;
if (field.isSynthetic) return;
setType = _variables!.decoratedElementType(field);
} else {
setType =
_variables!.decoratedElementType(setter).positionalParameters!.single;
}
Map<TypeParameterElement, DecoratedType?> getterSubstitution = const {};
Map<TypeParameterElement, DecoratedType?> setterSubstitution = const {};
if (class_ != null) {
var getterClass = getter.enclosingElement as ClassElement;
if (!identical(class_, getterClass)) {
getterSubstitution = _decoratedClassHierarchy!
.getDecoratedSupertype(class_, getterClass)
.asSubstitution;
}
var setterClass = setter.enclosingElement as ClassElement;
if (!identical(class_, setterClass)) {
setterSubstitution = _decoratedClassHierarchy!
.getDecoratedSupertype(class_, setterClass)
.asSubstitution;
}
}
_checkAssignment(
GetterSetterCorrespondenceOrigin(source, node), FixReasonTarget.root,
source: getType!.substitute(getterSubstitution),
destination: setType!.substitute(setterSubstitution),
hard: true);
}
/// Instantiate [type] with [argumentTypes], assigning [argumentTypes] to
/// [bounds].
DecoratedType _handleInstantiation(DecoratedType type,
List<DecoratedType> argumentTypes, List<EdgeOrigin> edgeOrigins) {
for (var i = 0; i < argumentTypes.length; ++i) {
_checkAssignment(
edgeOrigins.elementAt(i), FixReasonTarget.root.typeArgument(i),
source: argumentTypes[i],
destination: DecoratedTypeParameterBounds.current!
.get((type.type as FunctionType).typeFormals[i])!,
hard: true);
}
return type.instantiate(argumentTypes);
}
/// Creates the necessary constraint(s) for an [ArgumentList] when invoking an
/// executable element whose type is [calleeType].
///
/// Only pass [typeArguments] or [typeArgumentTypes] depending on the use
/// case; only one will be used.
///
/// Returns the decorated return type of the invocation, after any necessary
/// substitutions.
DecoratedType? _handleInvocationArguments(
AstNode node,
Iterable<AstNode> arguments,
TypeArgumentList? typeArguments,
Iterable<DartType?>? typeArgumentTypes,
DecoratedType calleeType,
List<TypeParameterElement>? constructorTypeParameters,
{DartType? invokeType}) {
var typeFormals = constructorTypeParameters ?? calleeType.typeFormals!;
var target = NullabilityNodeTarget.text('invocation').withCodeRef(node);
if (typeFormals.isNotEmpty) {
if (typeArguments != null) {
var argumentTypes = typeArguments.arguments
.map((t) => _variables!.decoratedTypeAnnotation(source, t))
.toList();
var origins = typeArguments.arguments
.map((typeAnnotation) =>
TypeParameterInstantiationOrigin(source, typeAnnotation))
.toList();
if (constructorTypeParameters != null) {
calleeType = calleeType.substitute(
Map<TypeParameterElement, DecoratedType>.fromIterables(
constructorTypeParameters, argumentTypes));
} else {
calleeType = _handleInstantiation(calleeType, argumentTypes, origins);
}
} else {
if (invokeType is FunctionType) {
var argumentTypes = typeArgumentTypes!
.map((argType) => DecoratedType.forImplicitType(
typeProvider, argType, _graph, target))
.toList();
instrumentation?.implicitTypeArguments(source, node, argumentTypes);
calleeType = _handleInstantiation(
calleeType,
argumentTypes,
List.filled(argumentTypes.length,
InferredTypeParameterInstantiationOrigin(source, node)));
} else if (constructorTypeParameters != null) {
// No need to instantiate; caller has already substituted in the
// correct type arguments.
} else {
assert(
false,
'invoke type should be a non-null function type, or '
'dynamic/Function, which have no type arguments. '
'(got $invokeType)');
}
}
}
int i = 0;
var suppliedNamedParameters = <String>{};
for (var argument in arguments) {
String? name;
Expression? expression;
if (argument is NamedExpression) {
name = argument.name.label.name;
expression = argument.expression;
} else if (argument is FormalParameter) {
if (argument.isNamed) {
name = argument.identifier!.name;
}
expression = argument.identifier;
} else {
expression = argument as Expression;
}
DecoratedType? parameterType;
if (name != null) {
parameterType = calleeType.namedParameters![name];
if (parameterType == null) {
// TODO(paulberry)
_unimplemented(expression, 'Missing type for named parameter');
}
suppliedNamedParameters.add(name);
} else {
if (calleeType.positionalParameters!.length <= i) {
// TODO(paulberry)
_unimplemented(node, 'Missing positional parameter at $i');
}
parameterType = calleeType.positionalParameters![i++];
}
_handleAssignment(expression, destinationType: parameterType);
}
// Any parameters not supplied must be optional.
for (var entry in calleeType.namedParameters!.entries) {
if (suppliedNamedParameters.contains(entry.key)) continue;
entry.value!.node!.recordNamedParameterNotSupplied(
_guards, _graph, NamedParameterNotSuppliedOrigin(source, node));
}
return calleeType.returnType;
}
DecoratedType? _handleNullCheckHint(
Expression expression, DecoratedType? type) {
// Sometimes we think we're looking at an expression but we're really not
// because we're inside a type name. If this happens, ignore trailing
// `/*!*/`s because they're not expression null check hints, they're type
// non-nullability hints (which are handled by NodeBuilder).
if (_typeNameNesting > 0) return type;
var token = expression.endToken;
if (_nullCheckHints.containsKey(token)) {
// Already visited this location.
return type;
}
var hint = _nullCheckHints[token] = getPostfixHint(token);
if (hint != null && hint.kind == HintCommentKind.bang) {
_variables!.recordNullCheckHint(source, expression, hint);
return type!.withNode(_graph.never);
} else {
return type;
}
}
DecoratedType? _handlePropertyAccess(Expression node, Expression? target,
SimpleIdentifier propertyName, bool isNullAware, bool isCascaded) {
if (!isCascaded && _isPrefix(target)) {
return _dispatch(propertyName, skipNullCheckHint: true);
}
var callee = getWriteOrReadElement(propertyName);
return _handlePropertyAccessGeneralized(
node: node,
target: target,
propertyName: propertyName.name,
isNullAware: isNullAware,
isCascaded: isCascaded,
inSetterContext: propertyName.inSetterContext(),
callee: callee);
}
DecoratedType? _handlePropertyAccessGeneralized(
{required Expression node,
required Expression? target,
required String propertyName,
required bool isNullAware,
required bool isCascaded,
required bool inSetterContext,
required Element? callee}) {
DecoratedType? targetType;
bool calleeIsStatic = callee is ExecutableElement && callee.isStatic;
if (isCascaded) {
targetType = _currentCascadeTargetType;
} else if (calleeIsStatic) {
_dispatch(target);
} else if (isNullAware) {
targetType = _dispatch(target);
} else {
targetType = _handleTarget(target, propertyName, callee);
}
DecoratedType? calleeType;
if (targetType != null &&
targetType.type is FunctionType &&
propertyName == 'call') {
// If `X` has a function type, then in the expression `X.call`, the
// function being torn off is `X` itself, so the callee type is simply the
// non-nullable counterpart to the type of `X`.
var nullabilityNodeTarget =
NullabilityNodeTarget.text('expression').withCodeRef(node);
var nullabilityNode =
NullabilityNode.forInferredType(nullabilityNodeTarget);
_graph.makeNonNullableUnion(
nullabilityNode, CallTearOffOrigin(source, node));
calleeType = targetType.withNode(nullabilityNode);
} else if (callee != null) {
calleeType = getOrComputeElementType(node, callee,
targetType: targetType, targetExpression: target);
}
if (calleeType == null) {
// Dynamic dispatch.
return _makeNullableDynamicType(node);
}
if (inSetterContext) {
if (isNullAware) {
_conditionalNodes[node] = targetType!.node;
}
return calleeType.positionalParameters![0];
} else {
var expressionType = callee is PropertyAccessorElement
? calleeType.returnType
: calleeType;
if (isNullAware) {
expressionType = expressionType!.withNode(
NullabilityNode.forLUB(targetType!.node, expressionType.node));
_variables!.recordDecoratedExpressionType(node, expressionType);
}
return expressionType;
}
}
/// Check whether [node] is a call to the quiver package's [`checkNotNull`],
/// and if so, potentially mark the first argument as non-nullable.
///
/// [`checkNotNull`]: https://pub.dev/documentation/quiver/latest/quiver.check/checkNotNull.html
void _handleQuiverCheckNotNull(MethodInvocation node) {
var callee = node.methodName.staticElement;
var calleeUri = callee?.library?.source.uri;
var isQuiverCheckNull = callee?.name == 'checkNotNull' &&
calleeUri != null &&
calleeUri.isScheme('package') &&
calleeUri.path.startsWith('quiver/');
if (isQuiverCheckNull && node.argumentList.arguments.isNotEmpty) {
var argument = node.argumentList.arguments.first;
if (argument is SimpleIdentifier && _isReferenceInScope(argument)) {
var argumentType =
getOrComputeElementType(argument, argument.staticElement!);
_graph.makeNonNullable(
argumentType.node, QuiverCheckNotNullOrigin(source, argument));
}
}
}
DecoratedType? _handleTarget(
Expression? target, String name, Element? callee) {
if (isDeclaredOnObject(name)) {
return _dispatch(target);
} else if ((callee is MethodElement || callee is PropertyAccessorElement) &&
callee!.enclosingElement is ExtensionElement) {
// Extension methods can be called on a `null` target, when the `on` type
// of the extension is nullable. Note: we don't need to check whether the
// target type is assignable to the extended type; that is done in
// [getOrComputeElementType].
return _dispatch(target);
} else {
return _checkExpressionNotNull(target!);
}
}
void _handleUninitializedFields(AstNode node, Set<FieldElement?> fields) {
for (var field in fields) {
_graph.makeNullable(_variables!.decoratedElementType(field!).node!,
FieldNotInitializedOrigin(source, node));
}
}
/// Returns whether [_currentFunctionExpression] is an argument to the test
/// package's `setUp` function.
bool _isCurrentFunctionExpressionFoundInTestSetUpCall() {
var parent = _currentFunctionExpression?.parent;
if (parent is ArgumentList) {
var grandParent = parent.parent;
if (grandParent is MethodInvocation) {
var enclosingInvocation = grandParent.methodName;
if (enclosingInvocation.name == 'setUp') {
var uri = enclosingInvocation.staticElement!.library?.source.uri;
if (uri != null &&
uri.isScheme('package') &&
uri.path.startsWith('test_core/')) {
return true;
}
}
}
}
return false;
}
bool _isPrefix(Expression? e) =>
e is SimpleIdentifier && e.staticElement is PrefixElement;
bool _isReferenceInScope(Expression expression) {
var element = _referencedElement(expression);
return element != null && _postDominatedLocals.isInScope(element);
}
bool _isUntypedParameter(NormalFormalParameter parameter) {
if (parameter is SimpleFormalParameter) {
return parameter.type == null;
} else if (parameter is FieldFormalParameter) {
return parameter.type == null;
} else {
return false;
}
}
void _linkDecoratedTypeParameters(
DecoratedType x, DecoratedType? y, EdgeOrigin origin,
{bool isUnion = true}) {
for (int i = 0;
i < x.positionalParameters!.length &&
i < y!.positionalParameters!.length;
i++) {
_linkDecoratedTypes(
x.positionalParameters![i]!, y.positionalParameters![i], origin,
isUnion: isUnion);
}
for (var entry in x.namedParameters!.entries) {
var superParameterType = y!.namedParameters![entry.key];
if (superParameterType != null) {
_linkDecoratedTypes(entry.value!, y.namedParameters![entry.key], origin,
isUnion: isUnion);
}
}
}
void _linkDecoratedTypes(DecoratedType x, DecoratedType? y, EdgeOrigin origin,
{bool isUnion = true}) {
if (isUnion) {
_graph.union(x.node!, y!.node!, origin);
} else {
_graph.connect(x.node, y!.node!, origin, hard: true);
}
_linkDecoratedTypeParameters(x, y, origin, isUnion: isUnion);
for (int i = 0;
i < x.typeArguments.length && i < y.typeArguments.length;
i++) {
_linkDecoratedTypes(x.typeArguments[i]!, y.typeArguments[i], origin,
isUnion: isUnion);
}
if (x.returnType != null && y.returnType != null) {
_linkDecoratedTypes(x.returnType!, y.returnType, origin,
isUnion: isUnion);
}
}
EdgeOrigin _makeEdgeOrigin(DecoratedType sourceType, Expression? expression,
{bool isSetupAssignment = false}) {
if (sourceType.type!.isDynamic) {
return DynamicAssignmentOrigin(source, expression);
} else {
ExpressionChecksOrigin expressionChecksOrigin = ExpressionChecksOrigin(
source, expression, ExpressionChecks(),
isSetupAssignment: isSetupAssignment);
_variables!
.recordExpressionChecks(source, expression!, expressionChecksOrigin);
return expressionChecksOrigin;
}
}
DecoratedType _makeNonNullableBoolType(Expression expression) {
assert(expression.staticType!.isDartCoreBool);
var target =
NullabilityNodeTarget.text('expression').withCodeRef(expression);
var nullabilityNode = NullabilityNode.forInferredType(target);
_graph.makeNonNullableUnion(
nullabilityNode, NonNullableBoolTypeOrigin(source, expression));
return DecoratedType(typeProvider.boolType, nullabilityNode);
}
DecoratedType _makeNonNullLiteralType(Expression expression,
{List<DecoratedType?> typeArguments = const []}) {
var target =
NullabilityNodeTarget.text('expression').withCodeRef(expression);
var nullabilityNode = NullabilityNode.forInferredType(target);
_graph.makeNonNullableUnion(
nullabilityNode, LiteralOrigin(source, expression));
return DecoratedType(expression.staticType, nullabilityNode,
typeArguments: typeArguments);
}
DecoratedType _makeNullableDynamicType(AstNode astNode) {
var target =
NullabilityNodeTarget.text('dynamic type').withCodeRef(astNode);
var decoratedType = DecoratedType.forImplicitType(
typeProvider, typeProvider.dynamicType, _graph, target);
_graph.makeNullable(
decoratedType.node!, AlwaysNullableTypeOrigin(source, astNode, false));
return decoratedType;
}
DecoratedType _makeNullableVoidType(SimpleIdentifier astNode) {
var target = NullabilityNodeTarget.text('void type').withCodeRef(astNode);
var decoratedType = DecoratedType.forImplicitType(
typeProvider, typeProvider.voidType, _graph, target);
_graph.makeNullable(
decoratedType.node!, AlwaysNullableTypeOrigin(source, astNode, true));
return decoratedType;
}
NullabilityNode _nullabilityNodeForGLB(
AstNode astNode, NullabilityNode leftNode, NullabilityNode rightNode) {
var node = NullabilityNode.forGLB();
var origin = GreatestLowerBoundOrigin(source, astNode);
_graph.connect(leftNode, node, origin, guards: [rightNode]);
_graph.connect(node, leftNode, origin);
_graph.connect(node, rightNode, origin);
return node;
}
/// Returns the element referenced directly by [expression], if any; otherwise
/// returns `null`.
Element? _referencedElement(Expression expression) {
expression = expression.unParenthesized;
if (expression is SimpleIdentifier) {
return _favorFieldFormalElements(expression.staticElement);
} else if (expression is ThisExpression || expression is SuperExpression) {
return _extensionThis;
} else {
return null;
}
}
/// Determines whether uses of [expression] should cause hard edges to be
/// created in the nullability graph.
///
/// If [isConditionallyExecuted] is `true`, that indicates that [expression]
/// appears in a context where it might not get executed (e.g. on the RHS of
/// a `??=`).
bool _shouldUseHardEdge(Expression expression,
{bool isConditionallyExecuted = false}) {
expression = expression.unParenthesized;
if (expression is ListLiteral || expression is SetOrMapLiteral) {
// List, set, and map literals have either explicit or implicit type
// arguments. If supplying a nullable type for one of these type
// arguments would lead to an error (e.g. `f(<int?>[])` where `f` requires
// a `List<int>`), then we should use a hard edge, to ensure that the
// migrated type argument will be non-nullable.
return true;
} else if (expression is AsExpression) {
// "as" expressions have an explicit type. If making this type nullable
// would lead to an error (e.g. `f(x as int?)` where `f` requires an
// `int`),then we should use a hard edge, to ensure that the migrated type
// will be non-nullable.
return true;
}
// For other expressions, we should use a hard edge only if (a) the
// expression is unconditionally executed, and (b) the expression is a
// reference to a local variable or parameter and it post-dominates the
// declaration of that local variable or parameter.
return !isConditionallyExecuted && _isReferenceInScope(expression);
}
DecoratedType? _thisOrSuper(Expression node) {
if (_currentClassOrExtension == null) {
return null;
}
NullabilityNode makeNonNullableNode(NullabilityNodeTarget target) {
var nullabilityNode = NullabilityNode.forInferredType(target);
_graph.makeNonNullableUnion(nullabilityNode,
ThisOrSuperOrigin(source, node, node is ThisExpression));
return nullabilityNode;
}
var token = node.beginToken.lexeme;
var target =
NullabilityNodeTarget.text('$token expression').withCodeRef(node);
if (_currentClassOrExtension is ClassElement) {
final type = (_currentClassOrExtension as ClassElement).thisType;
// Instantiate the type, and any type arguments, with non-nullable types,
// because the type of `this` is always `ClassName<Param, Param, ...>`
// with no `?`s. (Even if some of the type parameters are allowed to be
// instantiated with nullable types at runtime, a reference to `this`
// can't be migrated in such a way that forces them to be nullable.)
var index = 0;
return DecoratedType(type, makeNonNullableNode(target),
typeArguments: type.typeArguments
.map((t) => DecoratedType(
t, makeNonNullableNode(target.typeArgument(index++))))
.toList());
} else {
assert(_currentClassOrExtension is ExtensionElement);
assert(_currentExtendedType != null);
return _currentExtendedType;
}
}
Never _unimplemented(AstNode? node, String message) {
StringBuffer buffer = StringBuffer();
buffer.write(message);
if (node != null) {
CompilationUnit unit = node.root as CompilationUnit;
buffer.write(' in "');
buffer.write(node.toSource());
buffer.write('" on line ');
buffer.write(unit.lineInfo!.getLocation(node.offset).lineNumber);
buffer.write(' of "');
buffer.write(unit.declaredElement!.source.fullName);
buffer.write('"');
}
throw UnimplementedError(buffer.toString());
}
/// Produce Future<flatten(T)> for some T, however, we would like to merely
/// upcast T to that type if possible, skipping the flatten when not
/// necessary.
DecoratedType _wrapFuture(DecoratedType type, AstNode? node) {
var dartType = type.type!;
if (dartType.isDartCoreNull || dartType.isBottom) {
return _futureOf(type, node!);
}
if (dartType is InterfaceType &&
dartType.element == typeProvider.futureOrElement) {
var typeArguments = type.typeArguments;
if (typeArguments.length == 1) {
// Wrapping FutureOr<T?1>?2 should produce Future<T?3>, where either 1
// or 2 being nullable causes 3 to become nullable.
var typeArgument = typeArguments[0]!;
return _futureOf(
typeArgument
.withNode(NullabilityNode.forLUB(typeArgument.node, type.node)),
node!);
}
}
if (_typeSystem.isSubtypeOf(dartType, typeProvider.futureDynamicType)) {
return _decoratedClassHierarchy!
.asInstanceOf(type, typeProvider.futureDynamicType.element);
}
return _futureOf(type, node!);
}
/// If the [node] is the finishing identifier of an assignment, return its
/// "writeElement", otherwise return its "staticElement", which might be
/// thought as the "readElement".
static Element? getWriteOrReadElement(AstNode node) {
var writeElement = _getWriteElement(node);
if (writeElement != null) {
return writeElement;
}
if (node is IndexExpression) {
return node.staticElement;
} else if (node is SimpleIdentifier) {
return node.staticElement;
} else {
return null;
}
}
/// If the [node] is the target of a [CompoundAssignmentExpression],
/// return the corresponding "writeElement", which is the local variable,
/// the setter referenced with a [SimpleIdentifier] or a [PropertyAccess],
/// or the `[]=` operator.
static Element? _getWriteElement(AstNode node) {
var parent = node.parent;
if (parent is AssignmentExpression && parent.leftHandSide == node) {
return parent.writeElement;
} else if (parent is PostfixExpression) {
return parent.writeElement;
} else if (parent is PrefixExpression) {
return parent.writeElement;
}
if (parent is PrefixedIdentifier && parent.identifier == node) {
return _getWriteElement(parent);
} else if (parent is PropertyAccess && parent.propertyName == node) {
return _getWriteElement(parent);
} else {
return null;
}
}
}
/// Implementation of [_checkAssignment] for [EdgeBuilder].
///
/// This has been moved to its own mixin to allow it to be more easily unit
/// tested.
mixin _AssignmentChecker {
TypeProvider get typeProvider;
DecoratedClassHierarchy? get _decoratedClassHierarchy;
TypeSystem get _typeSystem;
/// Creates the necessary constraint(s) for an assignment from [source] to
/// [destination]. [origin] should be used as the origin for any edges
/// created. [hard] indicates whether a hard edge should be created.
/// [sourceIsFunctionLiteral] indicates whether the source of the assignment
/// is a function literal expression.
void _checkAssignment(EdgeOrigin origin, FixReasonTarget edgeTarget,
{required DecoratedType source,
required DecoratedType destination,
required bool hard,
bool checkable = true,
bool sourceIsFunctionLiteral = false}) {
var sourceType = source.type!;
var destinationType = destination.type!;
if (!_typeSystem.isSubtypeOf(sourceType, destinationType)) {
// Not a proper upcast assignment.
if (_typeSystem.isSubtypeOf(destinationType, sourceType)) {
// But rather a downcast.
_checkDowncast(origin,
source: source, destination: destination, hard: hard);
return;
}
if (destinationType is FunctionType) {
var callMethodType = _getCallMethodType(source);
if (callMethodType != null) {
// Handle implicit `.call` coercion
_checkAssignment(origin, edgeTarget,
source: callMethodType,
destination: destination,
hard: false,
checkable: false,
sourceIsFunctionLiteral: sourceIsFunctionLiteral);
return;
}
}
// A side cast. This may be an explicit side cast, or illegal code. There
// is no nullability we can infer here.
assert(
_assumeNonNullabilityInCasts,
'side cast not supported without assuming non-nullability:'
' $sourceType to $destinationType');
_connect(source.node, destination.node, origin, edgeTarget, hard: hard);
return;
}
_connect(source.node, destination.node, origin, edgeTarget,
hard: hard, checkable: checkable);
_checkAssignment_recursion(origin, edgeTarget,
source: source,
destination: destination,
sourceIsFunctionLiteral: sourceIsFunctionLiteral,
hard: hard);
}
/// Does the recursive part of [_checkAssignment], visiting all of the types
/// constituting [source] and [destination], and creating the appropriate
/// edges between them. [sourceIsFunctionLiteral] indicates whether the
/// source of the assignment is a function literal expression.
void _checkAssignment_recursion(EdgeOrigin origin, FixReasonTarget edgeTarget,
{required DecoratedType source,
required DecoratedType destination,
bool sourceIsFunctionLiteral = false,
bool hard = false}) {
var sourceType = source.type!;
var destinationType = destination.type!;
assert(_typeSystem.isSubtypeOf(sourceType, destinationType));
if (destinationType.isDartAsyncFutureOr) {
var s1 = destination.typeArguments[0];
if (sourceType.isDartAsyncFutureOr) {
// This is a special case not in the subtyping spec. The subtyping spec
// covers this case by expanding the LHS first, which is fine but
// leads to redundant edges (which might be confusing for users)
// if T0 is FutureOr<S0> then:
// - T0 <: T1 iff Future<S0> <: T1 and S0 <: T1
// Since T1 is FutureOr<S1>, this is equivalent to:
// - T0 <: T1 iff (Future<S0> <: Future<S1> or Future<S0> <: S1) and
// (S0 <: Future<S1> or S0 <: S1)
// Which is equivalent to:
// - T0 <: T1 iff (S0 <: S1 or Future<S0> <: S1) and
// (S0 <: Future<S1> or S0 <: S1)
// Which is equivalent to (distributing the "and"):
// - T0 <: T1 iff (S0 <: S1 and (S0 <: Future<S1> or S0 <: S1)) or
// (Future<S0> <: S1 and (S0 <: Future<S1> or S0 <: S1))
// Which is equivalent to (distributing the "and"s):
// - T0 <: T1 iff (S0 <: S1 and S0 <: Future<S1>) or
// (S0 <: S1 and S0 <: S1) or
// (Future<S0> <: S1 and S0 <: Future<S1>) or
// (Future<S0> <: S1 and S0 <: S1)
// If S0 <: S1, the relation is satisfied. Otherwise the only term that
// matters is (Future<S0> <: S1 and S0 <: Future<S1>), so this is
// equivalent to:
// - T0 <: T1 iff S0 <: S1 or (Future<S0> <: S1 and S0 <: Future<S1>)
// Let's consider whether there are any cases where the RHS of this "or"
// can be satisfied but not the LHS. That is, assume that
// Future<S0> <: S1 and S0 <: Future<S1> hold, but not S0 <: S1. S1
// must not be a top type (otherwise S0 <: S1 would hold), so the only
// way Future<S0> <: S1 can hold is if S1 is Future<A> or FutureOr<A>
// for some A. In either case, Future<S1> simplifies to Future<A>, so
// we know that S0 <: Future<A>. Also, in either case, Future<A> <: S1.
// Combining these, we have that S0 <: S1, contradicting our assumption.
// So the RHS of the "or" is redundant, and we can simplify to:
// - S0 <: S1.
var s0 = source.typeArguments[0]!;
_checkAssignment(origin, edgeTarget.yieldedType,
source: s0, destination: s1!, hard: false);
return;
}
// (From the subtyping spec):
// if T1 is FutureOr<S1> then T0 <: T1 iff any of the following hold:
// - either T0 <: Future<S1>
if (_typeSystem.isSubtypeOf(
sourceType, typeProvider.futureType(s1!.type!))) {
// E.g. FutureOr<int> = (... as Future<int>)
// This is handled by the InterfaceType logic below, since we treat
// FutureOr as a supertype of Future.
}
// - or T0 <: S1
else if (_typeSystem.isSubtypeOf(sourceType, s1.type!)) {
// E.g. FutureOr<int> = (... as int)
_checkAssignment_recursion(origin, edgeTarget.yieldedType,
source: source, destination: s1);
return;
}
// - or T0 is X0 and X0 has bound S0 and S0 <: T1
// - or T0 is X0 & S0 and S0 <: T1
else if (sourceType is TypeParameterType) {
throw UnimplementedError('TODO(paulberry)');
} else {
// Not a subtype. This should never happen, since we handle the
// implicit downcast case above.
assert(false, 'not a subtype');
}
}
if (sourceType.isBottom || sourceType.isDartCoreNull) {
// No further edges need to be created, since all types are trivially
// supertypes of bottom (and of Null, in the pre-migration world).
} else if (sourceType is TypeParameterType) {
// Handle this before handling dynamic/object/void, to correctly infer
// nullabilities in `Object o = T`.
if (destinationType is TypeParameterType) {
// No further edges need to be created, since type parameter types
// aren't made up of other types.
} else {
// Effectively this is an assignment from the type parameter's bound to
// the destination type.
_checkAssignment(origin, edgeTarget,
source: _getTypeParameterTypeBound(source)!,
destination: destination,
hard: false);
return;
}
} else if (destinationType.isDynamic ||
destinationType.isVoid ||
destinationType.isDartCoreObject) {
// No further edges need to be created, since all types are trivially
// subtypes of dynamic, Object, and void, since all are treated as
// equivalent to dynamic for subtyping purposes.
} else if (sourceType is InterfaceType &&
destinationType is InterfaceType) {
var rewrittenSource = _decoratedClassHierarchy!
.asInstanceOf(source, destinationType.element);
assert(rewrittenSource.typeArguments.length ==
destination.typeArguments.length);
for (int i = 0; i < rewrittenSource.typeArguments.length; i++) {
_checkAssignment(origin, edgeTarget.typeArgument(i),
source: rewrittenSource.typeArguments[i]!,
destination: destination.typeArguments[i]!,
hard: hard,
checkable: false);
}
} else if (sourceType is FunctionType && destinationType is FunctionType) {
// If the source is a function literal, we want a hard edge, so that if a
// function returning non-null is required, we will insure that the
// function literal has a non-nullable return type (e.g. by inserting null
// checks into the function literal).
_checkAssignment(origin, edgeTarget.returnType,
source: source.returnType!,
destination: destination.returnType!,
hard: sourceIsFunctionLiteral,
checkable: false);
if (source.typeArguments.isNotEmpty ||
destination.typeArguments.isNotEmpty) {
throw UnimplementedError('TODO(paulberry)');
}
for (int i = 0;
i < source.positionalParameters!.length &&
i < destination.positionalParameters!.length;
i++) {
// Note: source and destination are swapped due to contravariance.
_checkAssignment(origin, edgeTarget.positionalParameter(i),
source: destination.positionalParameters![i]!,
destination: source.positionalParameters![i]!,
hard: false,
checkable: false);
}
for (var entry in destination.namedParameters!.entries) {
// Note: source and destination are swapped due to contravariance.
_checkAssignment(origin, edgeTarget.namedParameter(entry.key),
source: entry.value!,
destination: source.namedParameters![entry.key]!,
hard: false,
checkable: false);
}
} else if (destinationType.isDynamic || sourceType.isDynamic) {
// ok; nothing further to do.
} else if (destinationType is InterfaceType && sourceType is FunctionType) {
// Either this is an upcast to Function or Object, or it is erroneous
// code. In either case we don't need to create any additional edges.
} else {
throw '$destination <= $source'; // TODO(paulberry)
}
}
void _checkDowncast(EdgeOrigin origin,
{required DecoratedType source,
required DecoratedType destination,
required bool hard}) {
var destinationType = destination.type!;
assert(_typeSystem.isSubtypeOf(destinationType, source.type!));
// Nullability should narrow to maintain subtype relationship.
_connect(source.node, destination.node, origin, FixReasonTarget.root,
hard: hard);
if (source.type!.isDynamic ||
source.type!.isDartCoreObject ||
source.type!.isVoid) {
if (destinationType is InterfaceType) {
for (final param in destinationType.element.typeParameters) {
assert(param.bound == null,
'downcast to type parameters with bounds not supported');
}
}
if (destinationType is FunctionType) {
// Nothing else to do.
return;
}
} else if (destinationType.isDartCoreNull) {
// There's not really much we can infer from trying to assign a type to
// Null. We could say that the source of the assignment must be nullable,
// but that's not really useful because the nullability won't propagate
// anywhere. Besides, the code is probably erroneous (e.g. the user is
// trying to store a value into a `List<Null>`). So do nothing.
return;
} else if (destinationType is TypeParameterType) {
if (source.type is! TypeParameterType) {
// Assume an assignment to the type parameter's bound.
_checkAssignment(origin, FixReasonTarget.root,
source: source,
destination: _getTypeParameterTypeBound(destination)!,
hard: false);
} else if (destinationType == source.type) {
// Nothing to do.
return;
}
} else if (source.type!.isDartAsyncFutureOr) {
if (destination.type!.isDartAsyncFuture) {
// FutureOr<T?> is nullable, so the Future<T> should be nullable too.
_connect(source.typeArguments[0]!.node, destination.node, origin,
FixReasonTarget.root.yieldedType,
hard: hard);
_checkDowncast(origin,
source: source.typeArguments[0]!,
destination: destination.typeArguments[0]!,
hard: false);
} else if (destination.type!.isDartAsyncFutureOr) {
_checkDowncast(origin,
source: source.typeArguments[0]!,
destination: destination.typeArguments[0]!,
hard: false);
} else {
_checkDowncast(origin,
source: source.typeArguments[0]!,
destination: destination,
hard: false);
}
} else if (destinationType is InterfaceType) {
if (source.type is InterfaceType) {
final target = _decoratedClassHierarchy!
.asInstanceOf(destination, source.type!.element as ClassElement?);
for (var i = 0; i < source.typeArguments.length; ++i) {
_checkDowncast(origin,
source: source.typeArguments[i]!,
destination: target.typeArguments[i]!,
hard: false);
}
} else {
assert(false,
'downcasting from ${source.type.runtimeType} to interface type');
}
} else if (destinationType is FunctionType) {
if (source.type!.isDartCoreFunction) {
// Nothing else to do.
return;
}
} else {
assert(
false,
'downcasting from ${source.type.runtimeType} to '
'${destinationType.runtimeType} not supported. (${source.type} $destinationType)');
}
}
void _connect(NullabilityNode? source, NullabilityNode? destination,
EdgeOrigin origin, FixReasonTarget edgeTarget,
{bool hard = false, bool checkable = true});
/// If [type] represents a class containing a `call` method, returns the
/// decorated type of the `call` method, with appropriate substitutions.
/// Otherwise returns `null`.
DecoratedType? _getCallMethodType(DecoratedType type);
/// Given a [type] representing a type parameter, retrieves the type's bound.
DecoratedType? _getTypeParameterTypeBound(DecoratedType type);
}
/// Information about a binary expression whose boolean value could possibly
/// affect nullability analysis.
class _ConditionInfo {
/// The [expression] of interest.
final Expression condition;
/// Indicates whether [condition] is pure (free from side effects).
///
/// For example, a condition like `x == null` is pure (assuming `x` is a local
/// variable or static variable), because evaluating it has no user-visible
/// effect other than returning a boolean value.
final bool isPure;
/// Indicates whether the intents postdominate the intent node declarations.
final bool? postDominatingIntent;
/// If not `null`, the [NullabilityNode] that would need to be nullable in
/// order for [condition] to evaluate to `true`.
final NullabilityNode? trueGuard;
/// If not `null`, the [NullabilityNode] that would need to be nullable in
/// order for [condition] to evaluate to `false`.
final NullabilityNode? falseGuard;
/// If not `null`, the [NullabilityNode] that should be asserted to have
/// non-null intent if [condition] is asserted to be `true`.
final NullabilityNode? trueDemonstratesNonNullIntent;
/// If not `null`, the [NullabilityNode] that should be asserted to have
/// non-null intent if [condition] is asserted to be `false`.
final NullabilityNode? falseDemonstratesNonNullIntent;
_ConditionInfo(this.condition,
{required this.isPure,
this.postDominatingIntent,
this.trueGuard,
this.falseGuard,
this.trueDemonstratesNonNullIntent,
this.falseDemonstratesNonNullIntent});
/// Returns a new [_ConditionInfo] describing the boolean "not" of `this`.
_ConditionInfo not(Expression condition) => _ConditionInfo(condition,
isPure: isPure,
postDominatingIntent: postDominatingIntent,
trueGuard: falseGuard,
falseGuard: trueGuard,
trueDemonstratesNonNullIntent: falseDemonstratesNonNullIntent,
falseDemonstratesNonNullIntent: trueDemonstratesNonNullIntent);
}