mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-22475 Fixed: Switch multi-parameters handling to multi-resolved functions API
Update multi-callee problem registration in PyTypeCheckerInspection: * drop callees with unmapped arguments or unfilled parameters * in case of one callee show message in old way * in case of some callees show message with actual types and possible sets of them
This commit is contained in:
committed by
Semyon Proshev
parent
77ee34ea5c
commit
b7513de6d5
@@ -148,7 +148,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
|
||||
final String qname = function.getQualifiedName();
|
||||
if (qname != null) {
|
||||
if (OPEN_FUNCTIONS.contains(qname) && callSite instanceof PyCallExpression) {
|
||||
return getOpenFunctionType(qname, PyCallExpressionHelper.mapArguments(callSite, function, context), callSite);
|
||||
return getOpenFunctionType(qname, PyCallExpressionHelper.mapArguments(callSite, function, context).getMappedParameters(), callSite);
|
||||
}
|
||||
else if ("tuple.__init__".equals(qname) && callSite instanceof PyCallExpression) {
|
||||
return getTupleInitializationType((PyCallExpression)callSite, context);
|
||||
|
||||
@@ -15,17 +15,12 @@
|
||||
*/
|
||||
package com.jetbrains.python.inspections;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.hash.LinkedHashMap;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
@@ -33,12 +28,15 @@ import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
@@ -182,18 +180,20 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
}
|
||||
|
||||
private void checkCallSite(@NotNull PyCallSiteExpression callSite) {
|
||||
final List<PyTypeChecker.AnalyzeCallResults> resultsSet = PyTypeChecker.analyzeCallSite(callSite, myTypeEvalContext);
|
||||
final List<Map<PyExpression, Pair<String, ProblemHighlightType>>> problemsSet =
|
||||
new ArrayList<>();
|
||||
for (PyTypeChecker.AnalyzeCallResults results : resultsSet) {
|
||||
problemsSet.add(checkMapping(results.getReceiver(), results.getArguments()));
|
||||
final List<AnalyzeCalleeResults> calleesResults = StreamEx
|
||||
.of(PyTypeChecker.analyzeCallSite(callSite, myTypeEvalContext))
|
||||
.filter(Visitor::callDoesNotHaveUnmappedArgumentsAndUnfilledParameters)
|
||||
.map(this::analyzeCallee)
|
||||
.toList();
|
||||
|
||||
if (matchedCalleeResultsExist(calleesResults)) return;
|
||||
|
||||
if (calleesResults.size() == 1) {
|
||||
PyTypeCheckerInspectionProblemRegistrar.registerSingleCalleeProblem(this, calleesResults.get(0), myTypeEvalContext);
|
||||
}
|
||||
if (!problemsSet.isEmpty()) {
|
||||
final Map<PyExpression, Pair<String, ProblemHighlightType>> minProblems = Collections.min(problemsSet,
|
||||
Comparator.comparingInt(Map::size));
|
||||
for (Map.Entry<PyExpression, Pair<String, ProblemHighlightType>> entry : minProblems.entrySet()) {
|
||||
registerProblem(entry.getKey(), entry.getValue().getFirst(), entry.getValue().getSecond());
|
||||
}
|
||||
else if (!calleesResults.isEmpty()) {
|
||||
PyTypeCheckerInspectionProblemRegistrar
|
||||
.registerMultiCalleeProblem(this, callSite, getArgumentTypes(calleesResults), calleesResults, myTypeEvalContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,86 +210,84 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean callDoesNotHaveUnmappedArgumentsAndUnfilledParameters(@NotNull PyTypeChecker.AnalyzeCallResults callResults) {
|
||||
final PyCallExpressionHelper.ArgumentMappingResults mapping = callResults.getMapping();
|
||||
return mapping.getUnmappedArguments().isEmpty() && mapping.getUnmappedParameters().isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<PyExpression, Pair<String, ProblemHighlightType>> checkMapping(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyNamedParameter> mapping) {
|
||||
final Map<PyExpression, Pair<String, ProblemHighlightType>> problems =
|
||||
new HashMap<>();
|
||||
final Map<PyGenericType, PyType> substitutions = new LinkedHashMap<>();
|
||||
boolean genericsCollected = false;
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.entrySet()) {
|
||||
final PyNamedParameter param = entry.getValue();
|
||||
final PyExpression arg = entry.getKey();
|
||||
final PyType expectedArgType = param.getArgumentType(myTypeEvalContext);
|
||||
if (expectedArgType == null) {
|
||||
continue;
|
||||
}
|
||||
final PyType actualArgType = myTypeEvalContext.getType(arg);
|
||||
if (!genericsCollected) {
|
||||
substitutions.putAll(PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext));
|
||||
genericsCollected = true;
|
||||
}
|
||||
final Pair<String, ProblemHighlightType> problem = checkTypes(expectedArgType, actualArgType, myTypeEvalContext, substitutions);
|
||||
if (problem != null) {
|
||||
problems.put(arg, problem);
|
||||
}
|
||||
private AnalyzeCalleeResults analyzeCallee(@NotNull PyTypeChecker.AnalyzeCallResults results) {
|
||||
final List<AnalyzeArgumentResult> result = new ArrayList<>();
|
||||
final PyExpression receiver = results.getReceiver();
|
||||
|
||||
Map<PyGenericType, PyType> substitutions = null;
|
||||
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry : results.getMapping().getMappedParameters().entrySet()) {
|
||||
final AnalyzeArgumentResult argumentResult =
|
||||
analyzeArgument(receiver, entry.getValue(), entry.getKey(), substitutions);
|
||||
|
||||
substitutions = argumentResult.mySubstitutions;
|
||||
|
||||
result.add(argumentResult);
|
||||
}
|
||||
return problems;
|
||||
|
||||
return new AnalyzeCalleeResults(results.getCallable(), result);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, ProblemHighlightType> checkTypes(@Nullable PyType expected,
|
||||
@Nullable PyType actual,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> substitutions) {
|
||||
if (actual != null && expected != null) {
|
||||
if (!PyTypeChecker.match(expected, actual, context, substitutions)) {
|
||||
final String expectedName = PythonDocumentationProvider.getTypeName(expected, context);
|
||||
String quotedExpectedName = String.format("'%s'", expectedName);
|
||||
final boolean hasGenerics = PyTypeChecker.hasGenerics(expected, context);
|
||||
ProblemHighlightType highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
if (hasGenerics) {
|
||||
final PyType substitute = PyTypeChecker.substitute(expected, substitutions, context);
|
||||
if (substitute != null) {
|
||||
quotedExpectedName = String.format("'%s' (matched generic type '%s')",
|
||||
PythonDocumentationProvider.getTypeName(substitute, context),
|
||||
expectedName);
|
||||
highlightType = ProblemHighlightType.WEAK_WARNING;
|
||||
}
|
||||
}
|
||||
final String actualName = PythonDocumentationProvider.getTypeName(actual, context);
|
||||
String msg = String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName);
|
||||
if (expected instanceof PyStructuralType) {
|
||||
final Set<String> expectedAttributes = ((PyStructuralType)expected).getAttributeNames();
|
||||
final Set<String> actualAttributes = getAttributes(actual, context);
|
||||
if (actualAttributes != null) {
|
||||
final Sets.SetView<String> missingAttributes = Sets.difference(expectedAttributes, actualAttributes);
|
||||
if (missingAttributes.size() == 1) {
|
||||
msg = String.format("Type '%s' doesn't have expected attribute '%s'", actualName, missingAttributes.iterator().next());
|
||||
}
|
||||
else {
|
||||
msg = String.format("Type '%s' doesn't have expected attributes %s",
|
||||
actualName,
|
||||
StringUtil.join(missingAttributes, s -> String.format("'%s'", s), ", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.create(msg, highlightType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
private static boolean matchedCalleeResultsExist(@NotNull List<AnalyzeCalleeResults> calleesResults) {
|
||||
return calleesResults
|
||||
.stream()
|
||||
.anyMatch(calleeResults -> calleeResults.getResults().stream().allMatch(AnalyzeArgumentResult::isMatched));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Set<String> getAttributes(@NotNull PyType type, @NotNull TypeEvalContext context) {
|
||||
if (type instanceof PyStructuralType) {
|
||||
return ((PyStructuralType)type).getAttributeNames();
|
||||
@NotNull
|
||||
private static List<PyType> getArgumentTypes(@NotNull List<AnalyzeCalleeResults> calleesResults) {
|
||||
return calleesResults
|
||||
.stream()
|
||||
.map(AnalyzeCalleeResults::getResults)
|
||||
.max(Comparator.comparingInt(List::size))
|
||||
.orElse(Collections.emptyList())
|
||||
.stream()
|
||||
.map(AnalyzeArgumentResult::getActualType)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else if (type instanceof PyClassLikeType) {
|
||||
return ((PyClassLikeType)type).getMemberNames(true, context);
|
||||
|
||||
/**
|
||||
* @param receiver call receiver
|
||||
* @param parameter callee parameter
|
||||
* @param argument passed argument
|
||||
* @param substitutions generics substitutions
|
||||
* @return an object that contains expected argument type, expected argument type after substitution, actual argument type,
|
||||
* flag with result of matching actual type against expected one and generics substitutions
|
||||
* <i>Note: generics substitutions are not recalculated if they were calculated before</i>
|
||||
*/
|
||||
@NotNull
|
||||
private AnalyzeArgumentResult analyzeArgument(@Nullable PyExpression receiver,
|
||||
@NotNull PyNamedParameter parameter,
|
||||
@NotNull PyExpression argument,
|
||||
@Nullable Map<PyGenericType, PyType> substitutions) {
|
||||
final PyType expectedArgumentType = parameter.getArgumentType(myTypeEvalContext);
|
||||
final PyType actualArgumentType = myTypeEvalContext.getType(argument);
|
||||
|
||||
if (expectedArgumentType == null) {
|
||||
return new AnalyzeArgumentResult(argument, null, null, actualArgumentType, true, substitutions);
|
||||
}
|
||||
else {
|
||||
substitutions = substitutions != null ? substitutions : PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext);
|
||||
|
||||
final PyType expectedTypeAfterSubstitution = PyTypeChecker.hasGenerics(expectedArgumentType, myTypeEvalContext)
|
||||
? PyTypeChecker.substitute(expectedArgumentType, substitutions, myTypeEvalContext)
|
||||
: null;
|
||||
return new AnalyzeArgumentResult(
|
||||
argument,
|
||||
expectedArgumentType,
|
||||
expectedTypeAfterSubstitution,
|
||||
actualArgumentType,
|
||||
actualArgumentType == null || PyTypeChecker.match(expectedArgumentType, actualArgumentType, myTypeEvalContext, substitutions),
|
||||
substitutions
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -309,4 +307,87 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
public String getDisplayName() {
|
||||
return "Type checker";
|
||||
}
|
||||
|
||||
static class AnalyzeCalleeResults {
|
||||
|
||||
@NotNull
|
||||
private final PyCallable myCallable;
|
||||
|
||||
@NotNull
|
||||
private final List<AnalyzeArgumentResult> myResults;
|
||||
|
||||
public AnalyzeCalleeResults(@NotNull PyCallable callable,
|
||||
@NotNull List<AnalyzeArgumentResult> results) {
|
||||
myCallable = callable;
|
||||
myResults = results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyCallable getCallable() {
|
||||
return myCallable;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<AnalyzeArgumentResult> getResults() {
|
||||
return myResults;
|
||||
}
|
||||
}
|
||||
|
||||
static class AnalyzeArgumentResult {
|
||||
|
||||
@NotNull
|
||||
private final PyExpression myArgument;
|
||||
|
||||
@Nullable
|
||||
private final PyType myExpectedType;
|
||||
|
||||
@Nullable
|
||||
private final PyType myExpectedTypeAfterSubstitution;
|
||||
|
||||
@Nullable
|
||||
private final PyType myActualType;
|
||||
|
||||
private final boolean myIsMatched;
|
||||
|
||||
@Nullable
|
||||
private final Map<PyGenericType, PyType> mySubstitutions;
|
||||
|
||||
public AnalyzeArgumentResult(@NotNull PyExpression argument,
|
||||
@Nullable PyType expectedType,
|
||||
@Nullable PyType expectedTypeAfterSubstitution,
|
||||
@Nullable PyType actualType,
|
||||
boolean isMatched,
|
||||
@Nullable Map<PyGenericType, PyType> substitutions) {
|
||||
myArgument = argument;
|
||||
myExpectedType = expectedType;
|
||||
myExpectedTypeAfterSubstitution = expectedTypeAfterSubstitution;
|
||||
myActualType = actualType;
|
||||
myIsMatched = isMatched;
|
||||
mySubstitutions = substitutions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyExpression getArgument() {
|
||||
return myArgument;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyType getExpectedType() {
|
||||
return myExpectedType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyType getExpectedTypeAfterSubstitution() {
|
||||
return myExpectedTypeAfterSubstitution;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyType getActualType() {
|
||||
return myActualType;
|
||||
}
|
||||
|
||||
public boolean isMatched() {
|
||||
return myIsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.Sets;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.xml.util.XmlStringUtil;
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.psi.PyBinaryExpression;
|
||||
import com.jetbrains.python.psi.PyCallExpression;
|
||||
import com.jetbrains.python.psi.PyCallSiteExpression;
|
||||
import com.jetbrains.python.psi.PySubscriptionExpression;
|
||||
import com.jetbrains.python.psi.types.PyClassLikeType;
|
||||
import com.jetbrains.python.psi.types.PyStructuralType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class PyTypeCheckerInspectionProblemRegistrar {
|
||||
|
||||
static void registerSingleCalleeProblem(@NotNull PyInspectionVisitor visitor,
|
||||
@NotNull PyTypeCheckerInspection.AnalyzeCalleeResults calleeResults,
|
||||
@NotNull TypeEvalContext context) {
|
||||
for (PyTypeCheckerInspection.AnalyzeArgumentResult argumentResult : calleeResults.getResults()) {
|
||||
if (argumentResult.isMatched()) continue;
|
||||
|
||||
visitor.registerProblem(argumentResult.getArgument(),
|
||||
getSingleCalleeProblemMessage(argumentResult, context),
|
||||
getSingleCalleeHighlightType(argumentResult.getExpectedTypeAfterSubstitution()));
|
||||
}
|
||||
}
|
||||
|
||||
static void registerMultiCalleeProblem(@NotNull PyInspectionVisitor visitor,
|
||||
@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull List<PyType> argumentTypes,
|
||||
@NotNull List<PyTypeCheckerInspection.AnalyzeCalleeResults> calleesResults,
|
||||
@NotNull TypeEvalContext context) {
|
||||
visitor.registerProblem(getMultiCalleeElementToHighlight(callSite),
|
||||
getMultiCalleeProblemMessage(argumentTypes, calleesResults, context),
|
||||
getMultiCalleeHighlightType(calleesResults));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getSingleCalleeProblemMessage(@NotNull PyTypeCheckerInspection.AnalyzeArgumentResult argumentResult,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final PyType actualType = argumentResult.getActualType();
|
||||
final PyType expectedType = argumentResult.getExpectedType();
|
||||
|
||||
assert actualType != null; // see PyTypeCheckerInspection.Visitor.analyzeArgument()
|
||||
assert expectedType != null; // see PyTypeCheckerInspection.Visitor.analyzeArgument()
|
||||
|
||||
final String actualTypeName = PythonDocumentationProvider.getTypeName(actualType, context);
|
||||
|
||||
if (expectedType instanceof PyStructuralType) {
|
||||
final Set<String> expectedAttributes = ((PyStructuralType)expectedType).getAttributeNames();
|
||||
final Set<String> actualAttributes = getAttributes(actualType, context);
|
||||
|
||||
if (actualAttributes != null) {
|
||||
final Sets.SetView<String> missingAttributes = Sets.difference(expectedAttributes, actualAttributes);
|
||||
if (missingAttributes.size() == 1) {
|
||||
return String.format("Type '%s' doesn't have expected attribute '%s'", actualTypeName, missingAttributes.iterator().next());
|
||||
}
|
||||
else {
|
||||
return String.format("Type '%s' doesn't have expected attributes %s",
|
||||
actualTypeName,
|
||||
StringUtil.join(missingAttributes, s -> String.format("'%s'", s), ", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String expectedTypeRepresentation = getSingleCalleeExpectedTypeRepresentation(expectedType,
|
||||
argumentResult.getExpectedTypeAfterSubstitution(),
|
||||
context);
|
||||
|
||||
return String.format("Expected type %s, got '%s' instead", expectedTypeRepresentation, actualTypeName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ProblemHighlightType getSingleCalleeHighlightType(@Nullable PyType expectedTypeAfterSubstitution) {
|
||||
return expectedTypeAfterSubstitution == null ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.WEAK_WARNING;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement getMultiCalleeElementToHighlight(@NotNull PyCallSiteExpression callSite) {
|
||||
if (callSite instanceof PyCallExpression) {
|
||||
return ObjectUtils.notNull(((PyCallExpression)callSite).getArgumentList(), callSite);
|
||||
}
|
||||
else if (callSite instanceof PyBinaryExpression) {
|
||||
return ObjectUtils.notNull(((PyBinaryExpression)callSite).getPsiOperator(), callSite);
|
||||
}
|
||||
else if (callSite instanceof PySubscriptionExpression) {
|
||||
return ObjectUtils.notNull(((PySubscriptionExpression)callSite).getIndexExpression(), callSite);
|
||||
}
|
||||
else {
|
||||
return callSite;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMultiCalleeProblemMessage(@NotNull List<PyType> argumentTypes,
|
||||
@NotNull List<PyTypeCheckerInspection.AnalyzeCalleeResults> calleesResults,
|
||||
@NotNull TypeEvalContext context) {
|
||||
return XmlStringUtil.wrapInHtml("Unexpected type(s):<br>" +
|
||||
XmlStringUtil.escapeString(getMultiCalleeActualTypesRepresentation(argumentTypes, context)) + "<br>" +
|
||||
"Possible types:<br>" +
|
||||
XmlStringUtil.escapeString(getMultiCalleePossibleExpectedTypesRepresentation(calleesResults, context)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param calleesResults results of analyzing arguments passed to callees
|
||||
* @return {@link ProblemHighlightType#WEAK_WARNING} if all expected types were substituted for all callees,
|
||||
* {@link ProblemHighlightType#GENERIC_ERROR_OR_WARNING} otherwise.
|
||||
*/
|
||||
@NotNull
|
||||
private static ProblemHighlightType getMultiCalleeHighlightType(@NotNull List<PyTypeCheckerInspection.AnalyzeCalleeResults> calleesResults) {
|
||||
final boolean allExpectedTypesWereSubstituted = calleesResults
|
||||
.stream()
|
||||
.flatMap(calleeResults -> calleeResults.getResults().stream())
|
||||
.allMatch(argumentResult -> argumentResult.getExpectedTypeAfterSubstitution() != null);
|
||||
|
||||
return allExpectedTypesWereSubstituted ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Set<String> getAttributes(@NotNull PyType type, @NotNull TypeEvalContext context) {
|
||||
if (type instanceof PyStructuralType) {
|
||||
return ((PyStructuralType)type).getAttributeNames();
|
||||
}
|
||||
else if (type instanceof PyClassLikeType) {
|
||||
return ((PyClassLikeType)type).getMemberNames(true, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getSingleCalleeExpectedTypeRepresentation(@NotNull PyType expectedType,
|
||||
@Nullable PyType expectedTypeAfterSubstitution,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final String expectedTypeName = PythonDocumentationProvider.getTypeName(expectedType, context);
|
||||
|
||||
return expectedTypeAfterSubstitution == null
|
||||
? String.format("'%s'", expectedTypeName)
|
||||
: String.format("'%s' (matched generic type '%s')",
|
||||
PythonDocumentationProvider.getTypeName(expectedTypeAfterSubstitution, context),
|
||||
expectedTypeName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMultiCalleeActualTypesRepresentation(@NotNull List<PyType> argumentTypes, @NotNull TypeEvalContext context) {
|
||||
return argumentTypes
|
||||
.stream()
|
||||
.map(type -> PythonDocumentationProvider.getTypeName(type, context))
|
||||
.collect(Collectors.joining(", ", "(", ")"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMultiCalleePossibleExpectedTypesRepresentation(@NotNull List<PyTypeCheckerInspection.AnalyzeCalleeResults> calleesResults,
|
||||
@NotNull TypeEvalContext context) {
|
||||
return calleesResults
|
||||
.stream()
|
||||
.map(calleeResult -> getMultiCalleeExpectedTypesRepresentation(calleeResult.getResults(), context))
|
||||
.collect(Collectors.joining("<br>"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMultiCalleeExpectedTypesRepresentation(@NotNull List<PyTypeCheckerInspection.AnalyzeArgumentResult> calleeResults,
|
||||
@NotNull TypeEvalContext context) {
|
||||
return calleeResults
|
||||
.stream()
|
||||
.map(argumentResult -> ObjectUtils.chooseNotNull(argumentResult.getExpectedTypeAfterSubstitution(), argumentResult.getExpectedType()))
|
||||
.map(type -> PythonDocumentationProvider.getTypeName(type, context))
|
||||
.collect(Collectors.joining(", ", "(", ")"));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 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.
|
||||
@@ -143,7 +143,7 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
|
||||
final List<PyType> matchedTypes = new ArrayList<>();
|
||||
for (PyTypeChecker.AnalyzeCallResults result : results) {
|
||||
boolean matched = true;
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry : result.getArguments().entrySet()) {
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry : result.getMapping().getMappedParameters().entrySet()) {
|
||||
final PyExpression argument = entry.getKey();
|
||||
final PyNamedParameter parameter = entry.getValue();
|
||||
if (parameter.isPositionalContainer() || parameter.isKeywordContainer()) {
|
||||
|
||||
@@ -645,20 +645,20 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Map<PyExpression, PyNamedParameter> mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyCallable callable,
|
||||
@NotNull List<PyParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
public static ArgumentMappingResults mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyCallable callable,
|
||||
@NotNull List<PyParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final List<PyExpression> arguments = PyTypeChecker.getArguments(callSite, callable);
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
final List<PyParameter> explicitParameters = PyTypeChecker.filterExplicitParameters(parameters, callable, callSite, resolveContext);
|
||||
return analyzeArguments(arguments, explicitParameters).getMappedParameters();
|
||||
return analyzeArguments(arguments, explicitParameters);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Map<PyExpression, PyNamedParameter> mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyCallable callable,
|
||||
@NotNull TypeEvalContext context) {
|
||||
public static ArgumentMappingResults mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyCallable callable,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final List<PyParameter> parameters = PyUtil.getParameters(callable, context);
|
||||
return mapArguments(callSite, callable, parameters, context);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 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.
|
||||
@@ -224,7 +224,9 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
}
|
||||
|
||||
final PyExpression receiver = PyTypeChecker.getReceiver(callSite, this);
|
||||
final Map<PyExpression, PyNamedParameter> mapping = PyCallExpressionHelper.mapArguments(callSite, this, context);
|
||||
final Map<PyExpression, PyNamedParameter> mapping =
|
||||
PyCallExpressionHelper.mapArguments(callSite, this, context).getMappedParameters();
|
||||
|
||||
return getCallType(receiver, mapping, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -619,7 +619,7 @@ public class PyTypeChecker {
|
||||
final List<AnalyzeCallResults> results = new ArrayList<>();
|
||||
for (PyCallable callable : multiResolveCallee(callSite, context)) {
|
||||
final PyExpression receiver = getReceiver(callSite, callable);
|
||||
final Map<PyExpression, PyNamedParameter> mapping = PyCallExpressionHelper.mapArguments(callSite, callable, context);
|
||||
final PyCallExpressionHelper.ArgumentMappingResults mapping = PyCallExpressionHelper.mapArguments(callSite, callable, context);
|
||||
results.add(new AnalyzeCallResults(callable, receiver, mapping));
|
||||
}
|
||||
return results;
|
||||
@@ -817,13 +817,13 @@ public class PyTypeChecker {
|
||||
public static class AnalyzeCallResults {
|
||||
@NotNull private final PyCallable myCallable;
|
||||
@Nullable private final PyExpression myReceiver;
|
||||
@NotNull private final Map<PyExpression, PyNamedParameter> myArguments;
|
||||
@NotNull private final PyCallExpressionHelper.ArgumentMappingResults myMapping;
|
||||
|
||||
public AnalyzeCallResults(@NotNull PyCallable callable, @Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyNamedParameter> arguments) {
|
||||
@NotNull PyCallExpressionHelper.ArgumentMappingResults mapping) {
|
||||
myCallable = callable;
|
||||
myReceiver = receiver;
|
||||
myArguments = arguments;
|
||||
myMapping = mapping;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -837,8 +837,8 @@ public class PyTypeChecker {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyExpression, PyNamedParameter> getArguments() {
|
||||
return myArguments;
|
||||
public PyCallExpressionHelper.ArgumentMappingResults getMapping() {
|
||||
return myMapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 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.
|
||||
@@ -124,11 +124,11 @@ public class PyiTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
|
||||
final PyExpression receiver = PyTypeChecker.getReceiver(callSite, overload);
|
||||
final Map<PyExpression, PyNamedParameter> mapping = mapArguments(callSite, overload, context);
|
||||
final PyCallExpressionHelper.ArgumentMappingResults mapping = mapArguments(callSite, overload, context);
|
||||
if (mapping == null) {
|
||||
continue;
|
||||
}
|
||||
final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(receiver, mapping, context);
|
||||
final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(receiver, mapping.getMappedParameters(), context);
|
||||
|
||||
final PyType unifiedType = substitutions != null ? PyTypeChecker.substitute(returnType, substitutions, context) : null;
|
||||
if (unifiedType != null) {
|
||||
@@ -210,15 +210,17 @@ public class PyiTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Map<PyExpression, PyNamedParameter> mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyFunction function,
|
||||
@NotNull TypeEvalContext context) {
|
||||
private static PyCallExpressionHelper.ArgumentMappingResults mapArguments(@NotNull PyCallSiteExpression callSite,
|
||||
@NotNull PyFunction function,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final List<PyParameter> parameters = Arrays.asList(function.getParameterList().getParameters());
|
||||
final Map<PyExpression, PyNamedParameter> map = PyCallExpressionHelper.mapArguments(callSite, function, parameters, context);
|
||||
final PyCallExpressionHelper.ArgumentMappingResults mapping =
|
||||
PyCallExpressionHelper.mapArguments(callSite, function, parameters, context);
|
||||
|
||||
final PyCallExpression callExpr = as(callSite, PyCallExpression.class);
|
||||
if (callExpr != null && callExpr.getArguments().length != map.size()) {
|
||||
if (callExpr != null && callExpr.getArguments().length != mapping.getMappedParameters().size()) {
|
||||
return null;
|
||||
}
|
||||
return map;
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ def test():
|
||||
float(False)
|
||||
complex(False)
|
||||
divmod(False, False)
|
||||
divmod(<warning descr="Expected type 'int', got 'str' instead">'foo'</warning>, <warning descr="Expected type 'int', got 'unicode' instead">u'bar'</warning>)
|
||||
divmod<warning descr="Unexpected type(s):(str, unicode)Possible types:(float, float)(int, int)">('foo', u'bar')</warning>
|
||||
pow(False, True)
|
||||
round(False, 'foo')
|
||||
round<warning descr="Unexpected type(s):(bool, str)Possible types:(SupportsRound, int)(float, int)">(False, 'foo')</warning>
|
||||
|
||||
@@ -13,4 +13,4 @@ def test_numerics():
|
||||
divmod(False, False)
|
||||
divmod(<warning descr="Expected type 'TypeVar('_N', int, float)', got 'bytes' instead">b'foo'</warning>, <warning descr="Expected type 'TypeVar('_N', int, float)', got 'str' instead">'bar'</warning>)
|
||||
pow(False, True)
|
||||
round(False, 'foo')
|
||||
round<warning descr="Unexpected type(s):(bool, str)Possible types:(SupportsRound, int)(float, int)">(False, 'foo')</warning>
|
||||
|
||||
@@ -7,7 +7,7 @@ def test_second_form():
|
||||
|
||||
|
||||
def test_second_form_fail():
|
||||
for chunk in iter(<weak_warning descr="Expected type 'Iterable' (matched generic type 'Iterable[TypeVar('_T')]'), got 'int' instead">10</weak_warning>, ''):
|
||||
for chunk in iter(<weak_warning descr="Expected type '() -> Any' (matched generic type '() -> TypeVar('_T')'), got 'int' instead">10</weak_warning>, ''):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from m1 import g, Gen
|
||||
|
||||
g(<warning descr="Expected type 'dict', got 'int' instead">Gen(10).get(10, 10)</warning>)
|
||||
g(Gen(10).get(10, <weak_warning descr="Expected type 'int' (matched generic type 'TypeVar('T')'), got 'str' instead">'foo'</weak_warning>))
|
||||
g(Gen('foo').get(10, <weak_warning descr="Expected type 'str' (matched generic type 'TypeVar('T')'), got 'int' instead">10</weak_warning>))
|
||||
g(Gen(10).get<warning descr="Unexpected type(s):(int, str)Possible types:(int, int)(str, int)">(10, 'foo')</warning>)
|
||||
g(Gen('foo').get<warning descr="Unexpected type(s):(int, int)Possible types:(int, str)(str, str)">(10, 10)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'str' instead">Gen('foo').get(10, 'foo')</warning>)
|
||||
|
||||
@@ -4,7 +4,7 @@ from m1 import f, g, C, stub_only
|
||||
def test_overloaded_function(x):
|
||||
g(<warning descr="Expected type 'dict', got 'int' instead">f(10)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'str' instead">f('foo')</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">f(<warning descr="Expected type 'int', got 'Dict[int, int]' instead">{1: 2}</warning>)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">f(<warning descr="Unexpected type(s):(Dict[int, int])Possible types:(str)(int)">{1: 2}</warning>)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">f(x)</warning>)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ def test_overloaded_subscription_operator_parameters():
|
||||
c = C()
|
||||
print(c[10])
|
||||
print(c['foo'])
|
||||
print(c[<warning descr="Expected type 'int', got 'Dict[int, int]' instead">{1: 2}</warning>])
|
||||
print(c[<warning descr="Unexpected type(s):(Dict[int, int])Possible types:(int)(str)">{1: 2}</warning>])
|
||||
|
||||
|
||||
def test_overloaded_binary_operator_parameters():
|
||||
@@ -26,4 +26,4 @@ def test_stub_only_function(x):
|
||||
g(<warning descr="Expected type 'dict', got 'int' instead">stub_only(10)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'str' instead">stub_only('foo')</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">stub_only(x)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">stub_only(<warning descr="Expected type 'int', got 'Dict[int, int]' instead">{1: 2}</warning>)</warning>)
|
||||
g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">stub_only(<warning descr="Unexpected type(s):(Dict[int, int])Possible types:(str)(int)">{1: 2}</warning>)</warning>)
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
from m1 import f, g, h
|
||||
|
||||
|
||||
def test_different_number_of_parameters():
|
||||
f(5)
|
||||
f(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>)
|
||||
|
||||
f(5, "a")
|
||||
f(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>, "b")
|
||||
f(5, <warning descr="Expected type 'str', got 'int' instead">6</warning>)
|
||||
f(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>, <warning descr="Expected type 'str', got 'int' instead">5</warning>)
|
||||
|
||||
|
||||
def test_same_number_of_parameters_but_one_is_default():
|
||||
g(5)
|
||||
g(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>)
|
||||
|
||||
g<warning descr="Unexpected type(s):(str, bool)Possible types:(int, str)(int, Optional[bool])">("a", False)</warning>
|
||||
g<warning descr="Unexpected type(s):(int, int)Possible types:(int, str)(int, Optional[bool])">(5, 6)</warning>
|
||||
g<warning descr="Unexpected type(s):(bool, int)Possible types:(int, str)(int, Optional[bool])">(False, 5)</warning>
|
||||
|
||||
g(5, "a")
|
||||
g<warning descr="Unexpected type(s):(str, str)Possible types:(int, str)(int, Optional[bool])">("a", "b")</warning>
|
||||
g<warning descr="Unexpected type(s):(int, int)Possible types:(int, str)(int, Optional[bool])">(5, 6)</warning>
|
||||
g<warning descr="Unexpected type(s):(str, int)Possible types:(int, str)(int, Optional[bool])">("a", 5)</warning>
|
||||
|
||||
|
||||
def test_different_number_of_parameters_one_is_default():
|
||||
h(5)
|
||||
h(<warning descr="Unexpected type(s):((x: Any) -> Any)Possible types:(str)(int)">lambda x: x</warning>)
|
||||
|
||||
h("a")
|
||||
|
||||
h("a", False)
|
||||
h(<warning descr="Expected type 'str', got 'int' instead">5</warning>, False) # fail
|
||||
h("a", <warning descr="Expected type 'Optional[bool]', got 'int' instead">5</warning>) # fail
|
||||
h(<warning descr="Expected type 'str', got 'bool' instead">False</warning>, <warning descr="Expected type 'Optional[bool]', got 'str' instead">"a"</warning>) # fail
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(i: int) -> None: ...
|
||||
@overload
|
||||
def f(i: int, s: str) -> None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def g(i: int, b: bool = ...) -> None: ...
|
||||
@overload
|
||||
def g(i: int, s: str) -> None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def h(i: int) -> None: ...
|
||||
@overload
|
||||
def h(i: str, b: bool = ...) -> None: ...
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 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.
|
||||
@@ -63,6 +63,10 @@ public class PyiInspectionsTest extends PyTestCase {
|
||||
doPyTest(PyTypeCheckerInspection.class);
|
||||
}
|
||||
|
||||
public void testOverloadsWithDifferentNumberOfParameters() {
|
||||
doPyTest(PyTypeCheckerInspection.class);
|
||||
}
|
||||
|
||||
public void testOverloadedGenerics() {
|
||||
doPyTest(PyTypeCheckerInspection.class);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user