# type inference

# let statements and expressions

When no explicit type is given for a variable in a let statement or expression, the variable type is inferred from the initializer, provided one is present

let a_string = "12345";
let an_int = 12345;
let an_int_array = [1, 2, 3, 4, 5];

# list literal element type

The element type of list literals is inferred from the types of the elements. The compiler will try to find a type that is compatible with all the elements.

class Base is
    init() is si
si

class DERIVED: Base is
    init() is super.init() si
si

...

let array_of_base = [Base(), DERIVED()];
let array_of_object = [Base(), DERIVED(), object()];
let array_of_int = [1, 2, 3, 4, 5];

If a list contains tuple literals, the compiler will attempt to find compatible common types for each tuple element across all the elements in the list

let int_string = [(123, "hello"), (456, "goodbye")];

let int_object = [(123, 456), (798, "wibble")];

# if expression branch result type

The result type of an if expression is inferred from the types of all the branch results. The compiler will try to find a type that is compatible with all the results

let derived =
    if true then
        DERIVED()
    else
        DERIVED()
    fi;

let base =
    if true then
        DERIVED()
    else
        Base()
    fi;

# generic class, struct and variant constructors

When constructing a generic class, struct or variant, the actual generic type arguments will be inferred from the constructor method arguments if possible

class THING[T] is
    value: T;

    init(value: T) is
        self.value = value;
    si
si

...

let int_thing = THING(1234);
let string_thing = THING("hello");

This type of inference is only possible if all type arguments are referenced in the constructor actual arguments and if the constructor overload to call is unambiguous

# generic function or method calls

When calling a generic global function, a generic method, or a static method on a generic class or struct, the compiler will try to infer the generic type arguments from the types of the actual argument passed to the function or method

do_something[T](a: T, b: T) -> T;

...

let base = do_something(Base(), DERIVED());
let derived = do_something(DERIVED(), DERIVED());
let obj = do_something(object(), DERIVED());

# anonymous function return type

When constructing an anonymous function literal, the compiler will attempt to infer the return type from either the type of the expression body or from the type of return expressions in the block body

let returns_int = (i: int) => i * 2;
let returns_string = (s: string) = "{s}{s}";

# anonymous function argument types

When anonymous function literals are passed as arguments to a function or method and an unambiguous overload match can be made without knowing the exact function type, the compiler can figure out the argument types based on the matching overload.

[1, 2, 2, 4, 5] | .filter(i => i > 3);

In this example, self is already known to be Pipe[int], so Pipe[int].filter(predicate: int -> bool) -> Pipe[int] is the only overload that could match. Hence the predicate argument must be int -> bool and the type of i must be int