All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.inspections.PyArgumentListInspection Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 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 PyCallable callable = markedCallee.getCallable();
          int firstParamOffset =  markedCallee.getImplicitOffset();
          final List 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 PyCallable 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> argEntry : result.getArgumentFlags().entrySet()) {
      EnumSet 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 quickFixes = Lists.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 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 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()));
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy