blob: a3411f3976c3025dbc6f480db5a00bdafdaf15aa [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.intellij.codeInsight.controlflow.ControlFlow;
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.codeInsight.dataflow.DFALimitExceededException;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.inspections.quickfix.AddGlobalQuickFix;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyGlobalStatementNavigator;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* @author oleg
*/
public class PyUnboundLocalVariableInspection extends PyInspection {
private static Key<Set<ScopeOwner>> LARGE_FUNCTIONS_KEY = Key.create("PyUnboundLocalVariableInspection.LargeFunctions");
@NotNull
@Nls
public String getDisplayName() {
return PyBundle.message("INSP.NAME.unbound");
}
@NotNull
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull final LocalInspectionToolSession session) {
session.putUserData(LARGE_FUNCTIONS_KEY, new HashSet<ScopeOwner>());
return new Visitor(holder, session);
}
public static class Visitor extends PyInspectionVisitor {
public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyReferenceExpression(final PyReferenceExpression node) {
if (node.getContainingFile() instanceof PyExpressionCodeFragment) {
return;
}
// Ignore global statements arguments
if (PyGlobalStatementNavigator.getByArgument(node) != null) {
return;
}
// Ignore qualifier inspections
if (node.isQualified()) {
return;
}
// Ignore import subelements
if (PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class) != null) {
return;
}
final String name = node.getReferencedName();
if (name == null) {
return;
}
final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, name);
final Set<ScopeOwner> largeFunctions = getSession().getUserData(LARGE_FUNCTIONS_KEY);
assert largeFunctions != null;
if (owner == null || largeFunctions.contains(owner)) {
return;
}
// Ignore references declared in outer scopes
if (owner != ScopeUtil.getScopeOwner(node)) {
return;
}
final Scope scope = ControlFlowCache.getScope(owner);
// Ignore globals and if scope even doesn't contain such a declaration
if (scope.isGlobal(name) || (!scope.containsDeclaration(name))){
return;
}
// Start DFA from the assignment statement in case of augmented assignments
final PsiElement anchor;
final PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(node, PyAugAssignmentStatement.class);
if (augAssignment != null && name.equals(augAssignment.getTarget().getName())) {
anchor = augAssignment;
}
else {
anchor = node;
}
final ScopeVariable variable;
try {
variable = scope.getDeclaredVariable(anchor, name);
}
catch (DFALimitExceededException e) {
largeFunctions.add(owner);
registerLargeFunction(owner);
return;
}
if (variable == null) {
if (!isFirstUnboundRead(node, owner)) {
return;
}
final PsiPolyVariantReference ref = node.getReference(getResolveContext());
if (ref == null) {
return;
}
final PsiElement resolved = ref.resolve();
final boolean isBuiltin = PyBuiltinCache.getInstance(node).isBuiltin(resolved);
if (owner instanceof PyClass) {
if (isBuiltin || ScopeUtil.getDeclarationScopeOwner(owner, name) != null) {
return;
}
}
if (PyUnreachableCodeInspection.hasAnyInterruptedControlFlowPaths(node)) {
return;
}
if (owner instanceof PyFile) {
if (isBuiltin) {
return;
}
if (resolved != null && !PyUtil.inSameFile(node, resolved)) {
return;
}
registerProblem(node, PyBundle.message("INSP.unbound.name.not.defined", name));
}
else {
registerProblem(node, PyBundle.message("INSP.unbound.local.variable", node.getName()),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
null,
new AddGlobalQuickFix());
}
}
}
private static boolean isFirstUnboundRead(@NotNull PyReferenceExpression node, @NotNull ScopeOwner owner) {
final String nodeName = node.getReferencedName();
final Scope scope = ControlFlowCache.getScope(owner);
final ControlFlow flow = ControlFlowCache.getControlFlow(owner);
final Instruction[] instructions = flow.getInstructions();
final int num = ControlFlowUtil.findInstructionNumberByElement(instructions, node);
if (num < 0) {
return true;
}
final Ref<Boolean> first = Ref.create(true);
ControlFlowUtil.iteratePrev(num, instructions, new Function<Instruction, ControlFlowUtil.Operation>() {
@Override
public ControlFlowUtil.Operation fun(Instruction instruction) {
if (instruction instanceof ReadWriteInstruction) {
final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)instruction;
final String name = rwInstruction.getName();
final PsiElement element = rwInstruction.getElement();
if (element != null && name != null && name.equals(nodeName) && instruction.num() != num) {
try {
if (scope.getDeclaredVariable(element, name) == null) {
final ReadWriteInstruction.ACCESS access = rwInstruction.getAccess();
if (access.isReadAccess()) {
first.set(false);
return ControlFlowUtil.Operation.BREAK;
}
}
}
catch (DFALimitExceededException e) {
first.set(false);
}
return ControlFlowUtil.Operation.CONTINUE;
}
}
return ControlFlowUtil.Operation.NEXT;
}
});
return first.get();
}
@Override
public void visitPyNonlocalStatement(final PyNonlocalStatement node) {
for (PyTargetExpression var : node.getVariables()) {
final String name = var.getName();
final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(var, name);
if (owner == null || owner instanceof PyFile) {
registerProblem(var, PyBundle.message("INSP.unbound.nonlocal.variable", name),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}
}
}
private void registerLargeFunction(ScopeOwner owner) {
registerProblem((owner instanceof PyFunction) ? ((PyFunction)owner).getNameIdentifier() : owner,
PyBundle.message("INSP.unbound.function.too.large", owner.getName()),
ProblemHighlightType.WEAK_WARNING);
}
}
}