[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); handleNoGuard(node, i);
// Stack: (Expression, i * ExpressionCase, Pattern, Expression) // Stack: (Expression, i * ExpressionCase, Pattern, Expression)
} }
handleCaseHead(node, caseIndex: i, subIndex: 0); handleCaseHead(node, memberInfo.head, caseIndex: i, subIndex: 0);
} else { } else {
handleDefault(node, caseIndex: i, subIndex: 0); handleDefault(node, caseIndex: i, subIndex: 0);
} }
@ -1843,7 +1843,9 @@ mixin TypeAnalyzer<
} else { } else {
handleNoGuard(node, caseIndex); handleNoGuard(node, caseIndex);
} }
handleCaseHead(node, caseIndex: caseIndex, subIndex: headIndex); head = handleCaseHead(node, head,
caseIndex: caseIndex, subIndex: headIndex);
guard = head.guard;
} else { } else {
hasDefault = true; hasDefault = true;
handleDefault(node, caseIndex: caseIndex, subIndex: headIndex); handleDefault(node, caseIndex: caseIndex, subIndex: headIndex);
@ -2136,11 +2138,15 @@ mixin TypeAnalyzer<
/// Called after visiting a single `case` clause, consisting of a pattern and /// Called after visiting a single `case` clause, consisting of a pattern and
/// an optional guard. /// 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. /// [caseIndex] is the index of the `case` clause.
/// ///
/// Returns the updated case head.
///
/// Stack effect: pops (Pattern, Expression) and pushes (CaseHead). /// 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}); {required int caseIndex, required int subIndex});
/// Called after visiting a `default` clause. /// Called after visiting a `default` clause.

View file

@ -3727,7 +3727,8 @@ class _MiniAstTypeAnalyzer
} }
@override @override
void handleCaseHead(Node node, CaseHeadOrDefaultInfo<Node, Expression, Var> handleCaseHead(
Node node, CaseHeadOrDefaultInfo<Node, Expression, Var> head,
{required int caseIndex, required int subIndex}) { {required int caseIndex, required int subIndex}) {
Iterable<Var> variables = []; Iterable<Var> variables = [];
if (node is _SwitchExpression) { if (node is _SwitchExpression) {
@ -3748,6 +3749,8 @@ class _MiniAstTypeAnalyzer
_irBuilder.apply( _irBuilder.apply(
'head', [Kind.pattern, Kind.expression, Kind.variables], Kind.caseHead, 'head', [Kind.pattern, Kind.expression, Kind.variables], Kind.caseHead,
location: node.location); location: node.location);
return head;
} }
void handleDeclaredVariablePattern(covariant _VariablePattern node, void handleDeclaredVariablePattern(covariant _VariablePattern node,

View file

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

View file

@ -9864,10 +9864,14 @@ class InferenceVisitorImpl extends InferenceVisitorBase
} }
@override @override
void handleCaseHead( CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration>
covariant /* SwitchStatement | SwitchExpression */ Object node, handleCaseHead(
{required int caseIndex, covariant /* SwitchStatement | SwitchExpression */ Object node,
required int subIndex}) { CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration> head,
{required int caseIndex,
required int subIndex}) {
CaseHeadOrDefaultInfo<TreeNode, Expression, VariableDeclaration> result =
head;
int? stackBase; int? stackBase;
assert(checkStackBase(node as TreeNode, stackBase = stackHeight - 2)); assert(checkStackBase(node as TreeNode, stackBase = stackHeight - 2));
@ -9919,6 +9923,12 @@ class InferenceVisitorImpl extends InferenceVisitorBase
!identical(guardRewrite, patternGuard.guard)) { !identical(guardRewrite, patternGuard.guard)) {
patternGuard.guard = (guardRewrite as Expression) patternGuard.guard = (guardRewrite as Expression)
..parent = patternGuard; ..parent = patternGuard;
result = new CaseHeadOrDefaultInfo(
pattern: head.pattern,
guard: patternGuard.guard,
variables: head.variables,
);
} }
Object? rewrite = popRewrite(); Object? rewrite = popRewrite();
if (!identical(rewrite, patternGuard.pattern)) { if (!identical(rewrite, patternGuard.pattern)) {
@ -9972,6 +9982,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
hasGuard: patternGuard.guard != null, hasGuard: patternGuard.guard != null,
fileOffset: switchExpressionCase.fileOffset)); fileOffset: switchExpressionCase.fileOffset));
} }
return result;
} }
@override @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;
}
}
}
}