Simplified matching instances and definitions (PY-38786)

The problem was that every definition matches `TypeVar(..., bound=type)` since it's metaclass is `type`.
The metaclass successfully matches expected class but no substitution is created.

Tests were updated since now `A` matches `_C = TypeVar("_C", bound=type)` in attrs stubs and `A` is inferred.
In any case `attrs` support is tested in other tests, these tests are about unknown (for us) decorators.

GitOrigin-RevId: ced931bd2ff732a8f7a95726d3f2837417d2d6c2
This commit is contained in:
Semyon Proshev
2020-04-16 17:05:07 +00:00
committed by intellij-monorepo-bot
parent 17d5a4bbce
commit 04a502ed31
4 changed files with 44 additions and 47 deletions
@@ -97,13 +97,6 @@ public class PyTypeChecker {
}
}
if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType) {
Optional<Boolean> match = match((PyInstantiableType)expected, (PyInstantiableType)actual, context);
if (match.isPresent()) {
return match;
}
}
if (expected instanceof PyGenericType) {
return Optional.of(match((PyGenericType)expected, actual, context));
}
@@ -181,25 +174,6 @@ public class PyTypeChecker {
return Optional.empty();
}
@NotNull
private static Optional<Boolean> match(@NotNull PyInstantiableType expected, @NotNull PyInstantiableType actual,
@NotNull MatchContext context) {
if (expected instanceof PyGenericType && typeVarAcceptsBothClassAndInstanceTypes((PyGenericType)expected)) {
return Optional.empty();
}
if (expected.isDefinition() ^ actual.isDefinition()) {
if (actual.isDefinition() &&
actual instanceof PyClassLikeType &&
matchClassObjectAndMetaclass(expected, (PyClassLikeType)actual, context)) {
return Optional.of(true);
}
return Optional.of(false);
}
return Optional.empty();
}
/**
* Match {@code actual} versus {@code PyGenericType expected}.
*
@@ -234,6 +208,9 @@ public class PyTypeChecker {
}
if (actual != null) {
if (expected.isDefinition() && !(actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition())) {
return false;
}
context.substitutions.put(expected, actual);
}
else if (bound != null) {
@@ -275,6 +252,14 @@ public class PyTypeChecker {
return Optional.of(true);
}
if (expected.isDefinition() ^ actual.isDefinition()) {
if (!expected.isDefinition() && actual.isDefinition()) {
final PyClassLikeType metaClass = actual.getMetaClassType(context.context, true);
return Optional.of(metaClass != null && match((PyType)expected, metaClass.toInstance(), context).orElse(true));
}
return Optional.of(false);
}
if (expected instanceof PyTupleType && actual instanceof PyTupleType) {
return match((PyTupleType)expected, (PyTupleType)actual, context);
}
@@ -488,21 +473,6 @@ public class PyTypeChecker {
return Optional.empty();
}
private static boolean matchClassObjectAndMetaclass(@NotNull PyType expected,
@NotNull PyClassLikeType actual,
@NotNull MatchContext context) {
if (!actual.isDefinition()) {
return false;
}
final PyClassLikeType metaClass = actual.getMetaClassType(context.context, true);
return metaClass != null && match(expected, metaClass.toInstance(), context).orElse(true);
}
private static boolean typeVarAcceptsBothClassAndInstanceTypes(@NotNull PyGenericType typeVar) {
return !typeVar.isDefinition() && typeVar.getBound() == null;
}
private static boolean consistsOfSameElementNumberTuples(@NotNull PyUnionType unionType, int elementCount) {
for (PyType type : unionType.getMembers()) {
if (type instanceof PyTupleType) {
@@ -1,8 +1,8 @@
import attr
import abcd
class A(object):
a = attr.ib()
a = abcd.ib()
A = attr.s(A)
A = abcd.s(A)
A(a="test")
@@ -1,7 +1,7 @@
import attr
import abcd
@attr.s
@abcd.s
class A(object):
a = attr.ib()
a = abcd.ib()
A(a="test")
@@ -3877,6 +3877,33 @@ public class PyTypeTest extends PyTestCase {
"expr = example.__doc__");
}
// PY-38786
public void testParticularTypeAgainstTypeVarBoundedWithBuiltinType() {
runWithLanguageLevel(
LanguageLevel.getLatest(),
() -> doTest("Type[MyClass]",
"from typing import TypeVar, Type\n" +
"\n" +
"T = TypeVar(\"T\", bound=type)\n" +
"\n" +
"def foo(t: T) -> T:\n" +
" pass\n" +
"\n" +
"class MyClass:\n" +
" pass\n" +
"\n" +
"expr = foo(MyClass)")
);
}
// PY-38786
public void testDunderSubclasses() {
doTest("List[Type[Base]]",
"class Base(object):\n" +
" pass\n" +
"expr = Base.__subclasses__()");
}
private static List<TypeEvalContext> getTypeEvalContexts(@NotNull PyExpression element) {
return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());