mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PyBinaryExpression#getReceiver() is aware of chained comparisons
Otherwise, it might lead to some subtle problems when this method is used for unification of generic types. Also, PyPrefixExpression now extends PyCallSiteExpression since technically it is and should have been marked this way long ago.
This commit is contained in:
@@ -16,34 +16,13 @@
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface PyBinaryExpression extends PyQualifiedExpression, PyCallSiteExpression, PyReferenceOwner {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default PyExpression getReceiver(@Nullable PyCallable resolvedCallee) {
|
||||
return isRightOperator(resolvedCallee) ? getRightExpression() : getLeftExpression();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
default List<PyExpression> getArguments(@Nullable PyCallable resolvedCallee) {
|
||||
return Collections.singletonList(isRightOperator(resolvedCallee) ? getLeftExpression() : getRightExpression());
|
||||
}
|
||||
|
||||
default boolean isRightOperator(@Nullable PyCallable resolvedCallee) {
|
||||
return resolvedCallee != null && PyNames.isRightOperatorName(getReferencedName(), resolvedCallee.getName());
|
||||
}
|
||||
|
||||
PyExpression getLeftExpression();
|
||||
@Nullable PyExpression getRightExpression();
|
||||
|
||||
@@ -57,4 +36,6 @@ public interface PyBinaryExpression extends PyQualifiedExpression, PyCallSiteExp
|
||||
|
||||
PyExpression getOppositeExpression(PyExpression expression)
|
||||
throws IllegalArgumentException;
|
||||
|
||||
boolean isRightOperator(@Nullable PyCallable resolvedCallee);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,16 @@ import java.util.List;
|
||||
*/
|
||||
public interface PyCallSiteExpression extends PyExpression {
|
||||
|
||||
/**
|
||||
* Returns an expression that is treated as a receiver for this explicit or implicit (read, operator) call.
|
||||
* <p>
|
||||
* For most operator expressions it returns the result of {@code getOperator()} since it naturally represents
|
||||
* the object on which a special magic method is called. However for binary expressions that additionally
|
||||
* can be reversible such as {@code __add__} and {@code __radd__} it also takes into account name of the
|
||||
* actual callee method and chained comparisons order if any.
|
||||
*
|
||||
* @param resolvedCallee optional callee corresponding to the call. Without it the receiver is deduced purely syntactically.
|
||||
*/
|
||||
@Nullable
|
||||
PyExpression getReceiver(@Nullable PyCallable resolvedCallee);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface PyPrefixExpression extends PyQualifiedExpression, PyReferenceOwner {
|
||||
public interface PyPrefixExpression extends PyQualifiedExpression, PyReferenceOwner, PyCallSiteExpression {
|
||||
@Nullable
|
||||
PyExpression getOperand();
|
||||
PyElementType getOperator();
|
||||
|
||||
@@ -33,10 +33,9 @@ import com.jetbrains.python.psi.types.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -215,6 +214,34 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
|
||||
return op != null ? op.getNode() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyExpression getReceiver(@Nullable PyCallable resolvedCallee) {
|
||||
return isRightOperator(resolvedCallee) ? getRightExpression() : getChainedComparisonAwareLeftExpression();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyExpression> getArguments(@Nullable PyCallable resolvedCallee) {
|
||||
return Collections.singletonList(isRightOperator(resolvedCallee) ? getChainedComparisonAwareLeftExpression() : getRightExpression());
|
||||
}
|
||||
|
||||
public boolean isRightOperator(@Nullable PyCallable resolvedCallee) {
|
||||
return resolvedCallee != null && PyNames.isRightOperatorName(getReferencedName(), resolvedCallee.getName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PyExpression getChainedComparisonAwareLeftExpression() {
|
||||
final PyExpression leftOperand = getLeftExpression();
|
||||
if (PyTokenTypes.COMPARISON_OPERATIONS.contains(getOperator())) {
|
||||
final PyBinaryExpression leftBinaryExpr = as(leftOperand, PyBinaryExpression.class);
|
||||
if (leftBinaryExpr != null && PyTokenTypes.COMPARISON_OPERATIONS.contains(leftBinaryExpr.getOperator())) {
|
||||
return leftBinaryExpr.getRightExpression();
|
||||
}
|
||||
}
|
||||
return leftOperand;
|
||||
}
|
||||
|
||||
private static boolean operandIsKnown(@Nullable PyExpression operand, @NotNull TypeEvalContext context) {
|
||||
if (operand == null) return false;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -126,6 +127,18 @@ public class PyPrefixExpressionImpl extends PyElementImpl implements PyPrefixExp
|
||||
return op != null ? op.getNode() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyExpression getReceiver(@Nullable PyCallable resolvedCallee) {
|
||||
return getOperand();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyExpression> getArguments(@Nullable PyCallable resolvedCallee) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Ref<PyType> getGeneratorReturnType(@Nullable PyType type) {
|
||||
if (type instanceof PyClassLikeType && type instanceof PyCollectionType) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.jetbrains.python.psi.impl.references;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
@@ -33,8 +32,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@@ -92,31 +89,12 @@ public class PyOperatorReference extends PyReferenceImpl {
|
||||
|
||||
@Nullable
|
||||
public PyExpression getReceiver() {
|
||||
if (myElement instanceof PyBinaryExpression) {
|
||||
return getBinaryOperatorReceiver((PyBinaryExpression)myElement);
|
||||
}
|
||||
else if (myElement instanceof PySubscriptionExpression) {
|
||||
return ((PySubscriptionExpression)myElement).getOperand();
|
||||
}
|
||||
else if (myElement instanceof PyPrefixExpression) {
|
||||
return ((PyPrefixExpression)myElement).getOperand();
|
||||
if (myElement instanceof PyCallSiteExpression) {
|
||||
return ((PyCallSiteExpression)myElement).getReceiver(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyExpression getBinaryOperatorReceiver(PyBinaryExpression expr) {
|
||||
final PyExpression leftOperand = expr.getLeftExpression();
|
||||
// Chained comparisons
|
||||
if (PyTokenTypes.COMPARISON_OPERATIONS.contains(expr.getOperator())) {
|
||||
final PyBinaryExpression leftBinaryExpr = as(leftOperand, PyBinaryExpression.class);
|
||||
if (leftBinaryExpr != null && PyTokenTypes.COMPARISON_OPERATIONS.contains(leftBinaryExpr.getOperator())) {
|
||||
return leftBinaryExpr.getRightExpression();
|
||||
}
|
||||
}
|
||||
return leftOperand;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<RatedResolveResult> resolveLeftAndRightOperators(@NotNull PyBinaryExpression expr, @Nullable String name) {
|
||||
final List<RatedResolveResult> result = new ArrayList<>();
|
||||
@@ -125,7 +103,7 @@ public class PyOperatorReference extends PyReferenceImpl {
|
||||
typeEvalContext.trace("Trying to resolve left operator");
|
||||
typeEvalContext.traceIndent();
|
||||
try {
|
||||
result.addAll(resolveMember(getBinaryOperatorReceiver(expr), name));
|
||||
result.addAll(resolveMember(expr.getReceiver(null), name));
|
||||
}
|
||||
finally {
|
||||
typeEvalContext.traceUnindent();
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class MyClass(Generic[T]):
|
||||
def __init__(self, x: T):
|
||||
pass
|
||||
|
||||
def __lt__(self, other: 'MyClass[T]'):
|
||||
pass
|
||||
|
||||
|
||||
x = MyClass(1) < MyClass(2) < <weak_warning descr="Expected type 'MyClass[int]' (matched generic type 'MyClass[T]'), got 'MyClass[str]' instead">MyClass('foo')</weak_warning>
|
||||
@@ -273,4 +273,11 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
public void testMatchingOpenFunctionCallTypesPy3() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testChainedComparisonsGenericMatching() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> {
|
||||
myFixture.copyDirectoryToProject("typing/typing.py", TEST_DIRECTORY);
|
||||
doTest();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user