PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { if (!HighlightingFeature.PATTERNS.isAvailable(holder.getFile())) return PsiElementVisitor.EMPTY_VISITOR; return new JavaElementVisitor() { @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) { if (!HighlightingFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS.isAvailable(holder.getFile())) return; PsiTypeCastExpression qualifier = getQualifierReferenceExpression(call); if(qualifier==null) return; PsiInstanceOfExpression candidate = InstanceOfUtils.findPatternCandidate(qualifier); if (candidate == null) return; PsiPrimaryPattern pattern = candidate.getPattern(); if (pattern instanceof PsiDeconstructionPattern deconstruction) { PsiPatternVariable existingPatternVariable = findExistingPatternVariable(qualifier, deconstruction, call); if (existingPatternVariable == null) return; if (!isFinalOrEffectivelyFinal(existingPatternVariable)) return; String patternName = existingPatternVariable.getName(); if (PsiUtil.skipParenthesizedExprUp(call.getParent()) instanceof PsiLocalVariable localVariable && canReplaceLocalVariableWithPatternVariable(localVariable, existingPatternVariable)) { String name = localVariable.getName(); LocalQuickFix fix = new ExistingPatternVariableCanBeUsedFix(name, existingPatternVariable); holder.registerProblem(call, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message", patternName, name), fix); } else { String callText = call.getText(); LocalQuickFix fix = new ExistingPatternVariableCanBeUsedFix(callText, existingPatternVariable); holder.registerProblem(call, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message", patternName, callText), fix); } } } private static boolean isFinalOrEffectivelyFinal(@NotNull PsiPatternVariable variable) { return (!(variable.getPattern() instanceof PsiDeconstructionPattern) && variable.hasModifierProperty(PsiModifier.FINAL)) || !VariableAccessUtils.variableIsAssigned(variable, variable.getDeclarationScope()); } private static boolean canReplaceLocalVariableWithPatternVariable(@NotNull PsiLocalVariable localVariable, @NotNull PsiPatternVariable patternVariable) { PsiElement scope = PsiUtil.getVariableCodeBlock(localVariable, null); if (scope == null) return false; return localVariable.hasModifierProperty(PsiModifier.FINAL) || !patternVariable.hasModifierProperty(PsiModifier.FINAL) || HighlightControlFlowUtil.isEffectivelyFinal(localVariable, scope, null); } @Nullable private static PsiTypeCastExpression getQualifierReferenceExpression(@NotNull PsiMethodCallExpression call) { while (true) { if (!call.getArgumentList().isEmpty()) return null; PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()); PsiMethodCallExpression qualifierMethodCall = ObjectUtils.tryCast(qualifier, PsiMethodCallExpression.class); if (qualifierMethodCall == null) return ObjectUtils.tryCast(qualifier, PsiTypeCastExpression.class); call = qualifierMethodCall; } } private static PsiPatternVariable findExistingPatternVariable(@NotNull PsiExpression currentQualifier, @NotNull PsiDeconstructionPattern currentDeconstruction, @NotNull PsiMethodCallExpression call) { PsiElement parent = PsiUtil.skipParenthesizedExprUp(currentQualifier.getParent()); if (!(parent instanceof PsiReferenceExpression)) return null; PsiElement grandParent = parent.getParent(); if (grandParent instanceof PsiMethodCallExpression currentCall && currentCall.resolveMethod() instanceof LightRecordMethod recordMethod) { PsiClass aClass = recordMethod.getContainingClass(); PsiRecordComponent[] recordComponents = aClass.getRecordComponents(); PsiRecordComponent recordComponent = recordMethod.getRecordComponent(); int index = ArrayUtil.find(recordComponents, recordComponent); if (index < 0) return null; PsiPattern[] deconstructionComponents = currentDeconstruction.getDeconstructionList().getDeconstructionComponents(); if (index >= deconstructionComponents.length) return null; PsiPattern deconstructionComponent = deconstructionComponents[index]; PsiType componentType = JavaPsiPatternUtil.getPatternType(deconstructionComponent); if (componentType == null || !componentType.equals(recordComponent.getType())) return null; PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(deconstructionComponent); if (currentCall.equals(call)) return variable; if (deconstructionComponent instanceof PsiDeconstructionPattern deconstruction) { return findExistingPatternVariable(currentCall, deconstruction, call); } } return null; } @Override public void visitLocalVariable(@NotNull PsiLocalVariable variable) { PsiIdentifier identifier = variable.getNameIdentifier(); if (identifier == null) return; PsiTypeCastExpression cast = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(variable.getInitializer()), PsiTypeCastExpression.class); if (cast == null || cast.getCastType() == null) return; PsiExpression operand = cast.getOperand(); if (operand == null) return; PsiType castType = cast.getCastType().getType(); if (castType instanceof PsiPrimitiveType) return; if (!variable.getType().equals(castType)) return; PsiType operandType = operand.getType(); if (operandType == null || castType.isAssignableFrom(operandType)) return; PsiElement scope = PsiUtil.getVariableCodeBlock(variable, null); if (scope == null) return; PsiDeclarationStatement declaration = ObjectUtils.tryCast(variable.getParent(), PsiDeclarationStatement.class); if (declaration == null) return; PsiInstanceOfExpression instanceOf = InstanceOfUtils.findPatternCandidate(cast); if (instanceOf != null) { PsiPattern pattern = instanceOf.getPattern(); PsiPatternVariable existingPatternVariable = JavaPsiPatternUtil.getPatternVariable(pattern); //it is a deconstruction pattern and we can't add new variable here if (pattern != null && existingPatternVariable == null) { return; } String name = identifier.getText(); if (existingPatternVariable != null) { if (!canReplaceLocalVariableWithPatternVariable(variable, existingPatternVariable) || !isFinalOrEffectivelyFinal(existingPatternVariable)) { return; } holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message", existingPatternVariable.getName(), name), new ExistingPatternVariableCanBeUsedFix(name, existingPatternVariable)); } else { if (!isOnTheFly && InstanceOfUtils.hasConflictingDeclaredNames(variable, instanceOf)) { return; } holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.message", name), new PatternVariableCanBeUsedFix(name, instanceOf)); } } } }; }