Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
randolf-scholz wants to merge11 commits intopython:master
base:master
Choose a base branch
Loading
fromrandolf-scholz:fix_variance_polymorphic_constructor
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
11 commits
Select commitHold shift + click to select a range
d7b88cc
use plain_self as subtitution type for self
randolf-scholzJul 16, 2025
a0b037e
Update mypy/subtypes.py
randolf-scholzJul 16, 2025
ab680d1
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzJul 16, 2025
0f33c79
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzJul 27, 2025
5d16a08
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzJul 28, 2025
25e8efa
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzAug 4, 2025
9870139
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzSep 16, 2025
0b2aead
added unit test for 18334
randolf-scholzSep 16, 2025
307382e
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzOct 6, 2025
01e8651
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzOct 14, 2025
d41749b
Merge branch 'master' into fix_variance_polymorphic_constructor
randolf-scholzDec 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletionmypy/subtypes.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2223,7 +2223,8 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
settable = False

# TODO: handle settable properties with setter type different from getter.
typ = find_member(member, self_type, self_type)
plain_self = fill_typevars_with_any(info) # self-type without type variables
typ = find_member(member, self_type, plain_self)
if typ:
# It's okay for a method in a generic class with a contravariant type
# variable to return a generic instance of the class, if it doesn't involve
Expand Down
30 changes: 30 additions & 0 deletionstest-data/unit/check-python312.test
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -450,6 +450,36 @@ 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]()
Copy link
Member

Choose a reason for hiding this comment

The 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:

classSub(Cov[int]):defnew(self,arg:list[int])->Sub:print(arg[0].to_bytes())returnselfcov_pos:Cov[object]=Sub()cov_pos.new([object()])

On a more general level, how exactly this:

classCov[T]:defget(self)->T: ...defnew[S](self:Cov[S],arg:list[S])->Cov[S]: ...

is different from this

classCov[T]:defget(self)->T: ...defnew(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 ofuses ofCov where these two classes behave (or should behave) differently?

Copy link
ContributorAuthor

@randolf-scholzrandolf-scholzOct 15, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

? Maybe I am missing something but these two are literally identical in terms of semantics.

I think they are only identical when called as a bound method, but not when called on the class and passingself as a regular argument. Note that my original example in#19439 - which is how I came across this issue -- was in the context of classmethods, and theCov test case was really just breaking this down to the most elementary MWE.

classCov[T]:defget(self)->T: ...defnew[S](self:Cov[S],arg:list[S])->Cov[S]: ...x:Cov[int]Cov[str].new(x, [1,2,3])# OK

whereas

classCov[T]:defget(self)->T: ...defnew(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]

Copy link
ContributorAuthor

@randolf-scholzrandolf-scholzOct 15, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Consequently, I'd argue that yourSub example is not a proper subclass ofCov.pyright agrees thatSub.new is does not overrideCov.new in a compatible manner. Code sample inpyright playground

So I'd say this is actually a false negative in mypy.https://mypy-play.net/?mypy=latest&python=3.12&gist=f60a4694098406077af0b8627fc78557

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Access on class object is not a good argument. For two reasons:

  • First, prohibiting overrides that are incompatible on class object only would prohibita lot of use cases that people expect to be treated as safe. Most notably, overrides involving properties and various custom descriptors will stop working.
  • Second, my example doesn't involve or rely on class object access in any way. So it is at least weird to say the unsafety is caused by the class object access incompatibility.

Finally, things likeC[int].method are not really well specified (for good reasons, google type erasure). Support for this was added to mypy only recently, following a popular demand.

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):

classFoo[T](Sequence[T]):@classmethoddefnew[T2](cls:"type[Foo[T2]]",arg:list[T2])->"Foo[T2]": ...@classmethoddefnew2[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 likereturn cls(arg) which would give an error with the second method.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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 should be a simple way to override inferred variance

There is, it's calledtyping.TypeVar. AFAIC this ability was one of the reasons to not even consider deprecating "old-style" generics. I think it's rare enough to not warrant any extra syntax (though I'm a bad person to ask about that, IMO PEP695 should have never been implemented at all), so not a PEP omission.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@sterliakov

I prefer to read the spec as an advice, not a mandatory requirement, but IDK if that matches the project attitude.

Yes, this is the official mypy stance: internal consistency is more important than consistency with the spec.

AFAIC this ability was one of the reasons to not even consider deprecating "old-style" generics

OK, I see :-)

sterliakov reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

there should be a simple way to override inferred variance.

Feel free to comment or👍 my suggestion for explicit variance spec for PEP695 type hintsin discourse.

ilevkivskyi reacted with thumbs up emoji
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 testPEP695SelfAttribute]
# https://github.com/python/mypy/issues/18334
from typing import Self

class Foo[T]:
instance: Self
def foo(self) -> T: ... # type:ignore[empty-body]

class Bar(Foo[int]): ...

# OK
foo: Foo[object] = Bar()


[case testPEP695InheritInvariant]
class Invariant[T]:
x: T
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp