Fix joining variables in switch statement.
Instead of joining pattern variables in shared case scope after every top-level pattern, we now accumulate them, and join at the end of the shared scope. Change-Id: Icc1d426e53ccfe48a72e3194e1d7e96e6e90d781 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279264 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
b41c70463e
commit
68c32274c7
@@ -67,45 +67,9 @@ abstract class VariableBinder<Node extends Object, Variable extends Object> {
|
||||
Map<String, Variable> variables = _variables.removeLast();
|
||||
|
||||
if (sharedCaseScopeKey != null) {
|
||||
Map<String, Variable> right = variables;
|
||||
_SharedCaseScope<Variable> sharedScope = _sharedCaseScopes.last;
|
||||
assert(sharedScope.key == sharedCaseScopeKey);
|
||||
Map<String, Variable>? left = sharedScope.variables;
|
||||
if (left == null) {
|
||||
sharedScope.variables = right;
|
||||
} else {
|
||||
Map<String, Variable> result = {};
|
||||
for (MapEntry<String, Variable> leftEntry in left.entries) {
|
||||
String name = leftEntry.key;
|
||||
Variable leftVariable = leftEntry.value;
|
||||
Variable? rightVariable = right[name];
|
||||
if (rightVariable != null) {
|
||||
result[name] = joinPatternVariables(
|
||||
key: sharedCaseScopeKey,
|
||||
components: [leftVariable, rightVariable],
|
||||
isConsistent: true,
|
||||
);
|
||||
} else {
|
||||
result[name] = joinPatternVariables(
|
||||
key: sharedCaseScopeKey,
|
||||
components: [leftVariable],
|
||||
isConsistent: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (MapEntry<String, Variable> rightEntry in right.entries) {
|
||||
String name = rightEntry.key;
|
||||
Variable rightVariable = rightEntry.value;
|
||||
if (!left.containsKey(name)) {
|
||||
result[name] = joinPatternVariables(
|
||||
key: sharedCaseScopeKey,
|
||||
components: [rightVariable],
|
||||
isConsistent: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
sharedScope.variables = result;
|
||||
}
|
||||
sharedScope.addAll(variables);
|
||||
}
|
||||
|
||||
return variables;
|
||||
@@ -201,20 +165,7 @@ abstract class VariableBinder<Node extends Object, Variable extends Object> {
|
||||
void switchStatementSharedCaseScopeEmpty(Object key) {
|
||||
_SharedCaseScope<Variable> sharedScope = _sharedCaseScopes.last;
|
||||
assert(sharedScope.key == key);
|
||||
Map<String, Variable>? left = sharedScope.variables;
|
||||
if (left != null) {
|
||||
Map<String, Variable> result = {};
|
||||
for (MapEntry<String, Variable> leftEntry in left.entries) {
|
||||
String name = leftEntry.key;
|
||||
Variable leftVariable = leftEntry.value;
|
||||
result[name] = joinPatternVariables(
|
||||
key: key,
|
||||
components: [leftVariable],
|
||||
isConsistent: false,
|
||||
);
|
||||
}
|
||||
sharedScope.variables = result;
|
||||
}
|
||||
sharedScope.addAll({});
|
||||
}
|
||||
|
||||
/// Notifies that computing of the shared case scope was finished, returns
|
||||
@@ -226,7 +177,23 @@ abstract class VariableBinder<Node extends Object, Variable extends Object> {
|
||||
assert(_variables.isEmpty);
|
||||
_SharedCaseScope<Variable> sharedScope = _sharedCaseScopes.removeLast();
|
||||
assert(sharedScope.key == key);
|
||||
return sharedScope.variables ?? {};
|
||||
|
||||
Map<String, Variable> result = {};
|
||||
for (MapEntry<String, _SharedCaseScopeVariable<Variable>> entry
|
||||
in sharedScope.variables.entries) {
|
||||
_SharedCaseScopeVariable<Variable> sharedVariable = entry.value;
|
||||
List<Variable> variables = sharedVariable.variables;
|
||||
if (sharedVariable.isConsistent && variables.length == 1) {
|
||||
result[entry.key] = variables[0];
|
||||
} else {
|
||||
result[entry.key] = joinPatternVariables(
|
||||
key: key,
|
||||
components: variables,
|
||||
isConsistent: sharedVariable.isConsistent,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Notifies that computing new shared case scope should be started.
|
||||
@@ -262,7 +229,51 @@ abstract class VariableBinderErrors<Node extends Object,
|
||||
|
||||
class _SharedCaseScope<Variable extends Object> {
|
||||
final Object key;
|
||||
Map<String, Variable>? variables;
|
||||
bool isEmpty = true;
|
||||
Map<String, _SharedCaseScopeVariable<Variable>> variables = {};
|
||||
|
||||
_SharedCaseScope(this.key);
|
||||
|
||||
/// Adds [newVariables] to [variables], marking absent variables as not
|
||||
/// consistent. If [isEmpty], just sets given variables as the starting set.
|
||||
void addAll(Map<String, Variable> newVariables) {
|
||||
if (isEmpty) {
|
||||
isEmpty = false;
|
||||
for (MapEntry<String, Variable> entry in newVariables.entries) {
|
||||
String name = entry.key;
|
||||
Variable variable = entry.value;
|
||||
_getVariable(name).variables.add(variable);
|
||||
}
|
||||
} else {
|
||||
for (MapEntry<String, _SharedCaseScopeVariable<Variable>> entry
|
||||
in variables.entries) {
|
||||
String name = entry.key;
|
||||
_SharedCaseScopeVariable<Variable> variable = entry.value;
|
||||
Variable? newVariable = newVariables[name];
|
||||
if (newVariable != null) {
|
||||
variable.variables.add(newVariable);
|
||||
} else {
|
||||
variable.isConsistent = false;
|
||||
}
|
||||
}
|
||||
for (MapEntry<String, Variable> newEntry in newVariables.entries) {
|
||||
String name = newEntry.key;
|
||||
Variable newVariable = newEntry.value;
|
||||
if (!variables.containsKey(name)) {
|
||||
_getVariable(name)
|
||||
..isConsistent = false
|
||||
..variables.add(newVariable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_SharedCaseScopeVariable _getVariable(String name) {
|
||||
return variables[name] ??= new _SharedCaseScopeVariable();
|
||||
}
|
||||
}
|
||||
|
||||
class _SharedCaseScopeVariable<Variable extends Object> {
|
||||
bool isConsistent = true;
|
||||
final List<Variable> variables = [];
|
||||
}
|
||||
|
||||
@@ -113,15 +113,27 @@ main() {
|
||||
expectedVariables: {'x: [1, 3]', 'y: notConsistent [2]'},
|
||||
);
|
||||
});
|
||||
test('Has default', () {
|
||||
h.runSwitchStatementSharedBody(
|
||||
sharedCaseScopeKey: 0,
|
||||
casePatterns: [
|
||||
_VarPattern('x', 1),
|
||||
],
|
||||
hasDefault: true,
|
||||
expectedVariables: {'x: notConsistent [1]'},
|
||||
);
|
||||
group('Has default', () {
|
||||
test('First', () {
|
||||
h.runSwitchStatementSharedBody(
|
||||
sharedCaseScopeKey: 0,
|
||||
casePatterns: [
|
||||
_VarPattern('x', 1),
|
||||
],
|
||||
hasDefaultFirst: true, // does not happen normally
|
||||
expectedVariables: {'x: notConsistent [1]'},
|
||||
);
|
||||
});
|
||||
test('Last', () {
|
||||
h.runSwitchStatementSharedBody(
|
||||
sharedCaseScopeKey: 0,
|
||||
casePatterns: [
|
||||
_VarPattern('x', 1),
|
||||
],
|
||||
hasDefaultLast: true,
|
||||
expectedVariables: {'x: notConsistent [1]'},
|
||||
);
|
||||
});
|
||||
});
|
||||
group('With logical-or', () {
|
||||
test('Both have', () {
|
||||
@@ -254,11 +266,16 @@ class _Harness {
|
||||
void runSwitchStatementSharedBody({
|
||||
required Object sharedCaseScopeKey,
|
||||
required List<_Node> casePatterns,
|
||||
bool hasDefault = false,
|
||||
bool hasDefaultFirst = false,
|
||||
bool hasDefaultLast = false,
|
||||
List<String> expectErrors = const [],
|
||||
required Set<String> expectedVariables,
|
||||
}) {
|
||||
assert(!(hasDefaultFirst && hasDefaultLast));
|
||||
_binder.switchStatementSharedCaseScopeStart(sharedCaseScopeKey);
|
||||
if (hasDefaultFirst) {
|
||||
_binder.switchStatementSharedCaseScopeEmpty(sharedCaseScopeKey);
|
||||
}
|
||||
for (var casePattern in casePatterns) {
|
||||
_binder.casePatternStart();
|
||||
casePattern._visit(this);
|
||||
@@ -266,7 +283,7 @@ class _Harness {
|
||||
sharedCaseScopeKey: sharedCaseScopeKey,
|
||||
);
|
||||
}
|
||||
if (hasDefault) {
|
||||
if (hasDefaultLast) {
|
||||
_binder.switchStatementSharedCaseScopeEmpty(sharedCaseScopeKey);
|
||||
}
|
||||
var variables =
|
||||
|
||||
@@ -1230,27 +1230,27 @@ class ResolutionVisitor extends RecursiveAstVisitor<void> {
|
||||
node.expression.accept(this);
|
||||
|
||||
for (var group in node.memberGroups) {
|
||||
_patternVariables.switchStatementSharedCaseScopeStart(node);
|
||||
_patternVariables.switchStatementSharedCaseScopeStart(group);
|
||||
for (var member in group.members) {
|
||||
_buildLabelElements(member.labels, true);
|
||||
if (member is SwitchCaseImpl) {
|
||||
member.expression.accept(this);
|
||||
} else if (member is SwitchDefaultImpl) {
|
||||
_patternVariables.switchStatementSharedCaseScopeEmpty(node);
|
||||
_patternVariables.switchStatementSharedCaseScopeEmpty(group);
|
||||
} else if (member is SwitchPatternCaseImpl) {
|
||||
_resolveGuardedPattern(
|
||||
member.guardedPattern,
|
||||
sharedCaseScopeKey: node,
|
||||
sharedCaseScopeKey: group,
|
||||
);
|
||||
} else {
|
||||
throw UnimplementedError('(${member.runtimeType}) $member');
|
||||
}
|
||||
}
|
||||
if (group.hasLabels) {
|
||||
_patternVariables.switchStatementSharedCaseScopeEmpty(node);
|
||||
_patternVariables.switchStatementSharedCaseScopeEmpty(group);
|
||||
}
|
||||
group.variables =
|
||||
_patternVariables.switchStatementSharedCaseScopeFinish(node);
|
||||
_patternVariables.switchStatementSharedCaseScopeFinish(group);
|
||||
_withNameScope(() {
|
||||
var statements = group.statements;
|
||||
_buildLocalElements(statements);
|
||||
@@ -1688,18 +1688,20 @@ class _VariableBinder
|
||||
var first = components.first;
|
||||
List<PatternVariableElementImpl> expandedVariables;
|
||||
if (key is LogicalOrPatternImpl) {
|
||||
expandedVariables = components.expand((component) {
|
||||
component as PatternVariableElementImpl;
|
||||
if (component is JoinPatternVariableElementImpl) {
|
||||
return component.variables;
|
||||
expandedVariables = components.expand((variable) {
|
||||
variable as PatternVariableElementImpl;
|
||||
if (variable is JoinPatternVariableElementImpl) {
|
||||
return variable.variables;
|
||||
} else {
|
||||
return [component];
|
||||
return [variable];
|
||||
}
|
||||
}).toList(growable: false);
|
||||
} else {
|
||||
} else if (key is SwitchStatementCaseGroup) {
|
||||
expandedVariables = components
|
||||
.map((e) => e as PatternVariableElementImpl)
|
||||
.toList(growable: false);
|
||||
} else {
|
||||
throw UnimplementedError('(${key.runtimeType}) $key');
|
||||
}
|
||||
return JoinPatternVariableElementImpl(
|
||||
first.name,
|
||||
|
||||
@@ -1064,6 +1064,67 @@ SwitchStatement
|
||||
''');
|
||||
}
|
||||
|
||||
test_variables_joinedCase_hasDefault2() async {
|
||||
await assertErrorsInCode(r'''
|
||||
void f(Object? x) {
|
||||
switch (x) {
|
||||
case var a:
|
||||
case var a:
|
||||
default:
|
||||
a;
|
||||
}
|
||||
}
|
||||
''', [
|
||||
error(
|
||||
CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
|
||||
86,
|
||||
1),
|
||||
]);
|
||||
|
||||
final node = findNode.switchStatement('switch');
|
||||
assertResolvedNodeText(node, r'''
|
||||
SwitchStatement
|
||||
switchKeyword: switch
|
||||
leftParenthesis: (
|
||||
expression: SimpleIdentifier
|
||||
token: x
|
||||
staticElement: self::@function::f::@parameter::x
|
||||
staticType: Object?
|
||||
rightParenthesis: )
|
||||
leftBracket: {
|
||||
members
|
||||
SwitchPatternCase
|
||||
keyword: case
|
||||
guardedPattern: GuardedPattern
|
||||
pattern: DeclaredVariablePattern
|
||||
keyword: var
|
||||
name: a
|
||||
declaredElement: hasImplicitType a@48
|
||||
type: Object?
|
||||
colon: :
|
||||
SwitchPatternCase
|
||||
keyword: case
|
||||
guardedPattern: GuardedPattern
|
||||
pattern: DeclaredVariablePattern
|
||||
keyword: var
|
||||
name: a
|
||||
declaredElement: hasImplicitType a@64
|
||||
type: Object?
|
||||
colon: :
|
||||
SwitchDefault
|
||||
keyword: default
|
||||
colon: :
|
||||
statements
|
||||
ExpressionStatement
|
||||
expression: SimpleIdentifier
|
||||
token: a
|
||||
staticElement: notConsistent a[a@48, a@64]
|
||||
staticType: Object?
|
||||
semicolon: ;
|
||||
rightBracket: }
|
||||
''');
|
||||
}
|
||||
|
||||
test_variables_joinedCase_hasLabel() async {
|
||||
await assertErrorsInCode(r'''
|
||||
void f(Object? x) {
|
||||
@@ -1140,6 +1201,110 @@ SwitchStatement
|
||||
''');
|
||||
}
|
||||
|
||||
test_variables_joinedCase_notConsistent3() async {
|
||||
await assertErrorsInCode(r'''
|
||||
void f(Object? x) {
|
||||
switch (x) {
|
||||
case int a:
|
||||
case double b:
|
||||
case String c:
|
||||
a;
|
||||
b;
|
||||
c;
|
||||
}
|
||||
}
|
||||
''', [
|
||||
error(
|
||||
CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
|
||||
95,
|
||||
1),
|
||||
error(
|
||||
CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
|
||||
104,
|
||||
1),
|
||||
error(
|
||||
CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
|
||||
113,
|
||||
1),
|
||||
]);
|
||||
|
||||
final node = findNode.switchStatement('switch');
|
||||
assertResolvedNodeText(node, r'''
|
||||
SwitchStatement
|
||||
switchKeyword: switch
|
||||
leftParenthesis: (
|
||||
expression: SimpleIdentifier
|
||||
token: x
|
||||
staticElement: self::@function::f::@parameter::x
|
||||
staticType: Object?
|
||||
rightParenthesis: )
|
||||
leftBracket: {
|
||||
members
|
||||
SwitchPatternCase
|
||||
keyword: case
|
||||
guardedPattern: GuardedPattern
|
||||
pattern: DeclaredVariablePattern
|
||||
type: NamedType
|
||||
name: SimpleIdentifier
|
||||
token: int
|
||||
staticElement: dart:core::@class::int
|
||||
staticType: null
|
||||
type: int
|
||||
name: a
|
||||
declaredElement: a@48
|
||||
type: int
|
||||
colon: :
|
||||
SwitchPatternCase
|
||||
keyword: case
|
||||
guardedPattern: GuardedPattern
|
||||
pattern: DeclaredVariablePattern
|
||||
type: NamedType
|
||||
name: SimpleIdentifier
|
||||
token: double
|
||||
staticElement: dart:core::@class::double
|
||||
staticType: null
|
||||
type: double
|
||||
name: b
|
||||
declaredElement: b@67
|
||||
type: double
|
||||
colon: :
|
||||
SwitchPatternCase
|
||||
keyword: case
|
||||
guardedPattern: GuardedPattern
|
||||
pattern: DeclaredVariablePattern
|
||||
type: NamedType
|
||||
name: SimpleIdentifier
|
||||
token: String
|
||||
staticElement: dart:core::@class::String
|
||||
staticType: null
|
||||
type: String
|
||||
name: c
|
||||
declaredElement: c@86
|
||||
type: String
|
||||
colon: :
|
||||
statements
|
||||
ExpressionStatement
|
||||
expression: SimpleIdentifier
|
||||
token: a
|
||||
staticElement: notConsistent a[a@48]
|
||||
staticType: int
|
||||
semicolon: ;
|
||||
ExpressionStatement
|
||||
expression: SimpleIdentifier
|
||||
token: b
|
||||
staticElement: notConsistent b[b@67]
|
||||
staticType: double
|
||||
semicolon: ;
|
||||
ExpressionStatement
|
||||
expression: SimpleIdentifier
|
||||
token: c
|
||||
staticElement: notConsistent c[c@86]
|
||||
staticType: String
|
||||
semicolon: ;
|
||||
rightBracket: }
|
||||
''');
|
||||
}
|
||||
|
||||
test_variables_logicalOr() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
void f(Object? x) {
|
||||
|
||||
Reference in New Issue
Block a user