@Nullable static HighlightInfo checkVariableMustBeFinal( PsiVariable variable, PsiJavaCodeReferenceElement context, @NotNull LanguageLevel languageLevel) { if (variable.hasModifierProperty(PsiModifier.FINAL)) return null; final PsiClass innerClass = getInnerClassVariableReferencedFrom(variable, context); if (innerClass != null) { if (variable instanceof PsiParameter) { final PsiElement parent = variable.getParent(); if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && notAccessedForWriting( variable, new LocalSearchScope(((PsiParameter) variable).getDeclarationScope()))) { return null; } } if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && isEffectivelyFinal(variable, innerClass, context)) { return null; } final String description = JavaErrorMessages.message("variable.must.be.final", context.getText()); final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(context) .descriptionAndTooltip(description) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); return highlightInfo; } final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { final PsiElement parent = variable.getParent(); if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { return null; } if (!isEffectivelyFinal(variable, lambdaExpression, context)) { return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(context) .descriptionAndTooltip("Variable used in lambda expression should be effectively final") .create(); } } return null; }
private static boolean canWriteToFinal( @NotNull PsiVariable variable, @NotNull PsiExpression expression, @NotNull PsiReferenceExpression reference, @NotNull PsiFile containingFile) { if (variable.hasInitializer()) return false; if (variable instanceof PsiParameter) return false; PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); if (variable instanceof PsiField) { // if inside some field initializer if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) return true; // assignment from within inner class is illegal always PsiField field = (PsiField) variable; if (innerClass != null && !containingFile .getManager() .areElementsEquivalent(innerClass, field.getContainingClass())) return false; final PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); return enclosingCtrOrInitializer != null && isSameField(enclosingCtrOrInitializer, field, reference, containingFile); } if (variable instanceof PsiLocalVariable) { boolean isAccessedFromOtherClass = innerClass != null; if (isAccessedFromOtherClass) { return false; } } return true; }
@Nullable public static PsiElement getInnerClassVariableReferencedFrom( @NotNull PsiVariable variable, @NotNull PsiElement context) { final PsiElement[] scope; if (variable instanceof PsiResourceVariable) { scope = ((PsiResourceVariable) variable).getDeclarationScope(); } else if (variable instanceof PsiLocalVariable) { final PsiElement parent = variable.getParent(); scope = new PsiElement[] { parent != null ? parent.getParent() : null }; // code block or for statement } else if (variable instanceof PsiParameter) { scope = new PsiElement[] {((PsiParameter) variable).getDeclarationScope()}; } else { scope = new PsiElement[] {variable.getParent()}; } if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) return null; PsiElement parent = context.getParent(); PsiElement prevParent = context; outer: while (parent != null) { for (PsiElement scopeElement : scope) { if (parent.equals(scopeElement)) break outer; } if (parent instanceof PsiClass && !(prevParent instanceof PsiExpressionList && parent instanceof PsiAnonymousClass)) { return parent; } if (parent instanceof PsiLambdaExpression) { return parent; } prevParent = parent; parent = parent.getParent(); } return null; }
@Nullable static HighlightInfo checkVariableMustBeFinal( @NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context, @NotNull LanguageLevel languageLevel) { if (variable.hasModifierProperty(PsiModifier.FINAL)) return null; final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, context); if (innerClass instanceof PsiClass) { if (variable instanceof PsiParameter) { final PsiElement parent = variable.getParent(); if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && notAccessedForWriting( variable, new LocalSearchScope(((PsiParameter) variable).getDeclarationScope()))) { return null; } } final boolean isToBeEffectivelyFinal = languageLevel.isAtLeast(LanguageLevel.JDK_1_8); if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, innerClass, context)) { return null; } final String description = JavaErrorMessages.message( isToBeEffectivelyFinal ? "variable.must.be.final.or.effectively.final" : "variable.must.be.final", context.getText()); final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(context) .descriptionAndTooltip(description) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); return highlightInfo; } return checkWriteToFinalInsideLambda(variable, context); }
public static boolean isReassigned( PsiVariable variable, Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) { if (variable instanceof PsiLocalVariable) { final PsiElement parent = variable.getParent(); if (parent == null) return false; final PsiElement declarationScope = parent.getParent(); Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope); return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null)); } else if (variable instanceof PsiParameter) { final PsiParameter parameter = (PsiParameter) variable; return isAssigned(parameter); } else { return false; } }
private static HighlightInfo checkWriteToFinalInsideLambda( @NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) { final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { final PsiElement parent = variable.getParent(); if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { return null; } if (!isEffectivelyFinal(variable, lambdaExpression, context)) { String text = JavaErrorMessages.message("lambda.variable.must.be.final"); HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(context) .descriptionAndTooltip(text) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, lambdaExpression)); return highlightInfo; } } return null; }
@Nullable static HighlightInfo checkCannotWriteToFinal( @NotNull PsiExpression expression, @NotNull PsiFile containingFile) { PsiReferenceExpression reference = null; boolean readBeforeWrite = false; if (expression instanceof PsiAssignmentExpression) { final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression) expression; final PsiExpression left = PsiUtil.skipParenthesizedExprDown(assignmentExpression.getLExpression()); if (left instanceof PsiReferenceExpression) { reference = (PsiReferenceExpression) left; } readBeforeWrite = assignmentExpression.getOperationTokenType() != JavaTokenType.EQ; } else if (expression instanceof PsiPostfixExpression) { final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPostfixExpression) expression).getOperand()); final IElementType sign = ((PsiPostfixExpression) expression).getOperationTokenType(); if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { reference = (PsiReferenceExpression) operand; } readBeforeWrite = true; } else if (expression instanceof PsiPrefixExpression) { final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPrefixExpression) expression).getOperand()); final IElementType sign = ((PsiPrefixExpression) expression).getOperationTokenType(); if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { reference = (PsiReferenceExpression) operand; } readBeforeWrite = true; } final PsiElement resolved = reference == null ? null : reference.resolve(); PsiVariable variable = resolved instanceof PsiVariable ? (PsiVariable) resolved : null; if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) return null; final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null; if (readBeforeWrite || !canWrite) { final String name = variable.getName(); String description = canWrite ? JavaErrorMessages.message("variable.not.initialized", name) : JavaErrorMessages.message("assignment.to.final.variable", name); final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(reference.getTextRange()) .descriptionAndTooltip(description) .create(); final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); if (innerClass == null || variable instanceof PsiField) { QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); } else { QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); } return highlightInfo; } return null; }
@Nullable public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo( @NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression, @NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) { if (!PsiUtil.isAccessedForWriting(expression)) return null; final PsiElement scope = variable instanceof PsiField ? variable.getParent() : variable.getParent() == null ? null : variable.getParent().getParent(); PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); if (codeBlock == null) return null; Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock); boolean alreadyAssigned = false; for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) { if (variableInfo.expression == expression) { alreadyAssigned = true; break; } } if (!alreadyAssigned) { if (!(variable instanceof PsiField)) return null; final PsiField field = (PsiField) variable; final PsiClass aClass = field.getContainingClass(); if (aClass == null) return null; // field can get assigned in other field initializers final PsiField[] fields = aClass.getFields(); boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); for (PsiField psiField : fields) { PsiExpression initializer = psiField.getInitializer(); if (psiField != field && psiField.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic && initializer != null && initializer != codeBlock && !variableDefinitelyNotAssignedIn(field, initializer)) { alreadyAssigned = true; break; } } if (!alreadyAssigned) { // field can get assigned in class initializers final PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); if (enclosingConstructorOrInitializer == null || !aClass .getManager() .areElementsEquivalent( enclosingConstructorOrInitializer.getContainingClass(), aClass)) { return null; } final PsiClassInitializer[] initializers = aClass.getInitializers(); for (PsiClassInitializer initializer : initializers) { if (initializer.hasModifierProperty(PsiModifier.STATIC) == field.hasModifierProperty(PsiModifier.STATIC)) { final PsiCodeBlock body = initializer.getBody(); if (body == codeBlock) return null; try { final ControlFlow controlFlow = getControlFlow(body); if (!ControlFlowUtil.isVariableDefinitelyNotAssigned(field, controlFlow)) { alreadyAssigned = true; break; } } catch (AnalysisCanceledException e) { // incomplete code return null; } } } } if (!alreadyAssigned && !field.hasModifierProperty(PsiModifier.STATIC)) { // then check if instance field already assigned in other constructor final PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod ? (PsiMethod) codeBlock.getParent() : null; // assignment to final field in several constructors threatens us only if these are linked // (there is this() call in the beginning) final List<PsiMethod> redirectedConstructors = ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : null; for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { PsiMethod redirectedConstructor = redirectedConstructors.get(j); PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { alreadyAssigned = true; break; } } } } if (alreadyAssigned) { String description = JavaErrorMessages.message("variable.already.assigned", variable.getName()); final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(expression) .descriptionAndTooltip(description) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createDeferFinalAssignmentFix(variable, expression)); return highlightInfo; } return null; }
@Nullable public static HighlightInfo checkVariableInitializedBeforeUsage( @NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable, @NotNull Map<PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems, @NotNull PsiFile containingFile) { if (variable instanceof ImplicitVariable) return null; if (!PsiUtil.isAccessedForReading(expression)) return null; int startOffset = expression.getTextRange().getStartOffset(); final PsiElement topBlock; if (variable.hasInitializer()) { topBlock = PsiUtil.getVariableCodeBlock(variable, variable); if (topBlock == null) return null; } else { PsiElement scope = variable instanceof PsiField ? ((PsiField) variable).getContainingClass() : variable.getParent() != null ? variable.getParent().getParent() : null; if (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) { scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); } topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); if (variable instanceof PsiField) { // non final field already initialized with default value if (!variable.hasModifierProperty(PsiModifier.FINAL)) return null; // final field may be initialized in ctor or class initializer only // if we're inside non-ctr method, skip it if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null && HighlightUtil.findEnclosingFieldInitializer(expression) == null) { return null; } if (topBlock == null) return null; final PsiElement parent = topBlock.getParent(); // access to final fields from inner classes always allowed if (inInnerClass(expression, ((PsiField) variable).getContainingClass(), containingFile)) return null; final PsiCodeBlock block; final PsiClass aClass; if (parent instanceof PsiMethod) { PsiMethod constructor = (PsiMethod) parent; if (!containingFile .getManager() .areElementsEquivalent( constructor.getContainingClass(), ((PsiField) variable).getContainingClass())) return null; // static variables already initialized in class initializers if (variable.hasModifierProperty(PsiModifier.STATIC)) return null; // as a last chance, field may be initialized in this() call final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { PsiMethod redirectedConstructor = redirectedConstructors.get(j); // variable must be initialized before its usage // ??? // if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { return null; } } block = constructor.getBody(); aClass = constructor.getContainingClass(); } else if (parent instanceof PsiClassInitializer) { final PsiClassInitializer classInitializer = (PsiClassInitializer) parent; if (!containingFile .getManager() .areElementsEquivalent( classInitializer.getContainingClass(), ((PsiField) variable).getContainingClass())) return null; block = classInitializer.getBody(); aClass = classInitializer.getContainingClass(); } else { // field reference outside code block // check variable initialized before its usage final PsiField field = (PsiField) variable; aClass = field.getContainingClass(); if (aClass == null || isFieldInitializedInOtherFieldInitializer( aClass, field, field.hasModifierProperty(PsiModifier.STATIC))) { return null; } final PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class); int offset = startOffset; if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) { offset = 0; } block = null; // initializers will be checked later final PsiMethod[] constructors = aClass.getConstructors(); for (PsiMethod constructor : constructors) { // variable must be initialized before its usage if (offset < constructor.getTextRange().getStartOffset()) continue; PsiCodeBlock body = constructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { return null; } // as a last chance, field may be initialized in this() call final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { PsiMethod redirectedConstructor = redirectedConstructors.get(j); // variable must be initialized before its usage if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue; PsiCodeBlock redirectedBody = redirectedConstructor.getBody(); if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) { return null; } } } } if (aClass != null) { // field may be initialized in class initializer final PsiClassInitializer[] initializers = aClass.getInitializers(); for (PsiClassInitializer initializer : initializers) { PsiCodeBlock body = initializer.getBody(); if (body == block) break; // variable referenced in initializer must be initialized in initializer preceding // assignment // variable referenced in field initializer or in class initializer boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer; if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) continue; if (initializer.hasModifierProperty(PsiModifier.STATIC) == variable.hasModifierProperty(PsiModifier.STATIC)) { if (variableDefinitelyAssignedIn(variable, body)) return null; } } } } } if (topBlock == null) return null; Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock); if (codeBlockProblems == null) { try { final ControlFlow controlFlow = getControlFlow(topBlock); codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); } catch (AnalysisCanceledException | IndexNotReadyException e) { codeBlockProblems = Collections.emptyList(); } uninitializedVarProblems.put(topBlock, codeBlockProblems); } if (codeBlockProblems.contains(expression)) { final String name = expression.getElement().getText(); String description = JavaErrorMessages.message("variable.not.initialized", name); HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(expression) .descriptionAndTooltip(description) .create(); QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(variable)); if (variable instanceof PsiField) { QuickFixAction.registerQuickFixAction( highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); } return highlightInfo; } return null; }