Type inference
Expected types
Many expressions in crow can have multiple possible types.
For example:
-
7
can be anat
,int
, orfloat
(and more). -
"foo"
can be astring
orsymbol
(and more). -
()
callsnew
with no arguments. This function has many overloads returning different types. - In general, any two functions may differ only in their return type.
The meaning of an expression may be disambiguated based on its context. The term for this is an expected type.
When an expected type is missing, the type-checker will still attempt to check the expression.
But, this may lead to a compile error.
Keep this in mind, since many compile errors can be fixed by adding an expected type.
(And even if that's not sufficient, it's a good way to start.)
Contexts that have an expected type
An expected type will be present in these cases:
-
From a return type:
The expression returned from a function has the type the function was declared to return.
main void() () # void -
From a function parameter:
When a function has only one remaining overload possible,
an argument to it has the type of the corresponding parameter.
main void() () call-me # () is nat call-me void(_ nat) () # void Overloads are filtered by the expected return type.
main void() () call-me # () is nat call-me void(_ nat) () # void # This overload can be ignored because it returns 'bool' # and the callsite expects 'void'. call-me bool(a bool) a -
From special expressions:
Any expression not at the end of a block must be
void
.
The condition of anif
,elif
,unless
,while
, oruntil
must be abool
.main void() () # void if () # bool () # void (since the 'if' must be void, so must the branch) -
From a type on a local variable:
The initializer for a local variable has the variable's type as its expected type.
main void() x nat = () # Converting string to json to string causes it to be quoted info log "{x}" -
From an inline type annotation:
There is a
::t
syntax that provides an expected type for the expression to its left.
main void() info log "{1.5::float.to::nat.to::json}"
Contexts that do not have an expected type
-
For a local variable without an explicit type:
When you write
x =
, the only way to determine the type of the expression to the right of the=
is to check it without an expected type.
(The type-checker doesn't try to infer based on where the variable is used later.)main void() x = () # This will fail to compile info log "{x}" -
When there an unresolved overload:
If there are multiple functions with the same name that can't yet be disambiguated,
an argument won't have a single expected type.
main void() () foo # This will fail to compile foo void(_ nat) () foo void(_ string) () However, it can sometimes be disambiguated when only one of the overloads makes sense.
main void() bar foo foo void(a nat) info log "got nat {a}" foo void(a string) info log "got string {a}" bar nat() 3 bar int() 4 In the above example, the call tobar
must return anat
orstring
(the possible parameter types offoo
), so thebar
returningint
is ignored.
Overloading is safe
Since there are no implicit conversions in crow, overloading is always unambiguous.
That means that adding a new function will never silently change an existing function call.
Instead, it will always be a compile error if multiple overloads match.
Type checking runs left-to-right
Crow always checks expressions top-to-bottom and left-to-right.
It never goes back to re-check something based on future information.
When crow looks at all the overloads for a function, it first filters out those
that can't return the expected type (if any).
Then it checks arguments left-to-right, filtering out overloads that can't accept the actual argument types.
That means that in general, first argument of a function will often not have an expected type,
while the last argument usually will.
In 0::nat == x
, the annotation 0::nat
is needed because
0
is the first thing checked and there are many
==
functions that take a numeric type.
In x == 0
, x
is checked first
and is known to be a nat
.
So, the only remaining ==
overload is == bool(a nat, b nat)
.