@@ -8,17 +8,17 @@ generally makes about types in Python:
88
99In order for a type checker's assumptions to be sound, it is crucial for the type checker to enforce
1010the Liskov Substitution Principle on code that it checks. In practice, this usually manifests as
11- three checks for a type checker to perform when it checks a subclass ` B ` of a class ` A ` :
11+ several checks for a type checker to perform when it checks a subclass ` B ` of a class ` A ` :
1212
13131 . Read-only attributes should only ever be overridden covariantly: if a property ` A.p ` resolves to
1414 ` int ` when accessed, accessing ` B.p ` should either resolve to ` int ` or a subtype of ` int ` .
15- 1 . Method return types should only ever be overidden covariantly: if a method ` A.f ` returns ` int `
15+ 1 . Method return types should only ever be overridden covariantly: if a method ` A.f ` returns ` int `
1616 when called, calling ` B.f ` should also resolve to ` int or a subtype of ` int\` .
17171 . Method parameters should only ever be overridden contravariantly: if a method ` A.f ` can be called
1818 with an argument of type ` bool ` , then the method ` B.f ` must also be callable with type ` bool `
1919 (though it is permitted for the override to also accept other types)
20201 . Mutable attributes should only ever be overridden invariantly: if a mutable attribute ` A.attr `
21- resolves to type ` str ` , it can only be overidden on a subclass with exactly the same type.
21+ resolves to type ` str ` , it can only be overridden on a subclass with exactly the same type.
2222
2323## Method return types
2424
@@ -53,7 +53,7 @@ class Sub1(Super):
5353 def method (self , x : int , / ): ... # fine
5454
5555class Sub2 (Super ):
56- def method (self , x : object , / ): ... # fine: `method` still accepts any argument of type `bool `
56+ def method (self , x : object , / ): ... # fine: `method` still accepts any argument of type `int `
5757
5858class Sub4 (Super ):
5959 def method (self , x : int | str , / ): ... # fine
@@ -87,7 +87,7 @@ class Sub12(Super):
8787
8888class Sub13 (Super ):
8989 # Some calls permitted by the superclass are now no longer allowed
90- # (the method can no longer be passed with exactly one argument!)
90+ # (the method can no longer be passed exactly one argument!)
9191 def method (self , x , y , / ): ... # error: [invalid-method-override]
9292
9393class Sub14 (Super ):
@@ -464,8 +464,6 @@ source-code definitions. There are several scenarios to consider here:
464464 subclass
4654651 . A "normal" method on a superclass is overridden by a synthesized method on a subclass
4664661 . A synthesized method on a superclass is overridden by a synthesized method on a subclass
467- 1 . No methods are overridden, but the Liskov Substitution Principle is arguably violated anyway
468- because a generated method on a superclass is incompatible with subclassing(!)
469467
470468<!-- snapshot-diagnostics -->
471469
@@ -490,13 +488,22 @@ class Bar(Foo):
490488class Bar2 (Foo ):
491489 y: str
492490
493- # TODO : Although this class does not override any methods of `Foo`, it nonetheless
494- # arguably violates the Liskov Substitution Principle. Instances of `Bar3` cannot be
495- # substituted wherever an instance of `Foo` is expected, because the generated
496- # `__lt__` method on `Foo` raises an error unless the r.h.s. and `l.h.s.` have exactly
497- # the same `__class__` (it does not permit instances of `Foo` to be compared with
498- # instances of subclasses of `Foo`). We could therefore consider treating all
499- # `order=True` dataclasses as implicitly `@final` in order to enforce Liskov soundness.
491+ # TODO : Although this class does not override any methods of `Foo`, the design of the
492+ # `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
493+ # Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
494+ # expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
495+ # and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
496+ # be compared with instances of subclasses of `Foo`).
497+ #
498+ # Many users would probably like their type checkers to alert them to cases where instances
499+ # of subclasses cannot be substituted for instances of superclasses, as this violates many
500+ # assumptions a type checker will make and makes it likely that a type checker will fail to
501+ # catch type errors elsewhere in the user's code. We could therefore consider treating all
502+ # `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
503+ # this probably shouldn't be reported with the same error code as Liskov violations, since
504+ # the error does not stem from any method signatures written by the user. The example is
505+ # only included here for completeness.
506+ #
500507# Note that no other type checker catches this error as of 2025-11-21.
501508class Bar3 (Foo ): ...
502509
0 commit comments