Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse multiple Patterns in a case statement #9

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions org.eclipse.jdt.core.compiler.batch/grammar/java.g
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ Goal ::= '->' SwitchLabelCaseLhs
Goal ::= RestrictedIdentifiersealed Modifiersopt
Goal ::= RestrictedIdentifierpermits PermittedSubclasses
-- jsr 427 --
Goal ::= BeginCaseElement Pattern
Goal ::= CaseLabelPatternElements
Goal ::= RestrictedIdentifierWhen Expression
/:$readableName Goal:/

Expand Down Expand Up @@ -1598,34 +1598,41 @@ SwitchLabelCaseLhs ::= 'case' CaseLabelElements

-- END SwitchExpression (JEP 325) --

CaseLabelElements -> CaseLabelElement
CaseLabelElements ::= CaseLabelElements ',' CaseLabelElement
CaseLabelElements -> CaseLabelConstantElements
CaseLabelElements -> CaseLabelPatternElements

CaseLabelConstantElements ::= CaseLabelConstantElement
CaseLabelConstantElements ::= CaseLabelConstantElements ',' CaseLabelConstantElement
/.$putCase consumeCaseLabelElements(); $break ./
/:$readableName CaseLabelElements:/
/:$readableName CaseLabelConstantElements:/

-- Production name hardcoded in parser. Must be ::= and not -> (need to hook at cCLE)
CaseLabelElement ::= ConstantExpression
CaseLabelConstantElement ::= ConstantExpression
/.$putCase consumeCaseLabelElement(CaseLabelKind.CASE_EXPRESSION); $break ./
/:$readableName CaseLabelElement:/

-- following 'null' in CASE_EXPRESSION - passes through existing grammar
-- CaseLabelElement -> 'null'

CaseLabelElement ::= 'default'
CaseLabelConstantElement ::= 'default'
/.$putCase consumeCaseLabelElement(CaseLabelKind.CASE_DEFAULT); $break ./
/:$readableName CaseLabelElement:/

CaseLabelElement ::= CaseLabelElementPattern
/.$putCase consumeCaseLabelElement(CaseLabelKind.CASE_PATTERN); $break ./
CaseLabelPatternElements ::= CaseLabelPatternElementsInternal
/:$readableName CaseLabelElement:/

CaseLabelElement ::= CaseLabelElementPattern Guard
/.$putCase consumeCaseLabelElement(CaseLabelKind.CASE_PATTERN); $break ./
/:$readableName CaseLabelElement:/
CaseLabelPatternElementsInternal ::= BeginCaseElement CasePatternList
CaseLabelPatternElementsInternal ::= BeginCaseElement CasePatternList Guard

CaseLabelElementPattern ::= BeginCaseElement Pattern
/.$putCase consumeCaseLabelElementPattern(); $break ./
/:$readableName CaseLabelElementPattern:/
CasePatternList ::= CasePattern
/:$readableName CasePatternList :/
CasePatternList ::= CasePatternList ',' CasePattern
/.$putCase consumeCaseLabelElements(); $break ./
/:$readableName CasePatternList :/

CasePattern ::= Pattern
/.$putCase consumeCaseLabelElement(CaseLabelKind.CASE_PATTERN); $break ./
/:$readableName CasePattern:/

