[Python-ideas] Semantics for type checking (was: What should a good type checker do?)
Koos Zevenhoven
k7hoven at gmail.com
Sat Sep 3 17:26:58 EDT 2016
On Friday, while replying to a post on python-dev about PEP 526
(variable annotations), I ended up mentioning things that I think a
good type checker should do, which seem to differ from the general
understanding. The discussion should continue here, though, because
the aim of PEP 526 is not to define type-checking semantics.
I'll recap the thoughts in this post, but here are links to the
original posts, for reference:
[1] https://mail.python.org/pipermail/python-dev/2016-September/146069.html
[2] https://mail.python.org/pipermail/python-dev/2016-September/146090.html
Quoting [1], with added explanations in between:
On Fri, Sep 2, 2016 at 1:10 PM, Koos Zevenhoven <k7hoven at gmail.com> wrote:
[...]
>
> The more you infer types, the less you check them. It's up to the
> programmers to choose the amount of annotation.
>
[...]
>
> I'm also a little worried about not being able to reannotate a name.
>
IOW, this should be allowed:
x : int = 1
foo(x)
x : float = 0.5
bar(x)
It turns out that disallowing this was not the PEP's intention,
because type-checking semantics is up to the type checker. This has
now been clarified in the PEP. Good.
[...]
>> def eggs(cond:bool):
>> x: Optional[List[int]]
>> if cond:
>> x = None # Now legal
>> else:
>> x: = []
>> spam(x)
>>
The above is an example from Mark Shannon's email, and below is my response:
>
> A good checker should be able to infer that x is a union type at the
> point that it's passed to spam, even without the type annotation. For
> example:
>
> def eggs(cond:bool):
> if cond:
> x = 1
> else:
> x = 1.5
> spam(x) # a good type checker infers that x is of type Union[int, float]
>
Here, there are two inferred types for x, but in different branches of
the if statement. So x, which is dynamically typed at runtime, becomes
a Union at "static-check time".
> Or with annotations:
>
> def eggs(cond:bool):
> if cond:
> x : int = foo() # foo may not have a return type hint
> else:
> x : float = bar() # bar may not have a return type hint
> spam(x) # a good type checker infers that x is of type Union[int, float]
>
>
Two explicit annotations, and again neither of them "wins". The
inferred type after the if statement is therefore a Union.
Below, I'm quoting [2], my response to an email that disagreed with me
about the above being something "a good type checker" would do:
On Fri, Sep 2, 2016 at 6:49 PM, Koos Zevenhoven <k7hoven at gmail.com> wrote:
>>> A good checker should be able to infer that x is a union type at the
>>> point that it's passed to spam, even without the type annotation. For
>>> example:
>>>
>>> def eggs(cond:bool):
>>> if cond:
>>> x = 1
>>> else:
>>> x = 1.5
>>> spam(x) # a good type checker infers that x is of type Union[int, float]
>>
>> Oh I really hope not. I wouldn't call that a *good* type checker. I
>> would call that a type checker that is overly permissive.
>
> I guess it's perfectly fine if we disagree about type checking ideals,
> and I can imagine the justification for you thinking that way. There
> can also be different type checkers, and which can have different
> modes.
>
> But assume (a) that the above function is perfectly working code, and
> spam(...) accepts Union[int, float]. Why would I want the type checker
> to complain?
>
> Then again, (b) instead of that being working code, it might be an
> error and spam only takes float. No problem, the type checker will
> catch that.
>
> In case of (b), to get the behavior you want (but in my hypothetical
> semantics), this could be annotated as
>
> def eggs(cond:bool):
> x : float
> if cond:
> x = 1 # type checker says error
> else:
> x = 1.5
> spam(x)
>
> So here the programmer thinks the type of x should be more constrained
> than what spam(...) accepts.
The reasoning here is that x is explicitly annotated without an
assignment, so the inferred types subsequently assigned to it (int and
float) must be compatible with the annotation. Therefore "the good
type checker" would say assigning 1 is an error.
>
> Or you might have something like this
>
> def eggs(cond:bool):
> if cond:
> x = 1
> else:
> x = 1.5
> # type checker has inferred x to be Union[int, float]
> x : float # type checker finds an error
> spam(x)
>
> Here, the same error is found, but at a different location.
>
Here, x is explicitly annotated as float while its value remains a
Union[int, float], as inferred by "the good type checker". Therefore
this is an error. One could consider this a type assertation (at
static-check time).
"The good type checker" should also be able to tell the programmer
that it's "x = 1" that made the subsequent "type assertion" fail.
In addition to being type *hints*, the annotations can also be
considered as "check points".
-- Koos
--
+ Koos Zevenhoven + http://twitter.com/k7hoven +
More information about the Python-ideas
mailing list