-
Notifications
You must be signed in to change notification settings - Fork 107
/
CaseStatement.java
500 lines (463 loc) · 19.2 KB
/
CaseStatement.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/*******************************************************************************
* Copyright (c) 2000, 2023 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.BranchLabel;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.IntConstant;
import org.eclipse.jdt.internal.compiler.impl.JavaFeature;
import org.eclipse.jdt.internal.compiler.impl.StringConstant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
public class CaseStatement extends Statement {
public BranchLabel targetLabel;
public Expression[] constantExpressions; // case with multiple expressions - if you want a under-the-hood view, use peeledLabelExpressions()
public BranchLabel[] targetLabels; // for multiple expressions
public boolean isExpr = false;
public SwitchStatement swich; // owning switch
public int typeSwitchIndex; // for the first pattern among this.constantExpressions
public CaseStatement(Expression[] constantExpressions, int sourceStart, int sourceEnd) {
this.constantExpressions = constantExpressions;
this.sourceStart = sourceStart;
this.sourceEnd = sourceEnd;
}
/** Provide an under-the-hood view of label expressions, peeling away any abstractions that package many expressions as one
* @return flattened array of label expressions
*/
public Expression [] peeledLabelExpressions() {
Expression [] constants = Expression.NO_EXPRESSIONS;
for (Expression e : this.constantExpressions) {
if (e instanceof Pattern p1) {
constants = Stream.concat(Arrays.stream(constants), Arrays.stream(p1.getAlternatives())).toArray(Expression[]::new);
} else {
constants = Stream.concat(Arrays.stream(constants), Stream.of(e)).toArray(Expression[]::new);
}
}
return constants;
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
int nullPatternCount = 0;
for (int i = 0, length = this.constantExpressions.length; i < length; i++) {
Expression e = this.constantExpressions[i];
for (LocalVariableBinding local : e.bindingsWhenTrue()) {
local.useFlag = LocalVariableBinding.USED; // these are structurally required even if not touched
}
nullPatternCount += e instanceof NullLiteral ? 1 : 0;
if (i > 0 && (e instanceof Pattern) && !JavaFeature.UNNAMMED_PATTERNS_AND_VARS.isSupported(currentScope.compilerOptions().sourceLevel, currentScope.compilerOptions().enablePreviewFeatures)) {
if (!(i == nullPatternCount && e instanceof TypePattern))
currentScope.problemReporter().IllegalFallThroughToPattern(e);
}
flowInfo = analyseConstantExpression(currentScope, flowContext, flowInfo, e);
if (nullPatternCount > 0 && e instanceof TypePattern) {
LocalVariableBinding binding = ((TypePattern) e).local.binding;
if (binding != null)
flowInfo.markNullStatus(binding, FlowInfo.POTENTIALLY_NULL);
}
}
return flowInfo;
}
private FlowInfo analyseConstantExpression(
BlockScope currentScope,
FlowContext flowContext,
FlowInfo flowInfo,
Expression e) {
if (e.constant == Constant.NotAConstant
&& !e.resolvedType.isEnum()) {
boolean caseNullorDefaultAllowed =
JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(currentScope.compilerOptions())
&& (e instanceof NullLiteral || e instanceof FakeDefaultLiteral);
if (!caseNullorDefaultAllowed)
currentScope.problemReporter().caseExpressionMustBeConstant(e);
if (e instanceof NullLiteral && flowContext.associatedNode instanceof SwitchStatement) {
Expression switchValue = ((SwitchStatement) flowContext.associatedNode).expression;
if (switchValue != null && switchValue.nullStatus(flowInfo, flowContext) == FlowInfo.NON_NULL) {
currentScope.problemReporter().unnecessaryNullCaseInSwitchOverNonNull(this);
}
}
}
return e.analyseCode(currentScope, flowContext, flowInfo);
}
@Override
public StringBuilder printStatement(int tab, StringBuilder output) {
printIndent(tab, output);
if (this.constantExpressions == Expression.NO_EXPRESSIONS) {
output.append("default "); //$NON-NLS-1$
output.append(this.isExpr ? "->" : ":"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
output.append("case "); //$NON-NLS-1$
for (int i = 0, l = this.constantExpressions.length; i < l; ++i) {
this.constantExpressions[i].printExpression(0, output);
if (i < l -1) output.append(',');
}
output.append(this.isExpr ? " ->" : " :"); //$NON-NLS-1$ //$NON-NLS-2$
}
return output;
}
/**
* Case code generation
*/
@Override
public void generateCode(BlockScope currentScope, CodeStream codeStream) {
if ((this.bits & ASTNode.IsReachable) == 0) {
return;
}
int pc = codeStream.position;
if (this.targetLabels != null) {
for (BranchLabel label : this.targetLabels) {
label.place();
}
}
if (this.targetLabel != null)
this.targetLabel.place();
if (containsPatternVariable(true)) {
BranchLabel patternMatchLabel = new BranchLabel(codeStream);
BranchLabel matchFailLabel = new BranchLabel(codeStream);
Pattern pattern = (Pattern) this.constantExpressions[0];
codeStream.load(this.swich.dispatchPatternCopy);
pattern.generateCode(currentScope, codeStream, patternMatchLabel, matchFailLabel);
codeStream.goto_(patternMatchLabel);
matchFailLabel.place();
if (pattern.matchFailurePossible()) {
/* We are generating a "thunk"/"trampoline" of sorts now, that flow analysis has no clue about.
We need to manage the live variables manually. Pattern bindings are not definitely
assigned here as we are in the else region.
*/
final LocalVariableBinding[] bindingsWhenTrue = pattern.bindingsWhenTrue();
Stream.of(bindingsWhenTrue).forEach(v->v.recordInitializationEndPC(codeStream.position));
int caseIndex = this.typeSwitchIndex + pattern.getAlternatives().length;
codeStream.loadInt(this.swich.nullProcessed ? caseIndex - 1 : caseIndex);
codeStream.store(this.swich.restartIndexLocal, false);
codeStream.goto_(this.swich.switchPatternRestartTarget);
Stream.of(bindingsWhenTrue).forEach(v->v.recordInitializationStartPC(codeStream.position));
}
patternMatchLabel.place();
} else {
if (this.swich.containsNull) {
this.swich.nullProcessed |= true;
}
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
/**
* No-op : should use resolveCase(...) instead.
*/
@Override
public void resolve(BlockScope scope) {
// no-op : should use resolveCase(...) instead.
}
public static class ResolvedCase {
static final ResolvedCase[] UnresolvedCase = new ResolvedCase[0];
public Constant c;
public Expression e;
public TypeBinding t; // For ease of access. This.e contains the type binding anyway.
public int index;
private int intValue;
private final boolean isPattern;
private final boolean isQualifiedEnum;
public int enumDescIdx;
public int classDescIdx;
ResolvedCase(Constant c, Expression e, TypeBinding t, int index, boolean isQualifiedEnum) {
this.c = c;
this.e = e;
this.t= t;
this.index = index;
if (c.typeID() == TypeIds.T_JavaLangString) {
this.intValue = c.stringValue().hashCode();
} else {
this.intValue = c.intValue();
}
this.isPattern = e instanceof Pattern;
this.isQualifiedEnum = isQualifiedEnum;
}
public int intValue() {
return this.intValue;
}
public boolean isPattern() {
return this.isPattern;
}
public boolean isQualifiedEnum() {
return this.isQualifiedEnum;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("case "); //$NON-NLS-1$
builder.append(this.e);
builder.append(" [CONSTANT="); //$NON-NLS-1$
builder.append(this.c);
builder.append("]"); //$NON-NLS-1$
return builder.toString();
}
}
private Expression getFirstValidExpression(BlockScope scope, SwitchStatement switchStatement) {
assert this.constantExpressions != null;
Expression ret = null;
int nullCaseLabelCount = 0;
boolean patternSwitchAllowed = JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(scope.compilerOptions());
if (patternSwitchAllowed) {
int exprCount = 0;
for (Expression e : this.constantExpressions) {
++exprCount;
if (e instanceof FakeDefaultLiteral) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
flagDuplicateDefault(scope, switchStatement,
this.constantExpressions.length > 1 ? e : this);
if (exprCount != 2 || nullCaseLabelCount < 1) {
scope.problemReporter().patternSwitchCaseDefaultOnlyAsSecond(e);
}
continue;
}
if (e instanceof Pattern) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
} else if (e instanceof NullLiteral) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
if (switchStatement.nullCase == null) {
switchStatement.nullCase = this;
}
nullCaseLabelCount++;
// note: case null or case null, default are the only constructs allowed with null
// second condition added since duplicate case label will anyway be flagged
if (exprCount > 1 && nullCaseLabelCount < 2) {
scope.problemReporter().patternSwitchNullOnlyOrFirstWithDefault(e);
return e; // Return and avoid secondary errors
}
}
if (ret == null) ret = e;
}
} else {
for (Expression e : this.constantExpressions) {
if (e instanceof Pattern
|| e instanceof NullLiteral
|| e instanceof FakeDefaultLiteral) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
continue;
}
if (ret == null) ret = e;
}
}
return ret;
}
/**
* Returns the constant intValue or ordinal for enum constants. If constant is NotAConstant, then answers Float.MIN_VALUE
*/
public ResolvedCase[] resolveCase(BlockScope scope, TypeBinding switchExpressionType, SwitchStatement switchStatement) {
this.swich = switchStatement;
scope.enclosingCase = this; // record entering in a switch case block
if (this.constantExpressions == Expression.NO_EXPRESSIONS) {
flagDuplicateDefault(scope, switchStatement, this);
return ResolvedCase.UnresolvedCase;
}
if (getFirstValidExpression(scope, switchStatement) == null) {
return ResolvedCase.UnresolvedCase;
}
switchStatement.cases[switchStatement.caseCount++] = this;
List<ResolvedCase> cases = new ArrayList<>();
for (Expression e : this.constantExpressions) {
// tag constant name with enum type for privileged access to its members
if (switchExpressionType != null && switchExpressionType.isEnum() && (e instanceof SingleNameReference)) {
((SingleNameReference) e).setActualReceiverType((ReferenceBinding)switchExpressionType);
} else if (e instanceof FakeDefaultLiteral) {
continue; // already processed
}
e.setExpressionContext(ExpressionContext.INSTANCEOF_CONTEXT);
e.setExpectedType(switchExpressionType);
TypeBinding caseType = e.resolveType(scope);
if (caseType == null || switchExpressionType == null)
return ResolvedCase.UnresolvedCase;
if (caseType.isValidBinding()) {
if (e instanceof Pattern) {
for (Pattern p : ((Pattern) e).getAlternatives()) {
Constant con = resolveConstantExpression(scope, p.resolvedType, switchExpressionType, switchStatement, p);
if (con != Constant.NotAConstant) {
int index = switchStatement.constantIndex++;
cases.add(new ResolvedCase(con, p, p.resolvedType, index, false));
}
}
} else {
Constant con = resolveConstantExpression(scope, caseType, switchExpressionType, switchStatement, e, cases);
if (con != Constant.NotAConstant) {
int index = this == switchStatement.nullCase && e instanceof NullLiteral ?
-1 : switchStatement.constantIndex++;
cases.add(new ResolvedCase(con, e, caseType, index, false));
}
}
}
}
return cases.toArray(new ResolvedCase[cases.size()]);
}
private void flagDuplicateDefault(BlockScope scope, SwitchStatement switchStatement, ASTNode node) {
// remember the default case into the associated switch statement
if (switchStatement.defaultCase != null)
scope.problemReporter().duplicateDefaultCase(node);
// on error the last default will be the selected one ...
switchStatement.defaultCase = this;
if ((switchStatement.switchBits & SwitchStatement.TotalPattern) != 0) {
scope.problemReporter().illegalTotalPatternWithDefault(this);
}
}
@Override
public LocalVariableBinding[] bindingsWhenTrue() {
LocalVariableBinding [] variables = NO_VARIABLES;
for (Expression e : this.constantExpressions) {
variables = LocalVariableBinding.merge(variables, e.bindingsWhenTrue());
}
return variables;
}
public Constant resolveConstantExpression(BlockScope scope,
TypeBinding caseType,
TypeBinding switchType,
SwitchStatement switchStatement,
Expression expression,
List<ResolvedCase> cases) {
CompilerOptions options = scope.compilerOptions();
boolean patternSwitchAllowed = JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(options);
if (patternSwitchAllowed) {
if (expression instanceof Pattern) {
return resolveConstantExpression(scope, caseType, switchType,
switchStatement,(Pattern) expression);
} else if (expression instanceof NullLiteral) {
if (!(switchType instanceof ReferenceBinding)) {
scope.problemReporter().typeMismatchError(TypeBinding.NULL, switchType, expression, null);
}
switchStatement.switchBits |= SwitchStatement.NullCase;
return IntConstant.fromValue(-1);
} else if (expression instanceof FakeDefaultLiteral) {
// do nothing
} else {
if (switchStatement.isNonTraditional) {
if (switchType.isBaseType() && !expression.isConstantValueOfTypeAssignableToType(caseType, switchType)) {
scope.problemReporter().typeMismatchError(caseType, switchType, expression, null);
return Constant.NotAConstant;
}
}
}
}
boolean boxing = !patternSwitchAllowed ||
switchStatement.isAllowedType(switchType);
if (expression.isConstantValueOfTypeAssignableToType(caseType, switchType)
||(caseType.isCompatibleWith(switchType)
&& !(expression instanceof StringLiteral))) {
if (caseType.isEnum()) {
if (((expression.bits & ASTNode.ParenthesizedMASK) >> ASTNode.ParenthesizedSHIFT) != 0) {
scope.problemReporter().enumConstantsCannotBeSurroundedByParenthesis(expression);
}
if (expression instanceof NameReference
&& (expression.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) {
NameReference reference = (NameReference) expression;
FieldBinding field = reference.fieldBinding();
if ((field.modifiers & ClassFileConstants.AccEnum) == 0) {
scope.problemReporter().enumSwitchCannotTargetField(reference, field);
} else if (reference instanceof QualifiedNameReference) {
if (options.complianceLevel < ClassFileConstants.JDK21) {
scope.problemReporter().cannotUseQualifiedEnumConstantInCaseLabel(reference, field);
} else if (!TypeBinding.equalsEquals(caseType, switchType)) {
switchStatement.switchBits |= SwitchStatement.QualifiedEnum;
StringConstant constant = (StringConstant) StringConstant.fromValue(new String(field.name));
cases.add(new ResolvedCase(constant, expression, caseType, -1, true));
return Constant.NotAConstant;
}
}
return IntConstant.fromValue(field.original().id + 1); // (ordinal value + 1) zero should not be returned see bug 141810
}
} else {
return expression.constant;
}
} else if (boxing && isBoxingCompatible(caseType, switchType, expression, scope)) {
// constantExpression.computeConversion(scope, caseType, switchExpressionType); - do not report boxing/unboxing conversion
return expression.constant;
}
scope.problemReporter().typeMismatchError(expression.resolvedType, switchType, expression, switchStatement.expression);
return Constant.NotAConstant;
}
private Constant resolveConstantExpression(BlockScope scope,
TypeBinding caseType,
TypeBinding switchExpressionType,
SwitchStatement switchStatement,
Pattern e) {
Constant constant = Constant.NotAConstant;
TypeBinding type = e.resolvedType;
if (type != null) {
constant = IntConstant.fromValue(switchStatement.constantIndex);
switchStatement.caseLabelElements.add(e);
switchStatement.caseLabelElementTypes.add(type);
TypeBinding expressionType = switchStatement.expression.resolvedType;
// The following code is copied from InstanceOfExpression#resolve()
// But there are enough differences to warrant a copy
if (!type.isReifiable()) {
if (expressionType != TypeBinding.NULL && !(e instanceof RecordPattern)) {
boolean isLegal = e.checkCastTypesCompatibility(scope, type, expressionType, e, false);
if (!isLegal || (e.bits & ASTNode.UnsafeCast) != 0) {
scope.problemReporter().unsafeCastInInstanceof(e, type, expressionType);
}
}
} else if (type.isValidBinding()) {
// if not a valid binding, an error has already been reported for unresolved type
if (type.isPrimitiveType()) {
scope.problemReporter().unexpectedTypeinSwitchPattern(type, e);
return Constant.NotAConstant;
}
if (type.isBaseType()
|| !e.checkCastTypesCompatibility(scope, type, expressionType, null, false)) {
scope.problemReporter().typeMismatchError(expressionType, type, e, null);
return Constant.NotAConstant;
}
}
if (e.coversType(expressionType)) {
if ((switchStatement.switchBits & SwitchStatement.TotalPattern) != 0) {
scope.problemReporter().duplicateTotalPattern(e);
return IntConstant.fromValue(-1);
}
switchStatement.switchBits |= SwitchStatement.Exhaustive;
if (e.isUnconditional(expressionType)) {
switchStatement.switchBits |= SwitchStatement.TotalPattern;
if (switchStatement.defaultCase != null && !(e instanceof RecordPattern))
scope.problemReporter().illegalTotalPatternWithDefault(this);
switchStatement.totalPattern = e;
}
e.isTotalTypeNode = true;
if (switchStatement.nullCase == null)
constant = IntConstant.fromValue(-1);
}
}
return constant;
}
@Override
public void traverse(ASTVisitor visitor, BlockScope blockScope) {
if (visitor.visit(this, blockScope)) {
for (Expression e : this.constantExpressions) {
e.traverse(visitor, blockScope);
}
}
visitor.endVisit(this, blockScope);
}
}