blob: dcd6b3328a6b26394b0b9f05cd9275ff7407563b [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.google.common.collect.Lists;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.inspections.quickfix.PyRemoveArgumentQuickFix;
import com.jetbrains.python.inspections.quickfix.PyRenameArgumentQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.PyABCUtil;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeChecker;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
/**
* Looks at argument lists.
* @author dcheryasov
*/
public class PyArgumentListInspection extends PyInspection {
@Nls
@NotNull
public String getDisplayName() {
return PyBundle.message("INSP.NAME.incorrect.call.arguments");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
public static class Visitor extends PyInspectionVisitor {
public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyArgumentList(final PyArgumentList node) {
// analyze
inspectPyArgumentList(node, getHolder(), myTypeEvalContext);
}
@Override
public void visitPyDecoratorList(final PyDecoratorList node) {
PyDecorator[] decorators = node.getDecorators();
for (PyDecorator deco : decorators) {
if (deco.hasArgumentList()) continue;
final PyCallExpression.PyMarkedCallee markedCallee = deco.resolveCallee(getResolveContext());
if (markedCallee != null && !markedCallee.isImplicitlyResolved()) {
final Callable callable = markedCallee.getCallable();
int firstParamOffset = markedCallee.getImplicitOffset();
final List<PyParameter> params = PyUtil.getParameters(callable, myTypeEvalContext);
final PyNamedParameter allegedFirstParam = params.size() < firstParamOffset ?
null : params.get(firstParamOffset-1).getAsNamed();
if (allegedFirstParam == null || allegedFirstParam.isKeywordContainer()) {
// no parameters left to pass function implicitly, or wrong param type
registerProblem(deco, PyBundle.message("INSP.func.$0.lacks.first.arg", callable.getName())); // TODO: better names for anon lambdas
}
else { // possible unfilled params
for (int i = firstParamOffset; i < params.size(); i += 1) {
final PyParameter parameter = params.get(i);
if (parameter instanceof PySingleStarParameter) continue;
final PyNamedParameter par = parameter.getAsNamed();
// param tuples, non-starred or non-default won't do
if (par == null || (!par.isKeywordContainer() && !par.isPositionalContainer() &&!par.hasDefaultValue())) {
String parameterName = par != null ? par.getName() : "(...)";
registerProblem(deco, PyBundle.message("INSP.parameter.$0.unfilled", parameterName));
}
}
}
}
// else: this case is handled by arglist visitor
}
}
}
public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context, int implicitOffset) {
if (node.getParent() instanceof PyClass) return; // class Foo(object) is also an arg list
CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context), implicitOffset);
final PyCallExpression.PyMarkedCallee callee = result.getMarkedCallee();
if (callee != null) {
final Callable callable = callee.getCallable();
// Decorate functions may have different parameter lists. We don't match arguments with parameters of decorators yet
if (callable instanceof PyFunction && PyUtil.hasCustomDecorators((PyFunction)callable)) {
return;
}
}
highlightIncorrectArguments(holder, result, context);
highlightMissingArguments(node, holder, result);
highlightStarArgumentTypeMismatch(node, holder, context);
}
public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context) {
inspectPyArgumentList(node, holder, context, 0);
}
private static void highlightIncorrectArguments(ProblemsHolder holder, CallArgumentsMapping result, @NotNull TypeEvalContext context) {
for (Map.Entry<PyExpression, EnumSet<CallArgumentsMapping.ArgFlag>> argEntry : result.getArgumentFlags().entrySet()) {
EnumSet<CallArgumentsMapping.ArgFlag> flags = argEntry.getValue();
if (!flags.isEmpty()) { // something's wrong
PyExpression arg = argEntry.getKey();
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP)) {
holder.registerProblem(arg, PyBundle.message("INSP.duplicate.argument"), new PyRemoveArgumentQuickFix());
}
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP_KWD)) {
holder.registerProblem(arg, PyBundle.message("INSP.duplicate.doublestar.arg"), new PyRemoveArgumentQuickFix());
}
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP_TUPLE)) {
holder.registerProblem(arg, PyBundle.message("INSP.duplicate.star.arg"), new PyRemoveArgumentQuickFix());
}
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_POS_PAST_KWD)) {
holder.registerProblem(arg, PyBundle.message("INSP.cannot.appear.past.keyword.arg"), ProblemHighlightType.ERROR, new PyRemoveArgumentQuickFix());
}
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_UNMAPPED)) {
ArrayList<LocalQuickFix> quickFixes = Lists.<LocalQuickFix>newArrayList(new PyRemoveArgumentQuickFix());
if (arg instanceof PyKeywordArgument) {
quickFixes.add(new PyRenameArgumentQuickFix());
}
holder.registerProblem(arg, PyBundle.message("INSP.unexpected.arg"), quickFixes.toArray(new LocalQuickFix[quickFixes.size()-1]));
}
if (flags.contains(CallArgumentsMapping.ArgFlag.IS_TOO_LONG)) {
final PyCallExpression.PyMarkedCallee markedCallee = result.getMarkedCallee();
String parameterName = null;
if (markedCallee != null) {
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
for (int i = parameters.size() - 1; i >= 0; --i) {
final PyParameter param = parameters.get(i);
if (param instanceof PyNamedParameter) {
final List<PyNamedParameter> unmappedParams = result.getUnmappedParams();
if (!((PyNamedParameter)param).isPositionalContainer() && !((PyNamedParameter)param).isKeywordContainer() &&
param.getDefaultValue() == null && !unmappedParams.contains(param)) {
parameterName = param.getName();
break;
}
}
}
holder.registerProblem(arg, parameterName != null ? PyBundle.message("INSP.multiple.values.resolve.to.positional.$0", parameterName)
: PyBundle.message("INSP.more.args.that.pos.params"));
}
}
}
}
}
private static void highlightStarArgumentTypeMismatch(PyArgumentList node, ProblemsHolder holder, TypeEvalContext context) {
for (PyExpression arg : node.getArguments()) {
if (arg instanceof PyStarArgument) {
PyExpression content = PyUtil.peelArgument(PsiTreeUtil.findChildOfType(arg, PyExpression.class));
if (content != null) {
PyType inside_type = context.getType(content);
if (inside_type != null && !PyTypeChecker.isUnknown(inside_type)) {
if (((PyStarArgument)arg).isKeyword()) {
if (!PyABCUtil.isSubtype(inside_type, PyNames.MAPPING, context)) {
holder.registerProblem(arg, PyBundle.message("INSP.expected.dict.got.$0", inside_type.getName()));
}
}
else { // * arg
if (!PyABCUtil.isSubtype(inside_type, PyNames.ITERABLE, context)) {
holder.registerProblem(arg, PyBundle.message("INSP.expected.iter.got.$0", inside_type.getName()));
}
}
}
}
}
}
}
private static void highlightMissingArguments(PyArgumentList node, ProblemsHolder holder, CallArgumentsMapping result) {
ASTNode our_node = node.getNode();
if (our_node != null) {
ASTNode close_paren = our_node.findChildByType(PyTokenTypes.RPAR);
if (close_paren != null) {
for (PyNamedParameter param : result.getUnmappedParams()) {
holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", param.getName()));
}
}
}
}
}