8291769: Translation of switch with record patterns could be improved

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2022-12-05 12:04:23 +00:00
parent eab0ada3a1
commit 2300ed458d
13 changed files with 1375 additions and 166 deletions

View File

@ -26,7 +26,7 @@
package com.sun.tools.javac.comp;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.CaseTree.CaseKind;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Kinds;
@ -50,7 +50,6 @@ import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCInstanceOf;
import com.sun.tools.javac.tree.JCTree.JCLabeledStatement;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
@ -64,12 +63,14 @@ import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.Options;
import java.util.HashMap;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.RecordComponent;
@ -79,13 +80,16 @@ import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
import com.sun.tools.javac.jvm.Target;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCBlock.PatternMatchingCatch;
import com.sun.tools.javac.tree.JCTree.JCBreak;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCContinue;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCConstantCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
@ -96,10 +100,11 @@ import com.sun.tools.javac.tree.JCTree.JCPatternCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.LetExpr;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
/**
@ -165,16 +170,14 @@ public class TransPatterns extends TreeTranslator {
}
};
JCLabeledStatement pendingMatchLabel = null;
boolean debugTransPatterns;
private ClassSymbol currentClass = null;
private JCClassDecl currentClassTree = null;
private ListBuffer<JCTree> pendingMethods = null;
private MethodSymbol currentMethodSym = null;
private VarSymbol currentValue = null;
private Map<RecordComponent, MethodSymbol> component2Proxy = null;
private Set<JCMethodInvocation> deconstructorCalls;
private int variableIndex = 0;
protected TransPatterns(Context context) {
context.put(transPatternsKey, this);
@ -187,16 +190,27 @@ public class TransPatterns extends TreeTranslator {
names = Names.instance(context);
target = Target.instance(context);
preview = Preview.instance(context);
debugTransPatterns = Options.instance(context).isSet("debug.patterns");
}
@Override
public void visitTypeTest(JCInstanceOf tree) {
if (tree.pattern instanceof JCPattern) {
//E instanceof $pattern
if (tree.pattern instanceof JCPattern pattern) {
//first, resolve any parenthesized and record patterns:
pattern = TreeInfo.skipParens(pattern);
JCExpression extraConditions = null;
if (pattern instanceof JCRecordPattern recordPattern) {
UnrolledRecordPattern unrolledRecordPattern = unrollRecordPattern(recordPattern);
pattern = unrolledRecordPattern.primaryPattern();
extraConditions = unrolledRecordPattern.newGuard();
}
//$pattern is now always a binding pattern, $extraConditions are possibly additional tests
//implementing to the record pattern
//
//E instanceof $patternType $patternName && $extraConditions
//=>
//(let T' N$temp = E; N$temp instanceof typeof($pattern) && <desugared $pattern>)
//note the pattern desugaring performs binding variable assignments
//(let $patternType N$temp = E; N$temp instanceof $patternType &&
// (let $patternName = ($patternType) N$temp; true) &&
// $extraConditions)
Type tempType = tree.expr.type.hasTag(BOT) ?
syms.objectType
: tree.expr.type;
@ -212,16 +226,23 @@ public class TransPatterns extends TreeTranslator {
currentValue = (VarSymbol) exprSym;
} else {
currentValue = new VarSymbol(Flags.FINAL | Flags.SYNTHETIC,
names.fromString("patt" + tree.pos + target.syntheticNameChar() + "temp"),
names.fromString("patt" + variableIndex++ + target.syntheticNameChar() + "temp"),
tempType,
currentMethodSym);
}
Type principalType = principalType((JCPattern) tree.pattern);
JCExpression resultExpression=
makeBinary(Tag.AND,
makeTypeTest(make.Ident(currentValue), make.Type(principalType)),
(JCExpression) this.<JCTree>translate(tree.pattern));
Type principalType = types.erasure(TreeInfo.primaryPatternType((pattern)));
JCExpression resultExpression= (JCExpression) this.<JCTree>translate(pattern);
if (!tree.allowNull || !types.isSubtype(currentValue.type, principalType)) {
resultExpression =
makeBinary(Tag.AND,
makeTypeTest(make.Ident(currentValue), make.Type(principalType)),
resultExpression);
}
if (extraConditions != null) {
extraConditions = translate(extraConditions);
resultExpression = makeBinary(Tag.AND, resultExpression, extraConditions);
}
if (currentValue != exprSym) {
resultExpression =
make.at(tree.pos).LetExpr(make.VarDef(currentValue, translatedExpr),
@ -242,7 +263,7 @@ public class TransPatterns extends TreeTranslator {
public void visitBindingPattern(JCBindingPattern tree) {
//it is assumed the primary type has already been checked:
BindingSymbol binding = (BindingSymbol) tree.var.sym;
Type castTargetType = principalType(tree);
Type castTargetType = types.erasure(TreeInfo.primaryPatternType(tree));
VarSymbol bindingVar = bindingContext.bindingDeclared(binding);
if (bindingVar != null) {
@ -265,60 +286,68 @@ public class TransPatterns extends TreeTranslator {
@Override
public void visitRecordPattern(JCRecordPattern tree) {
//type test already done, finish handling of deconstruction patterns ("T(PATT1, PATT2, ...)")
//record patterns should be resolved by the constructs that use them.
Assert.error();
}
private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern) {
//Convert a record pattern in the basic binding pattern and additional conditions
//implementing the record pattern:
//$record($nestedPattern1, $nestedPattern2, ...) $r
//=>
//<PATT1-handling> && <PATT2-handling> && ...
List<? extends RecordComponent> components = tree.record.getRecordComponents();
List<? extends Type> nestedFullComponentTypes = tree.fullComponentTypes;
List<? extends JCPattern> nestedPatterns = tree.nested;
JCExpression test = null;
while (components.nonEmpty() && nestedFullComponentTypes.nonEmpty() && nestedPatterns.nonEmpty()) {
//PATTn for record component COMPn of type Tn;
//PATTn is a type test pattern or a deconstruction pattern:
//=>
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); <PATTn extractor>)
//or
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); $c$COMPn != null && <PATTn extractor>)
//or
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); $c$COMPn instanceof T' && <PATTn extractor>)
//$record $r; type-test-of($nestedPattern1) && type-test-of($nestedPattern2) && ... &&
// nested-conditions-of($nestedPattern1) && nested-conditions-of($nestedPattern2)
Type recordType = recordPattern.record.erasure(types);
BindingSymbol tempBind = new BindingSymbol(Flags.SYNTHETIC,
names.fromString(target.syntheticNameChar() + "b" + target.syntheticNameChar() + variableIndex++), recordType,
currentMethodSym);
JCVariableDecl recordBindingVar = make.VarDef(tempBind, null);
VarSymbol recordBinding = recordBindingVar.sym;
List<? extends RecordComponent> components = recordPattern.record.getRecordComponents();
List<? extends Type> nestedFullComponentTypes = recordPattern.fullComponentTypes;
List<? extends JCPattern> nestedPatterns = recordPattern.nested;
JCExpression firstLevelChecks = null;
JCExpression secondLevelChecks = null;
while (components.nonEmpty()) {
RecordComponent component = components.head;
JCPattern nested = nestedPatterns.head;
VarSymbol nestedTemp = new VarSymbol(Flags.SYNTHETIC,
names.fromString(target.syntheticNameChar() + "c" + target.syntheticNameChar() + component.name),
component.erasure(types),
currentMethodSym);
Symbol accessor = getAccessor(tree.pos(), component);
JCVariableDecl nestedTempVar =
make.VarDef(nestedTemp,
make.App(make.QualIdent(accessor),
List.of(convert(make.Ident(currentValue), tree.type))));
JCExpression extracted;
VarSymbol prevCurrentValue = currentValue;
try {
currentValue = nestedTemp;
extracted = (JCExpression) this.<JCTree>translate(nested);
} finally {
currentValue = prevCurrentValue;
}
JCExpression extraTest = null;
if (!types.isAssignable(nestedTemp.type, nested.type)) {
if (!types.isAssignable(nestedFullComponentTypes.head, nested.type)) {
extraTest = makeTypeTest(make.Ident(nestedTemp),
make.Type(nested.type));
Type componentType = types.erasure(nestedFullComponentTypes.head);
JCPattern nestedPattern = TreeInfo.skipParens(nestedPatterns.head);
JCBindingPattern nestedBinding;
boolean allowNull;
if (nestedPattern instanceof JCRecordPattern nestedRecordPattern) {
UnrolledRecordPattern nestedDesugared = unrollRecordPattern(nestedRecordPattern);
JCExpression newGuard = nestedDesugared.newGuard();
if (newGuard != null) {
if (secondLevelChecks == null) {
secondLevelChecks = newGuard;
} else {
secondLevelChecks = mergeConditions(secondLevelChecks, newGuard);
}
}
} else if (nested.type.isReference() && nested.hasTag(Tag.RECORDPATTERN)) {
extraTest = makeBinary(Tag.NE, make.Ident(nestedTemp), makeNull());
}
if (extraTest != null) {
extracted = makeBinary(Tag.AND, extraTest, extracted);
}
LetExpr getAndRun = make.LetExpr(nestedTempVar, extracted);
getAndRun.needsCond = true;
getAndRun.setType(syms.booleanType);
if (test == null) {
test = getAndRun;
nestedBinding = nestedDesugared.primaryPattern();
allowNull = false;
} else {
test = makeBinary(Tag.AND, test, getAndRun);
nestedBinding = (JCBindingPattern) nestedPattern;
allowNull = true;
}
JCMethodInvocation componentAccessor =
make.App(make.Select(convert(make.Ident(recordBinding), recordBinding.type), //TODO - cast needed????
component.accessor));
if (deconstructorCalls == null) {
deconstructorCalls = Collections.newSetFromMap(new IdentityHashMap<>());
}
deconstructorCalls.add(componentAccessor);
JCExpression accessedComponentValue =
convert(componentAccessor, componentType);
JCInstanceOf firstLevelCheck = (JCInstanceOf) make.TypeTest(accessedComponentValue, nestedBinding).setType(syms.booleanType);
//TODO: verify deep/complex nesting with nulls
firstLevelCheck.allowNull = allowNull;
if (firstLevelChecks == null) {
firstLevelChecks = firstLevelCheck;
} else {
firstLevelChecks = mergeConditions(firstLevelChecks, firstLevelCheck);
}
components = components.tail;
nestedFullComponentTypes = nestedFullComponentTypes.tail;
@ -326,46 +355,17 @@ public class TransPatterns extends TreeTranslator {
}
Assert.check(components.isEmpty() == nestedPatterns.isEmpty());
Assert.check(components.isEmpty() == nestedFullComponentTypes.isEmpty());
result = test != null ? test : makeLit(syms.booleanType, 1);
JCExpression guard = null;
if (firstLevelChecks != null) {
guard = firstLevelChecks;
if (secondLevelChecks != null) {
guard = mergeConditions(guard, secondLevelChecks);
}
}
return new UnrolledRecordPattern((JCBindingPattern) make.BindingPattern(recordBindingVar).setType(recordBinding.type), guard);
}
private MethodSymbol getAccessor(DiagnosticPosition pos, RecordComponent component) {
return component2Proxy.computeIfAbsent(component, c -> {
MethodType type = new MethodType(List.of(component.owner.erasure(types)),
types.erasure(component.type),
List.nil(),
syms.methodClass);
MethodSymbol proxy = new MethodSymbol(Flags.PRIVATE | Flags.STATIC | Flags.SYNTHETIC,
names.fromString("$proxy$" + component.name),
type,
currentClass);
JCStatement accessorStatement =
make.Return(make.App(make.Select(make.Ident(proxy.params().head), c.accessor)));
VarSymbol ctch = new VarSymbol(Flags.SYNTHETIC,
names.fromString("catch" + currentClassTree.pos + target.syntheticNameChar()),
syms.throwableType,
currentMethodSym);
JCNewClass newException = makeNewClass(syms.matchExceptionType,
List.of(makeApply(make.Ident(ctch),
names.toString,
List.nil()),
make.Ident(ctch)));
JCTree.JCCatch catchClause = make.Catch(make.VarDef(ctch, null),
make.Block(0, List.of(make.Throw(newException))));
JCStatement tryCatchAll = make.Try(make.Block(0, List.of(accessorStatement)),
List.of(catchClause),
null);
JCMethodDecl md = make.MethodDef(proxy,
proxy.externalType(types),
make.Block(0, List.of(tryCatchAll)));
pendingMethods.append(md);
currentClass.members().enter(proxy);
return proxy;
});
}
record UnrolledRecordPattern(JCBindingPattern primaryPattern, JCExpression newGuard) {}
@Override
public void visitSwitch(JCSwitch tree) {
@ -391,27 +391,33 @@ public class TransPatterns extends TreeTranslator {
Assert.check(preview.isEnabled());
Assert.check(preview.usesPreview(env.toplevel.sourcefile));
//rewrite pattern matching switches:
//rewrite pattern matching switches, performed in several steps:
//1. record patterns are unrolled into a binding pattern and guards using unrollRecordPattern
// (guards implement the nested pattern checks)
// the switch only has constants and binding patterns as the
//2. the cases are processed through processCases, that will group cases with the same
// binding pattern and similar guards, and will factor out the common binding pattern,
// creating nested switches.
//3. the simplified binding-only switch with guards is then converted to an ordinary switch:
//switch ($obj) {
// case $constant: $stats$
// case $pattern1: $stats$
// case $pattern2, null: $stats$
// case $pattern3: $stats$
// case $constant: $stats1$
// case $pattern2 when $guard2: $stats2$
// case $pattern3, null: $stats3$
// case $pattern4: $stats4$
//}
//=>
//int $idx = 0;
//$RESTART: switch (invokeDynamic typeSwitch($constant, typeof($pattern1), typeof($pattern2), typeof($pattern3))($obj, $idx)) {
// case 0:
// if (!(<desugared $pattern1>)) { $idx = 1; continue $RESTART; }
// $stats$
// $stats1$
// case 1:
// if (!(<desugared $pattern1>)) { $idx = 2; continue $RESTART; }
// if (!(<desugared $pattern2> && $guard2)) { $idx = 2; continue $RESTART; }
// $stats$
// case 2, -1:
// if (!(<desugared $pattern1>)) { $idx = 3; continue $RESTART; }
// if (!(<desugared $pattern3>)) { $idx = 3; continue $RESTART; }
// $stats$
// case 3:
// if (!(<desugared $pattern1>)) { $idx = 4; continue $RESTART; }
// if (!(<desugared $pattern4>)) { $idx = 4; continue $RESTART; }
// $stats$
//}
//notes:
@ -426,9 +432,28 @@ public class TransPatterns extends TreeTranslator {
// return -1 when the input is null
//
//note the selector is evaluated only once and stored in a temporary variable
ListBuffer<JCCase> newCases = new ListBuffer<>();
for (List<JCCase> c = cases; c.nonEmpty(); c = c.tail) {
c.head.labels = c.head.labels.map(l -> {
if (l instanceof JCPatternCaseLabel patternLabel) {
JCPattern pattern = TreeInfo.skipParens(patternLabel.pat);
if (pattern instanceof JCRecordPattern recordPattern) {
UnrolledRecordPattern deconstructed = unrollRecordPattern(recordPattern);
JCExpression guard = deconstructed.newGuard();
if (patternLabel.guard != null) {
guard = mergeConditions(guard, patternLabel.guard);
}
return make.PatternCaseLabel(deconstructed.primaryPattern(), guard);
}
}
return l;
});
newCases.add(c.head);
}
cases = processCases(tree, newCases.toList());
ListBuffer<JCStatement> statements = new ListBuffer<>();
VarSymbol temp = new VarSymbol(Flags.SYNTHETIC,
names.fromString("selector" + tree.pos + target.syntheticNameChar() + "temp"),
names.fromString("selector" + variableIndex++ + target.syntheticNameChar() + "temp"),
seltype,
currentMethodSym);
boolean hasNullCase = cases.stream()
@ -442,7 +467,7 @@ public class TransPatterns extends TreeTranslator {
statements.append(make.at(tree.pos).VarDef(temp, needsNullCheck ? attr.makeNullCheck(selector)
: selector));
VarSymbol index = new VarSymbol(Flags.SYNTHETIC,
names.fromString(tree.pos + target.syntheticNameChar() + "index"),
names.fromString("index" + target.syntheticNameChar() + variableIndex++),
syms.intType,
currentMethodSym);
statements.append(make.at(tree.pos).VarDef(index, makeLit(syms.intType, 0)));
@ -540,6 +565,9 @@ public class TransPatterns extends TreeTranslator {
} else {
c.stats = translate(c.stats);
}
fixupContinue(tree, c, index, i);
ListBuffer<JCCaseLabel> translatedLabels = new ListBuffer<>();
for (var p : c.labels) {
if (p.hasTag(Tag.DEFAULTCASELABEL)) {
@ -596,6 +624,23 @@ public class TransPatterns extends TreeTranslator {
super.visitSwitchExpression((JCSwitchExpression) tree);
}
}
//where:
private void fixupContinue(JCTree switchTree, JCCase c, VarSymbol indexVariable, int currentCaseIndex) {
//inject 'index = currentCaseIndex + 1;` before continue which has the current switch as the target
new TreeScanner() {
@Override
public void visitCase(JCCase c) {
if (c.stats.size() == 1 && c.stats.head instanceof JCContinue cont &&
cont.target == switchTree) {
JCExpressionStatement setIndex =
make.Exec(make.Assign(make.Ident(indexVariable),
makeLit(syms.intType, currentCaseIndex + 1))
.setType(syms.intType));
c.stats = c.stats.prepend(setIndex);
}
}
}.scan(c.stats);
}
JCMethodInvocation makeApply(JCExpression selector, Name name, List<JCExpression> args) {
MethodSymbol method = rs.resolveInternalMethod(
@ -616,6 +661,165 @@ public class TransPatterns extends TreeTranslator {
return tree;
}
/**
* Considering a list of cases, find consecutive cases with the same binding pattern as their label,
* and type tests with binding patterns as the first element in the guard. These cases are then
* merged into a single case, and a nested switch is generated from the first element of the guard.
*
* For example:
*
* OUTER:
* switch (selector) {
* case Box b when b.o() instanceof String s -> {}
* case Box b when b.o() instanceof Integer i-> {}
* case Box b when b.o() instanceof Number n -> {}
* ...
* }
* =>
* OUTER:
* switch (selector) {
* case Box b ->
* switch (b.o()) {
* case String s -> {}
* case Integer i -> {}
* case Number n -> {}
* default -> continue OUTER; //continue matching on next case of the outer switch
* }
* ...
* }
*/
private List<JCCase> processCases(JCTree currentSwitch, List<JCCase> inputCases) {
interface AccummulatorResolver {
public void resolve(VarSymbol commonBinding,
JCExpression commonNestedExpression,
VarSymbol commonNestedBinding);
}
ListBuffer<JCCase> accummulator = new ListBuffer<>();
ListBuffer<JCCase> result = new ListBuffer<>();
AccummulatorResolver resolveAccummulator = (commonBinding, commonNestedExpression, commonNestedBinding) -> {
boolean hasUnconditional = false;
if (accummulator.size() > 1) {
Assert.check(commonBinding != null &&
commonNestedExpression != null &&
commonNestedBinding != null,
() -> "commonBinding: " + commonBinding +
"commonNestedExpression: " + commonNestedExpression +
"commonNestedBinding: " + commonNestedBinding);
ListBuffer<JCCase> nestedCases = new ListBuffer<>();
for(List<JCCase> accList = accummulator.toList(); accList.nonEmpty(); accList = accList.tail) {
var accummulated = accList.head;
JCPatternCaseLabel accummulatedFirstLabel =
(JCPatternCaseLabel) accummulated.labels.head;
JCBindingPattern accummulatedPattern =
(JCBindingPattern) accummulatedFirstLabel.pat;
VarSymbol accummulatedBinding = accummulatedPattern.var.sym;
TreeScanner replaceNested =
new ReplaceVar(Map.of(accummulatedBinding, commonBinding));
replaceNested.scan(accummulated);
JCExpression newGuard;
JCInstanceOf instanceofCheck;
if (accummulatedFirstLabel.guard instanceof JCBinary binOp) {
newGuard = binOp.rhs;
instanceofCheck = (JCInstanceOf) binOp.lhs;
} else {
newGuard = null;
instanceofCheck = (JCInstanceOf) accummulatedFirstLabel.guard;
}
JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern;
hasUnconditional =
instanceofCheck.allowNull &&
types.isSubtype(commonNestedExpression.type,
types.boxedTypeOrType(types.erasure(binding.type))) &&
accList.tail.isEmpty();
List<JCCaseLabel> newLabel;
if (hasUnconditional) {
newLabel = List.of(make.ConstantCaseLabel(makeNull()),
make.DefaultCaseLabel());
} else {
newLabel = List.of(make.PatternCaseLabel(binding, newGuard));
}
nestedCases.add(make.Case(CaseKind.STATEMENT, newLabel, accummulated.stats, null));
}
if (!hasUnconditional) {
JCContinue continueSwitch = make.Continue(null);
continueSwitch.target = currentSwitch;
nestedCases.add(make.Case(CaseKind.STATEMENT,
List.of(make.ConstantCaseLabel(makeNull()),
make.DefaultCaseLabel()),
List.of(continueSwitch),
null));
}
JCSwitch newSwitch = make.Switch(commonNestedExpression, nestedCases.toList());
newSwitch.patternSwitch = true;
JCPatternCaseLabel leadingTest =
(JCPatternCaseLabel) accummulator.first().labels.head;
leadingTest.guard = null;
result.add(make.Case(CaseKind.STATEMENT,
List.of(leadingTest),
List.of(newSwitch),
null));
} else {
result.addAll(accummulator);
}
accummulator.clear();
};
VarSymbol commonBinding = null;
JCExpression commonNestedExpression = null;
VarSymbol commonNestedBinding = null;
for (List<JCCase> c = inputCases; c.nonEmpty(); c = c.tail) {
VarSymbol currentBinding = null;
JCExpression currentNestedExpression = null;
VarSymbol currentNestedBinding = null;
if (c.head.labels.size() == 1 &&
c.head.labels.head instanceof JCPatternCaseLabel patternLabel) {
if (patternLabel.guard instanceof JCBinary binOp &&
binOp.lhs instanceof JCInstanceOf instanceofCheck &&
instanceofCheck.pattern instanceof JCBindingPattern binding) {
currentBinding = ((JCBindingPattern) patternLabel.pat).var.sym;
currentNestedExpression = instanceofCheck.expr;
currentNestedBinding = binding.var.sym;
} else if (patternLabel.guard instanceof JCInstanceOf instanceofCheck &&
instanceofCheck.pattern instanceof JCBindingPattern binding) {
currentBinding = ((JCBindingPattern) patternLabel.pat).var.sym;
currentNestedExpression = instanceofCheck.expr;
currentNestedBinding = binding.var.sym;
}
}
if (commonBinding == null) {
if (currentBinding != null) {
commonBinding = currentBinding;
commonNestedExpression = currentNestedExpression;
commonNestedBinding = currentNestedBinding;
accummulator.add(c.head);
} else {
result.add(c.head);
}
} else if (currentBinding != null &&
commonBinding.type.tsym == currentBinding.type.tsym &&
new TreeDiffer(List.of(commonBinding), List.of(currentBinding))
.scan(commonNestedExpression, currentNestedExpression)) {
accummulator.add(c.head);
} else {
resolveAccummulator.resolve(commonBinding, commonNestedExpression, commonNestedBinding);
if (currentBinding != null) {
accummulator.add(c.head);
} else {
result.add(c.head);
}
commonBinding = currentBinding;
commonNestedExpression = currentNestedExpression;
commonNestedBinding = currentNestedBinding;
}
}
resolveAccummulator.resolve(commonBinding, commonNestedExpression, commonNestedBinding);
return result.toList();
}
private Type principalType(JCTree p) {
return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p)));
}
@ -787,11 +991,18 @@ public class TransPatterns extends TreeTranslator {
@Override
public void visitMethodDef(JCMethodDecl tree) {
MethodSymbol prevMethodSym = currentMethodSym;
int prevVariableIndex = variableIndex;
Set<JCMethodInvocation> prevDeconstructorCalls = deconstructorCalls;
try {
currentMethodSym = tree.sym;
variableIndex = 0;
deconstructorCalls = null;
super.visitMethodDef(tree);
preparePatternMatchingCatchIfNeeded(tree.body);
} finally {
variableIndex = prevVariableIndex;
currentMethodSym = prevMethodSym;
deconstructorCalls = prevDeconstructorCalls;
}
}
@ -833,21 +1044,38 @@ public class TransPatterns extends TreeTranslator {
}
};
MethodSymbol oldMethodSym = currentMethodSym;
int prevVariableIndex = variableIndex;
try {
if (currentMethodSym == null) {
// Block is a static or instance initializer.
currentMethodSym =
new MethodSymbol(tree.flags | Flags.BLOCK,
names.empty, null,
currentClass);
}
for (List<JCStatement> l = tree.stats; l.nonEmpty(); l = l.tail) {
statements.append(translate(l.head));
boolean isInit = currentMethodSym == null;
Set<JCMethodInvocation> prevDeconstructorCalls = deconstructorCalls;
try {
if (isInit) {
// Block is a static or instance initializer.
currentMethodSym =
new MethodSymbol(tree.flags | Flags.BLOCK,
names.empty, null,
currentClass);
variableIndex = 0;
deconstructorCalls = null;
}
for (List<JCStatement> l = tree.stats; l.nonEmpty(); l = l.tail) {
statements.append(translate(l.head));
}
if (isInit) {
preparePatternMatchingCatchIfNeeded(tree);
}
} finally {
if (isInit) {
deconstructorCalls = prevDeconstructorCalls;
}
}
tree.stats = statements.toList();
result = tree;
} finally {
variableIndex = prevVariableIndex;
currentMethodSym = oldMethodSym;
bindingContext.pop();
}
@ -856,10 +1084,31 @@ public class TransPatterns extends TreeTranslator {
@Override
public void visitLambda(JCLambda tree) {
BindingContext prevContent = bindingContext;
int prevVariableIndex = variableIndex;
try {
bindingContext = new BindingDeclarationFenceBindingContext();
super.visitLambda(tree);
variableIndex = 0;
tree.params = translate(tree.params);
Set<JCMethodInvocation> prevDeconstructorCalls = deconstructorCalls;
try {
deconstructorCalls = null;
tree.body = translate(tree.body);
if (deconstructorCalls != null) {
if (tree.body instanceof JCExpression value) {
tree.body = make.Block(0, List.of(make.Return(value)));
}
if (tree.body instanceof JCBlock block) {
preparePatternMatchingCatchIfNeeded(block);
} else {
throw Assert.error("Unexpected lambda body type: " + tree.body.getKind());
}
}
} finally {
deconstructorCalls = prevDeconstructorCalls;
}
result = tree;
} finally {
variableIndex = prevVariableIndex;
bindingContext = prevContent;
}
}
@ -890,6 +1139,7 @@ public class TransPatterns extends TreeTranslator {
public void visitVarDef(JCVariableDecl tree) {
MethodSymbol prevMethodSym = currentMethodSym;
int prevVariableIndex = variableIndex;
try {
tree.mods = translate(tree.mods);
tree.vartype = translate(tree.vartype);
@ -899,14 +1149,53 @@ public class TransPatterns extends TreeTranslator {
new MethodSymbol((tree.mods.flags&Flags.STATIC) | Flags.BLOCK,
names.empty, null,
currentClass);
variableIndex = 0;
}
if (tree.init != null) tree.init = translate(tree.init);
result = tree;
} finally {
variableIndex = prevVariableIndex;
currentMethodSym = prevMethodSym;
}
}
@Override
public void visitTry(JCTry tree) {
tree.resources = translate(tree.resources);
Set<JCMethodInvocation> prevDeconstructorCalls = deconstructorCalls;
try {
deconstructorCalls = null;
tree.body = translate(tree.body);
preparePatternMatchingCatchIfNeeded(tree.body);
} finally {
deconstructorCalls = prevDeconstructorCalls;
}
tree.catchers = translateCatchers(tree.catchers);
tree.finalizer = translate(tree.finalizer);
result = tree;
}
private void preparePatternMatchingCatchIfNeeded(JCBlock tree) {
if (deconstructorCalls != null) {
VarSymbol ctch = new VarSymbol(Flags.SYNTHETIC,
names.fromString("catch" + variableIndex++ + target.syntheticNameChar()),
syms.throwableType,
currentMethodSym);
JCCatch patternMatchingCatch =
make.Catch(make.VarDef(ctch, null),
make.Block(0,
List.of(make.Throw(makeNewClass(syms.matchExceptionType,
List.of(makeApply(make.Ident(ctch),
names.toString,
List.nil()),
make.Ident(ctch)))))));
tree.patternMatchingCatch =
new PatternMatchingCatch(patternMatchingCatch, deconstructorCalls);
deconstructorCalls = null;
}
}
public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
try {
this.make = make;
@ -956,11 +1245,27 @@ public class TransPatterns extends TreeTranslator {
}
JCExpression convert(JCExpression expr, Type target) {
if (types.isSubtype(expr.type, target)) {
//cast not needed
return expr;
}
JCExpression result = make.at(expr.pos()).TypeCast(make.Type(target), expr);
result.type = target;
return result;
}
JCExpression mergeConditions(JCExpression left, JCExpression right) {
if (left instanceof JCBinary lastBinary) {
while (lastBinary.rhs instanceof JCBinary nextBinary) {
lastBinary = nextBinary;
}
lastBinary.rhs = makeBinary(Tag.AND, lastBinary.rhs, right);
return left;
} else {
return makeBinary(Tag.AND, left, right);
}
}
abstract class BindingContext {
abstract VarSymbol bindingDeclared(BindingSymbol varSymbol);
abstract VarSymbol getBindingFor(BindingSymbol varSymbol);
@ -984,7 +1289,7 @@ public class TransPatterns extends TreeTranslator {
VarSymbol bindingDeclared(BindingSymbol varSymbol) {
VarSymbol res = parent.bindingDeclared(varSymbol);
if (res == null) {
res = new VarSymbol(varSymbol.flags(), varSymbol.name, varSymbol.type, currentMethodSym);
res = new VarSymbol(varSymbol.flags() & ~Flags.MATCH_BINDING, varSymbol.name, varSymbol.type, currentMethodSym);
res.setTypeAttributes(varSymbol.getRawTypeAttributes());
hoistedVarMap.put(varSymbol, res);
}
@ -1086,4 +1391,19 @@ public class TransPatterns extends TreeTranslator {
JCExpression makeNull() {
return makeLit(syms.botType, null);
}
private class ReplaceVar extends TreeScanner {
private final Map<Symbol, Symbol> fromTo;
public ReplaceVar(Map<Symbol, Symbol> fromTo) {
this.fromTo = fromTo;
}
@Override
public void visitIdent(JCIdent tree) {
tree.sym = fromTo.getOrDefault(tree.sym, tree.sym);
super.visitIdent(tree);
}
}
}

View File

@ -26,6 +26,7 @@
package com.sun.tools.javac.comp;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
@ -44,6 +45,7 @@ import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCConditional;
import com.sun.tools.javac.tree.JCTree.JCConstantCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCContinue;
import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
@ -69,6 +71,7 @@ import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCOpens;
import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
import com.sun.tools.javac.tree.JCTree.JCPatternCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCProvides;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
@ -297,6 +300,18 @@ public class TreeDiffer extends TreeScanner {
result = scan(tree.labels, that.labels) && scan(tree.stats, that.stats);
}
@Override
public void visitConstantCaseLabel(JCConstantCaseLabel tree) {
JCConstantCaseLabel that = (JCConstantCaseLabel) parameter;
result = scan(tree.expr, that.expr);
}
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
JCPatternCaseLabel that = (JCPatternCaseLabel) parameter;
result = scan(tree.pat, that.pat) && scan(tree.guard, that.guard);
}
@Override
public void visitDefaultCaseLabel(JCDefaultCaseLabel tree) {
result = true;

View File

@ -27,6 +27,7 @@ package com.sun.tools.javac.jvm;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
import com.sun.tools.javac.tree.TreeInfo.PosKind;
@ -170,6 +171,8 @@ public class Gen extends JCTree.Visitor {
Chain switchExpressionFalseChain;
List<LocalItem> stackBeforeSwitchExpression;
LocalItem switchResult;
Set<JCMethodInvocation> invocationsWithPatternMatchingCatch = Set.of();
ListBuffer<int[]> patternMatchingInvocationRanges;
/** Generate code to load an integer constant.
* @param n The integer to be loaded.
@ -1049,6 +1052,29 @@ public class Gen extends JCTree.Visitor {
}
public void visitBlock(JCBlock tree) {
if (tree.patternMatchingCatch != null) {
Set<JCMethodInvocation> prevInvocationsWithPatternMatchingCatch = invocationsWithPatternMatchingCatch;
ListBuffer<int[]> prevRanges = patternMatchingInvocationRanges;
State startState = code.state.dup();
try {
invocationsWithPatternMatchingCatch = tree.patternMatchingCatch.calls2Handle();
patternMatchingInvocationRanges = new ListBuffer<>();
doVisitBlock(tree);
} finally {
Chain skipCatch = code.branch(goto_);
JCCatch handler = tree.patternMatchingCatch.handler();
code.entryPoint(startState, handler.param.sym.type);
genPatternMatchingCatch(handler, env, patternMatchingInvocationRanges.toList());
code.resolve(skipCatch);
invocationsWithPatternMatchingCatch = prevInvocationsWithPatternMatchingCatch;
patternMatchingInvocationRanges = prevRanges;
}
} else {
doVisitBlock(tree);
}
}
private void doVisitBlock(JCBlock tree) {
int limit = code.nextreg;
Env<GenContext> localEnv = env.dup(tree, new GenContext());
genStats(tree.stats, localEnv);
@ -1611,18 +1637,33 @@ public class Gen extends JCTree.Visitor {
}
}
}
VarSymbol exparam = tree.param.sym;
code.statBegin(tree.pos);
code.markStatBegin();
int limit = code.nextreg;
code.newLocal(exparam);
items.makeLocalItem(exparam).store();
code.statBegin(TreeInfo.firstStatPos(tree.body));
genStat(tree.body, env, CRT_BLOCK);
code.endScopes(limit);
code.statBegin(TreeInfo.endPos(tree.body));
genCatchBlock(tree, env);
}
}
void genPatternMatchingCatch(JCCatch tree,
Env<GenContext> env,
List<int[]> ranges) {
for (int[] range : ranges) {
JCExpression subCatch = tree.param.vartype;
int catchType = makeRef(tree.pos(), subCatch.type);
registerCatch(tree.pos(),
range[0], range[1], code.curCP(),
catchType);
}
genCatchBlock(tree, env);
}
void genCatchBlock(JCCatch tree, Env<GenContext> env) {
VarSymbol exparam = tree.param.sym;
code.statBegin(tree.pos);
code.markStatBegin();
int limit = code.nextreg;
code.newLocal(exparam);
items.makeLocalItem(exparam).store();
code.statBegin(TreeInfo.firstStatPos(tree.body));
genStat(tree.body, env, CRT_BLOCK);
code.endScopes(limit);
code.statBegin(TreeInfo.endPos(tree.body));
}
// where
List<Pair<List<Attribute.TypeCompound>, JCExpression>> catchTypesWithAnnotations(JCCatch tree) {
return TreeInfo.isMultiCatch(tree) ?
@ -1839,7 +1880,13 @@ public class Gen extends JCTree.Visitor {
if (!msym.isDynamic()) {
code.statBegin(tree.pos);
}
result = m.invoke();
if (invocationsWithPatternMatchingCatch.contains(tree)) {
int start = code.curCP();
result = m.invoke();
patternMatchingInvocationRanges.add(new int[] {start, code.curCP()});
} else {
result = m.invoke();
}
}
public void visitConditional(JCConditional tree) {

View File

@ -89,11 +89,15 @@ import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.main.Option.*;
import com.sun.tools.javac.tree.JCTree.JCBindingPattern;
import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*;
import static javax.tools.StandardLocation.CLASS_OUTPUT;
import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
/** This class could be the main entry point for GJC when GJC is used as a
* component in a larger software system. It provides operations to
@ -1465,6 +1469,7 @@ public class JavaCompiler {
class ScanNested extends TreeScanner {
Set<Env<AttrContext>> dependencies = new LinkedHashSet<>();
protected boolean hasLambdas;
protected boolean hasPatterns;
@Override
public void visitClassDef(JCClassDecl node) {
Type st = types.supertype(node.sym.type);
@ -1475,16 +1480,19 @@ public class JavaCompiler {
if (stEnv != null && env != stEnv) {
if (dependencies.add(stEnv)) {
boolean prevHasLambdas = hasLambdas;
boolean prevHasPatterns = hasPatterns;
try {
scan(stEnv.tree);
} finally {
/*
* ignore any updates to hasLambdas made during
* the nested scan, this ensures an initialized
* LambdaToMethod is available only to those
* classes that contain lambdas
* ignore any updates to hasLambdas and hasPatterns
* made during the nested scan, this ensures an
* initialized LambdaToMethod or TransPatterns is
* available only to those classes that contain
* lambdas or patterns, respectivelly
*/
hasLambdas = prevHasLambdas;
hasPatterns = prevHasPatterns;
}
}
envForSuperTypeFound = true;
@ -1503,6 +1511,31 @@ public class JavaCompiler {
hasLambdas = true;
super.visitReference(tree);
}
@Override
public void visitBindingPattern(JCBindingPattern tree) {
hasPatterns = true;
super.visitBindingPattern(tree);
}
@Override
public void visitRecordPattern(JCRecordPattern that) {
hasPatterns = true;
super.visitRecordPattern(that);
}
@Override
public void visitParenthesizedPattern(JCTree.JCParenthesizedPattern tree) {
hasPatterns = true;
super.visitParenthesizedPattern(tree);
}
@Override
public void visitSwitch(JCSwitch tree) {
hasPatterns |= tree.patternSwitch;
super.visitSwitch(tree);
}
@Override
public void visitSwitchExpression(JCSwitchExpression tree) {
hasPatterns |= tree.patternSwitch;
super.visitSwitchExpression(tree);
}
}
ScanNested scanner = new ScanNested();
scanner.scan(env.tree);
@ -1551,7 +1584,10 @@ public class JavaCompiler {
if (shouldStop(CompileState.TRANSPATTERNS))
return;
env.tree = TransPatterns.instance(context).translateTopLevelClass(env, env.tree, localMake);
if (scanner.hasPatterns) {
env.tree = TransPatterns.instance(context).translateTopLevelClass(env, env.tree, localMake);
}
compileStates.put(env, CompileState.TRANSPATTERNS);
if (scanner.hasLambdas) {

View File

@ -1074,6 +1074,12 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public List<JCStatement> stats;
/** Position of closing brace, optional. */
public int endpos = Position.NOPOS;
/** If this block contains record pattern, it is necessary to catch
* exceptions from the deconstructors and wrap them.
* The {@code patternMatchingCatch} keeps the list of the deconstructor
* invocations, and the additional catch block that wraps the exceptions.
*/
public PatternMatchingCatch patternMatchingCatch;
protected JCBlock(long flags, List<JCStatement> stats) {
this.stats = stats;
this.flags = flags;
@ -1098,6 +1104,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public Tag getTag() {
return BLOCK;
}
public record PatternMatchingCatch(JCCatch handler, Set<JCMethodInvocation> calls2Handle) {}
}
/**
@ -2218,6 +2226,9 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public static class JCInstanceOf extends JCExpression implements InstanceOfTree {
public JCExpression expr;
public JCTree pattern;
/**{@code true} if this instanceof test should have
* value {@code true} when the {@code expr} is {@code null}.*/
public boolean allowNull;
protected JCInstanceOf(JCExpression expr, JCTree pattern) {
this.expr = expr;
this.pattern = pattern;

View File

@ -838,6 +838,15 @@ public class TreeInfo {
return tree;
}
/** Skip parens and return the enclosed expression
*/
public static JCPattern skipParens(JCPattern tree) {
while (tree.hasTag(PARENTHESIZEDPATTERN)) {
tree = ((JCParenthesizedPattern) tree).pattern;
}
return tree;
}
/** Return the types of a list of trees.
*/
public static List<Type> types(List<? extends JCTree> trees) {

View File

@ -209,27 +209,27 @@ public class Patterns {
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=251, length=11, index=2}
0: #_A_(): LOCAL_VARIABLE, {start_pc=316, length=11, index=2}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=290, length=11, index=3}
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=359, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
2: #_A_(): LOCAL_VARIABLE, {start_pc=26, length=11, index=1}
2: #_A_(): LOCAL_VARIABLE, {start_pc=30, length=11, index=1}
Patterns$DeconstructionPattern$A
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=63, length=11, index=1}
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=71, length=11, index=1}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
4: #_A_(): LOCAL_VARIABLE, {start_pc=101, length=11, index=2}
4: #_A_(): LOCAL_VARIABLE, {start_pc=114, length=11, index=2}
Patterns$DeconstructionPattern$A
5: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=140, length=11, index=3}
5: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=157, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
6: #_A_(): LOCAL_VARIABLE, {start_pc=176, length=11, index=2}
6: #_A_(): LOCAL_VARIABLE, {start_pc=215, length=11, index=2}
Patterns$DeconstructionPattern$A
7: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=215, length=11, index=3}
7: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=258, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
@ -238,30 +238,26 @@ public class Patterns {
descriptor: ()V
flags: (0x0000)
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=23, length=11, index=2}
0: #_A_(): LOCAL_VARIABLE, {start_pc=28, length=11, index=2}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=62, length=11, index=3}
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=71, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
private static java.lang.String $proxy$s(Patterns$DeconstructionPattern$R);
descriptor: (LPatterns$DeconstructionPattern$R;)Ljava/lang/String;
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=26, length=11, index=0}
0: #_A_(): LOCAL_VARIABLE, {start_pc=28, length=11, index=0}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=62, length=11, index=0}
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=66, length=11, index=0}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
2: #_A_(): LOCAL_VARIABLE, {start_pc=98, length=11, index=1}
2: #_A_(): LOCAL_VARIABLE, {start_pc=106, length=11, index=1}
Patterns$DeconstructionPattern$A
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=134, length=11, index=2}
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=147, length=11, index=2}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8291769
* @summary Verify more complex switches work properly
* @compile --enable-preview -source ${jdk.version} DeconstructionDesugaring.java
* @run main/othervm --enable-preview DeconstructionDesugaring
*/
import java.util.function.ToIntFunction;
public class DeconstructionDesugaring {
public static void main(String... args) throws Throwable {
new DeconstructionDesugaring().test();
}
private void test() {
test(this::runCheckStatement);
test(this::runCheckExpression);
assertEquals(runCheckExpressionWithUnconditional(new R5(new R4(new Sub3()))), 3);
assertEquals(runCheckExpressionWithUnconditional(new R5(new R4(null))), 3);
assertEquals(runCheckExpressionWithUnconditional1(new R5(new R4(null))), 2);
assertEquals(runCheckExpressionWithUnconditional1(new R5(null)), 3);
}
private void test(ToIntFunction<Object> task) {
assertEquals(1, task.applyAsInt(new R1(new R2(""))));
assertEquals(2, task.applyAsInt(new R1(new R2(1))));
assertEquals(3, task.applyAsInt(new R1(new R2(1.0))));
assertEquals(-1, task.applyAsInt(new R1(new R2(null))));
assertEquals(4, task.applyAsInt(new R1(new R2(new StringBuilder()))));
assertEquals(5, task.applyAsInt(new R1(new R3(""))));
assertEquals(6, task.applyAsInt(new R1(new R3(1))));
assertEquals(7, task.applyAsInt(new R1(new R3(1.0))));
assertEquals(8, task.applyAsInt(new R1(new R3(new StringBuilder()))));
assertEquals(-1, task.applyAsInt(new R1(1.0f)));
assertEquals(-1, task.applyAsInt("foo"));
}
private int runCheckStatement(Object o) {
switch (o) {
case (((R1((((R2((((String s))))))))))) -> { return 1; }
case R1(R2(Integer i)) -> { return 2; }
case R1(R2(Double d)) -> { return 3; }
case R1(R2(CharSequence cs)) -> { return 4; }
case R1(R3(String s)) -> { return 5; }
case R1(R3(Integer i)) -> { return 6; }
case R1(R3(Double f)) -> { return 7; }
case R1(R3(CharSequence cs)) -> { return 8; }
default -> { return -1; }
}
}
private int runCheckExpression(Object o) {
return switch (o) {
case (((R1((((R2((((String s))))))))))) -> 1;
case R1(R2(Integer i)) -> 2;
case R1(R2(Double d)) -> 3;
case R1(R2(CharSequence cs)) -> 4;
case R1(R3(String s)) -> 5;
case R1(R3(Integer i)) -> 6;
case R1(R3(Double f)) -> 7;
case R1(R3(CharSequence cs)) -> 8;
default -> -1;
};
}
private int runCheckExpressionWithUnconditional(R5 o) {
return switch (o) {
case R5(R4(Sub1 s)) -> 1;
case R5(R4(Sub2 s)) -> 2;
case R5(R4(Super s)) -> 3;
};
}
private int runCheckExpressionWithUnconditional1(R5 o) {
return switch (o) {
case R5(R4(Sub1 s)) -> 1;
case R5(R4(Super s)) -> 2;
case R5(Object obj) -> 3;
};
}
private void assertEquals(int expected, int actual) {
if (expected != actual) {
throw new AssertionError("expected: " + expected + ", " +
"actual: " + actual);
}
}
record R1(Object o) {}
record R2(Object o) {}
record R3(Object o) {}
sealed class Super permits Sub1, Sub2, Sub3 {}
final class Sub1 extends Super {}
final class Sub2 extends Super {}
final class Sub3 extends Super {}
record R4(Super o) {}
record R5(R4 o) {}
}

View File

@ -55,6 +55,7 @@ public class Guards {
assertEquals("one", convert.apply(1));
assertEquals("other", convert.apply(-1));
assertEquals("box with empty", convert.apply(new Box("")));
assertEquals("box with non-empty", convert.apply(new Box("a")));
assertEquals("any", convert.apply(""));
}
@ -70,6 +71,7 @@ public class Guards {
case Integer i when i == 1: return "one";
case Integer i: return "other";
case Box(String s) when s.isEmpty(): return "box with empty";
case Box(String s) : return "box with non-empty";
case Object x: return "any";
}
}
@ -80,6 +82,7 @@ public class Guards {
case Integer i when i == 1 -> { yield "one"; }
case Integer i -> "other";
case Box(String s) when s.isEmpty() -> "box with empty";
case Box(String s) -> "box with non-empty";
case Object x -> "any";
};
}
@ -91,6 +94,7 @@ public class Guards {
case Integer i when i == 1 -> { x = "one"; yield true; }
case Integer i -> { x = "other"; yield true; }
case Box(String s) when s.isEmpty() -> {x = "box with empty"; yield true; }
case Box(String s) -> {x = "box with non-empty"; yield true; }
case Object other -> (x = "any") != null;
}) {
return x;

View File

@ -0,0 +1,201 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8291769
* @summary Verify the compiled code does not have unwanted constructs.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* jdk.jdeps/com.sun.tools.javap
* @build toolbox.ToolBox toolbox.JavacTask
* @run main PatternDesugaring
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Consumer;
import toolbox.TestRunner;
import toolbox.JavacTask;
import toolbox.JavapTask;
import toolbox.Task;
import toolbox.ToolBox;
public class PatternDesugaring extends TestRunner {
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
ToolBox tb;
public static void main(String... args) throws Exception {
new PatternDesugaring().runTests();
}
PatternDesugaring() {
super(System.err);
tb = new ToolBox();
}
public void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
@Test
public void testPrimitiveNoBoxUnbox(Path base) throws Exception {
doTest(base,
new String[0],
"""
package test;
public class Test {
public int test(Object obj) {
return switch (obj) {
case R(int i) -> i;
default -> -1;
};
}
record R(int i) {}
}
""",
decompiled -> {
if (decompiled.contains("intValue") || decompiled.contains("valueOf")) {
throw new AssertionError("Has boxing/unboxing.");
}
});
doTest(base,
new String[0],
"""
package test;
public class Test {
public int test(Object obj) {
return obj instanceof R(int i) ? i : -1;
}
record R(int i) {}
}
""",
decompiled -> {
if (decompiled.contains("intValue") || decompiled.contains("valueOf")) {
throw new AssertionError("Has boxing/unboxing.");
}
});
}
@Test
public void testCacheRecordsForRecordPatterns(Path base) throws Exception {
doTest(base,
new String[0],
"""
package test;
public class Test {
public int test(Object obj) {
return switch (obj) {
case R(Integer i, Integer j, Integer k) -> i + j + k;
default -> -1;
};
}
record R(Integer i, Integer j, Integer k) {}
}
""",
decompiled -> {
if (decompiled.split("checkcast").length != 2) {
throw new AssertionError("Unexpected number of checkcasts.");
}
});
doTest(base,
new String[0],
"""
package test;
public class Test {
public int test(Object obj) {
return obj instanceof R(Integer i, Integer j, Integer k) ? i + j + k: -1;
}
record R(Integer i, Integer j, Integer k) {}
}
""",
decompiled -> {
if (decompiled.split("checkcast").length != 2) {
throw new AssertionError("Unexpected number of checkcasts.");
}
});
}
private void doTest(Path base, String[] libraryCode, String testCode, Consumer<String> validate) throws IOException {
Path current = base.resolve(".");
Path libClasses = current.resolve("libClasses");
Files.createDirectories(libClasses);
if (libraryCode.length != 0) {
Path libSrc = current.resolve("lib-src");
for (String code : libraryCode) {
tb.writeJavaFiles(libSrc, code);
}
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION)
.outdir(libClasses)
.files(tb.findJavaFiles(libSrc))
.run();
}
Path src = current.resolve("src");
tb.writeJavaFiles(src, testCode);
Path classes = current.resolve("libClasses");
Files.createDirectories(libClasses);
var log =
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"--class-path", libClasses.toString(),
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(tb.findJavaFiles(src))
.run(Task.Expect.SUCCESS)
.writeAll();
var decompiled =
new JavapTask(tb)
.classpath(classes.toString())
.classes("test.Test")
.options("-s", "-verbose")
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
System.err.println("decompiled: " + decompiled);
validate.accept(decompiled);
}
}

