Skip to content

Commit e1f0d03

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: rework analyzer/CFE handling of initialized variables.
Previously, the analyzer and CFE were responsible for two pieces of logic that arguably should be in the shared flow analysis engine: - Deciding whether or not it's necessary to tell flow analysis about initializer expressions. - Deciding whether or not to promote the variable at the time of initialization (we do this when the variable is implicitly typed, and the initializer is a promoted type variable type). It's better to just always tell flow analysis about the initializer expression and let it decide what to do about it. This paves the way for fixing dart-lang/language#1785 (which results from initializer expressions sometimes being ignored when they shouldn't be), by consolidating the broken logic into the flow_analysis library, where it will be easy to unit test the fix. Bug: dart-lang/language#1785 Change-Id: Iec832e92995eb4f8d0c1fbd4e9be6c897e0917b5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/211180 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent d809398 commit e1f0d03

File tree

9 files changed

+173
-78
lines changed

9 files changed

+173
-78
lines changed

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

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,9 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
634634
/// Call this method after visiting the initializer of a variable declaration.
635635
void initialize(
636636
Variable variable, Type initializerType, Expression initializerExpression,
637-
{required bool isFinal, required bool isLate});
637+
{required bool isFinal,
638+
required bool isLate,
639+
required bool isImplicitlyTyped});
638640

639641
/// Return whether the [variable] is definitely assigned in the current state.
640642
bool isAssigned(Variable variable);
@@ -724,11 +726,6 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
724726
void parenthesizedExpression(
725727
Expression outerExpression, Expression innerExpression);
726728

727-
/// Attempt to promote [variable] to [type]. The client may use this to
728-
/// ensure that a variable declaration of the form `var x = expr;` promotes
729-
/// `x` to type `X&T` in the circumstance where the type of `expr` is `X&T`.
730-
void promote(Variable variable, Type type);
731-
732729
/// Retrieves the type that the [variable] is promoted to, if the [variable]
733730
/// is currently promoted. Otherwise returns `null`.
734731
Type? promotedType(Variable variable);
@@ -1225,13 +1222,18 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
12251222
@override
12261223
void initialize(
12271224
Variable variable, Type initializerType, Expression initializerExpression,
1228-
{required bool isFinal, required bool isLate}) {
1225+
{required bool isFinal,
1226+
required bool isLate,
1227+
required bool isImplicitlyTyped}) {
12291228
_wrap(
12301229
'initialize($variable, $initializerType, $initializerExpression, '
1231-
'isFinal: $isFinal, isLate: $isLate)',
1230+
'isFinal: $isFinal, isLate: $isLate, '
1231+
'isImplicitlyTyped: $isImplicitlyTyped)',
12321232
() => _wrapped.initialize(
12331233
variable, initializerType, initializerExpression,
1234-
isFinal: isFinal, isLate: isLate));
1234+
isFinal: isFinal,
1235+
isLate: isLate,
1236+
isImplicitlyTyped: isImplicitlyTyped));
12351237
}
12361238

12371239
@override
@@ -1340,11 +1342,6 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
13401342
_wrapped.parenthesizedExpression(outerExpression, innerExpression));
13411343
}
13421344