Guard ::= RestrictedIdentifierWhen Expression
/.$putCase consumeGuard(); $break ./
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2576,6 +2576,10 @@ public interface IProblem {
*/
int IllegalRecordPattern = TypeRelated + 1941;

/**
* @since 3.37
*/
int CaseLabelWithPatternMustHaveExactlyOnePattern = Internal + 1942;

/**
* @since 3.35
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,5 @@ public boolean isUnnamed(BlockScope scope) {
&& scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK21
&& scope.compilerOptions().enablePreviewFeatures;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public FlowInfo analyseCode(
local.useFlag = LocalVariableBinding.USED; // these are structurally required even if not touched
}
nullPatternCount += e instanceof NullLiteral ? 1 : 0;
if (i > 0 && (e instanceof Pattern)) {
if (i > 0 && (e instanceof Pattern)
&& (currentScope.compilerOptions().sourceLevel < ClassFileConstants.JDK21 || !currentScope.compilerOptions().enablePreviewFeatures)) {
if (!(i == nullPatternCount && e instanceof TypePattern))
currentScope.problemReporter().IllegalFallThroughToPattern(e);
}
Expand Down Expand Up @@ -172,14 +173,28 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) {

private void casePatternExpressionGenerateCode(BlockScope currentScope, CodeStream codeStream) {
if (this.patternIndex != -1) {
Pattern pattern = ((Pattern) this.constantExpressions[this.patternIndex]);
if (containsPatternVariable()) {
BranchLabel thenTarget = new BranchLabel(codeStream);
for (int i = 0; i < this.constantExpressions.length - 1; i++) {
Pattern pattern = ((Pattern)this.constantExpressions[i]);
pattern.thenTarget = thenTarget;
LocalVariableBinding local = currentScope.findVariable(SwitchStatement.SecretPatternVariableName, null);
codeStream.load(local);
pattern.generateCode(currentScope, codeStream);
pattern.fullWrapupGeneration(codeStream);
codeStream.goto_(pattern.thenTarget);
pattern.elseTarget.place();
}
Pattern pattern = ((Pattern)this.constantExpressions[this.constantExpressions.length - 1]);
pattern.thenTarget = thenTarget;
LocalVariableBinding local = currentScope.findVariable(SwitchStatement.SecretPatternVariableName, null);
codeStream.load(local);
pattern.generateCode(currentScope, codeStream);
if (pattern.countNamedVariables(currentScope) > 0) {
pattern.wrapupGeneration(codeStream);
} else {
pattern.setTargets(codeStream);
pattern.fullWrapupGeneration(codeStream);
}
pattern.setTargets(codeStream);

if (!(pattern instanceof GuardedPattern))
codeStream.goto_(pattern.thenTarget);
Expand Down Expand Up @@ -260,10 +275,16 @@ private Expression getFirstValidExpression(BlockScope scope, SwitchStatement swi
if (e instanceof Pattern) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
if (this.constantExpressions.length > 1) {
scope.problemReporter().illegalCaseConstantCombination(e);
return e;
if (this.constantExpressions.length > 1 || e instanceof GuardedPattern gp && gp.patterns.length > 1) {
int count = 0;
for (Expression expr : this.constantExpressions) {
count += ((Pattern)expr).countNamedVariables(scope);
}
if (count > 0) {
scope.problemReporter().illegalCaseLabelWithMultiplePatterns(this);
}
}
return e;
} else if (e instanceof NullLiteral) {
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.PATTERN_MATCHING_IN_SWITCH,
e.sourceStart, e.sourceEnd);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,49 @@

public class GuardedPattern extends Pattern {

public Pattern primaryPattern;
public Pattern[] patterns;
public Expression condition;
int thenInitStateIndex1 = -1;
int thenInitStateIndex2 = -1;
public int restrictedIdentifierStart = -1; // used only for 'when' restricted keyword.

public GuardedPattern(Pattern primaryPattern, Expression conditionalAndExpression) {
this.primaryPattern = primaryPattern;
public GuardedPattern(Pattern patterns[], Expression conditionalAndExpression) {
this.patterns = patterns;
this.condition = conditionalAndExpression;
this.sourceStart = primaryPattern.sourceStart;
if (patterns.length > 0) {
this.sourceStart = this.patterns[0].sourceStart;
} else {
this.sourceStart = conditionalAndExpression.sourceStart;
}
this.sourceEnd = conditionalAndExpression.sourceEnd;
}

@Override
public LocalDeclaration getPatternVariable() {
return this.primaryPattern.getPatternVariable();
if (this.patterns.length == 1) {
return this.patterns[0].getPatternVariable();
}
return null;
}

@Override
public LocalVariableBinding[] bindingsWhenTrue() {
return LocalVariableBinding.merge(this.primaryPattern.bindingsWhenTrue(),
this.condition.bindingsWhenTrue());
LocalVariableBinding[] patternsBindings = new LocalVariableBinding[] {};
for (Pattern pattern : this.patterns) {
patternsBindings = LocalVariableBinding.merge(patternsBindings, pattern.bindingsWhenTrue());
}
return LocalVariableBinding.merge(patternsBindings, this.condition.bindingsWhenTrue());
}

@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
flowInfo = this.primaryPattern.analyseCode(currentScope, flowContext, flowInfo);
this.thenInitStateIndex1 = currentScope.methodScope().recordInitializationStates(flowInfo);
if (this.patterns.length >= 1) {
flowInfo = this.patterns[0].analyseCode(currentScope, flowContext, flowInfo);
this.thenInitStateIndex1 = currentScope.methodScope().recordInitializationStates(flowInfo);
}
for (int i = 1; i < this.patterns.length; i++) {
flowInfo = this.patterns[i].analyseCode(currentScope, flowContext, flowInfo);
}
FlowInfo mergedFlow = this.condition.analyseCode(currentScope, flowContext, flowInfo);
mergedFlow = mergedFlow.safeInitsWhenTrue();
this.thenInitStateIndex2 = currentScope.methodScope().recordInitializationStates(mergedFlow);
Expand All @@ -65,7 +80,29 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl
public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel) {
this.thenTarget = new BranchLabel(codeStream);
this.elseTarget = new BranchLabel(codeStream);
this.primaryPattern.generateOptimizedBoolean(currentScope, codeStream, this.thenTarget, this.elseTarget);

LocalVariableBinding local = currentScope.findVariable(SwitchStatement.SecretPatternVariableName, null);
BranchLabel toGuard = new BranchLabel(codeStream);

for (int i = 0; i < this.patterns.length - 1; i++) {
Pattern pattern = this.patterns[i];
pattern.thenTarget = toGuard;
pattern.generateCode(currentScope, codeStream);
pattern.fullWrapupGeneration(codeStream);
codeStream.goto_(pattern.thenTarget);
pattern.elseTarget.place();
codeStream.load(local);
}
Pattern pattern = this.patterns[this.patterns.length - 1];
pattern.thenTarget = toGuard;
pattern.elseTarget = this.elseTarget;
pattern.generateCode(currentScope, codeStream);
if (pattern.countNamedVariables(currentScope) > 0) {
pattern.wrapupGeneration(codeStream);
} else {
pattern.fullWrapupGeneration(codeStream);
}
toGuard.place();
Constant cst = this.condition.optimizedBooleanConstant();

setGuardedElseTarget(currentScope, this.elseTarget);
Expand All @@ -75,6 +112,9 @@ public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStr
this.thenTarget,
null,
cst == Constant.NotAConstant);
if (cst != Constant.NotAConstant) {
codeStream.goto_(this.thenTarget);
}
if (this.thenInitStateIndex2 != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex2);
codeStream.addDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex2);
Expand Down Expand Up @@ -104,25 +144,42 @@ public boolean isAlwaysTrue() {
}
@Override
public boolean coversType(TypeBinding type) {
return this.primaryPattern.coversType(type) && isAlwaysTrue();
if (!isAlwaysTrue()) {
return false;
}
for (Pattern pattern : this.patterns) {
if (pattern.coversType(type)) {
return true;
}
}
return false;
}

@Override
public boolean dominates(Pattern p) {
if (isAlwaysTrue())
return this.primaryPattern.dominates(p);
if (isAlwaysTrue()) {
for (Pattern pattern : this. patterns) {
if (pattern.dominates(p)) {
return true;
}
}
}
return false;
}

@Override
public TypeBinding resolveType(BlockScope scope) {
if (this.resolvedType != null || this.primaryPattern == null)
if (this.resolvedType != null || this.patterns[0] == null)
return this.resolvedType;
this.resolvedType = this.primaryPattern.resolveType(scope);
this.resolvedType = this.patterns[0].resolveType(scope);
// The following call (as opposed to resolveType() ensures that
// the implicitConversion code is set properly and thus the correct
// unboxing calls are generated.
this.condition.resolveTypeExpectingWithBindings(this.primaryPattern.bindingsWhenTrue(), scope, TypeBinding.BOOLEAN);
LocalVariableBinding[] patternsBindings = new LocalVariableBinding[] {};
for (Pattern pattern: this.patterns) {
patternsBindings = LocalVariableBinding.merge(patternsBindings, pattern.bindingsWhenTrue());
}
this.condition.resolveTypeExpectingWithBindings(patternsBindings, scope, TypeBinding.BOOLEAN);
Constant cst = this.condition.optimizedBooleanConstant();
if (cst.typeID() == TypeIds.T_boolean && cst.booleanValue() == false) {
scope.problemReporter().falseLiteralInGuard(this.condition);
Expand All @@ -148,20 +205,27 @@ public boolean visit(
return false;
}
}, scope);
return this.resolvedType = this.primaryPattern.resolvedType;
return this.resolvedType = this.patterns[0].resolvedType;
}

@Override
public StringBuilder printExpression(int indent, StringBuilder output) {
this.primaryPattern.print(indent, output).append(" when "); //$NON-NLS-1$
for (int i = 0; i < this.patterns.length; i++) {
this.patterns[i].print(indent, output);
if (i < this.patterns.length - 1) {
output.append(", "); //$NON-NLS-1$
}
}
output.append(" when "); //$NON-NLS-1$
return this.condition.print(indent, output);
}

@Override
public void traverse(ASTVisitor visitor, BlockScope scope) {
if (visitor.visit(this, scope)) {
if (this.primaryPattern != null)
this.primaryPattern.traverse(visitor, scope);
for (Pattern pattern : this.patterns) {
pattern.traverse(visitor, scope);
}
if (this.condition != null)
this.condition.traverse(visitor, scope);
}
Expand All @@ -170,24 +234,32 @@ public void traverse(ASTVisitor visitor, BlockScope scope) {
@Override
public void suspendVariables(CodeStream codeStream, BlockScope scope) {
codeStream.removeNotDefinitelyAssignedVariables(scope, this.thenInitStateIndex1);
this.primaryPattern.suspendVariables(codeStream, scope);
if (this.patterns[0] != null) {
this.patterns[0].suspendVariables(codeStream, scope);
}
}
@Override
public void resumeVariables(CodeStream codeStream, BlockScope scope) {
codeStream.addDefinitelyAssignedVariables(scope, this.thenInitStateIndex2);
this.primaryPattern.resumeVariables(codeStream, scope);
if (this.patterns[0] != null) {
this.patterns[0].resumeVariables(codeStream, scope);
}
}
@Override
protected boolean isPatternTypeCompatible(TypeBinding other, BlockScope scope) {
return this.primaryPattern.isPatternTypeCompatible(other, scope);
return this.patterns[0].isPatternTypeCompatible(other, scope);
}
@Override
public void wrapupGeneration(CodeStream codeStream) {
this.primaryPattern.wrapupGeneration(codeStream);
this.patterns[0].wrapupGeneration(codeStream);
}
@Override
public void fullWrapupGeneration(CodeStream codeStream) {
this.patterns[0].fullWrapupGeneration(codeStream);
}
@Override
protected void generatePatternVariable(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel,
BranchLabel falseLabel) {
this.primaryPattern.generatePatternVariable(currentScope, codeStream, trueLabel, falseLabel);
this.patterns[0].generatePatternVariable(currentScope, codeStream, trueLabel, falseLabel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,30 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean

int pc = codeStream.position;

if (this.elementVariable != null) {
if (this.elementVariable != null || this.pattern != null) {
addAssignment(currentScope, codeStream, this.secretInstanceOfPatternExpressionValue);
codeStream.load(this.secretInstanceOfPatternExpressionValue);
} else {
this.expression.generateCode(currentScope, codeStream, true);
}

codeStream.instance_of(this.type, this.type.resolvedType);
if (this.elementVariable != null) {
if (this.pattern != null) {
BranchLabel trueLabel = new BranchLabel(codeStream);
BranchLabel falseLabel = new BranchLabel(codeStream);
BranchLabel continueLabel = new BranchLabel(codeStream);
codeStream.ifeq(falseLabel);
codeStream.load(this.secretInstanceOfPatternExpressionValue);
this.pattern.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel);
this.pattern.fullWrapupGeneration(codeStream);
codeStream.removeVariable(this.secretInstanceOfPatternExpressionValue);
trueLabel.place();
codeStream.iconst_1();
codeStream.goto_(continueLabel);
falseLabel.place();
codeStream.iconst_0();
continueLabel.place();
} else if (this.elementVariable != null) {
BranchLabel actionLabel = new BranchLabel(codeStream);
codeStream.dup();
codeStream.ifeq(actionLabel);
Expand Down
Loading