View File

@ -92,6 +92,10 @@ public class Switches {
assertEquals("a", deconstructExpression(new R("a")));
assertEquals("1", deconstructExpression(new R(1)));
assertEquals("other", deconstructExpression(""));
assertEquals("a", translationTest("a"));
assertEquals("Rb", translationTest(new R("b")));
assertEquals("R2c", translationTest(new R2("c")));
assertEquals("other", translationTest(0));
assertEquals("OK", totalPatternAndNull(Integer.valueOf(42)));
assertEquals("OK", totalPatternAndNull(null));
assertEquals("1", nullAfterTotal(Integer.valueOf(42)));
@ -99,6 +103,8 @@ public class Switches {
emptyFallThrough(1);
emptyFallThrough("");
emptyFallThrough(1.0);
testSimpleSwitch();
testSimpleSwitchExpression();
}
void run(Function<Object, Integer> mapper) {
@ -636,6 +642,15 @@ public class Switches {
};
}
String translationTest(Object o) {
return switch (o) {
case R(String s) -> "R" + s;
case String s -> s;
case R2(String s) -> "R2" + s;
default -> "other";
};
}
String totalPatternAndNull(Integer in) {
return switch (in) {
case -1: { yield "";}
@ -659,6 +674,23 @@ public class Switches {
}
}
void testSimpleSwitch() {
Object o = "";
int res;
switch (o) {
default -> res = 1;
};
assertEquals(1, res);
}
void testSimpleSwitchExpression() {
Object o = "";
int res = switch (o) {
default -> 1;
};
assertEquals(1, res);
}
//verify that for cases like:
//case ConstantClassClash ->
//ConstantClassClash is interpreted as a field, not as a class
@ -700,4 +732,5 @@ public class Switches {
}
record R(Object o) {}
record R2(Object o) {}
}

View File

@ -0,0 +1,331 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8291769
* @summary Check expected translation of various pattern related constructs
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.comp
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.tree
* jdk.compiler/com.sun.tools.javac.util
* @build toolbox.ToolBox toolbox.JavacTask
* @run main TranslationTest
*/
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.TransPatterns;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Context.Factory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import toolbox.TestRunner;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.ToolBox;
public class TranslationTest extends TestRunner {
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
ToolBox tb;
public static void main(String... args) throws Exception {
new TranslationTest().runTests();
}
TranslationTest() {
super(System.err);
tb = new ToolBox();
}
public void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
@Test
public void testSimple(Path base) throws Exception {
doTest(base,
new String[]{"""
package lib;
public record Box(Object o) {}
"""},
"""
package test;
import lib.*;
public class Test {
private int test(Object obj) {
return switch (obj) {
case Box(String s) -> 0;
case Box(Integer i) -> 0;
case Box(Number n) -> 0;
case Box(CharSequence cs) -> 0;
default -> -1;
};
}
}
""",
toplevel -> printSwitchStructure(toplevel),
"""
switch
case
switch
case
case
case
case
case ..., default
default
""");
}
@Test
public void testMultiComponent(Path base) throws Exception {
doTest(base,
new String[]{"""
package lib;
public record Pair(Object o1, Object o2) {}
""",
"""
package lib;
public record Triplet(Object o1, Object o2, Object o3) {}
"""},
"""
package test;
import lib.*;
public class Test {
private int test(Object obj) {
return switch (obj) {
case Pair(String c1, Pair(String o2, String s2)) -> 0;
case Pair(String c1, Pair(String o2, Integer s2)) -> 0;
case Pair(String c1, Pair(Integer o2, String s2)) -> 0;
case Pair(String c1, Pair(Integer o2, Integer s2)) -> 0;
case Pair(Integer c1, Pair(String o2, String s2)) -> 0;
case Pair(Integer c1, Pair(String o2, Integer s2)) -> 0;
case Pair(Integer c1, Pair(Integer o2, String s2)) -> 0;
case Pair(Integer c1, Pair(Integer o2, Integer s2)) -> 0;
default -> -1;
};
}
}
""",
toplevel -> printSwitchStructure(toplevel),
"""
switch
case
switch
case
switch
case
switch
case
switch
case
case
case ..., default
case
switch
case
case
case ..., default
case ..., default
case ..., default
case
switch
case
switch
case
switch
case
case
case ..., default
case
switch
case
case
case ..., default
case ..., default
case ..., default
case ..., default
default
""");
}
private void doTest(Path base, String[] libraryCode, String testCode,
Callback callback, String expectedOutput) throws IOException {
Path current = base.resolve(".");
Path libClasses = current.resolve("libClasses");
Files.createDirectories(libClasses);
if (libraryCode.length != 0) {
Path libSrc = current.resolve("lib-src");
for (String code : libraryCode) {
tb.writeJavaFiles(libSrc, code);
}
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION)
.outdir(libClasses)
.files(tb.findJavaFiles(libSrc))
.run();
}
Path src = current.resolve("src");
tb.writeJavaFiles(src, testCode);
Path classes = current.resolve("libClasses");
Files.createDirectories(libClasses);
List<String> output = new ArrayList<>();
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-Xlint:-preview",
"--class-path", libClasses.toString(),
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(tb.findJavaFiles(src))
.callback(task -> {
Context ctx = ((JavacTaskImpl) task).getContext();
TestTransPatterns.preRegister(ctx, callback, output);
})
.run()
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (output.size() != 1 || !expectedOutput.equals(output.get(0))) {
throw new AssertionError("Unexpected output:\n" + output);
}
}
private String printSwitchStructure(JCTree topLevel) {
StringBuilder structure = new StringBuilder();
new TreeScanner() {
private static final int INDENT = 4;
private int indent = 0;
@Override
public void visitSwitch(JCSwitch node) {
int prevIndent = indent;
appendLine("switch");
try {
indent += INDENT;
super.visitSwitch(node);
} finally {
indent = prevIndent;
}
}
@Override
public void visitSwitchExpression(JCSwitchExpression node) {
int prevIndent = indent;
appendLine("switch");
try {
indent += INDENT;
super.visitSwitchExpression(node);
} finally {
indent = prevIndent;
}
}
@Override
public void visitCase(JCCase node) {
int prevIndent = indent;
if (node.labels.size() == 1 && node.labels.head.hasTag(Tag.DEFAULTCASELABEL)) {
appendLine("default");
} else if (node.labels.stream().anyMatch(l -> l.hasTag(Tag.DEFAULTCASELABEL))) {
appendLine("case ..., default");
} else {
appendLine("case");
}
try {
indent += INDENT;
super.visitCase(node);
} finally {
indent = prevIndent;
}
}
private void appendLine(String what) {
for (int i = 0; i < indent; i++) {
structure.append(' ');
}
structure.append(what);
structure.append('\n');
}
}.scan(topLevel);
return structure.toString();
}
public interface Callback {
public String patternsTranslated(JCTree topLevel);
}
private static final class TestTransPatterns extends TransPatterns {
public static void preRegister(Context ctx, Callback validator, List<String> output) {
ctx.put(transPatternsKey, (Factory<TransPatterns>) c -> new TestTransPatterns(c, validator, output));
}
private final Callback callback;
private final List<String> output;
public TestTransPatterns(Context context, Callback callback, List<String> output) {
super(context);
this.callback = callback;
this.output = output;
}
@Override
public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
JCTree result = super.translateTopLevelClass(env, cdef, make);
output.add(callback.patternsTranslated(cdef));
return result;
}
}
}

