[java-highlighting] IDEA-354239 support exhaustiveness for record patterns with cross-section

GitOrigin-RevId: 3a5ce8c4aeb88270fc750f1b671d02e0e3a9b7a7
This commit is contained in:
Mikhail Pyltsin
2024-05-30 11:28:10 +02:00
committed by intellij-monorepo-bot
parent fd9c6b6773
commit 717a44934b
4 changed files with 241 additions and 46 deletions

View File

@@ -38,21 +38,25 @@ final class PatternHighlightingModel {
private static final int MAX_ITERATION_COVERAGE = 5_000;
private static final int MAX_GENERATED_PATTERN_NUMBER = 10;
static boolean createDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern, @NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
static boolean createDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (deconstructionPattern == null) return false;
PsiTypeElement typeElement = deconstructionPattern.getTypeElement();
PsiType recordType = typeElement.getType();
ClassResolveResult resolveResult = recordType instanceof PsiClassType classType ? classType.resolveGenerics() : ClassResolveResult.EMPTY;
ClassResolveResult resolveResult =
recordType instanceof PsiClassType classType ? classType.resolveGenerics() : ClassResolveResult.EMPTY;
PsiClass recordClass = resolveResult.getElement();
if (recordClass == null || !recordClass.isRecord()) {
String message = JavaErrorBundle.message("deconstruction.pattern.requires.record", JavaHighlightUtil.formatType(recordType));
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
errorSink.accept(info);
return true;
}
if (resolveResult.getInferenceError() != null) {
String message = JavaErrorBundle.message("error.cannot.infer.pattern.type", resolveResult.getInferenceError());
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
errorSink.accept(info);
return true;
}
@@ -153,12 +157,15 @@ final class PatternHighlightingModel {
* record SomeClass(RecordComponentType component)
* (a instanceof SomeClass(PatternType obj))
* </code></pre>
*
* @param recordComponentType the type of the record component
* @param patternType the type of the pattern
* @param languageLevel the language level to consider
* @param patternType the type of the pattern
* @param languageLevel the language level to consider
* @return true if the record component type is applicable for the pattern type, false otherwise
*/
private static boolean isApplicableForRecordComponent(@NotNull PsiType recordComponentType, @Nullable PsiType patternType, @NotNull LanguageLevel languageLevel) {
private static boolean isApplicableForRecordComponent(@NotNull PsiType recordComponentType,
@Nullable PsiType patternType,
@NotNull LanguageLevel languageLevel) {
if ((recordComponentType instanceof PsiPrimitiveType || patternType instanceof PsiPrimitiveType) &&
!JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(languageLevel)) {
return recordComponentType.equals(patternType);
@@ -168,9 +175,9 @@ final class PatternHighlightingModel {
@NotNull
private static HighlightInfo.Builder createIncorrectNumberOfNestedPatternsError(@NotNull PsiDeconstructionPattern deconstructionPattern,
PsiPattern @NotNull [] patternComponents,
PsiRecordComponent @NotNull [] recordComponents,
boolean needQuickFix) {
PsiPattern @NotNull [] patternComponents,
PsiRecordComponent @NotNull [] recordComponents,
boolean needQuickFix) {
assert patternComponents.length != recordComponents.length;
String message = JavaErrorBundle.message("incorrect.number.of.nested.patterns", recordComponents.length, patternComponents.length);
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).description(message).escapedToolTip(message);
@@ -178,8 +185,10 @@ final class PatternHighlightingModel {
if (needQuickFix) {
if (patternComponents.length < recordComponents.length) {
builder.range(deconstructionList);
PsiRecordComponent[] missingRecordComponents = Arrays.copyOfRange(recordComponents, patternComponents.length, recordComponents.length);
List<Pattern> missingPatterns = ContainerUtil.map(missingRecordComponents, component -> Pattern.create(component, deconstructionList));
PsiRecordComponent[] missingRecordComponents =
Arrays.copyOfRange(recordComponents, patternComponents.length, recordComponents.length);
List<Pattern> missingPatterns =
ContainerUtil.map(missingRecordComponents, component -> Pattern.create(component, deconstructionList));
ModCommandAction fix = new AddMissingDeconstructionComponentsFix(deconstructionList, missingPatterns);
builder.registerFix(fix, null, null, null, null);
}
@@ -273,9 +282,10 @@ final class PatternHighlightingModel {
Map<ReduceResultCacheContext, RecordExhaustivenessResult> cacheResult = ConcurrentFactoryMap.createMap(cacheContext -> {
Set<? extends PatternDescription> patterns = cacheContext.currentPatterns;
PsiType selectorType = cacheContext.mySelectorType;
HashMap<ReduceResultCacheContext, ReduceResult> cache = new HashMap<>();
ReduceCache cache = ReduceCache.init();
LoopReduceResult result = reduceInLoop(selectorType, context, new HashSet<>(patterns),
(descriptionPatterns, type) -> coverSelectorType(context, descriptionPatterns, selectorType), cache);
(descriptionPatterns, type) -> coverSelectorType(context, descriptionPatterns, selectorType),
cache, true);
if (result.stopped()) {
return RecordExhaustivenessResult.createExhaustiveResult();
}
@@ -291,21 +301,48 @@ final class PatternHighlightingModel {
}).get(new ReduceResultCacheContext(targetType, new HashSet<>(descriptions)));
}
private record ReduceCache(@NotNull Map<ReduceResultCacheContext, ReduceResult> loopReduceCache,
@NotNull Map<ReduceUnwrapContext, ReduceResult> unwrapCache,
@NotNull Map<PsiClass, Map<PsiClass, Boolean>> sealedPath){
static ReduceCache init() {
return new ReduceCache(new HashMap<>(), new HashMap<>(), new HashMap<>());
}
}
private record ReduceUnwrapContext(@NotNull Set<PatternDeconstructionDescription> currentPatterns) {
}
/**
* Reduces a set of patterns in a loop until a stopping condition is met.
*
* @param selectorType the type of the selector
* @param context the context element
* @param patterns the initial set of patterns
* @param stopAt a condition that determines if the reduction should stop
* @param cache the cache of reduce results
* @param tryToExpand a flag indicating whether to try to expand sealed types
* @return the result of the reduction loop
*/
@NotNull
static LoopReduceResult reduceInLoop(@NotNull PsiType selectorType,
@NotNull PsiElement context,
@NotNull Set<? extends PatternDescription> patterns,
@NotNull BiPredicate<Set<? extends PatternDescription>, PsiType> stopAt,
@NotNull Map<ReduceResultCacheContext, ReduceResult> cache) {
private static LoopReduceResult reduceInLoop(@NotNull PsiType selectorType,
@NotNull PsiElement context,
@NotNull Set<? extends PatternDescription> patterns,
@NotNull BiPredicate<Set<? extends PatternDescription>, PsiType> stopAt,
@NotNull ReduceCache cache, boolean tryToExpand) {
boolean changed = false;
int currentIteration = 0;
Set<? extends PatternDescription> currentPatterns = new HashSet<>(patterns);
boolean tryToExpandNext = tryToExpand;
while (currentIteration < MAX_ITERATION_COVERAGE) {
currentIteration++;
if (stopAt.test(currentPatterns, selectorType)) {
return new LoopReduceResult(currentPatterns, true, true);
}
ReduceResult reduceResult = reduce(selectorType, context, currentPatterns, cache);
if (tryToExpandNext && !reduceResult.changed() && reduceResult.patterns().size() > 1) {
reduceResult = unwrapSealedTypes(reduceResult.patterns(), cache);
tryToExpandNext = false;
}
changed |= reduceResult.changed();
currentPatterns = reduceResult.patterns();
if (!reduceResult.changed()) {
@@ -314,8 +351,8 @@ final class PatternHighlightingModel {
}
LOG.error("The number of iteration is exceeded, length of set patterns: " + patterns.size() +
"max length deconstruction: " + patterns.stream()
.filter(t->t instanceof PatternDeconstructionDescription)
.map(t->((PatternDeconstructionDescription)t).list.size())
.filter(t -> t instanceof PatternDeconstructionDescription)
.map(t -> ((PatternDeconstructionDescription)t).list.size())
.max(Comparator.naturalOrder())
);
return new LoopReduceResult(currentPatterns, changed, false);
@@ -337,6 +374,7 @@ final class PatternHighlightingModel {
}
return reducedToTypeTest;
}
@NotNull
static List<PatternTypeTestDescription> reduceToTypeTest(@NotNull List<? extends PatternDescription> elements,
@NotNull PsiElement context) {
@@ -346,7 +384,7 @@ final class PatternHighlightingModel {
if (element instanceof PatternTypeTestDescription typeTestDescription) {
reducedToTypeTest.add(typeTestDescription);
}
if(element instanceof PatternDeconstructionDescription deconstructionDescription) {
if (element instanceof PatternDeconstructionDescription deconstructionDescription) {
deconstructionDescriptions.add(deconstructionDescription);
}
}
@@ -374,7 +412,8 @@ final class PatternHighlightingModel {
@NotNull PsiType itemType,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (!TypeConversionUtil.areTypesConvertible(itemType, patternType) &&
(!IncompleteModelUtil.isIncompleteModel(pattern) || !IncompleteModelUtil.isPotentiallyConvertible(patternType, itemType, pattern))) {
(!IncompleteModelUtil.isIncompleteModel(pattern) ||
!IncompleteModelUtil.isPotentiallyConvertible(patternType, itemType, pattern))) {
errorSink.accept(HighlightUtil.createIncompatibleTypeHighlightInfo(itemType, patternType, pattern.getTextRange(), 0));
return;
}
@@ -388,7 +427,7 @@ final class PatternHighlightingModel {
}
static final class ReduceResultCacheContext {
private static final class ReduceResultCacheContext {
private final @NotNull PsiType mySelectorType;
private final @Nullable PsiClass myPsiClass;
private final @NotNull Set<? extends PatternDescription> currentPatterns;
@@ -453,28 +492,121 @@ final class PatternHighlightingModel {
private static ReduceResult reduce(@NotNull PsiType selectorType,
@NotNull PsiElement context,
@NotNull Set<? extends PatternDescription> currentPatterns,
@NotNull Map<ReduceResultCacheContext, ReduceResult> cache) {
@NotNull ReduceCache cache) {
currentPatterns = new HashSet<>(currentPatterns);
ReduceResultCacheContext cacheContext = new ReduceResultCacheContext(selectorType, currentPatterns);
ReduceResult result = cache.get(cacheContext);
ReduceResult result = cache.loopReduceCache().get(cacheContext);
if (result == null) {
result = new ReduceResult(currentPatterns, false)
.reduceRecordPatterns(context, cache)
.reduceDeconstructionRecordToTypePattern(context)
.reduceClasses(selectorType, context);
cache.put(cacheContext, result);
cache.loopReduceCache().put(cacheContext, result);
}
return result;
}
record LoopReduceResult(Set<? extends PatternDescription> patterns, boolean changed, boolean stopped){}
record ReduceResult(Set<? extends PatternDescription> patterns, boolean changed) {
/**
* Unwrap sealed types in the given ReduceResult.
* This method can expand a set of patterns drastically, that's why it is called only once and only for highlighting.
* Usually, it tries to expand for cases like in {@link ReduceResult#reduceRecordPatterns(PsiElement, ReduceCache)}
* The idea is to choose sealed types from q0,..,qn deconstruction components, find their inheritances and add patterns with
* (q0,..,inheritance,..,qn) components
* Inheritance must be only on a sealed path
* This behavior was changed in javac.
* According to jep, it is not clear what is the correct behaviour.
* @return a new ReduceResult with expanded sealed types
*/
@NotNull
private static ReduceResult unwrapSealedTypes(@NotNull Set<? extends PatternDescription> existedPatterns,
@NotNull ReduceCache cache) {
Set<PatternDescription> result = new HashSet<>(existedPatterns);
if (existedPatterns.isEmpty()) return new ReduceResult(result, false);
List<PatternDeconstructionDescription> deconstructionExistedPatterns =
ContainerUtil.filterIsInstance(existedPatterns, PatternDeconstructionDescription.class);
if (deconstructionExistedPatterns.isEmpty()) return new ReduceResult(result, false);
ReduceUnwrapContext cacheContext = new ReduceUnwrapContext(new HashSet<>(deconstructionExistedPatterns));
ReduceResult cachedResult = cache.unwrapCache().get(cacheContext);
if (cachedResult != null) {
return new ReduceResult(cachedResult.patterns(), cachedResult.patterns().size() != existedPatterns.size());
}
PatternDeconstructionDescription first = deconstructionExistedPatterns.get(0);
for (int i = 0; i < first.list().size(); i++) {
for (PatternDeconstructionDescription basePattern : deconstructionExistedPatterns) {
PatternDescription baseDescription = basePattern.list().get(i);
if (!(baseDescription instanceof PatternTypeTestDescription baseTypeDescription)) continue;
if (baseTypeDescription.myPsiClass == null) continue;
if (!PatternsInSwitchBlockHighlightingModel.isAbstractSealed(baseTypeDescription.myPsiClass)) continue;
for (PatternDeconstructionDescription comparedPattern : deconstructionExistedPatterns) {
if (comparedPattern == basePattern) continue;
PatternDescription comparedDescription = comparedPattern.list().get(i);
if (!(comparedDescription instanceof PatternTypeTestDescription comparedTypeDescription)) continue;
if (comparedTypeDescription.myPsiClass == null) continue;
if (baseTypeDescription.myPsiClass.getManager()
.areElementsEquivalent(baseTypeDescription.myPsiClass, comparedTypeDescription.myPsiClass)) {
continue;
}
if (!baseTypeDescription.type.isAssignableFrom(comparedTypeDescription.type)) continue;
if (!isDirectSealedPath(comparedTypeDescription.myPsiClass, baseTypeDescription.myPsiClass, cache, new HashSet<>())) {
continue;
}
result.addAll(createPatternsFrom(i, Set.of(comparedDescription), basePattern));
}
}
}
ReduceResult reduceResult = new ReduceResult(result, result.size() != existedPatterns.size());
cache.unwrapCache().put(cacheContext, reduceResult);
ReduceUnwrapContext newContext =
new ReduceUnwrapContext(StreamEx.of(reduceResult.patterns()).select(PatternDeconstructionDescription.class).toSet());
cache.unwrapCache().put(newContext, reduceResult);
return reduceResult;
}
private static boolean isDirectSealedPath(@Nullable PsiClass from, @Nullable PsiClass to, @NotNull ReduceCache cache, @NotNull Set<PsiClass> visited) {
if (from == null || to == null) return false;
Map<PsiClass, Boolean> toCache = cache.sealedPath().get(from);
if (toCache != null) {
Boolean isSealedPath = toCache.get(to);
if (isSealedPath != null) return isSealedPath;
}
if (!visited.add(from)) return false;
if (from.getManager().areElementsEquivalent(from, to)) {
boolean result = PatternsInSwitchBlockHighlightingModel.isAbstractSealed(to);
addToSealedCache(from, to, cache, result);
return result;
}
if (!from.isInheritor(to, true)) {
boolean result = false;
addToSealedCache(from, to, cache, result);
return result;
}
boolean result = ContainerUtil.exists(from.getSupers(),
superClass -> PatternsInSwitchBlockHighlightingModel.isAbstractSealed(superClass) &&
isDirectSealedPath(superClass, to, cache, visited));
addToSealedCache(from, to, cache, result);
return result;
}
private static void addToSealedCache(@Nullable PsiClass from,
@Nullable PsiClass to,
@NotNull PatternHighlightingModel.ReduceCache cache,
boolean result) {
cache.sealedPath().computeIfAbsent(from, k -> new HashMap<>()).put(to, result);
}
private record LoopReduceResult(@NotNull Set<? extends PatternDescription> patterns, boolean changed, boolean stopped) {
}
private record ReduceResult(@NotNull Set<? extends PatternDescription> patterns, boolean changed) {
/**
* Reduce i-component for a set of deconstruction patterns.
* Pattern(q0,...qi,.. qn)
* Pattern(q0,..,qi,.. qn)
* This method finds all patterns, when q0..qk..qn (k!=i) equal across all patterns and
* recursively call all reduction types for a set of qi components
* This way leads that the next case is NOT exhaustive:
* This way leads that the next case is not exhaustive, but it was changed in javac, and it must be exhausted:
* <pre>{@code
* sealed interface I permits C, D {}
* final class C implements I {}
@@ -487,11 +619,12 @@ final class PatternHighlightingModel {
* case Pair<I>(D fst, I snd) -> {}
* }
* }</pre>
* because there are no components with equal types.
* BUT if this method can't convert the result, {@link PatternHighlightingModel#unwrapSealedTypes(Set, ReduceCache)} will be called.
* see {@link PatternHighlightingModel#unwrapSealedTypes(Set, ReduceCache)}
* Also, see <a href="https://bugs.openjdk.org/browse/JDK-8311815">bug in OpenJDK</a>
*/
@NotNull
private ReduceResult reduceRecordPatterns(@NotNull PsiElement context, @NotNull Map<ReduceResultCacheContext, ReduceResult> cache) {
private ReduceResult reduceRecordPatterns(@NotNull PsiElement context, @NotNull ReduceCache cache) {
boolean changed = false;
Map<PsiType, Set<PatternDeconstructionDescription>> byType = StreamEx.of(patterns)
.select(PatternDeconstructionDescription.class)
@@ -509,8 +642,7 @@ final class PatternHighlightingModel {
MultiMap<List<PatternDescription>, PatternDeconstructionDescription> groupWithoutOneComponent =
getGroupWithoutOneComponent(descriptions, i);
for (Map.Entry<List<PatternDescription>, Collection<PatternDeconstructionDescription>> value :
groupWithoutOneComponent.entrySet()) {
for (Map.Entry<List<PatternDescription>, Collection<PatternDeconstructionDescription>> value : groupWithoutOneComponent.entrySet()) {
Collection<PatternDeconstructionDescription> setWithOneDifferentElement = value.getValue();
if (setWithOneDifferentElement.isEmpty()) {
continue;
@@ -528,7 +660,7 @@ final class PatternHighlightingModel {
if (componentTypes == null || componentTypes.size() <= i) {
continue;
}
LoopReduceResult result = reduceInLoop(componentTypes.get(i), context, nestedDescriptions, (set, type) -> false, cache);
LoopReduceResult result = reduceInLoop(componentTypes.get(i), context, nestedDescriptions, (set, type) -> false, cache, true);
if (result.changed()) {
changed = true;
toRemove.addAll(setWithOneDifferentElement);
@@ -603,7 +735,7 @@ final class PatternHighlightingModel {
/**
* Try to reduce sealed classes to their supertypes or if selectorType is covered any of types,then return selectorType.
* Previous sealed classes are not excluded because they can be used in another combination.
* This method uses {@link PatternsInSwitchBlockHighlightingModel#findMissedClasses(PsiType, List, List, PsiElement) findMissedClassesForSealed}
* This method uses {@link PatternsInSwitchBlockHighlightingModel#findMissedClasses(PsiType, List, List, PsiElement) findMissedClasses}
* To prevent recursive calls, only TypeTest descriptions are passed to this method.
*/
@NotNull
@@ -623,10 +755,10 @@ final class PatternHighlightingModel {
}
private static @NotNull Collection<PatternDeconstructionDescription> createPatternsFrom(int differentElement,
@NotNull Set<? extends PatternDescription> nesterPatterns,
@NotNull Set<? extends PatternDescription> nestedPatterns,
@NotNull PatternDeconstructionDescription sample) {
HashSet<PatternDeconstructionDescription> descriptions = new HashSet<>();
for (PatternDescription nestedPattern : nesterPatterns) {
for (PatternDescription nestedPattern : nestedPatterns) {
descriptions.add(sample.createFor(differentElement, nestedPattern));
}
return descriptions;
@@ -719,7 +851,7 @@ final class PatternHighlightingModel {
private static List<? extends PatternDescription> findMissedRecordPatterns(@NotNull PsiType selectorType,
@NotNull Set<? extends PatternDescription> patterns,
@NotNull PsiElement context,
@NotNull Map<ReduceResultCacheContext, ReduceResult> cache) {
@NotNull ReduceCache cache) {
PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(selectorType);
if (selectorClass == null) {
return null;
@@ -816,7 +948,7 @@ final class PatternHighlightingModel {
missingRecordPatterns.addAll(missingRecordPatternsForThisIteration);
}
combinedPatterns.addAll(missingRecordPatternsForThisIteration);
LoopReduceResult reduceResult = reduceInLoop(selectorType, context, combinedPatterns, (set, type) -> false, cache);
LoopReduceResult reduceResult = reduceInLoop(selectorType, context, combinedPatterns, (set, type) -> false, cache, false);
//work only with reduced patterns to speed up
Set<? extends PatternDescription> newPatterns = new HashSet<>(reduceResult.patterns());
if (reduceResult.changed()) {
@@ -825,7 +957,7 @@ final class PatternHighlightingModel {
combinedPatterns.addAll(newPatterns);
}
}
LoopReduceResult reduceResult = reduceInLoop(selectorType, context, combinedPatterns, (set, type) -> false, cache);
LoopReduceResult reduceResult = reduceInLoop(selectorType, context, combinedPatterns, (set, type) -> false, cache, false);
return coverSelectorType(context, reduceResult.patterns(), selectorType) ? new ArrayList<>(missingRecordPatterns) : null;
}
@@ -840,8 +972,7 @@ final class PatternHighlightingModel {
@NotNull
private static MultiMap<List<PatternDescription>, PatternDeconstructionDescription> getGroupWithoutOneComponent(
@NotNull Set<? extends PatternDescription> combinedPatterns,
int i) {
@NotNull Set<? extends PatternDescription> combinedPatterns, int i) {
MultiMap<List<PatternDescription>, PatternDeconstructionDescription> groupWithoutOneComponent = new MultiMap<>();
for (PatternDescription description : combinedPatterns) {
if (!(description instanceof PatternDeconstructionDescription deconstructionDescription)) {

View File

@@ -233,7 +233,7 @@ class Basic {
}
//see com.intellij.codeInsight.daemon.impl.analysis.PatternHighlightingModel.reduceRecordPatterns
void exhaustinvenessWithInterface(Pair<I> pairI) {
switch (<error descr="'switch' statement does not cover all possible input values">pairI</error>) {
switch (pairI) {
case Pair<I>(C fst, D snd) -> {}
case Pair<I>(I fst, C snd) -> {}
case Pair<I>(D fst, I snd) -> {}
@@ -242,13 +242,53 @@ class Basic {
//see com.intellij.codeInsight.daemon.impl.analysis.PatternHighlightingModel.reduceRecordPatterns
void exhaustinvenessWithInterface2(Pair<? extends I> pairI) {
switch (<error descr="'switch' statement does not cover all possible input values">pairI</error>) {
switch (pairI) {
case Pair<? extends I>(C fst, D snd) -> {}
case Pair<? extends I>(I fst, C snd) -> {}
case Pair<? extends I>(D fst, I snd) -> {}
}
}
sealed interface A1 permits A11, A12 {}
sealed interface A2 permits A21, A22 {}
record A11() implements A1 {}
record A12() implements A1 {}
record A21() implements A2 { }
record A22() implements A2 { }
record R1(A2 a2) { }
record R2(A1 a1, R1 r1) { }
record R12(A1 a1, A2 a2) { }
record R21(A2 a2, A1 a1) { }
void exhaustivenessWithCrossSectionNestedRecords(R2 r2) {
switch (r2) {
case R2(A11 b1, R1(A2 b2)) -> System.out.println("1");
case R2(A12 b1, R1(A22 b2)) -> System.out.println("2");
case R2(A1 b1, R1(A21 b2)) -> System.out.println("3");
}
}
void exhaustivenessWithCrossSection1(R12 r12) {
switch (r12) {
case R12(A11 b1, A2 b2) -> System.out.println("1");
case R12(A12 b1, A22 b2) -> System.out.println("2");
case R12(A1 b1, A21 b2) -> System.out.println("3");
}
}
void exhaustivenessWithCrossSection2(R21 r21) {
switch (r21) {
case R21(A2 b2, A11 b1) -> System.out.println("1");
case R21(A22 b2, A12 b1) -> System.out.println("2");
case R21(A21 b2, A1 b1) -> System.out.println("3");
}
}
sealed interface Parent {}
record AAA() implements Parent {}
record BBB() implements Parent {}

View File

@@ -0,0 +1,21 @@
class Test {
sealed interface A1 permits A11, A12 {}
sealed interface A2 permits A21, A22 {}
<error descr="Cyclic inheritance involving 'Test.A11'">sealed interface A11 extends A11 <error descr="'implements' not allowed on interface">implements</error> A1</error> {}
record A12() implements A1 {}
record A21() implements A2 { }
record A22() implements A2 { }
record R1(A2 a2) { }
record R2(A1 a1, R1 r1) { }
static void r(R2 r2) {
switch (r2) {
case R2(A11 b1, R1(A2 b2)) -> System.out.println("1");
case R2(A12 b1, R1(A22 b2)) -> System.out.println("2");
case R2(A1 b1, R1(A21 b2)) -> System.out.println("3");
}
}
}

View File

@@ -61,6 +61,9 @@ public class LightPatternsForSwitchHighlightingTest extends LightJavaCodeInsight
public void testSwitchExhaustivenessIn21Java() {
doTest();
}
public void testSwitchExhaustivenessIn21JavaInfiniteRecursion() {
doTest();
}
public void testSwitchExhaustivenessForDirectClassesIn21Java() {
doTest();