Skip to content

Commit 9f4f7fa

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[cfe] Improve error message on incomplete joint pattern variables
Closes #51604 Closes #51962 Part of #49749 Change-Id: I9ada7a3a0194964af5c9e2afea1c2ea3ead67641 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292890 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Johnni Winther <[email protected]>
1 parent 906c1e9 commit 9f4f7fa

19 files changed

+2527
-48
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7519,6 +7519,62 @@ Message _withArgumentsInvokeNonFunction(String name) {
75197519
arguments: {'name': name});
75207520
}
75217521

7522+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7523+
const Template<
7524+
Message Function(
7525+
String
7526+
name)> templateJointPatternVariableNotInAll = const Template<
7527+
Message Function(String name)>(
7528+
problemMessageTemplate:
7529+
r"""The variable '#name' is available in some, but not all cases that share this body.""",
7530+
withArguments: _withArgumentsJointPatternVariableNotInAll);
7531+
7532+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7533+
const Code<Message Function(String name)> codeJointPatternVariableNotInAll =
7534+
const Code<Message Function(String name)>("JointPatternVariableNotInAll",
7535+
analyzerCodes: <String>[
7536+
"INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE"
7537+
]);
7538+
7539+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7540+
Message _withArgumentsJointPatternVariableNotInAll(String name) {
7541+
if (name.isEmpty) throw 'No name provided';
7542+
name = demangleMixinApplicationName(name);
7543+
return new Message(codeJointPatternVariableNotInAll,
7544+
problemMessage:
7545+
"""The variable '${name}' is available in some, but not all cases that share this body.""",
7546+
arguments: {'name': name});
7547+
}
7548+
7549+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7550+
const Template<
7551+
Message Function(
7552+
String
7553+
name)> templateJointPatternVariableWithLabelDefault = const Template<
7554+
Message Function(String name)>(
7555+
problemMessageTemplate:
7556+
r"""The variable '#name' is not available because there is a label or 'default' case.""",
7557+
withArguments: _withArgumentsJointPatternVariableWithLabelDefault);
7558+
7559+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7560+
const Code<Message Function(String name)>
7561+
codeJointPatternVariableWithLabelDefault =
7562+
const Code<Message Function(String name)>(
7563+
"JointPatternVariableWithLabelDefault",
7564+
analyzerCodes: <String>[
7565+
"INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE"
7566+
]);
7567+
7568+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
7569+
Message _withArgumentsJointPatternVariableWithLabelDefault(String name) {
7570+
if (name.isEmpty) throw 'No name provided';
7571+
name = demangleMixinApplicationName(name);
7572+
return new Message(codeJointPatternVariableWithLabelDefault,
7573+
problemMessage:
7574+
"""The variable '${name}' is not available because there is a label or 'default' case.""",
7575+
arguments: {'name': name});
7576+
}
7577+
75227578
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
75237579
const Template<
75247580
Message Function(

pkg/front_end/lib/src/fasta/kernel/body_builder.dart

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7836,10 +7836,12 @@ class BodyBuilder extends StackListenerImpl
78367836

78377837
List<VariableDeclaration>? jointPatternVariables;
78387838
List<VariableDeclaration>? jointPatternVariablesWithMismatchingFinality;
7839+
List<VariableDeclaration>? jointPatternVariablesNotInAll;
78397840
enterLocalScope(switchCaseScope!);
78407841
if (expressionCount > 1) {
7841-
for (ExpressionOrPatternGuardCase expressionOrPattern
7842-
in expressionOrPatterns) {
7842+
for (int i = 0; i < expressionOrPatterns.length; i++) {
7843+
ExpressionOrPatternGuardCase expressionOrPattern =
7844+
expressionOrPatterns[i];
78437845
PatternGuard? patternGuard = expressionOrPattern.patternGuard;
78447846
if (patternGuard != null) {
78457847
if (jointPatternVariables == null) {
@@ -7850,6 +7852,12 @@ class BodyBuilder extends StackListenerImpl
78507852
variable.fileOffset, variable.name!)
78517853
..isFinal = variable.isFinal
78527854
];
7855+
if (i != 0) {
7856+
// The previous heads were non-pattern ones, so no variables can
7857+
// be joined.
7858+
(jointPatternVariablesNotInAll ??= [])
7859+
.addAll(jointPatternVariables);
7860+
}
78537861
} else {
78547862
Map<String, VariableDeclaration> patternVariablesByName = {
78557863
for (VariableDeclaration variable
@@ -7859,19 +7867,34 @@ class BodyBuilder extends StackListenerImpl
78597867
for (VariableDeclaration jointVariable in jointPatternVariables) {
78607868
String jointVariableName = jointVariable.name!;
78617869
VariableDeclaration? patternVariable =
7862-
patternVariablesByName[jointVariableName];
7870+
patternVariablesByName.remove(jointVariableName);
78637871
if (patternVariable != null) {
78647872
if (patternVariable.isFinal != jointVariable.isFinal) {
78657873
(jointPatternVariablesWithMismatchingFinality ??= [])
78667874
.add(jointVariable);
78677875
}
7876+
} else {
7877+
(jointPatternVariablesNotInAll ??= []).add(jointVariable);
7878+
}
7879+
}
7880+
if (patternVariablesByName.isNotEmpty) {
7881+
for (VariableDeclaration variable
7882+
in patternVariablesByName.values) {
7883+
VariableDeclaration jointVariable = forest
7884+
.createVariableDeclaration(
7885+
variable.fileOffset, variable.name!)
7886+
..isFinal = variable.isFinal;
7887+
(jointPatternVariablesNotInAll ??= []).add(jointVariable);
7888+
jointPatternVariables.add(jointVariable);
78687889
}
78697890
}
78707891
}
78717892
} else {
78727893
// It's a non-pattern head, so no variables can be joined.
7873-
jointPatternVariables = null;
7874-
break;
7894+
if (jointPatternVariables != null) {
7895+
(jointPatternVariablesNotInAll ??= [])
7896+
.addAll(jointPatternVariables);
7897+
}
78757898
}
78767899
}
78777900
if (jointPatternVariables != null) {
@@ -7895,11 +7918,17 @@ class BodyBuilder extends StackListenerImpl
78957918
exitLocalScope(expectedScopeKinds: const [ScopeKind.caseHead]);
78967919
enterLocalScope(switchCaseScope);
78977920
}
7921+
push(jointPatternVariablesNotInAll ?? NullValues.VariableDeclarationList);
78987922
push(jointPatternVariablesWithMismatchingFinality ??
78997923
NullValues.VariableDeclarationList);
79007924
push(jointPatternVariables ?? NullValues.VariableDeclarationList);
79017925

7926+
createAndEnterLocalScope(
7927+
debugName: "switch-case-body", kind: ScopeKind.switchCaseBody);
7928+
79027929
assert(checkState(firstToken, [
7930+
ValueKinds.Scope,
7931+
ValueKinds.VariableDeclarationListOrNull,
79037932
ValueKinds.VariableDeclarationListOrNull,
79047933
ValueKinds.VariableDeclarationListOrNull,
79057934
ValueKinds.Scope,
@@ -7986,56 +8015,87 @@ class BodyBuilder extends StackListenerImpl
79868015
debugEvent("SwitchCase");
79878016
assert(checkState(firstToken, [
79888017
...repeatedKind(ValueKinds.Statement, statementCount),
8018+
ValueKinds.Scope,
8019+
ValueKinds.VariableDeclarationListOrNull,
79898020
ValueKinds.VariableDeclarationListOrNull,
79908021
ValueKinds.VariableDeclarationListOrNull,
79918022
ValueKinds.Scope,
79928023
ValueKinds.LabelListOrNull,
79938024
ValueKinds.Bool,
79948025
ValueKinds.ExpressionOrPatternGuardCaseList,
79958026
]));
8027+
79968028
// We always create a block here so that we later know that there's always
79978029
// one synthetic block when we finish compiling the switch statement and
79988030
// check this switch case to see if it falls through to the next case.
79998031
Statement block = popBlock(statementCount, firstToken, null);
8032+
exitLocalScope(expectedScopeKinds: const [ScopeKind.switchCaseBody]);
80008033
List<VariableDeclaration>? jointPatternVariables =
80018034
pop() as List<VariableDeclaration>?;
80028035
List<VariableDeclaration>? jointPatternVariablesWithMismatchingFinality =
80038036
pop() as List<VariableDeclaration>?;
8037+
List<VariableDeclaration>? jointPatternVariablesNotInAll =
8038+
pop() as List<VariableDeclaration>?;
80048039

8040+
// The current scope should be the scope of the body of the switch case
8041+
// because we want to lookup the first use of the pattern variables
8042+
// specifically in the body of the case, as opposed to, for example, the
8043+
// guard in one of the heads of the case.
8044+
assert(
8045+
scope.kind == ScopeKind.switchCase ||
8046+
scope.kind == ScopeKind.jointVariables,
8047+
"Expected the current scope to be of kind '${ScopeKind.switchCase}' "
8048+
"or '${ScopeKind.jointVariables}', but got '${scope.kind}.");
8049+
Map<String, int>? usedNamesOffsets = scope.usedNames;
8050+
8051+
bool hasDefaultOrLabels = defaultKeyword != null || labelCount > 0;
8052+
8053+
List<VariableDeclaration>? usedJointPatternVariables;
80058054
List<int>? jointVariableFirstUseOffsets;
80068055
if (jointPatternVariables != null) {
8007-
List<VariableDeclaration> usedJointPatternVariables = [];
8056+
usedJointPatternVariables = [];
80088057
Map<VariableDeclaration, int> firstUseOffsets = {};
8009-
Scope? jointVariablesScope = scope;
8010-
while (jointVariablesScope != null &&
8011-
jointVariablesScope.kind != ScopeKind.jointVariables) {
8012-
jointVariablesScope = jointVariablesScope.parent;
8013-
}
8014-
assert(jointVariablesScope != null,
8015-
"Can't find the scope the joint variables are declared in.");
80168058
for (VariableDeclaration variable in jointPatternVariables) {
8017-
int? firstUseOffset = jointVariablesScope?.usedNames?[variable.name!];
8059+
int? firstUseOffset = usedNamesOffsets?[variable.name!];
80188060
if (firstUseOffset != null) {
80198061
usedJointPatternVariables.add(variable);
80208062
firstUseOffsets[variable] = firstUseOffset;
80218063
}
80228064
}
8023-
jointPatternVariables = usedJointPatternVariables;
8024-
if (jointPatternVariablesWithMismatchingFinality != null) {
8025-
for (VariableDeclaration jointVariable in jointPatternVariables) {
8065+
if (jointPatternVariablesWithMismatchingFinality != null ||
8066+
jointPatternVariablesNotInAll != null ||
8067+
hasDefaultOrLabels) {
8068+
for (VariableDeclaration jointVariable in usedJointPatternVariables) {
80268069
if (jointPatternVariablesWithMismatchingFinality
8027-
.contains(jointVariable)) {
8070+
?.contains(jointVariable) ??
8071+
false) {
80288072
String jointVariableName = jointVariable.name!;
80298073
addProblem(
80308074
fasta.templateJointPatternVariablesMismatch
80318075
.withArguments(jointVariableName),
80328076
firstUseOffsets[jointVariable]!,
80338077
jointVariableName.length);
80348078
}
8079+
if (jointPatternVariablesNotInAll?.contains(jointVariable) ?? false) {
8080+
String jointVariableName = jointVariable.name!;
8081+
addProblem(
8082+
fasta.templateJointPatternVariableNotInAll
8083+
.withArguments(jointVariableName),
8084+
firstUseOffsets[jointVariable]!,
8085+
jointVariableName.length);
8086+
}
8087+
if (hasDefaultOrLabels) {
8088+
String jointVariableName = jointVariable.name!;
8089+
addProblem(
8090+
fasta.templateJointPatternVariableWithLabelDefault
8091+
.withArguments(jointVariableName),
8092+
firstUseOffsets[jointVariable]!,
8093+
jointVariableName.length);
8094+
}
80358095
}
80368096
}
80378097
jointVariableFirstUseOffsets = [
8038-
for (VariableDeclaration variable in jointPatternVariables)
8098+
for (VariableDeclaration variable in usedJointPatternVariables)
80398099
firstUseOffsets[variable]!
80408100
];
80418101
}
@@ -8051,6 +8111,28 @@ class BodyBuilder extends StackListenerImpl
80518111
bool containsPatterns = pop() as bool;
80528112
List<ExpressionOrPatternGuardCase> expressionsOrPatternGuards =
80538113
pop() as List<ExpressionOrPatternGuardCase>;
8114+
8115+
if (expressionCount == 1 &&
8116+
containsPatterns &&
8117+
hasDefaultOrLabels &&
8118+
usedNamesOffsets != null) {
8119+
PatternGuard? patternGuard =
8120+
expressionsOrPatternGuards.first.patternGuard;
8121+
if (patternGuard != null) {
8122+
for (VariableDeclaration variable
8123+
in patternGuard.pattern.declaredVariables) {
8124+
String variableName = variable.name!;
8125+
int? offset = usedNamesOffsets[variableName];
8126+
if (offset != null) {
8127+
addProblem(
8128+
fasta.templateJointPatternVariableWithLabelDefault
8129+
.withArguments(variableName),
8130+
offset,
8131+
variableName.length);
8132+
}
8133+
}
8134+
}
8135+
}
80548136
if (containsPatterns || libraryFeatures.patterns.isEnabled) {
80558137
// If patterns are enabled, we always use the pattern switch encoding.
80568138
// Otherwise, we use pattern switch encoding to handle the erroneous case
@@ -8072,7 +8154,7 @@ class BodyBuilder extends StackListenerImpl
80728154
firstToken.charOffset, caseOffsets, patternGuards, block,
80738155
isDefault: defaultKeyword != null,
80748156
hasLabel: labels != null,
8075-
jointVariables: jointPatternVariables ?? [],
8157+
jointVariables: usedJointPatternVariables ?? [],
80768158
jointVariableFirstUseOffsets: jointVariableFirstUseOffsets));
80778159
} else {
80788160
List<Expression> expressions = <Expression>[];

pkg/front_end/lib/src/fasta/scope.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ enum ScopeKind {
8181
/// This scope kind is used in assertion checks.
8282
switchCase,
8383

84+
/// Scope for switch case bodies
85+
///
86+
/// This is used to handle local variables of switch cases.
87+
switchCaseBody,
88+
8489
/// Scope for type parameters of declarations
8590
typeParameters,
8691
}

pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8371,6 +8371,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
83718371

83728372
for (int caseIndex = 0; caseIndex < node.cases.length; caseIndex++) {
83738373
PatternSwitchCase switchCase = node.cases[caseIndex];
8374+
List<VariableDeclaration> jointVariablesNotInAll = [];
83748375
for (int headIndex = 0;
83758376
headIndex < switchCase.patternGuards.length;
83768377
headIndex++) {
@@ -8398,13 +8399,23 @@ class InferenceVisitorImpl extends InferenceVisitorBase
83988399
};
83998400
if (headIndex == 0) {
84008401
for (VariableDeclaration jointVariable in switchCase.jointVariables) {
8401-
jointVariable.type = inferredVariableTypes[jointVariable.name!]!;
8402+
DartType? inferredType = inferredVariableTypes[jointVariable.name!];
8403+
if (inferredType != null) {
8404+
jointVariable.type = inferredType;
8405+
} else {
8406+
jointVariable.type = const InvalidType();
8407+
jointVariablesNotInAll.add(jointVariable);
8408+
}
84028409
}
84038410
} else {
84048411
for (int i = 0; i < switchCase.jointVariables.length; ++i) {
84058412
VariableDeclaration jointVariable = switchCase.jointVariables[i];
8406-
if (jointVariable.type !=
8407-
inferredVariableTypes[jointVariable.name!]) {
8413+
// The error on joint variables not present in all case heads is
8414+
// reported in BodyBuilder.
8415+
DartType? inferredType = inferredVariableTypes[jointVariable.name!];
8416+
if (!jointVariablesNotInAll.contains(jointVariable) &&
8417+
inferredType != null &&
8418+
jointVariable.type != inferredType) {
84088419
jointVariable.initializer = helper.buildProblem(
84098420
templateJointPatternVariablesMismatch
84108421
.withArguments(jointVariable.name!),

pkg/front_end/messages.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6827,3 +6827,15 @@ PatternVariableAssignmentInsideGuard:
68276827
int i when (i = 5) > 0 => 0,
68286828
_ => -1,
68296829
};
6830+
6831+
JointPatternVariableNotInAll:
6832+
problemMessage: "The variable '#name' is available in some, but not all cases that share this body."
6833+
analyzerCode: INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE
6834+
script: |
6835+
test(x) { switch(x) { case var a: case < 0: case [var a]: return a; default: return null; } }
6836+
6837+
JointPatternVariableWithLabelDefault:
6838+
problemMessage: "The variable '#name' is not available because there is a label or 'default' case."
6839+
analyzerCode: INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE
6840+
script: |
6841+
test(x) { switch(x) { case var a: default: return a; } }

0 commit comments

Comments
 (0)