[cfe] Split ensureAssignableResult into coersion and error reporting

It allows to use coersion without error reporting in the inference of
record literals, which makes the difference when the record patterns
are involved.

Closes https://github.com/dart-lang/sdk/issues/51587

Part of https://github.com/dart-lang/sdk/issues/49749

Change-Id: I64ea7e82eb3bc14d4410a47c6ed6ef278e092496
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290529
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Chloe Stefantsova 2023-03-22 17:21:45 +00:00 committed by Commit Queue
parent 6b4878b55f
commit 2eba00fabb
12 changed files with 399 additions and 22 deletions

View file

@ -9146,7 +9146,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
inferExpression(expression, contextType);
if (contextType is! UnknownType) {
expressionResult =
ensureAssignableResult(contextType, expressionResult);
coerceExpressionForAssignment(contextType, expressionResult) ??
expressionResult;
}
positionalTypes.add(
@ -9194,7 +9195,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
inferExpression(element.value, contextType);
if (contextType is! UnknownType) {
expressionResult =
ensureAssignableResult(contextType, expressionResult);
coerceExpressionForAssignment(contextType, expressionResult) ??
expressionResult;
}
Expression expression = expressionResult.expression;
DartType type = expressionResult.postCoercionType ??
@ -9225,7 +9227,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
inferExpression(element as Expression, contextType);
if (contextType is! UnknownType) {
expressionResult =
ensureAssignableResult(contextType, expressionResult);
coerceExpressionForAssignment(contextType, expressionResult) ??
expressionResult;
}
Expression expression = expressionResult.expression;
DartType type = expressionResult.postCoercionType ??

View file

@ -445,16 +445,102 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
.expression;
}
/// Same as [ensureAssignable], but accepts an [ExpressionInferenceResult]
/// rather than an expression and a type separately. If no change is made,
/// [inferenceResult] is returned unchanged.
ExpressionInferenceResult ensureAssignableResult(
/// Coerces expression ensuring its assignability to [contextType]
///
/// If the expression is assignable without coercion, [inferenceResult]
/// is returned unchanged. If no coercion is possible for the given types,
/// `null` is returned.
ExpressionInferenceResult? coerceExpressionForAssignment(
DartType contextType, ExpressionInferenceResult inferenceResult,
{int? fileOffset,
DartType? declaredContextType,
DartType? runtimeCheckedType,
bool isVoidAllowed = false,
bool coerceExpression = true,
bool coerceExpression = true}) {
// ignore: unnecessary_null_comparison
assert(contextType != null);
fileOffset ??= inferenceResult.expression.fileOffset;
contextType = computeGreatestClosure(contextType);
DartType initialContextType = runtimeCheckedType ?? contextType;
Template<Message Function(DartType, DartType, bool)>?
preciseTypeErrorTemplate =
_getPreciseTypeErrorTemplate(inferenceResult.expression);
AssignabilityResult assignabilityResult = _computeAssignabilityKind(
contextType, inferenceResult.inferredType,
isNonNullableByDefault: isNonNullableByDefault,
isVoidAllowed: isVoidAllowed,
isExpressionTypePrecise: preciseTypeErrorTemplate != null,
coerceExpression: coerceExpression);
if (assignabilityResult.needsTearOff) {
TypedTearoff typedTearoff = _tearOffCall(inferenceResult.expression,
inferenceResult.inferredType as InterfaceType, fileOffset);
inferenceResult = new ExpressionInferenceResult(
typedTearoff.tearoffType, typedTearoff.tearoff);
}
if (assignabilityResult.implicitInstantiation != null) {
inferenceResult = _applyImplicitInstantiation(
assignabilityResult.implicitInstantiation,
inferenceResult.inferredType,
inferenceResult.expression);
}
DartType expressionType = inferenceResult.inferredType;
Expression expression = inferenceResult.expression;
switch (assignabilityResult.kind) {
case AssignabilityKind.assignable:
return inferenceResult;
case AssignabilityKind.assignableCast:
// Insert an implicit downcast.
Expression asExpression =
new AsExpression(expression, initialContextType)
..isTypeError = true
..isForNonNullableByDefault = isNonNullableByDefault
..isForDynamic = expressionType is DynamicType
..fileOffset = fileOffset;
flowAnalysis.forwardExpression(asExpression, expression);
return new ExpressionInferenceResult(expressionType, asExpression,
postCoercionType: initialContextType);
case AssignabilityKind.unassignable:
// Error: not assignable. Perform error recovery.
return null;
case AssignabilityKind.unassignableVoid:
// Error: not assignable. Perform error recovery.
return null;
case AssignabilityKind.unassignablePrecise:
// The type of the expression is known precisely, so an implicit
// downcast is guaranteed to fail. Insert a compile-time error.
return null;
case AssignabilityKind.unassignableCantTearoff:
return null;
case AssignabilityKind.unassignableNullability:
return null;
default:
return unhandled("${assignabilityResult}", "ensureAssignable",
fileOffset, helper.uri);
}
}
/// Performs assignability checks on an expression
///
/// [inferenceResult.expression] of type [inferenceResult.inferredType] is
/// checked for assignability to [contextType]. The errors are reported on the
/// current library and the expression wrapped in an [InvalidExpression], if
/// needed. If no change is made, [inferenceResult] is returned unchanged.
///
/// If [isCoercionAllowed] is `true`, the assignability check is made
/// accounting for a possible coercion that may adjust the type of the
/// expression.
ExpressionInferenceResult reportAssignabilityErrors(
DartType contextType, ExpressionInferenceResult inferenceResult,
{int? fileOffset,
DartType? declaredContextType,
DartType? runtimeCheckedType,
bool isVoidAllowed = false,
bool isCoercionAllowed = true,
Template<Message Function(DartType, DartType, bool)>? errorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityErrorTemplate,
@ -492,8 +578,6 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
fileOffset ??= inferenceResult.expression.fileOffset;
contextType = computeGreatestClosure(contextType);
DartType initialContextType = runtimeCheckedType ?? contextType;
Template<Message Function(DartType, DartType, bool)>?
preciseTypeErrorTemplate =
_getPreciseTypeErrorTemplate(inferenceResult.expression);
@ -502,7 +586,7 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
isNonNullableByDefault: isNonNullableByDefault,
isVoidAllowed: isVoidAllowed,
isExpressionTypePrecise: preciseTypeErrorTemplate != null,
coerceExpression: coerceExpression);
coerceExpression: isCoercionAllowed);
if (assignabilityResult.needsTearOff) {
TypedTearoff typedTearoff = _tearOffCall(inferenceResult.expression,
@ -519,20 +603,12 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
DartType expressionType = inferenceResult.inferredType;
Expression expression = inferenceResult.expression;
Expression result;
DartType? postCoercionType;
DartType? postCoercionType = inferenceResult.postCoercionType;
Expression? result;
switch (assignabilityResult.kind) {
case AssignabilityKind.assignable:
result = expression;
break;
case AssignabilityKind.assignableCast:
// Insert an implicit downcast.
result = new AsExpression(expression, initialContextType)
..isTypeError = true
..isForNonNullableByDefault = isNonNullableByDefault
..isForDynamic = expressionType is DynamicType
..fileOffset = fileOffset;
postCoercionType = initialContextType;
break;
case AssignabilityKind.unassignable:
// Error: not assignable. Perform error recovery.
@ -615,7 +691,7 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
fileOffset, helper.uri);
}
if (!identical(result, expression)) {
if (result != null) {
flowAnalysis.forwardExpression(result, expression);
return new ExpressionInferenceResult(expressionType, result,
postCoercionType: postCoercionType);
@ -624,6 +700,57 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
}
}
/// Same as [ensureAssignable], but accepts an [ExpressionInferenceResult]
/// rather than an expression and a type separately. If no change is made,
/// [inferenceResult] is returned unchanged.
ExpressionInferenceResult ensureAssignableResult(
DartType contextType, ExpressionInferenceResult inferenceResult,
{int? fileOffset,
DartType? declaredContextType,
DartType? runtimeCheckedType,
bool isVoidAllowed = false,
bool coerceExpression = true,
Template<Message Function(DartType, DartType, bool)>? errorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityErrorTemplate,
Template<Message Function(DartType, bool)>? nullabilityNullErrorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityNullTypeErrorTemplate,
Template<Message Function(DartType, DartType, DartType, DartType, bool)>?
nullabilityPartErrorTemplate,
Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
// ignore: unnecessary_null_comparison
assert(contextType != null);
if (coerceExpression) {
ExpressionInferenceResult? coercionResult = coerceExpressionForAssignment(
contextType, inferenceResult,
fileOffset: fileOffset,
declaredContextType: declaredContextType,
runtimeCheckedType: runtimeCheckedType,
isVoidAllowed: isVoidAllowed,
coerceExpression: coerceExpression);
if (coercionResult != null) {
return coercionResult;
}
}
inferenceResult = reportAssignabilityErrors(contextType, inferenceResult,
fileOffset: fileOffset,
declaredContextType: declaredContextType,
runtimeCheckedType: runtimeCheckedType,
isVoidAllowed: isVoidAllowed,
isCoercionAllowed: coerceExpression,
errorTemplate: errorTemplate,
nullabilityErrorTemplate: nullabilityErrorTemplate,
nullabilityNullErrorTemplate: nullabilityNullErrorTemplate,
nullabilityNullTypeErrorTemplate: nullabilityNullTypeErrorTemplate,
nullabilityPartErrorTemplate: nullabilityPartErrorTemplate,
whyNotPromoted: whyNotPromoted);
return inferenceResult;
}
Expression _wrapTearoffErrorExpression(Expression expression,
DartType contextType, Template<Message Function(String)> template) {
// ignore: unnecessary_null_comparison

View file

@ -495,6 +495,7 @@ clue
code
coerce
coerced
coerces
coercing
coercion
coincides

View file

@ -0,0 +1,27 @@
// 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.
test1() {
num b = 0;
b as int;
b.isEven;
(b,) = (3.14,);
return b;
}
test2() {
num b = 0;
b as int;
b.isEven;
(b, foo: _) = (3.14, foo: "foo");
return b;
}
test3() {
num b = 0;
b as int;
b.isEven;
(foo: b) = (foo: 3.14);
return b;
}

View file

@ -0,0 +1,38 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method test1() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14);
if(!(let final dynamic #t1 = b = #0#0{(core::double)}.$1{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test2() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14, {foo: "foo"});
if(!((let final dynamic #t2 = b = #0#0{(core::double, {foo: core::String})}.$1{core::double} in true) && true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test3() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = ({foo: 3.14});
if(!(let final dynamic #t3 = b = #0#0{({foo: core::double})}.foo{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}

View file

@ -0,0 +1,45 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method test1() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14);
if(!(let final core::double #t1 = b = #0#0{(core::double)}.$1{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test2() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14, {foo: "foo"});
if(!((let final core::double #t2 = b = #0#0{(core::double, {foo: core::String})}.$1{core::double} in true) && true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test3() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = ({foo: 3.14});
if(!(let final core::double #t3 = b = #0#0{({foo: core::double})}.foo{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:9:10 -> RecordConstant(const (3.14))
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:17:17 -> RecordConstant(const (3.14, {foo: "foo"}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:25:14 -> RecordConstant(const ({foo: 3.14}))
Extra constant evaluation: evaluated: 46, effectively constant: 3

View file

@ -0,0 +1,3 @@
test1() {}
test2() {}
test3() {}

View file

@ -0,0 +1,3 @@
test1() {}
test2() {}
test3() {}

View file

@ -0,0 +1,38 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method test1() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14);
if(!(let final dynamic #t1 = b = #0#0{(core::double)}.$1{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test2() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14, {foo: "foo"});
if(!((let final dynamic #t2 = b = #0#0{(core::double, {foo: core::String})}.$1{core::double} in true) && true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test3() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = ({foo: 3.14});
if(!(let final dynamic #t3 = b = #0#0{({foo: core::double})}.foo{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}

View file

@ -0,0 +1,38 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method test1() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14);
if(!(let final dynamic #t1 = b = #0#0{(core::double)}.$1{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test2() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14, {foo: "foo"});
if(!((let final dynamic #t2 = b = #0#0{(core::double, {foo: core::String})}.$1{core::double} in true) && true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test3() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = ({foo: 3.14});
if(!(let final dynamic #t3 = b = #0#0{({foo: core::double})}.foo{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}

View file

@ -0,0 +1,9 @@
library /*isNonNullableByDefault*/;
import self as self;
static method test1() → dynamic
;
static method test2() → dynamic
;
static method test3() → dynamic
;

View file

@ -0,0 +1,45 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method test1() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14);
if(!(let final core::double #t1 = b = #0#0{(core::double)}.$1{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test2() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = (3.14, {foo: "foo"});
if(!((let final core::double #t2 = b = #0#0{(core::double, {foo: core::String})}.$1{core::double} in true) && true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
static method test3() → dynamic {
core::num b = 0;
b as{ForNonNullableByDefault} core::int;
b{core::int}.{core::int::isEven}{core::bool};
block {
final synthesized dynamic #0#0 = ({foo: 3.14});
if(!(let final core::double #t3 = b = #0#0{({foo: core::double})}.foo{core::double} in true))
throw new _in::ReachabilityError::•();
} =>#0#0;
return b;
}
Extra constant evaluation status:
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:9:10 -> RecordConstant(const (3.14))
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:17:17 -> RecordConstant(const (3.14, {foo: "foo"}))
Evaluated: RecordLiteral @ org-dartlang-testcase:///issue51587.dart:25:14 -> RecordConstant(const ({foo: 3.14}))
Extra constant evaluation: evaluated: 46, effectively constant: 3