8291769: Translation of switch with record patterns could be improved
Reviewed-by: vromero
This commit is contained in:
parent
eab0ada3a1
commit
2300ed458d
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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) {}
|
||||
}
|
@ -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;
|
||||
|
201
test/langtools/tools/javac/patterns/PatternDesugaring.java
Normal file
201
test/langtools/tools/javac/patterns/PatternDesugaring.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
|
331
test/langtools/tools/javac/patterns/TranslationTest.java
Normal file
331
test/langtools/tools/javac/patterns/TranslationTest.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 + "," +
|
||||
|
Loading…
x
Reference in New Issue
Block a user