You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Swapping some type variables gives incorrect results #20336
Two related issues (#19444 and #19362) were fixed in #19449, but I discovered that swapping type variables still gives incorrect results if the generic type contains some additional optional type variables.
Oh. This is not a PEP695 issue, "old-style" variables suffer from the same. The problem is ID collision in typeanal.fix_instance.
We see Foo[Y`2, X`1], and Y is the same (same ID) as the outer Y. That's correct, the variable is still the same, the namespace is the same. We compare against the declared generic args: [X`1, Y`2, Z], and they pair perfectly: two vars to two other vars, third one to default. So we create a mapping {1: Y, 2: X, 3: object}, and then we sub all variables... and get [X, Y, object].
To make it more obvious, here's how Y can interfere with X value (with the substitution chain X -> Y -> int):
I'm feeling dumb a bit, but I do not immediately see how to fix this. We do not want to allocate new namespace for that - type vars should remain intact. Temporary meta_level change, perhaps?..
cc @ilevkivskyi, I suggest raising this to p-1-normal - looks really annoying! (but I still don't feel like applying priority labels here)
Yeah this is quite annoying. But just to be clear: is this specific only to situations with type variables with defaults? If yes, perhaps some logic is simply wrong in fix_instance()?
It goes roughly as follows: for every (declared_typearg, provided_typearg) pair (zip_longest with None), map declared ID to the provided type (if provided is missing, we pick default or Any). Then expand the instance according to that mapping.
Keys and values can overlap in the generated mapping: we say "replace X with Y, replace Y with int", and both Y-s refer to the same variable. This is problematic here, because we want to treat them as distinct for the duration of this substitution?
So essentially the problem is because the substitution is "non-atomic"? Anyway, I will need to look a bit deeper into this. I mentioned couple times type variables need some re-work on a fundamental level (in particular I really don't like the in-place modifications in expand_type).
If there is some hot-fix I am fine with this (since I could imagine this issue may be quite annoying), but my point still stands, support for typevar defaults needs re-thinking.
Activity
sterliakov commentedon Dec 1, 2025
Oh. This is not a PEP695 issue, "old-style" variables suffer from the same. The problem is ID collision in
typeanal.fix_instance.We see
Foo[Y`2, X`1], andYis the same (same ID) as the outerY. That's correct, the variable is still the same, the namespace is the same. We compare against the declared generic args:[X`1, Y`2, Z], and they pair perfectly: two vars to two other vars, third one to default. So we create a mapping{1: Y, 2: X, 3: object}, and then we sub all variables... and get[X, Y, object].To make it more obvious, here's how Y can interfere with X value (with the substitution chain
X -> Y -> int):I'm feeling dumb a bit, but I do not immediately see how to fix this. We do not want to allocate new namespace for that - type vars should remain intact. Temporary
meta_levelchange, perhaps?..cc @ilevkivskyi, I suggest raising this to p-1-normal - looks really annoying! (but I still don't feel like applying priority labels here)
ilevkivskyi commentedon Dec 1, 2025
Yeah this is quite annoying. But just to be clear: is this specific only to situations with type variables with defaults? If yes, perhaps some logic is simply wrong in
fix_instance()?sterliakov commentedon Dec 1, 2025
It goes roughly as follows: for every
(declared_typearg, provided_typearg)pair (zip_longestwith None), map declared ID to the provided type (if provided is missing, we pick default or Any). Then expand the instance according to that mapping.Keys and values can overlap in the generated mapping: we say "replace X with Y, replace Y with int", and both Y-s refer to the same variable. This is problematic here, because we want to treat them as distinct for the duration of this substitution?
sterliakov commentedon Dec 1, 2025
...wait, can we just skip the provided args?
ilevkivskyi commentedon Dec 1, 2025
So essentially the problem is because the substitution is "non-atomic"? Anyway, I will need to look a bit deeper into this. I mentioned couple times type variables need some re-work on a fundamental level (in particular I really don't like the in-place modifications in
expand_type).ilevkivskyi commentedon Dec 1, 2025
If there is some hot-fix I am fine with this (since I could imagine this issue may be quite annoying), but my point still stands, support for typevar defaults needs re-thinking.
Fix incorrect TypeVar default expansion order (Fixes python#20336)
sterliakov commentedon Dec 1, 2025
No, my patch is still wrong - it fixes this case but not the following (expected
Foo[Y, X, Y]):I'll get back to this today after work.
ayushdoesdev commentedon Dec 1, 2025
Please check this PR:
Fix incorrect TypeVar default expansion order (Fixes #20336) #20343
Input
Output
ilevkivskyi commentedon Jun 8, 2026
This works correctly on master. Likely because of my recent fixes where I axed all the weird in-place type changes.