diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart index 08d274d8eae..ac830b2bc33 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart @@ -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 ?? diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart index a455deed89c..1dcd086aea1 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart @@ -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? + 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? errorTemplate, Template? nullabilityErrorTemplate, @@ -492,8 +578,6 @@ abstract class InferenceVisitorBase implements InferenceVisitor { fileOffset ??= inferenceResult.expression.fileOffset; contextType = computeGreatestClosure(contextType); - DartType initialContextType = runtimeCheckedType ?? contextType; - Template? 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? errorTemplate, + Template? + nullabilityErrorTemplate, + Template? nullabilityNullErrorTemplate, + Template? + nullabilityNullTypeErrorTemplate, + Template? + nullabilityPartErrorTemplate, + Map 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 template) { // ignore: unnecessary_null_comparison diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 7face1cc5a2..c6b7fa40115 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -495,6 +495,7 @@ clue code coerce coerced +coerces coercing coercion coincides diff --git a/pkg/front_end/testcases/patterns/issue51587.dart b/pkg/front_end/testcases/patterns/issue51587.dart new file mode 100644 index 00000000000..407753fbdc4 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart @@ -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; +} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.strong.expect b/pkg/front_end/testcases/patterns/issue51587.dart.strong.expect new file mode 100644 index 00000000000..b7e9a8a8327 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.strong.expect @@ -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; +} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/issue51587.dart.strong.transformed.expect new file mode 100644 index 00000000000..f4ef8a4e8df --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.strong.transformed.expect @@ -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 diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline.expect new file mode 100644 index 00000000000..3615dc47ce6 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline.expect @@ -0,0 +1,3 @@ +test1() {} +test2() {} +test3() {} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..3615dc47ce6 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.textual_outline_modelled.expect @@ -0,0 +1,3 @@ +test1() {} +test2() {} +test3() {} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.weak.expect b/pkg/front_end/testcases/patterns/issue51587.dart.weak.expect new file mode 100644 index 00000000000..b7e9a8a8327 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.weak.expect @@ -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; +} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.weak.modular.expect b/pkg/front_end/testcases/patterns/issue51587.dart.weak.modular.expect new file mode 100644 index 00000000000..b7e9a8a8327 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.weak.modular.expect @@ -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; +} diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.weak.outline.expect b/pkg/front_end/testcases/patterns/issue51587.dart.weak.outline.expect new file mode 100644 index 00000000000..958a7a5e8fe --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.weak.outline.expect @@ -0,0 +1,9 @@ +library /*isNonNullableByDefault*/; +import self as self; + +static method test1() → dynamic + ; +static method test2() → dynamic + ; +static method test3() → dynamic + ; diff --git a/pkg/front_end/testcases/patterns/issue51587.dart.weak.transformed.expect b/pkg/front_end/testcases/patterns/issue51587.dart.weak.transformed.expect new file mode 100644 index 00000000000..f4ef8a4e8df --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue51587.dart.weak.transformed.expect @@ -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