SealedResult (@NotNull PsiType selectorType, @NotNull List<? extends PatternDescription> elements, @NotNull List<PsiEnumConstant> enumConstants, @NotNull PsiElement context) { //Used to keep dependencies. The last dependency is one of the selector types. record ClassWithDependencies(PsiClass mainClass, List<PsiClass> dependencies) { } Set<PsiClass> coveredClasses = new HashSet<>(); Set<PsiClass> visitedNotCovered = new HashSet<>(); Set<PsiClass> missingClasses = new LinkedHashSet<>(); //reduce record patterns and enums to TypeTestDescription List<PatternTypeTestDescription> reducedDescriptions = reduceToTypeTest(elements, context); reducedDescriptions.addAll(reduceEnumConstantsToTypeTest(enumConstants)); MultiMap<PsiClass, PsiType> permittedPatternClasses = findPermittedClasses(reducedDescriptions); //according JEP 440-441, only direct abstract-sealed classes are allowed (14.11.1.1) Set<PsiClass> sealedUpperClasses = findSealedUpperClasses(permittedPatternClasses.keySet()); List<PatternTypeTestDescription> typeTestPatterns = ContainerUtil.filterIsInstance(elements, PatternTypeTestDescription.class); Set<PsiClass> selectorClasses = ContainerUtil.map2SetNotNull(getAllTypes(selectorType), type -> PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type))); if (selectorClasses.isEmpty()) return new SealedResult(Collections.emptySet(), Collections.emptySet()); Queue<ClassWithDependencies> nonVisited = new ArrayDeque<>(); Set<ClassWithDependencies> visited = new SmartHashSet<>(); for (PsiClass selectorClass : selectorClasses) { List<PsiClass> dependencies = new ArrayList<>(); dependencies.add(selectorClass); nonVisited.add(new ClassWithDependencies(selectorClass, dependencies)); } while (!nonVisited.isEmpty()) { ClassWithDependencies peeked = nonVisited.peek(); if (!visited.add(peeked)) continue; PsiClass psiClass = peeked.mainClass; PsiClass selectorClass = peeked.dependencies.get(peeked.dependencies.size() - 1); if (sealedUpperClasses.contains(psiClass) || //used to generate missed classes when the switch is empty (selectorClasses.contains(psiClass) && elements.isEmpty())) { for (PsiClass permittedClass : PatternsInSwitchBlockHighlightingModel.getPermittedClasses(psiClass)) { Collection<PsiType> patternTypes = permittedPatternClasses.get(permittedClass); PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(selectorClass, permittedClass, PsiSubstitutor.EMPTY); PsiType permittedType = JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass, substitutor); //if we don't have patternType and tree goes away from a target type, let's skip it if (patternTypes.isEmpty() && TypeConversionUtil.areTypesConvertible(selectorType, permittedType) || //if permittedClass is covered by existed patternType, we don't have to go further !patternTypes.isEmpty() && !ContainerUtil.exists(patternTypes, patternType -> oneOfUnconditional(patternType, TypeUtils.getType(permittedClass)))) { List<PsiClass> dependentClasses = new ArrayList<>(peeked.dependencies); dependentClasses.add(permittedClass); nonVisited.add(new ClassWithDependencies(permittedClass, dependentClasses)); } else { if (!patternTypes.isEmpty()) { coveredClasses.addAll(peeked.dependencies); } } } } else { PsiClassType targetType = TypeUtils.getType(psiClass); //there is a chance, that tree goes away from a target type if (TypeConversionUtil.areTypesConvertible(targetType, selectorType) || //we should consider items from the intersections in the usual way oneOfUnconditional(targetType, selectorType)) { if (//check a case, when we have something, which not in sealed hierarchy, but covers some leaves !ContainerUtil.exists(typeTestPatterns, pattern -> oneOfUnconditional(pattern.type(), targetType))) { missingClasses.add(psiClass); visitedNotCovered.addAll(peeked.dependencies); } else { coveredClasses.addAll(peeked.dependencies); } } } nonVisited.poll(); } coveredClasses.removeAll(visitedNotCovered); for (PsiClass selectorClass : selectorClasses) { if (coveredClasses.contains(selectorClass)) { //one of the selector classes is covered, so the selector type is covered missingClasses.clear(); break; } } return new SealedResult(missingClasses, coveredClasses); }