diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart index 8cc6f5ddc0d..422bc52059a 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart @@ -1744,7 +1744,7 @@ mixin TypeAnalyzer< handleNoGuard(node, i); // Stack: (Expression, i * ExpressionCase, Pattern, Expression) } - handleCaseHead(node, caseIndex: i, subIndex: 0); + handleCaseHead(node, memberInfo.head, caseIndex: i, subIndex: 0); } else { handleDefault(node, caseIndex: i, subIndex: 0); } @@ -1843,7 +1843,9 @@ mixin TypeAnalyzer< } else { handleNoGuard(node, caseIndex); } - handleCaseHead(node, caseIndex: caseIndex, subIndex: headIndex); + head = handleCaseHead(node, head, + caseIndex: caseIndex, subIndex: headIndex); + guard = head.guard; } else { hasDefault = true; handleDefault(node, caseIndex: caseIndex, subIndex: headIndex); @@ -2136,11 +2138,15 @@ mixin TypeAnalyzer< /// Called after visiting a single `case` clause, consisting of a pattern and /// an optional guard. /// - /// [node] is the enclosing switch statement or switch expression and + /// [node] is the enclosing switch statement or switch expression, + /// [head] is the head to be handled, and /// [caseIndex] is the index of the `case` clause. /// + /// Returns the updated case head. + /// /// Stack effect: pops (Pattern, Expression) and pushes (CaseHead). - void handleCaseHead(Node node, + CaseHeadOrDefaultInfo handleCaseHead( + Node node, CaseHeadOrDefaultInfo head, {required int caseIndex, required int subIndex}); /// Called after visiting a `default` clause. diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index 431db04dcde..e598a131505 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -3727,7 +3727,8 @@ class _MiniAstTypeAnalyzer } @override - void handleCaseHead(Node node, + CaseHeadOrDefaultInfo handleCaseHead( + Node node, CaseHeadOrDefaultInfo head, {required int caseIndex, required int subIndex}) { Iterable variables = []; if (node is _SwitchExpression) { @@ -3748,6 +3749,8 @@ class _MiniAstTypeAnalyzer _irBuilder.apply( 'head', [Kind.pattern, Kind.expression, Kind.variables], Kind.caseHead, location: node.location); + + return head; } void handleDeclaredVariablePattern(covariant _VariablePattern node, diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 4f15702ebaa..f120f9aa4f3 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -1085,8 +1085,9 @@ class ResolverVisitor extends ThrowingAstVisitor AstNode node, int caseIndex, Iterable variables) {} @override - void handleCaseHead( - covariant AstNodeImpl node, { + CaseHeadOrDefaultInfo handleCaseHead( + covariant AstNodeImpl node, + CaseHeadOrDefaultInfo head, { required int caseIndex, required int subIndex, }) { @@ -1101,6 +1102,8 @@ class ResolverVisitor extends ThrowingAstVisitor legacySwitchExhaustiveness ?.visitSwitchExpressionCase(node.cases[caseIndex]); } + + return head; } @override 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 461c71ddd3e..e9d97fea4cc 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 @@ -9864,10 +9864,14 @@ class InferenceVisitorImpl extends InferenceVisitorBase } @override - void handleCaseHead( - covariant /* SwitchStatement | SwitchExpression */ Object node, - {required int caseIndex, - required int subIndex}) { + CaseHeadOrDefaultInfo + handleCaseHead( + covariant /* SwitchStatement | SwitchExpression */ Object node, + CaseHeadOrDefaultInfo head, + {required int caseIndex, + required int subIndex}) { + CaseHeadOrDefaultInfo result = + head; int? stackBase; assert(checkStackBase(node as TreeNode, stackBase = stackHeight - 2)); @@ -9919,6 +9923,12 @@ class InferenceVisitorImpl extends InferenceVisitorBase !identical(guardRewrite, patternGuard.guard)) { patternGuard.guard = (guardRewrite as Expression) ..parent = patternGuard; + + result = new CaseHeadOrDefaultInfo( + pattern: head.pattern, + guard: patternGuard.guard, + variables: head.variables, + ); } Object? rewrite = popRewrite(); if (!identical(rewrite, patternGuard.pattern)) { @@ -9972,6 +9982,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase hasGuard: patternGuard.guard != null, fileOffset: switchExpressionCase.fileOffset)); } + + return result; } @override diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart new file mode 100644 index 00000000000..e9c62f44346 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart @@ -0,0 +1,14 @@ +// 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. + +test(String? x) { + switch (x) { + case String? foobar? when foobar is Never: + case String? foobar when foobar != null: + case String? foobar! when foobar == "foobar": + return foobar.startsWith("foo"); // The static type of 'foobar' is expected to be the non-nullable 'String'. + default: + return null; + } +} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.expect new file mode 100644 index 00000000000..d3aefd92e85 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.expect @@ -0,0 +1,25 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic { + #L1: + { + core::String? foobar; + core::String? foobar#1; + core::String? foobar#2; + final core::String? #0#0 = x; + dynamic #t1; + if((!(#0#0 == null) ?{core::bool} #0#0{core::String} is{ForNonNullableByDefault} core::String? && (let final dynamic #t2 = foobar = #0#0{core::String} in true) : false) && foobar{core::String} is{ForNonNullableByDefault} Never && (let final dynamic #t3 = #t1 = foobar in true) || #0#0 is{ForNonNullableByDefault} core::String? && (let final dynamic #t4 = foobar#1 = #0#0 in true) && !(foobar#1 == null) && (let final dynamic #t5 = #t1 = foobar#1 in true) || (let final dynamic #t6 = #0#0! in #0#0! is{ForNonNullableByDefault} core::String? && (let final dynamic #t7 = foobar#2 = #0#0! in true)) && foobar#2{core::String} =={core::String::==}{(core::Object) → core::bool} "foobar" && (let final dynamic #t8 = #t1 = foobar#2 in true)) { + core::String? foobar = #t1{core::String?}; + { + return foobar{core::String}.{core::String::startsWith}("foo"){(core::Pattern, [core::int]) → core::bool}; + } + } + else { + { + return null; + } + } + } +} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.transformed.expect new file mode 100644 index 00000000000..46b6a2edd31 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.strong.transformed.expect @@ -0,0 +1,25 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic { + #L1: + { + core::String? foobar; + core::String? foobar#1; + core::String? foobar#2; + final core::String? #0#0 = x; + dynamic #t1; + if((!(#0#0 == null) ?{core::bool} #0#0{core::String} is{ForNonNullableByDefault} core::String? && (let final core::String #t2 = foobar = #0#0{core::String} in true) : false) && foobar{core::String} is{ForNonNullableByDefault} Never && (let final core::String? #t3 = #t1 = foobar in true) || #0#0 is{ForNonNullableByDefault} core::String? && (let final core::String? #t4 = foobar#1 = #0#0 in true) && !(foobar#1 == null) && (let final core::String? #t5 = #t1 = foobar#1 in true) || (let final core::String? #t6 = #0#0! in #0#0! is{ForNonNullableByDefault} core::String? && (let final core::String? #t7 = foobar#2 = #0#0! in true)) && foobar#2{core::String} =={core::String::==}{(core::Object) → core::bool} "foobar" && (let final core::String? #t8 = #t1 = foobar#2 in true)) { + core::String? foobar = #t1{core::String?}; + { + return foobar{core::String}.{core::String::startsWith}("foo"){(core::Pattern, [core::int]) → core::bool}; + } + } + else { + { + return null; + } + } + } +} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline.expect new file mode 100644 index 00000000000..cad758a0824 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline.expect @@ -0,0 +1 @@ +test(String? x) {} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..cad758a0824 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.textual_outline_modelled.expect @@ -0,0 +1 @@ +test(String? x) {} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.expect new file mode 100644 index 00000000000..d3aefd92e85 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.expect @@ -0,0 +1,25 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic { + #L1: + { + core::String? foobar; + core::String? foobar#1; + core::String? foobar#2; + final core::String? #0#0 = x; + dynamic #t1; + if((!(#0#0 == null) ?{core::bool} #0#0{core::String} is{ForNonNullableByDefault} core::String? && (let final dynamic #t2 = foobar = #0#0{core::String} in true) : false) && foobar{core::String} is{ForNonNullableByDefault} Never && (let final dynamic #t3 = #t1 = foobar in true) || #0#0 is{ForNonNullableByDefault} core::String? && (let final dynamic #t4 = foobar#1 = #0#0 in true) && !(foobar#1 == null) && (let final dynamic #t5 = #t1 = foobar#1 in true) || (let final dynamic #t6 = #0#0! in #0#0! is{ForNonNullableByDefault} core::String? && (let final dynamic #t7 = foobar#2 = #0#0! in true)) && foobar#2{core::String} =={core::String::==}{(core::Object) → core::bool} "foobar" && (let final dynamic #t8 = #t1 = foobar#2 in true)) { + core::String? foobar = #t1{core::String?}; + { + return foobar{core::String}.{core::String::startsWith}("foo"){(core::Pattern, [core::int]) → core::bool}; + } + } + else { + { + return null; + } + } + } +} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.modular.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.modular.expect new file mode 100644 index 00000000000..d3aefd92e85 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.modular.expect @@ -0,0 +1,25 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic { + #L1: + { + core::String? foobar; + core::String? foobar#1; + core::String? foobar#2; + final core::String? #0#0 = x; + dynamic #t1; + if((!(#0#0 == null) ?{core::bool} #0#0{core::String} is{ForNonNullableByDefault} core::String? && (let final dynamic #t2 = foobar = #0#0{core::String} in true) : false) && foobar{core::String} is{ForNonNullableByDefault} Never && (let final dynamic #t3 = #t1 = foobar in true) || #0#0 is{ForNonNullableByDefault} core::String? && (let final dynamic #t4 = foobar#1 = #0#0 in true) && !(foobar#1 == null) && (let final dynamic #t5 = #t1 = foobar#1 in true) || (let final dynamic #t6 = #0#0! in #0#0! is{ForNonNullableByDefault} core::String? && (let final dynamic #t7 = foobar#2 = #0#0! in true)) && foobar#2{core::String} =={core::String::==}{(core::Object) → core::bool} "foobar" && (let final dynamic #t8 = #t1 = foobar#2 in true)) { + core::String? foobar = #t1{core::String?}; + { + return foobar{core::String}.{core::String::startsWith}("foo"){(core::Pattern, [core::int]) → core::bool}; + } + } + else { + { + return null; + } + } + } +} diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.outline.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.outline.expect new file mode 100644 index 00000000000..9bb876630ca --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.outline.expect @@ -0,0 +1,6 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic + ; diff --git a/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.transformed.expect b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.transformed.expect new file mode 100644 index 00000000000..46b6a2edd31 --- /dev/null +++ b/pkg/front_end/testcases/patterns/promotions_in_switch_case_guards.dart.weak.transformed.expect @@ -0,0 +1,25 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +static method test(core::String? x) → dynamic { + #L1: + { + core::String? foobar; + core::String? foobar#1; + core::String? foobar#2; + final core::String? #0#0 = x; + dynamic #t1; + if((!(#0#0 == null) ?{core::bool} #0#0{core::String} is{ForNonNullableByDefault} core::String? && (let final core::String #t2 = foobar = #0#0{core::String} in true) : false) && foobar{core::String} is{ForNonNullableByDefault} Never && (let final core::String? #t3 = #t1 = foobar in true) || #0#0 is{ForNonNullableByDefault} core::String? && (let final core::String? #t4 = foobar#1 = #0#0 in true) && !(foobar#1 == null) && (let final core::String? #t5 = #t1 = foobar#1 in true) || (let final core::String? #t6 = #0#0! in #0#0! is{ForNonNullableByDefault} core::String? && (let final core::String? #t7 = foobar#2 = #0#0! in true)) && foobar#2{core::String} =={core::String::==}{(core::Object) → core::bool} "foobar" && (let final core::String? #t8 = #t1 = foobar#2 in true)) { + core::String? foobar = #t1{core::String?}; + { + return foobar{core::String}.{core::String::startsWith}("foo"){(core::Pattern, [core::int]) → core::bool}; + } + } + else { + { + return null; + } + } + } +}