As someone who's very used to Typescript, I find these complaints very interesting, because they're often common for Typescript newcomers as well. However, I think they - quite understandably - misunderstand the purpose of these tools, and so get disappointed when the tools don't do what they want or expect.

I think the traditional view of types is that they're about declaring something to do with the memory layout of a particular variable. Maybe not always directly, but I think most people have this kind of intuition that an int is an int, a string is a string, a struct is a struct (and a particular, explicit one at that). In Java with subtyping, things get a bit more complex, but it's usually fine.

That's generally not a good way to approach Typescript/mypy. Typescript, for example, is absolutely unconcerned with whatever value lives inside a particular variable. It's very possible in Typescript to create types that can't even ever exist at runtime (type brands are a good example of this). The compilation process for Typescript is basically just stripping the types away as if they were comments, and leaving the Javascript behind.

What Typescript is is a slightly more complicated linter that requires annotations. The types you put in are basically just labels. When writing Typescript, your goal is to give every type a label, and then run a program that tells you if all of those labels are compatible with each other. If there is a label mismatch (you've used label "number" but you've done operation "push" on it) then the linter will simply tell you you've done something wrong. Moreover, these annotations can be very loose - annotate something as `any` (or `Any` in Python) and the linter will accept anything done to it. This means that you can always break free of the linter and tell it that you know best (which is true - it is probably impossible to statically validate whether a given Javascript program will throw an error or not, so any linter will have some cases where it prohibits a valid program - the programmer requires the power to override the linter).

Viewing Typescript and mypy in this context helps a lot, I find. It explains why casting doesn't throw runtime errors (a cast is simply a lint annotation saying that a particular expression has a particular label). It explains why JSON isn't validated when it's parsed (you're just starting that the data has a particular label/shape - it's up to you to ensure that that's true, but you can do that check however you like, including not at all if you're very confident in the quality of your data).

This raises the question about which process is better: one where types get transformed into real data structures where all code is kept valid at runtime, or one where the types act only as assertions for a linter validating whether the code ought to remain valid. In my experience, a lot of it depends on how valuable you find the dynamism in languages like Python and JavaScript. If it's very important for you, then traditional typechecking approaches are probably not very viable because no static type system can ever cover all correct Python programs. However, levels plus a linter will resolve most programme correctly, and still give the necessary overrides to allow for other options.

So when it comes to the questions in the article, I think the author would do a lot of good to approach Python type hints from the perspective of a linter that requires annotations.