Skip to content

Commit 6a5b3d0

Browse files
stereotype441Commit Queue
authored andcommitted
Flow analysis: don't capture type information in equalityOperand_end.
There are two pieces of information flow analysis needs to know about an equality test operand (i.e., an operands of `==`, `!=`, or `identical()`): - Their static types. This is used for reachability (e.g., flow analysis knows that if `f()` has type `Null`, then the body of `if (f() != null)` is unreachable). - Whether they take the form of a null literal or a reference to something promotable. This is used to determine when an `if` test should promote a something to a non-nullable type. Previous to this change, both pieces of information were captured by `FlowAnalysis.equalityOperand_end` into an `ExpressionInfo` object, and then those objects were passed into `FlowAnalysis.equalityOperation_end`. With this change, the client is now responsible for passing the static types of the operands as separate arguments to `FlowAnalysis.equalityOperation_end`, and the only information captured by `equalityOperand_end` is whether the operand is a null literal or a reference to something promotable. This has two advantages: - It avoids unnecessary allocations when analyzing code that doesn't have flow analysis consequences, since flow analysis no longer needs to allocate an `ExpressionInfo` for every equality test operand; it only has to allocate them for null literals and references to things that are promotable (which is a much smaller number of allocations). - It means that `FlowAnalysis.equalityOperation_end` no longer needs to use the `type` field of `ExpressionInfo`. This helps build toward an eventual goal I have of removing this field, so that `ExpressionInfo` will simply be a container for a pair of flow models (one representing the flow state if the expression is `true`, one representing the flow state if the expression is `false`). I believe this will make flow analysis easier to reason about, and will help build toward a long term goal of cleaning up bugs in the "why not promoted" logic. Making this change required adding a little bit of plumbing to the analyzer, so that when analyzing an invocation of `identical`, it keeps track of both the `ExpressionInfo` and the static type of the operands; previously it just had to keep track of an `ExpressionInfo` for each operand. The performance impact of this additional tracking should be negligible, since this tracking doesn't happen for invocations of anything other than `identical`. Change-Id: I3e5473af095f3c8a747e9f527d7e14a21269dc95 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389361 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Kallen Tu <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent e567b0a commit 6a5b3d0

File tree

6 files changed

+95
-60
lines changed

6 files changed

+95
-60
lines changed

pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -405,20 +405,21 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
405405
///
406406
/// Returns information about the expression that will later be needed by
407407
/// [equalityOperation_end].
408-
///
409-
/// Note: the return type is nullable because legacy type promotion doesn't
410-
/// need to record information about equality operands.
411-
ExpressionInfo<Type>? equalityOperand_end(Expression operand, Type type);
408+
ExpressionInfo<Type>? equalityOperand_end(Expression operand);
412409

413410
/// Call this method just after visiting the operands of a binary `==` or `!=`
414411
/// expression, or an invocation of `identical`.
415412
///
416413
/// [leftOperandInfo] and [rightOperandInfo] should be the values returned by
417-
/// [equalityOperand_end].
414+
/// [equalityOperand_end] for the left and right operands. [leftOperandType]
415+
/// and [rightOperandType] should be the static types of the left and right
416+
/// operands.
418417
void equalityOperation_end(
419418
Expression wholeExpression,
420419
ExpressionInfo<Type>? leftOperandInfo,
420+
Type leftOperandType,
421421
ExpressionInfo<Type>? rightOperandInfo,
422+
Type rightOperandType,
422423
{bool notEqual = false});
423424

424425
/// Call this method after processing a relational pattern that uses an
@@ -1412,22 +1413,25 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
14121413
}
14131414

14141415
@override
1415-
ExpressionInfo<Type>? equalityOperand_end(Expression operand, Type type) =>
1416-
_wrap('equalityOperand_end($operand, $type)',
1417-
() => _wrapped.equalityOperand_end(operand, type),
1418-
isQuery: true);
1416+
ExpressionInfo<Type>? equalityOperand_end(Expression operand) => _wrap(
1417+
'equalityOperand_end($operand)',
1418+
() => _wrapped.equalityOperand_end(operand),
1419+
isQuery: true);
14191420

