-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[PEP 695] Fix incorrect Variance Computation with Polymorphic Methods. #19466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
d7b88cc
a0b037e
ab680d1
0f33c79
5d16a08
25e8efa
9870139
0b2aead
307382e
01e8651
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -450,6 +450,21 @@ class Contra2[T]: | |
| d1: Contra2[int] = Contra2[float]() | ||
| d2: Contra2[float] = Contra2[int]() # E: Incompatible types in assignment (expression has type "Contra2[int]", variable has type "Contra2[float]") | ||
|
|
||
| [case testPEP695InferVariancePolymorphicMethod] | ||
| class Cov[T]: | ||
| def get(self) -> T: ... | ||
| def new[S](self: "Cov[S]", arg: list[S]) -> "Cov[S]": ... | ||
|
|
||
| cov_pos: Cov[object] = Cov[int]() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not safe, if I continue this example like this I get an error at runtime that is not detected: class Sub(Cov[int]):
def new(self, arg: list[int]) -> Sub:
print(arg[0].to_bytes())
return self
cov_pos: Cov[object] = Sub()
cov_pos.new([object()])On a more general level, how exactly this: class Cov[T]:
def get(self) -> T: ...
def new[S](self: Cov[S], arg: list[S]) -> Cov[S]: ...is different from this class Cov[T]:
def get(self) -> T: ...
def new(self, arg: list[T]) -> Cov[T]: ...? Maybe I am missing something but these two are literally identical in terms of semantics. Can you give an example of uses of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think they are only identical when called as a bound method, but not when called on the class and passing class Cov[T]:
def get(self) -> T: ...
def new[S](self: Cov[S], arg: list[S]) -> Cov[S]: ...
x: Cov[int]
Cov[str].new(x, [1,2,3]) # OKwhereas class Cov[T]:
def get(self) -> T: ...
def new(self, arg: list[T]) -> Cov[T]: ...
x: Cov[int]
Cov[str].new(x, [1,2,3]) # not OK, self must be subtype of Cov[str]
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consequently, I'd argue that your So I'd say this is actually a false negative in mypy. https://mypy-play.net/?mypy=latest&python=3.12&gist=f60a4694098406077af0b8627fc78557
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Access on class object is not a good argument. For two reasons:
Finally, things like Btw #18334 is not a bug, it is a true positive. Coming back to your original issue: the two revealed types here are different (and both IMO correct): class Foo[T](Sequence[T]):
@classmethod
def new[T2](cls: "type[Foo[T2]]", arg: list[T2]) -> "Foo[T2]": ...
@classmethod
def new2[T2](cls, arg: list[T2]) -> "Foo[T2]": ...
tfi: type[Foo[int]]
reveal_type(tfi.new) # def (arg: builtins.list[builtins.int]) -> tstgrp3.Foo[builtins.int]
reveal_type(tfi.new2) # def [T2] (arg: builtins.list[T2`2]) -> tstgrp3.Foo[T2`2]and this is the reason why a class with the first method is considered invariant. I understand why do you want to have the first one: the body should include something like TBH I don't think this is solvable without special-casing alternative constructors somehow. Also it looks like a flaw in PEP 695, there should be a simple way to override inferred variance.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ilevkivskyi your unsafety example is very similar to what I pointed out before, and the problem is roughly "yes, this is obviously unsafe, but the spec says so". What's the official mypy stance on the spec conformance? I prefer to read the spec as an advice, not a mandatory requirement, but IDK if that matches the project attitude. If mypy considers spec conformance its goal, then this PR is correct, and a typing-sig discussion is needed to fix this spec unsoundness. If not, this PR makes the state of affairs worse, trading false positives for false negatives.
There is, it's called
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, this is the official mypy stance: internal consistency is more important than consistency with the spec.
OK, I see :-)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Feel free to comment or👍 my suggestion for explicit variance spec for PEP695 type hints in discourse. |
||
| cov_neg: Cov[int] = Cov[object]() # E: Incompatible types in assignment (expression has type "Cov[object]", variable has type "Cov[int]") | ||
|
|
||
| class Contra[T]: | ||
| def set(self, arg: T) -> None: ... | ||
| def new[S](self: "Contra[S]", arg: list[S]) -> "Contra[S]": ... | ||
|
|
||
| contra_pos: Contra[object] = Contra[int]() # E: Incompatible types in assignment (expression has type "Contra[int]", variable has type "Contra[object]") | ||
| contra_neg: Contra[int] = Contra[object]() | ||
|
|
||
| [case testPEP695InheritInvariant] | ||
| class Invariant[T]: | ||
| x: T | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.