1343-
@override
1344-
void promote(Variable variable, Type type) {
1345-
_wrap('promote($variable, $type', () => _wrapped.promote(variable, type));
1346-
}
1347-
13481345
@override
13491346
Type? promotedType(Variable variable) {
13501347
return _wrap(
@@ -2059,12 +2056,14 @@ class FlowModel<Variable extends Object, Type extends Object> {
20592056
Variable variable,
20602057
Type writtenType,
20612058
SsaNode<Variable, Type> newSsaNode,
2062-
TypeOperations<Variable, Type> typeOperations) {
2059+
TypeOperations<Variable, Type> typeOperations,
2060+
{bool promoteToTypeOfInterest = true}) {
20632061
VariableModel<Variable, Type>? infoForVar = variableInfo[variable];
20642062
if (infoForVar == null) return this;
20652063

20662064
VariableModel<Variable, Type> newInfoForVar = infoForVar.write(
2067-
nonPromotionReason, variable, writtenType, typeOperations, newSsaNode);
2065+
nonPromotionReason, variable, writtenType, typeOperations, newSsaNode,
2066+
promoteToTypeOfInterest: promoteToTypeOfInterest);
20682067
if (identical(newInfoForVar, infoForVar)) return this;
20692068

20702069
return _updateVariableInfo(new VariableReference(variable), newInfoForVar);
@@ -2663,6 +2662,9 @@ abstract class TypeOperations<Variable extends Object, Type extends Object> {
26632662
/// Return `true` if the [leftType] is a subtype of the [rightType].
26642663
bool isSubtypeOf(Type leftType, Type rightType);
26652664

2665+
/// Returns `true` if [type] is a reference to a type parameter.
2666+
bool isTypeParameterType(Type type);
2667+
26662668
/// Returns the non-null promoted version of [type].
26672669
///
26682670
/// Note that some types don't have a non-nullable version (e.g.
@@ -2819,7 +2821,8 @@ class VariableModel<Variable extends Object, Type extends Object> {
28192821
Variable variable,
28202822
Type writtenType,
28212823
TypeOperations<Variable, Type> typeOperations,
2822-
SsaNode<Variable, Type> newSsaNode) {
2824+
SsaNode<Variable, Type> newSsaNode,
2825+
{required bool promoteToTypeOfInterest}) {
28232826
if (writeCaptured) {
28242827
return new VariableModel<Variable, Type>(
28252828
promotedTypes: promotedTypes,
@@ -2834,8 +2837,10 @@ class VariableModel<Variable extends Object, Type extends Object> {
28342837
List<Type>? newPromotedTypes = demotionResult.promotedTypes;
28352838

28362839
Type declaredType = typeOperations.variableType(variable);
2837-
newPromotedTypes = _tryPromoteToTypeOfInterest(
2838-
typeOperations, declaredType, newPromotedTypes, writtenType);
2840+
if (promoteToTypeOfInterest) {
2841+
newPromotedTypes = _tryPromoteToTypeOfInterest(
2842+
typeOperations, declaredType, newPromotedTypes, writtenType);
2843+
}
28392844
// TODO(paulberry): remove demotions from demotionResult.nonPromotionHistory
28402845
// that are no longer in effect due to re-promotion.
28412846
if (identical(promotedTypes, newPromotedTypes) && assigned) {
@@ -3798,21 +3803,37 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
37983803
@override
37993804
void initialize(
38003805
Variable variable, Type initializerType, Expression initializerExpression,
3801-
{required bool isFinal, required bool isLate}) {
3802-
ExpressionInfo<Variable, Type>? expressionInfo =
3803-
_getExpressionInfo(initializerExpression);
3804-
SsaNode<Variable, Type> newSsaNode = new SsaNode<Variable, Type>(isLate
3805-
? null
3806-
: expressionInfo is _TrivialExpressionInfo
3807-
? null
3808-
: expressionInfo);
3809-
if (isFinal) {
3810-
// We don't promote final variables on initialization, so pretend the
3811-
// written type is the variable's declared type.
3812-
initializerType = typeOperations.variableType(variable);
3806+
{required bool isFinal,
3807+
required bool isLate,
3808+
required bool isImplicitlyTyped}) {
3809+
ExpressionInfo<Variable, Type>? expressionInfo;
3810+
if (isLate) {
3811+
// Don't get expression info for late variables, since we don't know when
3812+
// they'll be initialized.
3813+
} else if (isImplicitlyTyped) {
3814+
// We don't get expression info for implicitly typed variables yet (bug
3815+
// https://github.com/dart-lang/language/issues/1785).
3816+
// TODO(paulberry): fix this.
3817+
} else {
3818+
expressionInfo = _getExpressionInfo(initializerExpression);
38133819
}
3820+
SsaNode<Variable, Type> newSsaNode = new SsaNode<Variable, Type>(
3821+
expressionInfo is _TrivialExpressionInfo ? null : expressionInfo);
38143822
_current = _current.write(
3815-
null, variable, initializerType, newSsaNode, typeOperations);
3823+
null, variable, initializerType, newSsaNode, typeOperations,
3824+
promoteToTypeOfInterest: !isImplicitlyTyped && !isFinal);
3825+
if (isImplicitlyTyped &&
3826+
typeOperations.isTypeParameterType(initializerType)) {
3827+
_current = _current
3828+
.tryPromoteForTypeCheck(
3829+
typeOperations,
3830+
new ReferenceWithType<Variable, Type>(
3831+
new VariableReference<Variable, Type>(variable),
3832+
promotedType(variable) ??
3833+
typeOperations.variableType(variable)),
3834+
initializerType)
3835+
.ifTrue;
3836+
}
38163837
}
38173838

38183839
@override
@@ -3957,19 +3978,6 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
39573978
forwardExpression(outerExpression, innerExpression);
39583979
}
39593980

3960-
@override
3961-
void promote(Variable variable, Type type) {
3962-
_current = _current
3963-
.tryPromoteForTypeCheck(
3964-
typeOperations,
3965-
new ReferenceWithType<Variable, Type>(
3966-
new VariableReference<Variable, Type>(variable),
3967-
promotedType(variable) ??
3968-
typeOperations.variableType(variable)),
3969-
type)
3970-
.ifTrue;
3971-
}
3972-
39733981
@override
39743982
Type? promotedType(Variable variable) {
39753983
return _current.infoFor(variable).promotedTypes?.last;
@@ -4544,7 +4552,9 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
45444552
@override
45454553
void initialize(
45464554
Variable variable, Type initializerType, Expression initializerExpression,
4547-
{required bool isFinal, required bool isLate}) {}
4555+
{required bool isFinal,
4556+
required bool isLate,
4557+
required bool isImplicitlyTyped}) {}
45484558

45494559
@override
45504560
bool isAssigned(Variable variable) {
@@ -4703,11 +4713,6 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
47034713
forwardExpression(outerExpression, innerExpression);
47044714
}
47054715

4706-
@override
4707-
void promote(Variable variable, Type type) {
4708-
throw new UnimplementedError('TODO(paulberry)');
4709-
}
4710-
47114716
@override
47124717
Type? promotedType(Variable variable) {
47134718
return _knownTypes[variable];

pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,75 @@ main() {
13081308
]);
13091309
});
13101310

1311+
group('initialize() promotes implicitly typed vars to type parameter types',
1312+
() {
1313+
test('when not final', () {
1314+
var h = Harness()
1315+
..addSubtype('T&int', 'T', true)
1316+
..addFactor('T', 'T&int', 'T');
1317+
var x = Var('x', 'T', isImplicitlyTyped: true);
1318+
h.run([
1319+
declareInitialized(x, expr('T&int')),
1320+
checkPromoted(x, 'T&int'),
1321+
]);
1322+
});
1323+
1324+
test('when final', () {
1325+
var h = Harness()
1326+
..addSubtype('T&int', 'T', true)
1327+
..addFactor('T', 'T&int', 'T');
1328+
var x = Var('x', 'T', isImplicitlyTyped: true);
1329+
h.run([
1330+
declareInitialized(x, expr('T&int'), isFinal: true),
1331+
checkPromoted(x, 'T&int'),
1332+
]);
1333+
});
1334+
});
1335+
1336+
group(
1337+
"initialize() doesn't promote explicitly typed vars to type "
1338+
'parameter types', () {
1339+
test('when not final', () {
1340+
var h = Harness();
1341+
var x = Var('x', 'T');
1342+
h.run([
1343+
declareInitialized(x, expr('T&int')),
1344+
checkNotPromoted(x),
1345+
]);
1346+
});
1347+
1348+
test('when final', () {
1349+
var h = Harness();
1350+
var x = Var('x', 'T');
1351+
h.run([
1352+
declareInitialized(x, expr('T&int'), isFinal: true),
1353+
checkNotPromoted(x),
1354+
]);
1355+
});
1356+
});
1357+
1358+
group(
1359+
"initialize() doesn't promote implicitly typed vars to ordinary types",
1360+
() {
1361+
test('when not final', () {
1362+
var h = Harness();
1363+
var x = Var('x', 'dynamic', isImplicitlyTyped: true);
1364+
h.run([
1365+
declareInitialized(x, expr('Null')),
1366+
checkNotPromoted(x),
1367+
]);
1368+
});
1369+
1370+
test('when final', () {
1371+
var h = Harness();
1372+
var x = Var('x', 'dynamic', isImplicitlyTyped: true);
1373+
h.run([
1374+
declareInitialized(x, expr('Null'), isFinal: true),
1375+
checkNotPromoted(x),
1376+
]);
1377+
});
1378+
});
1379+
13111380
test('initialize() stores expressionInfo when not late', () {
13121381
var h = Harness();
13131382
var x = Var('x', 'Object');
@@ -1338,6 +1407,19 @@ main() {
13381407
]);
13391408
});
13401409

1410+
test('initialize() does not store expressionInfo for implicitly typed vars',
1411+
() {
1412+
var h = Harness();
1413+
var x = Var('x', 'Object', isImplicitlyTyped: true);
1414+
var y = Var('y', 'int?');
1415+
h.run([
1416+
declareInitialized(x, y.expr.eq(nullLiteral)),
1417+
getSsaNodes((nodes) {
1418+
expect(nodes[x]!.expressionInfo, isNull);
1419+
}),
1420+
]);
1421+
});
1422+
13411423
test('initialize() does not store expressionInfo for trivial expressions',
13421424
() {
13431425
var h = Harness();

pkg/_fe_analyzer_shared/test/mini_ast.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ class Harness extends TypeOperations<Var, Type> {
459459
return _subtypes[query] ?? fail('Unknown subtype query: $query');
460460
}
461461

462+
@override
463+
bool isTypeParameterType(Type type) => type is PromotedTypeVariableType;
464+
462465
@override
463466
Type promoteToNonNull(Type type) {
464467
if (type.type.endsWith('?')) {
@@ -649,8 +652,10 @@ abstract class TryStatement extends Statement implements TryBuilder {
649652
class Var {
650653
final String name;
651654
final Type type;
655+
final bool isImplicitlyTyped;
652656

653-
Var(this.name, String typeStr) : type = Type(typeStr);
657+
Var(this.name, String typeStr, {this.isImplicitlyTyped = false})
658+
: type = Type(typeStr);
654659

655660
/// Creates an L-value representing a reference to this variable.
656661
LValue get expr => new _VariableReference(this, null);
@@ -1608,7 +1613,9 @@ class _MiniAstTypeAnalyzer {
16081613
var initializerType = analyzeExpression(initializer);
16091614
flow.declare(variable, true);
16101615
flow.initialize(variable, initializerType, initializer,
1611-
isFinal: isFinal, isLate: isLate);
1616+
isFinal: isFinal,
1617+
isLate: isLate,
1618+
isImplicitlyTyped: variable.isImplicitlyTyped);
16121619
}
16131620
}
16141621

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,9 @@ class TypeSystemTypeOperations
399399
return typeSystem.isSubtypeOf(leftType, rightType);
400400
}
401401

402+
@override
403+
bool isTypeParameterType(DartType type) => type is TypeParameterType;
404+
402405
@override
403406
DartType promoteToNonNull(DartType type) {
404407
return typeSystem.promoteToNonNull(type);

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,17 +2014,11 @@ class ResolverVisitor extends ResolverBase with ErrorDetectionHelpers {
20142014
var declaredType = parent.type;
20152015
if (initializer != null) {
20162016
var initializerStaticType = initializer.typeOrThrow;
2017-
if (declaredType == null) {
2018-
if (_isNonNullableByDefault &&
2019-
initializerStaticType is TypeParameterType) {
2020-
flowAnalysis.flow?.promote(
2021-
declaredElement as PromotableElement, initializerStaticType);
2022-
}
2023-
} else {
2024-
flowAnalysis.flow?.initialize(declaredElement as PromotableElement,
2025-
initializerStaticType, initializer,
2026-
isFinal: parent.isFinal, isLate: parent.isLate);
2027-
}
2017+
flowAnalysis.flow?.initialize(declaredElement as PromotableElement,
2018+
initializerStaticType, initializer,
2019+
isFinal: parent.isFinal,
2020+
isLate: parent.isLate,
2021+
isImplicitlyTyped: declaredType == null);
20282022
}
20292023
}
20302024

pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6458,20 +6458,15 @@ class InferenceVisitor
64586458
}
64596459
if (initializerResult != null) {
64606460
DartType initializerType = initializerResult.inferredType;
6461-
if (node.isImplicitlyTyped) {
6462-
if (inferrer.isNonNullableByDefault &&
6463-
initializerType is TypeParameterType) {
6464-
inferrer.flowAnalysis.promote(node, initializerType);
6465-
}
6466-
} else {
6467-
// TODO(paulberry): `initializerType` is sometimes `null` during top
6468-
// level inference. Figure out how to prevent this.
6469-
// ignore: unnecessary_null_comparison
6470-
if (initializerType != null) {
6471-
inferrer.flowAnalysis.initialize(
6472-
node, initializerType, initializerResult.expression,
6473-
isFinal: node.isFinal, isLate: node.isLate);
6474-
}
6461+
// TODO(paulberry): `initializerType` is sometimes `null` during top
6462+
// level inference. Figure out how to prevent this.
6463+
// ignore: unnecessary_null_comparison
6464+
if (initializerType != null) {
6465+
inferrer.flowAnalysis.initialize(
6466+
node, initializerType, initializerResult.expression,
6467+
isFinal: node.isFinal,
6468+
isLate: node.isLate,
6469+
isImplicitlyTyped: node.isImplicitlyTyped);
64756470
}
64766471
Expression initializer = inferrer.ensureAssignableResult(
64776472
node.type, initializerResult,

0 commit comments

Comments
 (0)