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
This is not specific to __index__ but rather is a glaring hole in protocol compatibility checking. Here's a conceptually simpler repro without typeshed dependency:
The problem is that failed selftype checks only produce a diagnostic but still return the original signature. All diagnostics are silenced when checking protocol compatibility, so this error remains in mypy internals and never makes it to mypy output.
@yangdanny97 surprisingly, that's a different issue located in another part of the implementation, they do not share the same root cause (unless I'm missing some high-level connection, of course, but they really do not rely on the same helpers). Your snippet represents a problem with checking override validity after binding self (node_type_from_base in checker.py binds self, and override check sees def foo() -> None in both superclass and subclass). Binding itself succeeds there, please consider reporting this problem separately for better inventory management.
However, they both share a common issue: things get heated when overloads come into play (still not too hard when checking a concrete type against protocol, but worse hen checking overrides). And even without overloads, how should narrowing selftype behave? Note that we can't just compare callables before binding self, as that would mean any override violates LSP (and it does, Child.foo(Parent()) is unsafe, but we probably don't want to enforce that). So the first argument should undergo special treatment, depending on... what, exactly? The following makes some sense to me for non-overloads, but I'm not very certain:
Let's assume we're checking foo definition overridden in class Child that inherits its original implementation from Parent. That means Parent is in Child MRO directly, no structural types today.
Extract self argument type from Child.foo (let's call it B) and Parent.foo (A).
Map B to supertype A. If any arguments were ignored (see example below) during this transformation and they aren't wider than their widest possible values, declare override incompatible and exit. Otherwise, call the mapped result B' and proceed. I do not yet understand how to express this precisely, just a braindump - how do we identify such "extra" args in general case?
Check if B' is a proper subtype of A. If it is not, declare override incompatible and exit.
Bind self and check the rest of the signature (no changes here). Again, if incompatible, fail and exit.
Declare override compatible and stop.
"Lossy mapping" example (should be disallowed as any A instance can run a.foo() but the same isn't true for an arbitrary b):
...ough, and I haven't even mentioned protocols and overloads. This probably needs a spec or someone with understanding deep enough to lay out the core rules for such assignability.
Activity
[-]`self` annotation not checked?[/-][+]`self` annotation not checked in `__index__`?[/+]sterliakov commentedon Jun 25, 2025
This is not specific to
__index__but rather is a glaring hole in protocol compatibility checking. Here's a conceptually simpler repro without typeshed dependency:The problem is that failed selftype checks only produce a diagnostic but still return the original signature. All diagnostics are silenced when checking protocol compatibility, so this error remains in mypy internals and never makes it to mypy output.
yangdanny97 commentedon Jun 25, 2025
It's not just protocols, I think. If we change
SupportsFooto be a concrete class and extend it withBad, there is still no error:sterliakov commentedon Jun 25, 2025
@yangdanny97 surprisingly, that's a different issue located in another part of the implementation, they do not share the same root cause (unless I'm missing some high-level connection, of course, but they really do not rely on the same helpers). Your snippet represents a problem with checking override validity after binding self (
node_type_from_basein checker.py binds self, and override check seesdef foo() -> Nonein both superclass and subclass). Binding itself succeeds there, please consider reporting this problem separately for better inventory management.However, they both share a common issue: things get heated when overloads come into play (still not too hard when checking a concrete type against protocol, but worse hen checking overrides). And even without overloads, how should narrowing selftype behave? Note that we can't just compare callables before binding self, as that would mean any override violates LSP (and it does,
Child.foo(Parent())is unsafe, but we probably don't want to enforce that). So the first argument should undergo special treatment, depending on... what, exactly? The following makes some sense to me for non-overloads, but I'm not very certain:foodefinition overridden in classChildthat inherits its original implementation fromParent. That meansParentis inChildMRO directly, no structural types today.Child.foo(let's call itB) andParent.foo(A).Bto supertypeA. If any arguments were ignored (see example below) during this transformation and they aren't wider than their widest possible values, declare override incompatible and exit. Otherwise, call the mapped resultB'and proceed. I do not yet understand how to express this precisely, just a braindump - how do we identify such "extra" args in general case?B'is a proper subtype ofA. If it is not, declare override incompatible and exit."Lossy mapping" example (should be disallowed as any
Ainstance can runa.foo()but the same isn't true for an arbitraryb):"Lossy mapping" example that should be allowed:
...ough, and I haven't even mentioned protocols and overloads. This probably needs a spec or someone with understanding deep enough to lay out the core rules for such assignability.
Fix protocol subtyping in case of restricted self-types (#22106)