Skip to content

Commit 7dda27b

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
inference-update-3: use unpromoted type as context for local variable assignments.
This change implements one of the features of experimental feature `inference-update-3`: with the experimental feature enabled, assignments to local variables use the declared (or inferred) type of the local variable as the context for evaluating the RHS of the assignment, regardless of whether the local variable is promoted. With the experimental feature disabled, assignments to local variables continue to use the promoted type of the local variable as the context for evaluating the RHS of the assignment. This eliminates one of the scenarios in which the context type of an assignment is "aspirational" (i.e., not required to be met in order to prevent a compile time error). Once all aspirational context types have been removed from the language, we will be able to re-work coercions to be based on context type, which fixes a number of usability footguns in the language. See dart-lang/language#3471 for details. Bug: dart-lang/language#3471 Change-Id: Ic07ac1810b641a9208c168846cd5fd912088d62b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/338802 Reviewed-by: Bob Nystrom <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 13fdaa0 commit 7dda27b

File tree

5 files changed

+255
-3
lines changed

5 files changed

+255
-3
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
6+
import 'package:analyzer/dart/analysis/features.dart';
67
import 'package:analyzer/dart/ast/token.dart';
78
import 'package:analyzer/dart/element/element.dart';
89
import 'package:analyzer/dart/element/type.dart';
@@ -86,7 +87,9 @@ class AssignmentExpressionResolver {
8687
DartType? rhsContext;
8788
{
8889
var leftType = node.writeType;
89-
if (writeElement is VariableElement) {
90+
if (writeElement is VariableElement &&
91+
!_resolver.definingLibrary.featureSet
92+
.isEnabled(Feature.inference_update_3)) {
9093
leftType = _resolver.localVariableTypeProvider
9194
.getType(left as SimpleIdentifier, isRead: false);
9295
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -8596,7 +8596,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
85968596
}
85978597
DartType declaredOrInferredType = variable.lateType ?? variable.type;
85988598
DartType? promotedType;
8599-
if (isNonNullableByDefault) {
8599+
if (isNonNullableByDefault &&
8600+
!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) {
86008601
promotedType = flowAnalysis.promotedType(variable);
86018602
}
86028603
ExpressionInferenceResult rhsResult = inferExpression(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests that when `inference-update-3` is disabled, and an assignment is made
6+
// to a local variable (or parameter), the unpromoted type of the variable is
7+
// used as the context for the RHS of the assignment.
8+
9+
// @dart=3.3
10+
11+
import 'package:expect/expect.dart';
12+
13+
import '../static_type_helper.dart';
14+
15+
void testNonDemotingAssignmentOfParameter(num? x, num? y) {
16+
if (x != null) {
17+
x = contextType(1)..expectStaticType<Exactly<num>>();
18+
}
19+
if (y is int) {
20+
y = contextType(1)..expectStaticType<Exactly<int>>();
21+
}
22+
}
23+
24+
void testNonDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
25+
num? y = x;
26+
if (y != null) {
27+
y = contextType(1)..expectStaticType<Exactly<num>>();
28+
}
29+
y = x;
30+
if (y is int) {
31+
y = contextType(1)..expectStaticType<Exactly<int>>();
32+
}
33+
}
34+
35+
void testNonDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
36+
var y = x;
37+
if (y != null) {
38+
y = contextType(1)..expectStaticType<Exactly<num>>();
39+
}
40+
y = x;
41+
if (y is int) {
42+
y = contextType(1)..expectStaticType<Exactly<int>>();
43+
}
44+
}
45+
46+
void testDemotingAssignmentOfParameter(num? x, num? y) {
47+
if (x != null) {
48+
// A type error is expected at runtime, because type inference will fill in
49+
// a type of `num` for the type argument of `contextType`, and `contextType`
50+
// tries to cast its input to its type argument.
51+
//
52+
// We can't use `Expect.throwsTypeError` to check for this, because then the
53+
// assignment would be happening inside a function expression, blocking type
54+
// promotion.
55+
bool errorOccurred = false;
56+
try {
57+
x = contextType(null)..expectStaticType<Exactly<num>>();
58+
} on TypeError {
59+
errorOccurred = true;
60+
}
61+
Expect.equals(hasSoundNullSafety, errorOccurred);
62+
}
63+
if (y is int) {
64+
// A type error is expected at runtime, because type inference will fill in
65+
// a type of `int` for the type argument of `contextType`, and `contextType`
66+
// tries to cast its input to its type argument.
67+
//
68+
// We can't use `Expect.throwsTypeError` to check for this, because then the
69+
// assignment would be happening inside a function expression, blocking type
70+
// promotion.
71+
bool errorOccurred = false;
72+
try {
73+
y = contextType(1.5)..expectStaticType<Exactly<int>>();
74+
} on TypeError {
75+
errorOccurred = true;
76+
}
77+
Expect.isTrue(errorOccurred);
78+
}
79+
}
80+
81+
void testDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
82+
num? y = x;
83+
if (y != null) {
84+
// A type error is expected at runtime, because type inference will fill in
85+
// a type of `num` for the type argument of `contextType`, and `contextType`
86+
// tries to cast its input to its type argument.
87+
//
88+
// We can't use `Expect.throwsTypeError` to check for this, because then the
89+
// assignment would be happening inside a function expression, blocking type
90+
// promotion.
91+
bool errorOccurred = false;
92+
try {
93+
y = contextType(null)..expectStaticType<Exactly<num>>();
94+
} on TypeError {
95+
errorOccurred = true;
96+
}
97+
Expect.equals(hasSoundNullSafety, errorOccurred);
98+
}
99+
y = x;
100+
if (y is int) {
101+
// A type error is expected at runtime, because type inference will fill in
102+
// a type of `int` for the type argument of `contextType`, and `contextType`
103+
// tries to cast its input to its type argument.
104+
//
105+
// We can't use `Expect.throwsTypeError` to check for this, because then the
106+
// assignment would be happening inside a function expression, blocking type
107+
// promotion.
108+
bool errorOccurred = false;
109+
try {
110+
y = contextType(1.5)..expectStaticType<Exactly<int>>();
111+
} on TypeError {
112+
errorOccurred = true;
113+
}
114+
Expect.isTrue(errorOccurred);
115+
}
116+
}
117+
118+
void testDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
119+
var y = x;
120+
if (y != null) {
121+
// A type error is expected at runtime, because type inference will fill in
122+
// a type of `num` for the type argument of `contextType`, and `contextType`
123+
// tries to cast its input to its type argument.
124+
//
125+
// We can't use `Expect.throwsTypeError` to check for this, because then the
126+
// assignment would be happening inside a function expression, blocking type
127+
// promotion.
128+
bool errorOccurred = false;
129+
try {
130+
y = contextType(null)..expectStaticType<Exactly<num>>();
131+
} on TypeError {
132+
errorOccurred = true;
133+
}
134+
Expect.equals(hasSoundNullSafety, errorOccurred);
135+
}
136+
y = x;
137+
if (y is int) {
138+
// A type error is expected at runtime, because type inference will fill in
139+
// a type of `int` for the type argument of `contextType`, and `contextType`
140+
// tries to cast its input to its type argument.
141+
//
142+
// We can't use `Expect.throwsTypeError` to check for this, because then the
143+
// assignment would be happening inside a function expression, blocking type
144+
// promotion.
145+
bool errorOccurred = false;
146+
try {
147+
y = contextType(1.5)..expectStaticType<Exactly<int>>();
148+
} on TypeError {
149+
errorOccurred = true;
150+
}
151+
Expect.isTrue(errorOccurred);
152+
}
153+
}
154+
155+
main() {
156+
for (var x in [null, 0, 0.5]) {
157+
testNonDemotingAssignmentOfParameter(x, x);
158+
testNonDemotingAssignmentOfExplicitlyTypedLocal(x);
159+
testNonDemotingAssignmentOfImplicitlyTypedLocal(x);
160+
testDemotingAssignmentOfParameter(x, x);
161+
testDemotingAssignmentOfExplicitlyTypedLocal(x);
162+
testDemotingAssignmentOfImplicitlyTypedLocal(x);
163+
}
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests that when `inference-update-3` is enabled, and an assignment is made to
6+
// a local variable (or parameter), the unpromoted type of the variable is used
7+
// as the context for the RHS of the assignment.
8+
9+
// SharedOptions=--enable-experiment=inference-update-3
10+
11+
import '../static_type_helper.dart';
12+
13+
void testNonDemotingAssignmentOfParameter(num? x, num? y) {
14+
if (x != null) {
15+
x = contextType(1)..expectStaticType<Exactly<num?>>();
16+
}
17+
if (y is int) {
18+
y = contextType(1)..expectStaticType<Exactly<num?>>();
19+
}
20+
}
21+
22+
void testNonDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
23+
num? y = x;
24+
if (y != null) {
25+
y = contextType(1)..expectStaticType<Exactly<num?>>();
26+
}
27+
y = x;
28+
if (y is int) {
29+
y = contextType(1)..expectStaticType<Exactly<num?>>();
30+
}
31+
}
32+
33+
void testNonDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
34+
var y = x;
35+
if (y != null) {
36+
y = contextType(1)..expectStaticType<Exactly<num?>>();
37+
}
38+
y = x;
39+
if (y is int) {
40+
y = contextType(1)..expectStaticType<Exactly<num?>>();
41+
}
42+
}
43+
44+
void testDemotingAssignmentOfParameter(num? x, num? y) {
45+
if (x != null) {
46+
x = contextType(null)..expectStaticType<Exactly<num?>>();
47+
}
48+
if (y is int) {
49+
y = contextType(null)..expectStaticType<Exactly<num?>>();
50+
}
51+
}
52+
53+
void testDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
54+
num? y = x;
55+
if (y != null) {
56+
y = contextType(null)..expectStaticType<Exactly<num?>>();
57+
}
58+
y = x;
59+
if (y is int) {
60+
y = contextType(null)..expectStaticType<Exactly<num?>>();
61+
}
62+
}
63+
64+
void testDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
65+
var y = x;
66+
if (y != null) {
67+
y = contextType(null)..expectStaticType<Exactly<num?>>();
68+
}
69+
y = x;
70+
if (y is int) {
71+
y = contextType(null)..expectStaticType<Exactly<num?>>();
72+
}
73+
}
74+
75+
main() {
76+
for (var x in [null, 0, 0.5]) {
77+
testNonDemotingAssignmentOfParameter(x, x);
78+
testNonDemotingAssignmentOfExplicitlyTypedLocal(x);
79+
testNonDemotingAssignmentOfImplicitlyTypedLocal(x);
80+
testDemotingAssignmentOfParameter(x, x);
81+
testDemotingAssignmentOfExplicitlyTypedLocal(x);
82+
testDemotingAssignmentOfImplicitlyTypedLocal(x);
83+
}
84+
}

tests/language/static_type_helper.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Object? context<T>(T x) => x;
1414
/// ```dart
1515
/// int x = contextType(1 /* valid value */)..expectStaticType<Exactly<int>>;
1616
/// ```
17-
T contextType<T>(Object result) => result as T;
17+
T contextType<T>(Object? result) => result as T;
1818

1919
extension StaticType<T> on T {
2020
/// Check the static type.

0 commit comments

Comments
 (0)