Well dang, today I learn a little more about mypy. There have been countless times mypy has "failed" to catch obviously wrong typing in the past for me. Now I'm going to go back to those old PRs and start taking a look for unreachable code (according to the type checker). Ha!
@ruroruro11 күн бұрын
To be fair, this isn't the fault of mypy. Detecting unreachable code is generally outside the scope of a type checker (and it's undecidable in the general case). Type checkers are supposed to ensure type safety. Unreachable code is by definition type safe (you can't crash at runtime due to a type error if the runtime is guaranteed to never reach the "problematic" piece of code). Detecting unreachable code is USEFUL, but it's more of a linter feature than a type checker feature.
@preocts408611 күн бұрын
@@ruroruro I agree and wouldn't call it a fault of mypy. I would call it, for myself, previously unknown behavior or even limitation that will make my life more interesting now that I know it. My attempt to use the word "failed" loosely suffers from loss of tone in text.
@ruroruro11 күн бұрын
2:58 SUPER minor nitpick, but I wouldn't say that the problem is "how Never/NoReturn are treated in mypy". That kind of implies that there is a bug in mypy. mypy is doing "the right thing (tm)" in this case. This is exactly how Never is supposed to work.
@anthonywritescode11 күн бұрын
ehhh not really the point I was trying to make. silently not checking code is definitely a surprising and poor behaviour but also that this pattern doesn't even solve the problem to begin with
@ruroruro11 күн бұрын
@anthonywritescode yeah, the pattern obviously doesn't work, because the person suggesting it misunderstood the meaning of NoReturn/Never. I was talking more generally about how NoReturn/Never are handled by mypy, not about this case specifically. Regarding "silently not checking code", I'd say that this is highly debatable. IMHO, mypy is a typechecker, not a linter and "unreachable code detection" sounds like it should be a linter feature to me. Also, you could argue that from the POV of a type checker the unreachable "nonsense" code DOES actually have correct typing. For example, you expected print(function_that_never_returns()) to be an error, but according to type theory the typing is actually valid, because Never is the bottom type (meaning that it can substitute for any other type). function_that_never_returns() x: nonsense_typing = "here" Is also technically valid (from a type safety perspective). Never is guaranteed to not have any concrete implementations and can't be instantiated. Therefore, the type checker can PROVE that none of the code paths contain invalid typing. This works basically for the same reasons that type narrowing works, except that you have narrowed the list of possible types to an empty list and all([]) is True.
@dougmercer11 күн бұрын
@@ruroruro I was the person who misinterpreted NoReturn 👋🏻 If you take a look at the docs, this is the first example. from typing import Never # or NoReturn def stop() -> Never: raise RuntimeError('no way') So, I (incorrectly) assumed that mypy would report an error if you try to use the result in an expression. So, I figured "eh, we could probably overload the cases that the function fails for (str) into the return type that is supposed to handle code paths that always raise errors." But yeah, I was wrong ¯\_(ツ)_/¯ IMO, it's definitely unexpected behavior. At the very least, the docs need more details on what it actually does
@ruroruro11 күн бұрын
@@dougmercer I understand where your misconception came from. It's a very understandable mistake to make if you aren't intimately familiar with type theory. Actually, the official typing docs even say that NoReturn/Never can be used "to define a function that should never be called". If you are just skimming the docs, you might not realize that this only applies to the case where Never is taken as an argument. As in def assert_unreachable(arg: Never): pass However, that's beside the point. My main nitpick was that this ISN'T some kind of "weird" or "poor" behavior on the part of mypy. MyPy is 100% doing the right thing here according to type theory and according to the spec. The only possible complaint that I can see about mypy here is that it might be better for it to display "" or "Never" when attempting to reveal_type(anything) inside unreachable code, instead of not printing anything.
@ruroruro11 күн бұрын
@@dougmercer I think, youtube ate my reply. It's a totally understandable mistake to make, type theory can be a bit arcane at times and the typing docs aren't super clear in this case)))
@matis978311 күн бұрын
thanks for this excellent explanation!
@d17-d7e11 күн бұрын
When will mypy be rewritten to rust (xD) and start checking types understandably?
@VladimirTheAesthete3 күн бұрын
That would actually be great, would make it having in a pre-commit hook a no-brainer.
@MurtagBYКүн бұрын
@@VladimirTheAesthete what blocks you from putting it as pre-commit hook already?
@VladimirTheAestheteКүн бұрын
@@MurtagBY I do, but it makes a commit a bit slow for huge projects.
@d17-d7e11 сағат бұрын
@@VladimirTheAesthete > a bit slow for huge projects. A bit? Lol xD
@drkspace11 күн бұрын
I think the docs say noreturn is explicitly for when a function raises and nothing else
@sadhlife11 күн бұрын
and infinite loops with no breaks, but yeah
@d3stinYwOw10 күн бұрын
If you don't use pyright, maybe checking how 'basedpyright' would behave would be great to hear :) In some places I needed to fight with mypy when I was using 'try/except pass' - I know, bad thing to do, but was necessary long time ago :)
@anthonywritescode10 күн бұрын
I'm not going to use software with a blatant dogwhistle in the name.
@ruroruro10 күн бұрын
@@anthonywritescode I am curious about what you mean. English is my second language so I might be missing something here. My understanding is that "dog whistle" refers to cases where a politician might say "I support family values", but imply something more along the lines of "I only support traditional nuclear heterosexual families". Are you saying that "based" is implying something racist/sexist/etc? I've only seen this world used in the context of brain rotting memes. The only implication that I can think of is that "basedpyright" implies that the normal "pyright" is "cringe". But that seems fairly benign? Most packages at least try to imply that they are better than their competitors (even if most of them don't do it right in the name). I genuinely want to understand, what do you see in the word "based", because I've been using it occasionally/jokingly and if there was some racist/sexist/etc context to it, then I'd like to know about it. Thanks.
@endersteph11 күн бұрын
Well I mean I guess the problem here is that the typing is just plain wrong without modifying the function as well. If you say it never returns you have to code the function such that that's true, like for example by raising an exception if the argument is a str.
@anthonywritescode11 күн бұрын
mmmm not really -- even if the implementation was modified (it doesn't need to be, imagine this is in a pyi stub) that doesn't change the outcome here
@spyr0th3dr4g0n11 күн бұрын
Changing the function to immediately raise an exception (matching the example given in the mypy docs) still prevents type checking of the rest of code, like shown in the video
@endersteph11 күн бұрын
@@anthonywritescode I'm not sure I understand, what outcome? The fact that mypy doesn't typecheck? Isn't that just a mypy problem? Am I wrong in saying that typing it with a Never return type is wrong given the current implementation? (as in, giving it a str will not stop execution)
@Willd2p211 күн бұрын
@@endersteph You're correct, but the issue in the first place was with catching the problem with static analysis, not with catching it at run time. It's quite easy to come up with solutions that will correctly raise an exception or otherwise stop execution if you pass a single string to the function but there's no good solution for catching it beforehand.
@endersteph11 күн бұрын
@@Willd2p2 Ah yeah you're right, I forgot the original problem was convincing the typechecker that passing in a str is wrong, thanks
@iliya-malecki11 күн бұрын
Wouldn't this be fixed by putting ut in a ()->None main function? Never is incompatible with None
@anthonywritescode11 күн бұрын
nope. a call to a NoReturn is valid anywhere (for example the `raise` statement is essentially a NoReturn)
@iliya-malecki11 күн бұрын
@anthonywritescode what the... Just checked it with raise, no error but at least pyright sees the unreachable code - but doing it with a ()->NoReturn function screws up everything including the unreachable code detection
@ruroruro11 күн бұрын
@@iliya-maleckiIt's the opposite, actually. Never is the "bottom type" (google it). This means that it's actually compatible with ANY TYPE.
@Max-mx5yc10 күн бұрын
you could make the string overload return object. that way it's also correct from a typing perspective, since the function does return an object (it doesn't return never tho)
@anthonywritescode9 күн бұрын
that still is nonsense and doesn't help the original generic problem
@cyber-brain9 күн бұрын
``` # This works because str.__contains__ does not accept object (either in typeshed or at runtime) class SequenceNotStr(Protocol[_T_co]): @overload def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... @overload def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T_co]: ... def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... ```
@thiemofischer22306 күн бұрын
there is no algorithm that can solve --warn-unreachable. it's the halting problem
@anthonywritescode6 күн бұрын
for a strict subset of the language it's fairly straightforward actually (you of course need to make some basic assumptions about various constructs but at least for the subset mypy targets it's not that unreasonable to "solve")
@thiemofischer22305 күн бұрын
@@anthonywritescode maybe. i don't know that much about how mypy would go about it, when just looking at types. take the str vs Sequence[str] example from the other video. make an expression to check if the elements of the Sequence are all one character strings. put that in an "if !expession: pass". use --warn-unreachable on that. This should "solve" for the type exclusion (just in theory). just wondering what assumptions would go in the algorithm... straightforward enough to make my brain melt at least
@PeterPCardenas11 күн бұрын
why do you stick with mypy?
@sadhlife11 күн бұрын
because mypy is pretty good
@TheAulto11 күн бұрын
Personally, I’m not pulling node in if I can avoid, which pyright requires
@mjiii11 күн бұрын
It seems the confusion here stems from Never being interpreted as "this function is never called" . NoReturn is probably the better alias because it is a bit harder to misunderstand.
@ruroruro11 күн бұрын
NoReturn can also be misconstrued to mean "this function doesn't return any value". Of course, experienced python programmers know that "not returning" anything from a function is equivalent to returning None, but you could still get confused upon seeing "NoReturn". On the other hand "Never" seems to be an established term for the bottom type in other languages (rust, kotlin, type script and other languages based on type theory). In this case Never refers to the return type. It's not an annotation on the function itself. It just means "a type that will Never be instantiated".
@bacon4life11 күн бұрын
Returning Never/NoReturn is just plain wrong.
@anthonywritescode11 күн бұрын
that's not universally correct. a function which always raises or loops forever is a valid NoReturn function
@bacon4life11 күн бұрын
@@anthonywritescode I meant in that specific overload you wrote.
@dougmercer11 күн бұрын
@@bacon4life The (incorrect) assumption I made was that-- if a function always raises an exception when given an input of a particular type, then I should be able to overload my function to have ReturnType of NoReturn for that overload. I wouldn't call that "plain wrong", but more so "unintuitively wrong"
@bacon4life10 күн бұрын
@@dougmercer I see your point, but I still think it's wrong. You could have a string that's a valid argument to subprocess.call and it wouldn't raise, making the annotation wrong. subprocess.call("echo") doesn't raise, for example.
@dougmercer10 күн бұрын
@@bacon4life sure, I didn't think too deeply about the implementation, but the implementation really doesn't matter because the type checker behaves the same regardless
@yehoshualevine11 күн бұрын
`Sequence[LiteralString]` instead of `Sequence[str]` should fix the original video's problem. If LiteralString isn't appropriate for our use case, then tuple/list/etc likely isn't appropriate either. It means you NEED dynamic checking so put the arg's into a class that checks that they follow the rules you want followed.
@anthonywritescode11 күн бұрын
I disagree and it's not appropriate for any interesting commands that aren't completely static. plus it doesn't handle any of the other places you would use Sequence[str]