[cfe] Provide pre-desugaring node to flow analysis to fix promotion

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

Change-Id: I2a70518975c809f28f0d0f72f7365492ca7d6e83
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287601
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Chloe Stefantsova 2023-03-09 15:58:49 +00:00 committed by Commit Queue
parent c974c70f31
commit b16acf4f35
13 changed files with 182 additions and 11 deletions

View file

@ -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<Node, Expression, Variable> handleCaseHead(
Node node, CaseHeadOrDefaultInfo<Node, Expression, Variable> head,
{required int caseIndex, required int subIndex});
/// Called after visiting a `default` clause.

View file

@ -3727,7 +3727,8 @@ class _MiniAstTypeAnalyzer
}
@override
void handleCaseHead(Node node,
CaseHeadOrDefaultInfo<Node, Expression, Var> handleCaseHead(
Node node, CaseHeadOrDefaultInfo<Node, Expression, Var> head,
{required int caseIndex, required int subIndex}) {
Iterable<Var> 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,

View file

@ -1085,8 +1085,9 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
AstNode node, int caseIndex, Iterable<PromotableElement> variables) {}
@override
void handleCaseHead(
covariant AstNodeImpl node, {
CaseHeadOrDefaultInfo<AstNode, Expression, PromotableElement> handleCaseHead(
covariant AstNodeImpl node,
CaseHeadOrDefaultInfo<AstNode, Expression, PromotableElement> head, {
required int caseIndex,
required int subIndex,
}) {
@ -1101,6 +1102,8 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
legacySwitchExhaustiveness
?.visitSwitchExpressionCase(node.cases[caseIndex]);
}
return head;
}
@override

View file

@ -9864,10 +9864,14 @@ class InferenceVisitorImpl extends InferenceVisitorBase
}
@override
void handleCaseHead(
covariant /* SwitchStatement | SwitchExpression */ Object node,
{required int caseIndex,
required int subIndex}) {
CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration>
handleCaseHead(
covariant /* SwitchStatement | SwitchExpression */ Object node,
CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration> head,
{required int caseIndex,
required int subIndex}) {
CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration> 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

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,6 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
static method test(core::String? x) → dynamic
;

View file

@ -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;
}
}
}
}