I disagree on both counts: It is possible to have static type checking without LSP, and LSP is not the best system to achieve static type checking.
The best system to achieve static type checking is what we call Protocol in Python, and what in Rust are called traits.
In practice I see that system works:
Rust does not have LSP. (Rust doesn’t even have inheritance.) Yet Rust does have incredibly good static type checking.
Rust has traits. ( impl - Rust ) .
In Rust you can declare which traits a type must have to be accepted by a function ( Traits: Defining Shared Behavior - The Rust Programming Language ) .
And famously Rust is incredibly type safe, more so than Java and C++/#.
I haven’t worked as a professional Python developer long yet. In the situations where type-hinting a class or a union of primitive python types was insufficient, I was able to type hint a Protocol, and it worked well. Pylance was able to pick up the type guarantees, etc. I had all the benefits of static type checking.
I don’t see how information from a Protocol can be incorrect.
LSP in contrast sometimes just doesn’t deliver what it promises.
LSP also places hard-to-satisfy demands on your code, such as the suggestion made in the OP of this thread. And relying on LSP encourages you to create deep inheritance trees, which I personally find very hard to work with. (And I feel they make programming error-prone.)
Protocols in contrast only require you to define your protocols.
Yeah, so that’s the consequence of attempting (to rely on) LSP.
If these functions were systematically type-hinted with Mapping & Hashable
or Mapping & Updatable
, or just plain Mapping
, the static type checker would know immediately which functions worked with frozen/dict.
Instead, we have to try and see whether the function throws an error.
Which, fortunately, isn’t a lot of work in Python.
(And there’s conventions that aren’t quite formalized, that dict
usually means the same as plain Mapping
, whilst frozendict
means Mapping & Hashable
. Which helps further reduce the workload. I just mean to stress the current situation is workable.)
(edit: functions that I worked with that are type-hinted with dict
often also rely the |
operator, which I don’t think is included in the Mapping
protocol. Which makes ‘correct’ type hinting more complicated.)
(I don’t know whether it is possible to specify a function input needs to satisfy 2 Protocols because I never needed it. But if it were possible I expect it would look like Mapping & Hashable
.)