[ty] default-specialize class-literal types in assignment to generic-alias types#21883
[ty] default-specialize class-literal types in assignment to generic-alias types#21883
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
|
Looks like this fixes a few current false positives on the ecosystem, but the main point of this is to unblock type-of-cls on classmethods, where this starts to come up a lot more. |
dhruvmanila
left a comment
There was a problem hiding this comment.
Can you also add similar tests for ParamSpec? I think that should just work but if not, you can add a TODO comment and I can look at it later:
from typing import final
@final
class Foo[**P]: ...
def expects_type_foo(f: type[Foo]): ...
def expects_type_foo_of_int(x: type[Foo[int]]): ...
expects_type_foo(Foo) # ok
expects_type_foo_of_int(Foo[int]) # ok
expects_type_foo_of_int(Foo[str]) # not ok
@final
class FooDefault[**P = [int, str]]: ...
def expects_type_foo_default(f: type[FooDefault]): ...
def expects_type_foo_default_of_int(f: type[FooDefault[int]]): ...
def expects_type_foo_default_of_int_str(f: type[FooDefault[int, str]]): ...
expects_type_foo_default(FooDefault) # ok
expects_type_foo_default(FooDefault[int, str]) # ok
expects_type_foo_default_of_int_str(FooDefault) # ok
expects_type_foo_default(FooDefault[int]) # not ok
expects_type_foo_default_of_int(FooDefault[str]) # not ok
expects_type_foo_default_of_int_str(FooDefault[str, int]) # not ok|
@dhruvmanila @AlexWaygood could one of you please review my changes here? (@dhruvmanila, I noticed that some of the |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
0 | 6 | 0 |
invalid-assignment |
0 | 1 | 0 |
unsupported-base |
0 | 1 | 0 |
| Total | 0 | 8 | 0 |
|
Does this now also fix astral-sh/ty#1513? Or do we also need to make some changes to Are the new ecosystem hits on |
It looks like it does!
Looking into it |
Amazing! Is it worth adding some tests for that specifically? |
|
Might want to double-check our alias<->class disjointness logic anyway, in case it's now inconsistent with our subtyping logic for these types |
I tried out this PR (without the new branch that you added) and it seems to be the following branch that's handling it: (Type::GenericAlias(alias), _) => ClassType::from(alias)
.metaclass_instance_type(db)
.has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
),I might have to spend some time here to understand if there's anything missing or not. |
76a47c9 to
573af3f
Compare
|
Ah, I think it's because the inferred variance of a from typing import final
class A: ...
class B(A): ...
class C(B): ...
class D: ...
@final
class Foo[**P]: ...
def expects_type_foo_of_b(x: type[Foo[B]]): ...
expects_type_foo_of_b(Foo[A]) # only Pyright error
expects_type_foo_of_b(Foo[B]) # ok
expects_type_foo_of_b(Foo[C]) # both Pyright and mypy error
expects_type_foo_of_b(Foo[D]) # both Pyright and mypy errorI can do it as a follow-up unless you want to add it in this PR. |
573af3f to
76245a3
Compare
AlexWaygood
left a comment
There was a problem hiding this comment.
LGTM, though I'd still love to double-check #21883 (comment) (do the property tests pass on this branch?)
CodSpeed Performance ReportMerging #21883 will not alter performanceComparing Summary
Footnotes
|
Yes, sorry. This was not a "please review this again" push, but rather just a "I want to see if the pandas false positives are gone now" push. I didn't want to move Carl's PR to draft 😄 |
76245a3 to
b84f253
Compare
The property tests pass on this branch, which might just be because we don't have the necessary "ingredients" in the type pool. I added some tests for |
b84f253 to
37ac3de
Compare
Fine by me! |
37ac3de to
d500d7e
Compare
carljm
left a comment
There was a problem hiding this comment.
Looks good, thank you for the additional fixes!
| # Also OK, the specialized `C[int, str]` is assignable to `type[C[int, str]]` | ||
| expects_type_c_of_int_and_str(C[int, str]) | ||
|
|
||
| # TODO: these should be errors |
There was a problem hiding this comment.
Do we know what we are lacking here? Is there an issue to track it?
There was a problem hiding this comment.
I think we should just add a use of P in the body of C, and then we will infer P as covariant (or invariant, depending what kind of use we add) and these should be fixed. That's what we do in the non-ParamSpec tests here already.
There was a problem hiding this comment.
I didn't manage to add P in an invariant or covariant position, and adding it contravariantly does not fix the tests. After talking to Carl, we'll postpone this as well. I'll open an issue.
I think inferring an "unused" ParamSpec type parameter (as in your example, and as in the current tests here) as bivariant is correct, according to our current variance inference logic. We do have an issue open to change this and always infer covariant for unused typevars: astral-sh/ty#1728. When we do this, it should naturally impact ParamSpec type parameters also, I think. If we want to match pyright and always infer all ParamSpec as invariant, regardless of use, that would be a separate change specific to ParamSpec. But I'm not sure I see the rationale for that. |
Fixes astral-sh/ty#1832, fixes astral-sh/ty#1513
Summary
A class object
C(for which we infer an unspecializedClassLiteraltype) should always be assignable to the typetype[C](which is default-specialized, ifCis generic). We already implemented this for most cases, but we missed the case of a generic final type, where we simplifytype[C]to theGenericAliastype for the default specialization ofC. So we also need to implement this assignability of genericClassLiteraltypes as-if default-specialized.Test Plan
Added mdtests that failed before this PR.