View File

@ -28,6 +28,7 @@
import java.util.Objects;
import java.util.function.Function;
import java.util.function.ToIntFunction;
public class TypedDeconstructionPatternExc {
@ -38,6 +39,9 @@ public class TypedDeconstructionPatternExc {
void run() {
run(this::testExpr);
run(this::testExprCond);
testTryExpr();
run(this::testLambda);
runBoxed();
}
void run(Function<Pair<String, Integer>, Integer> tested) {
@ -82,6 +86,82 @@ public class TypedDeconstructionPatternExc {
}
}
void testTryExpr() {
TEST: {
try {
var v = switch ((Pair<String, Integer>) (Object) new Pair<Integer, Integer>(1, 1)) {
case Pair<String, Integer>(String s, Integer i) -> s.length() + i;
case Object o -> -1;
};
} catch (ClassCastException ex) {
//OK
break TEST;
} catch (Throwable t) {
t.printStackTrace();
fail("Unexpected Throwable!");
}
fail("ClassCastException not thrown!");
}
TEST: {
try {
var v = switch (new Pair<String, Integer>("fail", 1)) {
case Pair<String, Integer>(String s, Integer i) -> s.length() + i;
case Object o -> -1;
};
} catch (MatchException ex) {
//OK
break TEST;
} catch (Throwable t) {
t.printStackTrace();
fail("Unexpected Throwable!");
}
fail("MatchException not thrown!");
}
}
int testLambda(Pair<String, Integer> p) {
var r = prepareLambda();
return r.applyAsInt(p);
}
ToIntFunction<Pair<String, Integer>> prepareLambda() {
return p -> switch (p) {
case Pair<String, Integer>(String s, Integer i) -> s.length() + i;
case Object o -> -1;
};
}
void runBoxed() {
assertEquals(2, testBoxed(new Box(new Pair<>("1", 1))));
try {
testBoxed(new Box((Pair<String, Integer>) (Object) new Pair<Integer, Integer>(1, 1)));
fail("Expected an exception, but none happened!");
} catch (ClassCastException ex) {
System.err.println("expected exception:");
ex.printStackTrace();
}
try {
testBoxed(new Box(new Pair<String, Integer>("fail", 1)));
fail("Expected an exception, but none happened!");
} catch (MatchException ex) {
assertEquals(TestPatternFailed.class.getName() + ": " + EXCEPTION_MESSAGE,
ex.getMessage());
if (ex.getCause() instanceof TestPatternFailed ex2) {
System.err.println("expected exception:");
ex2.printStackTrace();
} else {
fail("Not the correct exception.");
}
}
}
int testBoxed(Object p) {
return switch (p) {
case Box(Pair<String, Integer>(String s, Integer i)) -> s.length() + i;
case Object o -> -1;
};
}
static final String EXCEPTION_MESSAGE = "exception-message";
record Pair<L, R>(L l, R r) {
@ -96,6 +176,8 @@ public class TypedDeconstructionPatternExc {
}
}
record Box(Pair<String, Integer> boxed) {}
void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + "," +