14201421
@override
14211422
void equalityOperation_end(
14221423
Expression wholeExpression,
14231424
ExpressionInfo<Type>? leftOperandInfo,
1425+
Type leftOperandType,
14241426
ExpressionInfo<Type>? rightOperandInfo,
1427+
Type rightOperandType,
14251428
{bool notEqual = false}) {
14261429
_wrap(
14271430
'equalityOperation_end($wholeExpression, $leftOperandInfo, '
1428-
'$rightOperandInfo, notEqual: $notEqual)',
1429-
() => _wrapped.equalityOperation_end(
1430-
wholeExpression, leftOperandInfo, rightOperandInfo,
1431+
'$leftOperandType, $rightOperandInfo, $rightOperandType, notEqual: '
1432+
'$notEqual)',
1433+
() => _wrapped.equalityOperation_end(wholeExpression, leftOperandInfo,
1434+
leftOperandType, rightOperandInfo, rightOperandType,
14311435
notEqual: notEqual));
14321436
}
14331437

@@ -4689,23 +4693,24 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
46894693
}
46904694

46914695
@override
4692-
ExpressionInfo<Type> equalityOperand_end(Expression operand, Type type) =>
4693-
_getExpressionInfo(operand) ??
4694-
new ExpressionInfo<Type>.trivial(model: _current, type: type);
4696+
ExpressionInfo<Type>? equalityOperand_end(Expression operand) =>
4697+
_getExpressionInfo(operand);
46954698

