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
https://github.com/dart-lang/language/issues/3471 for details.

Bug: https://github.com/dart-lang/language/issues/3471
Change-Id: Ic07ac1810b641a9208c168846cd5fd912088d62b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/338802
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-12-07 23:30:06 +00:00 committed by Commit Queue
parent 13fdaa09ac
commit 7dda27b543
5 changed files with 255 additions and 3 deletions

View file

@ -3,6 +3,7 @@
// 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/analysis/features.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
@ -86,7 +87,9 @@ class AssignmentExpressionResolver {
DartType? rhsContext;
{
var leftType = node.writeType;
if (writeElement is VariableElement) {
if (writeElement is VariableElement &&
!_resolver.definingLibrary.featureSet
.isEnabled(Feature.inference_update_3)) {
leftType = _resolver.localVariableTypeProvider
.getType(left as SimpleIdentifier, isRead: false);
}

View file

@ -8596,7 +8596,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
}
DartType declaredOrInferredType = variable.lateType ?? variable.type;
DartType? promotedType;
if (isNonNullableByDefault) {
if (isNonNullableByDefault &&
!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) {
promotedType = flowAnalysis.promotedType(variable);
}
ExpressionInferenceResult rhsResult = inferExpression(

View file

@ -0,0 +1,164 @@
// Copyright (c) 2023, 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.
// Tests that when `inference-update-3` is disabled, and an assignment is made
// to a local variable (or parameter), the unpromoted type of the variable is
// used as the context for the RHS of the assignment.
// @dart=3.3
import 'package:expect/expect.dart';
import '../static_type_helper.dart';
void testNonDemotingAssignmentOfParameter(num? x, num? y) {
if (x != null) {
x = contextType(1)..expectStaticType<Exactly<num>>();
}
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<int>>();
}
}
void testNonDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
num? y = x;
if (y != null) {
y = contextType(1)..expectStaticType<Exactly<num>>();
}
y = x;
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<int>>();
}
}
void testNonDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
var y = x;
if (y != null) {
y = contextType(1)..expectStaticType<Exactly<num>>();
}
y = x;
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<int>>();
}
}
void testDemotingAssignmentOfParameter(num? x, num? y) {
if (x != null) {
// A type error is expected at runtime, because type inference will fill in
// a type of `num` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
x = contextType(null)..expectStaticType<Exactly<num>>();
} on TypeError {
errorOccurred = true;
}
Expect.equals(hasSoundNullSafety, errorOccurred);
}
if (y is int) {
// A type error is expected at runtime, because type inference will fill in
// a type of `int` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
y = contextType(1.5)..expectStaticType<Exactly<int>>();
} on TypeError {
errorOccurred = true;
}
Expect.isTrue(errorOccurred);
}
}
void testDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
num? y = x;
if (y != null) {
// A type error is expected at runtime, because type inference will fill in
// a type of `num` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
y = contextType(null)..expectStaticType<Exactly<num>>();
} on TypeError {
errorOccurred = true;
}
Expect.equals(hasSoundNullSafety, errorOccurred);
}
y = x;
if (y is int) {
// A type error is expected at runtime, because type inference will fill in
// a type of `int` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
y = contextType(1.5)..expectStaticType<Exactly<int>>();
} on TypeError {
errorOccurred = true;
}
Expect.isTrue(errorOccurred);
}
}
void testDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
var y = x;
if (y != null) {
// A type error is expected at runtime, because type inference will fill in
// a type of `num` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
y = contextType(null)..expectStaticType<Exactly<num>>();
} on TypeError {
errorOccurred = true;
}
Expect.equals(hasSoundNullSafety, errorOccurred);
}
y = x;
if (y is int) {
// A type error is expected at runtime, because type inference will fill in
// a type of `int` for the type argument of `contextType`, and `contextType`
// tries to cast its input to its type argument.
//
// We can't use `Expect.throwsTypeError` to check for this, because then the
// assignment would be happening inside a function expression, blocking type
// promotion.
bool errorOccurred = false;
try {
y = contextType(1.5)..expectStaticType<Exactly<int>>();
} on TypeError {
errorOccurred = true;
}
Expect.isTrue(errorOccurred);
}
}
main() {
for (var x in [null, 0, 0.5]) {
testNonDemotingAssignmentOfParameter(x, x);
testNonDemotingAssignmentOfExplicitlyTypedLocal(x);
testNonDemotingAssignmentOfImplicitlyTypedLocal(x);
testDemotingAssignmentOfParameter(x, x);
testDemotingAssignmentOfExplicitlyTypedLocal(x);
testDemotingAssignmentOfImplicitlyTypedLocal(x);
}
}

View file

@ -0,0 +1,84 @@
// Copyright (c) 2023, 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.
// Tests that when `inference-update-3` is enabled, and an assignment is made to
// a local variable (or parameter), the unpromoted type of the variable is used
// as the context for the RHS of the assignment.
// SharedOptions=--enable-experiment=inference-update-3
import '../static_type_helper.dart';
void testNonDemotingAssignmentOfParameter(num? x, num? y) {
if (x != null) {
x = contextType(1)..expectStaticType<Exactly<num?>>();
}
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<num?>>();
}
}
void testNonDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
num? y = x;
if (y != null) {
y = contextType(1)..expectStaticType<Exactly<num?>>();
}
y = x;
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<num?>>();
}
}
void testNonDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
var y = x;
if (y != null) {
y = contextType(1)..expectStaticType<Exactly<num?>>();
}
y = x;
if (y is int) {
y = contextType(1)..expectStaticType<Exactly<num?>>();
}
}
void testDemotingAssignmentOfParameter(num? x, num? y) {
if (x != null) {
x = contextType(null)..expectStaticType<Exactly<num?>>();
}
if (y is int) {
y = contextType(null)..expectStaticType<Exactly<num?>>();
}
}
void testDemotingAssignmentOfExplicitlyTypedLocal(num? x) {
num? y = x;
if (y != null) {
y = contextType(null)..expectStaticType<Exactly<num?>>();
}
y = x;
if (y is int) {
y = contextType(null)..expectStaticType<Exactly<num?>>();
}
}
void testDemotingAssignmentOfImplicitlyTypedLocal(num? x) {
var y = x;
if (y != null) {
y = contextType(null)..expectStaticType<Exactly<num?>>();
}
y = x;
if (y is int) {
y = contextType(null)..expectStaticType<Exactly<num?>>();
}
}
main() {
for (var x in [null, 0, 0.5]) {
testNonDemotingAssignmentOfParameter(x, x);
testNonDemotingAssignmentOfExplicitlyTypedLocal(x);
testNonDemotingAssignmentOfImplicitlyTypedLocal(x);
testDemotingAssignmentOfParameter(x, x);
testDemotingAssignmentOfExplicitlyTypedLocal(x);
testDemotingAssignmentOfImplicitlyTypedLocal(x);
}
}

View file

@ -14,7 +14,7 @@ Object? context<T>(T x) => x;
/// ```dart
/// int x = contextType(1 /* valid value */)..expectStaticType<Exactly<int>>;
/// ```
T contextType<T>(Object result) => result as T;
T contextType<T>(Object? result) => result as T;
extension StaticType<T> on T {
/// Check the static type.