[ty] Add subtyping between Callable types and class literals with __init__#17638
Conversation
|
|
I think i still have some thinking to do regarding the logic here, and perhaps without |
__init__
__init____init__
| class C: | ||
| def __new__(cls, *args, **kwargs) -> "C": | ||
| return super().__new__(cls) | ||
|
|
||
| def __init__(self, x: int) -> None: ... | ||
|
|
||
| static_assert(not is_subtype_of(TypeOf[C], Callable[[int], C])) |
There was a problem hiding this comment.
As per the spec this "shouldn't" pass, but there's maybe an argument that it should pass
There was a problem hiding this comment.
(As is the test without "not" should pass)
There was a problem hiding this comment.
Why does this not pass according to the spec? It seems like the spec would say in this case that the callable type of C would be the union of the __new__ signature, and the __init__ bound-method signature with replaced return type. That union seems like it should be a subtype of Callable[[int], C]?
There was a problem hiding this comment.
Ah, so yeah you're right, but what is happening here (after i added ":Any" to args and kwargs)
is we end up calling is_subtype with
self: (*args: Any, **kwargs: Any) -> C
target: (int, /) -> C
which returns false because self isnt fully static
|
Addressing some of the mypy primer issues
|
carljm
left a comment
There was a problem hiding this comment.
This looks pretty solid! Sorry for the slow review. A few comments/questions.
| class C: | ||
| def __new__(cls, *args, **kwargs) -> "C": | ||
| return super().__new__(cls) | ||
|
|
||
| def __init__(self, x: int) -> None: ... | ||
|
|
||
| static_assert(not is_subtype_of(TypeOf[C], Callable[[int], C])) |
There was a problem hiding this comment.
Why does this not pass according to the spec? It seems like the spec would say in this case that the callable type of C would be the union of the __new__ signature, and the __init__ bound-method signature with replaced return type. That union seems like it should be a subtype of Callable[[int], C]?
| ) | ||
| .symbol; | ||
|
|
||
| // TODO: should be the concrete value of `Self` |
There was a problem hiding this comment.
I'm not actually sure what the spec means by "the concrete value of Self" here; I don't know what else that could be, other than what you have here (for non-generic classes, anyway).
| .symbol; | ||
|
|
||
| // TODO: should be the concrete value of `Self` | ||
| let correct_return_type = Type::instance(db, ClassType::NonGeneric(self)); |
There was a problem hiding this comment.
What about generic classes? Can we add a test for that case? Do we need a TODO?
Should this just be self_ty.to_instance() instead, so we don't have to worry about those distinctions here?
There was a problem hiding this comment.
I think generic cases need some more work, do you think into_callable belongs in ClassLiteral or should it maybe be in ClassType?
| (Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => { | ||
| UnionType::from_elements( | ||
| db, | ||
| vec![dunder_new_function, synthesized_dunder_init_callable], |
There was a problem hiding this comment.
Shouldn't this be dunder_new_bound_method instead of dunder_new_function? Otherwise we wrongly include the cls argument. I feel like that might be why the one test you commented on above is saying "not subtype", when it seems like it should say "subtype".
There was a problem hiding this comment.
I already call into_bound_method_type further up
| (Some(dunder_constructor_function), None) | ||
| | (None, Some(dunder_constructor_function)) => dunder_constructor_function, |
There was a problem hiding this comment.
And similarly here I feel like we need to be returning the bound method version of __new__?
__init____init__
|
@carljm is there any urgency for this? If anyone can provide any more insights on another review I'd be happy to implement them asap. I'm aware you did give some comments but I'm still a bit unsure about them |
|
@MatthewMckee4 Sorry for the delay, I'm aware this is just waiting for me to look at it again! It's PyCon this week so a number of us are distracted :) |
|
All good, there's no urgency on my end, just wanted to make sure this wasn't forgotten. Thanks |
* main: (246 commits) [ty] Simplify signature types, use them in `CallableType` (astral-sh#18344) [ty] Support ephemeral uv virtual environments (astral-sh#18335) Add a `ViolationMetadata::rule` method (astral-sh#18234) Return `DiagnosticGuard` from `Checker::report_diagnostic` (astral-sh#18232) [flake8_use_pathlib]: Replace os.symlink with Path.symlink_to (PTH211) (astral-sh#18337) [ty] Support cancellation and retry in the server (astral-sh#18273) [ty] Synthetic function-like callables (astral-sh#18242) [ty] Support publishing diagnostics in the server (astral-sh#18309) Add Autofix for ISC003 (astral-sh#18256) [`pyupgrade`]: new rule UP050 (`useless-class-metaclass-type`) (astral-sh#18334) [pycodestyle] Make `E712` suggestion not assume a context (astral-sh#18328) put similar dunder-call tests next to each other (astral-sh#18343) [ty] Derive `PartialOrd, Ord` for `KnownInstanceType` (astral-sh#18340) [ty] Simplify `Type::try_bool()` (astral-sh#18342) [ty] Simplify `Type::normalized` slightly (astral-sh#18339) [ty] Move arviz off the list of selected primer projects (astral-sh#18336) [ty] Add --config-file CLI arg (astral-sh#18083) [ty] Tell the user why we inferred a certain Python version when reporting version-specific syntax errors (astral-sh#18295) [ty] Implement implicit inheritance from `Generic[]` for PEP-695 generic classes (astral-sh#18283) [ty] Add hint if async context manager is used in non-async with statement (astral-sh#18299) ...
4aa33be to
084a338
Compare
carljm
left a comment
There was a problem hiding this comment.
Made a few updates here, this looks good to me now, and the primer diff looks great. Gonna go ahead and merge!
Summary
Allow classes with
__init__to be subtypes ofCallableFixes astral-sh/ty#358
Test Plan
Update is_subtype_of.md