46964699
@override
46974700
void equalityOperation_end(
46984701
Expression wholeExpression,
46994702
ExpressionInfo<Type>? leftOperandInfo,
4703+
Type leftOperandType,
47004704
ExpressionInfo<Type>? rightOperandInfo,
4705+
Type rightOperandType,
47014706
{bool notEqual = false}) {
47024707
// Note: leftOperandInfo and rightOperandInfo are nullable in the base class
47034708
// to account for the fact that legacy type promotion doesn't record
47044709
// information about legacy operands. But since we are currently in full
47054710
// (post null safety) flow analysis logic, we can safely assume that they
47064711
// are not null.
4707-
_EqualityCheckResult equalityCheckResult =
4708-
_equalityCheck(leftOperandInfo!, rightOperandInfo!);
4712+
_EqualityCheckResult equalityCheckResult = _equalityCheck(
4713+
leftOperandInfo, leftOperandType, rightOperandInfo, rightOperandType);
47094714
if (equalityCheckResult is _GuaranteedEqual) {
47104715
// Both operands are known by flow analysis to compare equal, so the whole
47114716
// expression behaves equivalently to a boolean (either `true` or `false`
@@ -5760,13 +5765,13 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
57605765
}
57615766

57625767
/// Analyzes an equality check between the operands described by
5763-
/// [lhsInfo] and [rhsInfo].
5764-
_EqualityCheckResult _equalityCheck(
5765-
ExpressionInfo<Type> lhsInfo, ExpressionInfo<Type> rhsInfo) {
5768+
/// [lhsInfo] and [rhsInfo], having static types [lhsType] and [rhsType].
5769+
_EqualityCheckResult _equalityCheck(ExpressionInfo<Type>? lhsInfo,
5770+
Type lhsType, ExpressionInfo<Type>? rhsInfo, Type rhsType) {
57665771
TypeClassification leftOperandTypeClassification =
5767-
operations.classifyType(lhsInfo._type);
5772+
operations.classifyType(lhsType);
57685773
TypeClassification rightOperandTypeClassification =
5769-
operations.classifyType(rhsInfo._type);
5774+
operations.classifyType(rhsType);
57705775
if (leftOperandTypeClassification == TypeClassification.nullOrEquivalent &&
57715776
rightOperandTypeClassification == TypeClassification.nullOrEquivalent) {
57725777
return const _GuaranteedEqual();
@@ -5781,11 +5786,11 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
57815786
// analysis behavior to depend on mode, so we conservatively assume that
57825787
// either result is possible.
57835788
return const _NoEqualityInformation();
5784-
} else if (lhsInfo.isNull) {
5789+
} else if (lhsInfo != null && lhsInfo.isNull) {
57855790
return new _EqualityCheckIsNullCheck(
57865791
rhsInfo is _Reference<Type> ? rhsInfo : null,
57875792
isReferenceOnRight: true);
5788-
} else if (rhsInfo.isNull) {
5793+
} else if (rhsInfo != null && rhsInfo.isNull) {
57895794
return new _EqualityCheckIsNullCheck(
57905795
lhsInfo is _Reference<Type> ? lhsInfo : null,
57915796
isReferenceOnRight: false);
@@ -5967,8 +5972,8 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
59675972
_Reference<Type> newReference = context
59685973
.createReference(matchedValueType, _current)
59695974
.addPreviousInfo(context._matchedValueInfo, this, _current);
5970-
_EqualityCheckResult equalityCheckResult =
5971-
_equalityCheck(newReference, equalityOperand_end(operand, operandType));
5975+
_EqualityCheckResult equalityCheckResult = _equalityCheck(newReference,
5976+
matchedValueType, _getExpressionInfo(operand), operandType);
59725977
if (equalityCheckResult is _NoEqualityInformation) {
59735978
// We have no information so we have to assume the pattern might or
59745979
// might not match.
@@ -6560,14 +6565,15 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
65606565
void doStatement_end(Expression condition) {}
65616566

65626567
@override
6563-
ExpressionInfo<Type>? equalityOperand_end(Expression operand, Type type) =>
6564-
null;
6568+
ExpressionInfo<Type>? equalityOperand_end(Expression operand) => null;
65656569

65666570
@override
65676571
void equalityOperation_end(
65686572
Expression wholeExpression,
65696573
ExpressionInfo<Type>? leftOperandInfo,
6574+
Type leftOperandType,
65706575
ExpressionInfo<Type>? rightOperandInfo,
6576+
Type rightOperandType,
65716577
{bool notEqual = false}) {}
65726578

65736579
@override

pkg/_fe_analyzer_shared/test/mini_ast.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5529,14 +5529,14 @@ class _MiniAstTypeAnalyzer
55295529
var leftType = analyzeExpression(lhs, operations.unknownType);
55305530
ExpressionInfo<SharedTypeView<Type>>? leftInfo;
55315531
if (isEquals) {
5532-
leftInfo = flow.equalityOperand_end(lhs, leftType);
5532+
leftInfo = flow.equalityOperand_end(lhs);
55335533
} else if (isLogical) {
55345534
flow.logicalBinaryOp_rightBegin(lhs, node, isAnd: isAnd);
55355535
}
55365536
var rightType = analyzeExpression(rhs, operations.unknownType);
55375537
if (isEquals) {
55385538
flow.equalityOperation_end(
5539-
node, leftInfo, flow.equalityOperand_end(rhs, rightType),
5539+
node, leftInfo, leftType, flow.equalityOperand_end(rhs), rightType,
55405540
notEqual: isNot);
55415541
} else if (isLogical) {
55425542
flow.logicalBinaryOp_end(node, rhs, isAnd: isAnd);

pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ class BinaryExpressionResolver {
9999
ExpressionInfo<SharedTypeView<DartType>>? leftInfo;
100100
var leftExtensionOverride = left is ExtensionOverride;
101101
if (!leftExtensionOverride) {
102-
leftInfo =
103-
flow?.equalityOperand_end(left, SharedTypeView(left.typeOrThrow));
102+
leftInfo = flow?.equalityOperand_end(left);
104103
}
105104

106105
_resolver.analyzeExpression(
@@ -109,8 +108,12 @@ class BinaryExpressionResolver {
109108
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(right);
110109

111110
if (!leftExtensionOverride) {
112-
flow?.equalityOperation_end(node, leftInfo,
113-
flow.equalityOperand_end(right, SharedTypeView(right.typeOrThrow)),
111+
flow?.equalityOperation_end(
112+
node,
113+
leftInfo,
114+
SharedTypeView(left.typeOrThrow),
115+
flow.equalityOperand_end(right),
116+
SharedTypeView(right.typeOrThrow),
114117
notEqual: notEqual);
115118
}
116119

pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,12 @@ abstract class FullInvocationInferrer<Node extends AstNodeImpl>
241241
rawType.typeFormals, inferrer.choosePreliminaryTypes());
242242
}
243243

244-
List<ExpressionInfo<SharedTypeView<DartType>>?>? identicalInfo =
244+
List<_IdenticalArgumentInfo?>? identicalArgumentInfo =
245245
_isIdentical ? [] : null;
246246
var parameterMap = _computeParameterMap(rawType?.parameters ?? const []);
247247
var deferredFunctionLiterals = _visitArguments(
248248
parameterMap: parameterMap,
249-
identicalInfo: identicalInfo,
249+
identicalArgumentInfo: identicalArgumentInfo,
250250
substitution: substitution,
251251
inferrer: inferrer);
252252
if (deferredFunctionLiterals != null) {
@@ -264,7 +264,7 @@ abstract class FullInvocationInferrer<Node extends AstNodeImpl>
264264
}
265265
_resolveDeferredFunctionLiterals(
266266
deferredFunctionLiterals: stage,
267-
identicalInfo: identicalInfo,
267+
identicalArgumentInfo: identicalArgumentInfo,
268268
substitution: substitution,
269269
inferrer: inferrer);
270270
isFirstStage = false;
@@ -289,7 +289,7 @@ abstract class FullInvocationInferrer<Node extends AstNodeImpl>
289289
}
290290
var returnType = _refineReturnType(
291291
InvocationInferrer.computeInvokeReturnType(invokeType));
292-
_recordIdenticalInfo(identicalInfo);
292+
_recordIdenticalArgumentInfo(identicalArgumentInfo);
293293
return returnType;
294294
}
295295

@@ -472,19 +472,25 @@ class InvocationInferrer<Node extends AstNodeImpl> {
472472

473473
/// If the invocation being processed is a call to `identical`, informs flow
474474
/// analysis about it, so that it can do appropriate promotions.
475-
void _recordIdenticalInfo(
476-
List<ExpressionInfo<SharedTypeView<DartType>>?>? identicalInfo) {
475+
void _recordIdenticalArgumentInfo(
476+
List<_IdenticalArgumentInfo?>? identicalArgumentInfo) {
477477
var flow = resolver.flowAnalysis.flow;
478-
if (identicalInfo != null) {
479-
flow?.equalityOperation_end(argumentList.parent as Expression,
480-
identicalInfo[0], identicalInfo[1]);
478+
if (identicalArgumentInfo != null) {
479+
var leftOperandInfo = identicalArgumentInfo[0]!;
480+
var rightOperandInfo = identicalArgumentInfo[1]!;
481+
flow?.equalityOperation_end(
482+
argumentList.parent as Expression,
483+
leftOperandInfo.expressionInfo,
484+
SharedTypeView<DartType>(leftOperandInfo.staticType),
485+
rightOperandInfo.expressionInfo,
486+
SharedTypeView<DartType>(rightOperandInfo.staticType));
481487
}
482488
}
483489

484490
/// Resolves any function literals that were deferred by [_visitArguments].
485491
void _resolveDeferredFunctionLiterals(
486492
{required List<_DeferredParamInfo> deferredFunctionLiterals,
487-
List<ExpressionInfo<SharedTypeView<DartType>>?>? identicalInfo,
493+
List<_IdenticalArgumentInfo?>? identicalArgumentInfo,
488494
Substitution? substitution,
489495
GenericInferrer? inferrer}) {
490496
var flow = resolver.flowAnalysis.flow;
@@ -506,8 +512,9 @@ class InvocationInferrer<Node extends AstNodeImpl> {
506512
argument, SharedTypeSchemaView(parameterContextType));
507513
argument = resolver.popRewrite()!;
508514
if (flow != null) {
509-
identicalInfo?[deferredArgument.index] = flow.equalityOperand_end(
510-
argument, SharedTypeView(argument.typeOrThrow));
515+
identicalArgumentInfo?[deferredArgument.index] = _IdenticalArgumentInfo(
516+
expressionInfo: flow.equalityOperand_end(argument),
517+
staticType: argument.typeOrThrow);
511518
}
512519
if (parameter != null) {
513520
inferrer?.constrainArgument(
@@ -522,7 +529,7 @@ class InvocationInferrer<Node extends AstNodeImpl> {
522529
/// returned.
523530
List<_DeferredParamInfo>? _visitArguments(
524531
{required Map<Object, ParameterElement> parameterMap,
525-
List<ExpressionInfo<SharedTypeView<DartType>>?>? identicalInfo,
532+
List<_IdenticalArgumentInfo?>? identicalArgumentInfo,
526533
Substitution? substitution,
527534
GenericInferrer? inferrer}) {
528535
assert(whyNotPromotedList.isEmpty);
@@ -549,7 +556,7 @@ class InvocationInferrer<Node extends AstNodeImpl> {
549556
value is FunctionExpressionImpl) {
550557
(deferredFunctionLiterals ??= [])
551558
.add(_DeferredParamInfo(parameter, value, i, parameterKey));
552-
identicalInfo?.add(null);
559+
identicalArgumentInfo?.add(null);
553560
// The "why not promoted" list isn't really relevant for function
554561
// literals because promoting a function literal doesn't even make
555562
// sense. So we store an innocuous value in the list.
@@ -569,8 +576,9 @@ class InvocationInferrer<Node extends AstNodeImpl> {
569576
argument, SharedTypeSchemaView(parameterContextType));
570577
argument = resolver.popRewrite()!;
571578
if (flow != null) {
572-
identicalInfo?.add(flow.equalityOperand_end(
573-
argument, SharedTypeView(argument.typeOrThrow)));
579+
identicalArgumentInfo?.add(_IdenticalArgumentInfo(
580+
expressionInfo: flow.equalityOperand_end(argument),
581+
staticType: argument.typeOrThrow));
574582
whyNotPromotedList.add(flow.whyNotPromoted(argument));
575583
}
576584
if (parameter != null) {
@@ -712,6 +720,20 @@ class _FunctionLiteralDependencies extends FunctionLiteralDependencies<
712720
}
713721
}
714722

723+
/// Information tracked by [InvocationInferrer] about an argument passed to the
724+
/// `identical` function in `dart:core`.
725+
class _IdenticalArgumentInfo {
726+
/// The [ExpressionInfo] returned by [FlowAnalysis.equalityOperand_end] for
727+
/// the argument.
728+
final ExpressionInfo<SharedTypeView<DartType>>? expressionInfo;
729+
730+
/// The static type of the argument.
731+
final DartType staticType;
732+
733+
_IdenticalArgumentInfo(
734+
{required this.expressionInfo, required this.staticType});
735+
}
736+
715737
/// Information about an invocation argument that may or may not have already
716738
/// been resolved, as part of the deferred resolution mechanism for the
717739
/// `inference-update-1` feature.

pkg/front_end/lib/src/type_inference/inference_visitor.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6744,7 +6744,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
67446744
int fileOffset, Expression left, DartType leftType, Expression right,
67456745
{required bool isNot}) {
67466746
ExpressionInfo<SharedTypeView<DartType>>? equalityInfo =
6747-
flowAnalysis.equalityOperand_end(left, new SharedTypeView(leftType));
6747+
flowAnalysis.equalityOperand_end(left);
67486748

67496749
Expression? equals;
67506750
ExpressionInferenceResult rightResult =
@@ -6762,8 +6762,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
67626762
flowAnalysis.equalityOperation_end(
67636763
equals,
67646764
equalityInfo,
6765-
flowAnalysis.equalityOperand_end(rightResult.expression,
6766-
new SharedTypeView(rightResult.inferredType)),
6765+
new SharedTypeView(leftType),
6766+
flowAnalysis.equalityOperand_end(rightResult.expression),
6767+
new SharedTypeView(rightResult.inferredType),
67676768
notEqual: isNot);
67686769
return new ExpressionInferenceResult(
67696770
coreTypes.boolRawType(Nullability.nonNullable), equals);
@@ -6811,8 +6812,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
68116812
flowAnalysis.equalityOperation_end(
68126813
equals,
68136814
equalityInfo,
6814-
flowAnalysis.equalityOperand_end(
6815-
right, new SharedTypeView(rightResult.inferredType)),
6815+
new SharedTypeView(leftType),
6816+
flowAnalysis.equalityOperand_end(right),
6817+
new SharedTypeView(rightResult.inferredType),
68166818
notEqual: isNot);
68176819
return new ExpressionInferenceResult(
68186820
equalsTarget.isNever

pkg/front_end/lib/src/type_inference/inference_visitor_base.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,8 +1802,7 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
18021802
}
18031803
Expression expression =
18041804
_hoist(result.expression, inferredType, hoistedExpressions);
1805-
identicalInfo?.add(flowAnalysis.equalityOperand_end(
1806-
expression, new SharedTypeView(inferredType)));
1805+
identicalInfo?.add(flowAnalysis.equalityOperand_end(expression));
18071806
if (isExpression) {
18081807
arguments.positional[index] = expression..parent = arguments;
18091808
} else {
@@ -1843,8 +1842,7 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
18431842
DartType inferredType = _computeInferredType(result);
18441843
Expression expression = result.expression;
18451844
identicalInfo?[deferredArgument.evaluationOrderIndex] =
1846-
flowAnalysis.equalityOperand_end(
1847-
expression, new SharedTypeView(inferredType));
1845+
flowAnalysis.equalityOperand_end(expression);
18481846
if (deferredArgument.isNamed) {
18491847
NamedExpression namedArgument =
18501848
arguments.named[deferredArgument.index];
@@ -1862,7 +1860,11 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
18621860
}
18631861
if (identicalInfo != null) {
18641862
flowAnalysis.equalityOperation_end(
1865-
arguments.parent as Expression, identicalInfo[0], identicalInfo[1]);
1863+
arguments.parent as Expression,
1864+
identicalInfo[0],
1865+
new SharedTypeView(actualTypes[0]),
1866+
identicalInfo[1],
1867+
new SharedTypeView(actualTypes[1]));
18661868
}
18671869
assert(
18681870
positionalIndex == arguments.positional.length,

0 commit comments

Comments
 (0)