mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:19:48 +00:00
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:
parent
13fdaa09ac
commit
7dda27b543
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue