Skip to content

Latest commit

Β 

History

History
1080 lines (630 loc) Β· 34.1 KB

type-inference.md

File metadata and controls

1080 lines (630 loc) Β· 34.1 KB

Type inference

Type inference is a judgment of the form:

Ξ“ ⊒ t : T

... where:

  • Ξ“ (an input context) is the type inference context which relates identifiers to their types
  • t (an input expression) is the term to infer the type of
  • T (the output expression) is the inferred type

Type inference also type-checks the input expression, too, to ensure that it is well-typed.

To infer the type of a closed expression, supply an empty context:

Ρ ⊒ t : T

This judgment guarantees the invariant that the inferred type is safe to normalize.

Table of contents

Normalization

Types inferred according to the following rules will be in Ξ²-normal form, provided that all types in the input context Ξ“ are in Ξ²-normal form. The advantage of Ξ²-normalizing all inferred types is that repeated normalization of the same types can largely be avoided.

However implementations MAY choose to infer types that are not Ξ²-normalized as long as they are equivalent to the types specified here.

Constants

The first rules are that the inferred type of Type is Kind and the inferred type of Kind is Sort:

───────────────
Ξ“ ⊒ Type : Kind


───────────────
Ξ“ ⊒ Kind : Sort

In other words, Kind is the "type of types" and Sort serves as the foundation of the type system.

Note that you cannot infer the type of Sort as there is nothing above Sort in the type system's hierarchy. Inferring the type of Sort is a type error.

Variables

Infer the type of a variable by looking up the variable's type in the context:

Ξ“ ⊒ T : k
──────────────────
Ξ“, x : T ⊒ x@0 : T

Since x is a synonym for x@0, you can shorten this rule to:

Ξ“ ⊒ T : k
────────────────
Ξ“, x : T ⊒ x : T

The order of types in the context matters because there can be multiple type annotations in the context for the same variable. The DeBruijn index associated with each variable disambiguates which type annotation in the context to use:

Ξ“ ⊒ x@n : T
────────────────────────  ; 0 < n
Ξ“, x : A ⊒ x@(1 + n) : T


Ξ“ ⊒ x@n : T
──────────────────  ; x β‰  y
Ξ“, y : A ⊒ x@n : T

If the natural number associated with the variable is greater than or equal to the number of type annotations in the context matching the variable then that is a type error.

Bool

Bool is a Type:

───────────────
Ξ“ ⊒ Bool : Type

True and False have type Bool:

───────────────
Ξ“ ⊒ True : Bool


────────────────
Ξ“ ⊒ False : Bool

An if expression takes a predicate of type Bool and returns either the then or else branch of the expression, both of which must be the same type:

Ξ“ ⊒ t : Bool
Ξ“ ⊒ l : L
Ξ“ ⊒ r : R
Ξ“ ⊒ L : cβ‚€
Ξ“ ⊒ R : c₁
L ≑ R
──────────────────────────
Ξ“ ⊒ if t then l else r : L

If either branch of the if expression is not a term, type, or kind, then that is a type error.

If the predicate is not a Bool then that is a type error.

If the two branches of the if expression do not have the same type then that is a type error.

All of the logical operators take arguments of type Bool and return a result of type Bool:

Ξ“ ⊒ l : Bool   Ξ“ ⊒ r : Bool
───────────────────────────
Ξ“ ⊒ l || r : Bool


Ξ“ ⊒ l : Bool   Ξ“ ⊒ r : Bool
───────────────────────────
Ξ“ ⊒ l && r : Bool


Ξ“ ⊒ l : Bool   Ξ“ ⊒ r : Bool
───────────────────────────
Ξ“ ⊒ l == r : Bool


Ξ“ ⊒ l : Bool   Ξ“ ⊒ r : Bool
───────────────────────────
Ξ“ ⊒ l != r : Bool

If the operator arguments do not have type Bool then that is a type error.

Natural

Natural is a type:

──────────────────
Ξ“ ⊒ Natural : Type

Natural number literals have type Natural:

───────────────
Ξ“ ⊒ n : Natural

The arithmetic operators take arguments of type Natural and return a result of type Natural:

Ξ“ ⊒ x : Natural   Ξ“ ⊒ y : Natural
─────────────────────────────────
Ξ“ ⊒ x + y : Natural


Ξ“ ⊒ x : Natural   Ξ“ ⊒ y : Natural
─────────────────────────────────
Ξ“ ⊒ x * y : Natural

If the operator arguments do not have type Natural then that is a type error.

The built-in functions on Natural numbers have the following types:

─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ Natural/build : (βˆ€(natural : Type) β†’ βˆ€(succ : natural β†’ natural) β†’ βˆ€(zero : natural) β†’ natural) β†’ Natural


──────────────────────────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ Natural/fold : Natural β†’ βˆ€(natural : Type) β†’ βˆ€(succ : natural β†’ natural) β†’ βˆ€(zero : natural) β†’ natural


───────────────────────────────────
Ξ“ ⊒ Natural/isZero : Natural β†’ Bool


─────────────────────────────────
Ξ“ ⊒ Natural/even : Natural β†’ Bool


────────────────────────────────
Ξ“ ⊒ Natural/odd : Natural β†’ Bool


─────────────────────────────────────────
Ξ“ ⊒ Natural/toInteger : Natural β†’ Integer


─────────────────────────────────
Ξ“ ⊒ Natural/show : Natural β†’ Text


──────────────────────────────────────────────────
Ξ“ ⊒ Natural/subtract : Natural β†’ Natural β†’ Natural

Text

Text is a type:

───────────────
Ξ“ ⊒ Text : Type

Text literals have type Text:

──────────────
Ξ“ ⊒ "s" : Text


Ξ“ ⊒ t : Text   Ξ“ ⊒ "ss…" : Text
───────────────────────────────
Ξ“ ⊒ "s${t}ss…" : Text

The Text show function has the following type:

───────────────────────────
Ξ“ ⊒ Text/show : Text β†’ Text

The Text/replace function has the following type:

───────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ Text/replace : βˆ€(needle : Text) β†’ βˆ€(replacement : Text) β†’ βˆ€(haystack : Text) β†’ Text

The Text concatenation operator takes arguments of type Text and returns a result of type Text:

Ξ“ ⊒ x : Text   Ξ“ ⊒ y : Text
───────────────────────────
Ξ“ ⊒ x ++ y : Text

If the operator arguments do not have type Text, then that is a type error.

Date / Time / TimeZone

Date, Time, and TimeZone are all Types:

───────────────
Ξ“ ⊒ Date : Type


───────────────
Ξ“ ⊒ Time : Type


───────────────────
Ξ“ ⊒ TimeZone : Type

Date literals have type Date:

─────────────────────
Ξ“ ⊒ YYYY-MM-DD : Date

Time literals have type Time:

───────────────────
Ξ“ ⊒ hh:mm:ss : Time

… and TimeZone literals have type TimeZone:

─────────────────────
Ξ“ ⊒ Β±HH:MM : TimeZone

List

List is a function from a Type to another Type:

──────────────────────
Ξ“ ⊒ List : Type β†’ Type

A List literal's type is inferred either from the type of the elements (if non-empty) or from the type annotation (if empty):

Ξ“ ⊒ Tβ‚€ : c   Tβ‚€ β‡₯ List T₁   Ξ“ ⊒ T₁ : Type
─────────────────────────────────────────
Ξ“ ⊒ ([] : Tβ‚€) : List T₁


Ξ“ ⊒ t : Tβ‚€   Ξ“ ⊒ Tβ‚€ : Type   Ξ“ ⊒ [ ts… ] : List T₁   Tβ‚€ ≑ T₁
────────────────────────────────────────────────────────────
Ξ“ ⊒ [ t, ts… ] : List Tβ‚€

Note that the above rules forbid List elements that are Types. More generally, if the element type is not a Type then that is a type error.

If the list elements do not all have the same type then that is a type error.

If an empty list does not have a type annotation then that is a type error.

The List concatenation operator takes arguments that are both Lists of the same type and returns a List of the same type:

Ξ“ ⊒ x : List Aβ‚€
Ξ“ ⊒ y : List A₁
Aβ‚€ ≑ A₁
───────────────────
Ξ“ ⊒ x # y : List Aβ‚€

If the operator arguments are not Lists, then that is a type error.

If the arguments have different element types, then that is a type error.

The built-in functions on Lists have the following types:

───────────────────────────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ List/build : βˆ€(a : Type) β†’ (βˆ€(list : Type) β†’ βˆ€(cons : a β†’ list β†’ list) β†’ βˆ€(nil : list) β†’ list) β†’ List a


────────────────────────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ List/fold : βˆ€(a : Type) β†’ List a β†’ βˆ€(list : Type) β†’ βˆ€(cons : a β†’ list β†’ list) β†’ βˆ€(nil : list) β†’ list


────────────────────────────────────────────────
Ξ“ ⊒ List/length : βˆ€(a : Type) β†’ List a β†’ Natural


─────────────────────────────────────────────────
Ξ“ ⊒ List/head : βˆ€(a : Type) β†’ List a β†’ Optional a


─────────────────────────────────────────────────
Ξ“ ⊒ List/last : βˆ€(a : Type) β†’ List a β†’ Optional a


─────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ List/indexed : βˆ€(a : Type) β†’ List a β†’ List { index : Natural, value : a }


────────────────────────────────────────────────
Ξ“ ⊒ List/reverse : βˆ€(a : Type) β†’ List a β†’ List a

Optional

Optional is a function from a Type to another Type:

──────────────────────────
Ξ“ ⊒ Optional : Type β†’ Type

The Some constructor infers the type from the provided argument:

Ξ“ ⊒ a : A   Ξ“ ⊒ A : Type
────────────────────────
Ξ“ ⊒ Some a : Optional A

... and the None constructor is an ordinary function that is typeable in isolation:

───────────────────────────────────
Ξ“ ⊒ None : βˆ€(A : Type) β†’ Optional A

Note that the above rules forbid an Optional element that is a Type. More generally, if the element type is not a Type then that is a type error.

Records

Record types are "anonymous", meaning that they are uniquely defined by the names and types of their fields.

An empty record is a Type:

─────────────
Ξ“ ⊒ {} : Type

A non-empty record can store terms, types and kinds:

Ξ“ ⊒ T : tβ‚€   Ξ“ ⊒ { xs… } : t₁   tβ‚€ ⋁ t₁ = tβ‚‚
────────────────────────────────────────────  ; x βˆ‰ { xs… }
Ξ“ ⊒ { x : T, xs… } : tβ‚‚

If the type of a field is not Type, Kind, or Sort then that is a type error.

Carefully note that there should be no need to handle duplicate fields by this point because the desugaring rules for record literals merge duplicate fields into unique fields.

Record values are also anonymous. The inferred record type has sorted fields and normalized field types.

────────────
Ξ“ ⊒ {=} : {}


Ξ“ ⊒ t : T   Ξ“ ⊒ { xs… } : { ts… }   Ξ“ ⊒ { x : T, ts… } : i
──────────────────────────────────────────────────────────  ; x βˆ‰ { xs… }
Ξ“ ⊒ { x = t, xs… } : { x : T, ts… }

You can only select field(s) from the record if they are present:

Ξ“ ⊒ e : { x : T, xs… }
──────────────────────
Ξ“ ⊒ e.x : T


Ξ“ ⊒ e : { ts… }
───────────────
Ξ“ ⊒ e.{} : {}


Ξ“ ⊒ e : { x : T, ts₀… }   Ξ“ ⊒ e.{ xs… } : { ts₁… }
──────────────────────────────────────────────────  ; x βˆ‰ { xs… }
Ξ“ ⊒ e.{ x, xs… } : { x : T, ts₁… }

Record projection can also be done by specifying the target record type. For instance, provided that s is a record type and e is the source record, e.(s) produces another record of type s whose values are taken from the respective fields from e.

Ξ“ ⊒ e : { ts… }
Ξ“ ⊒ s : c
s β‡₯ {}
───────────────
Ξ“ ⊒ e.(s) : {}


Ξ“ ⊒ e : { x : Tβ‚€, ts… }
Ξ“ ⊒ s : c
s β‡₯ { x : T₁, ss… }
Tβ‚€ ≑ T₁
Ξ“ ⊒ e.({ ss… }) : U
───────────────────────────
Ξ“ ⊒ e.(s) : { x : T₁, ss… }

If you select a field from a value that is not a record, then that is a type error.

If the field is absent from the record then that is a type error.

Non-recursive right-biased merge also requires that both arguments are records:

Ξ“ ⊒ l : { ls… }
Ξ“ ⊒ r : {}
───────────────────
Ξ“ ⊒ l β«½ r : { ls… }


Ξ“ ⊒ l : { ls… }
Ξ“ ⊒ r : { a : A, rs… }
Ξ“ ⊒ { ls… } β«½ { rs… } : { ts… }
───────────────────────────────  ; a βˆ‰ ls
Ξ“ ⊒ l β«½ r : { a : A, ts… }


Ξ“ ⊒ l : { a : Aβ‚€, ls… }
Ξ“ ⊒ r : { a : A₁, rs… }
Ξ“ ⊒ { ls… } β«½ { rs… } : { ts… }
───────────────────────────────
Ξ“ ⊒ l β«½ r : { a : A₁, ts… }

If the operator arguments are not records then that is a type error.

Recursive record type merge requires that both arguments are record type literals. Any conflicting fields must be safe to recursively merge:

Ξ“ ⊒ l : t
Ξ“ ⊒ r : Type
l β‡₯ { ls… }
r β‡₯ {}
─────────────
Ξ“ ⊒ l β©“ r : t


Ξ“ ⊒ l : tβ‚€
Ξ“ ⊒ r : t₁
l β‡₯ { ls… }
r β‡₯ { a : A, rs… }
Ξ“ ⊒ { ls… } β©“ { rs… } : t
tβ‚€ ⋁ t₁ = tβ‚‚
─────────────────────────  ; a βˆ‰ ls
Ξ“ ⊒ l β©“ r : tβ‚‚


Ξ“ ⊒ l : tβ‚€
Ξ“ ⊒ r : t₁
l β‡₯ { a : Aβ‚€, ls… }
r β‡₯ { a : A₁, rs… }
Ξ“ ⊒ Aβ‚€ β©“ A₁ : Tβ‚€
Ξ“ ⊒ { ls… } β©“ { rs… } : T₁
tβ‚€ ⋁ t₁ = tβ‚‚
──────────────────────────
Ξ“ ⊒ l β©“ r : tβ‚‚

If the operator arguments are not record types then that is a type error.

If they share a field in common that is not a record type then that is a type error.

Recursive record merge requires that the types of both arguments can be combined with recursive record merge:

Ξ“ ⊒ l : Tβ‚€
Ξ“ ⊒ r : T₁
Ξ“ ⊒ Tβ‚€ β©“ T₁ : i
Tβ‚€ β©“ T₁ β‡₯ Tβ‚‚
───────────────
Ξ“ ⊒ l ∧ r : Tβ‚‚

The toMap operator can be applied only to a record value, and every field of the record must have the same type, which in turn must be a Type.

Ξ“ ⊒ e : { x : T, xs… }
Ξ“ ⊒ ( toMap { xs… } : List { mapKey : Text, mapValue : T } ) : List { mapKey : Text, mapValue : T }
───────────────────────────────────────────────────────────────────────────────────────────────────
Ξ“ ⊒ toMap e : List { mapKey : Text, mapValue : T }


Ξ“ ⊒ e : {}   Ξ“ ⊒ Tβ‚€ : Type   Tβ‚€ β‡₯ List { mapKey : Text, mapValue : T₁ }
───────────────────────────────────────────────────────────────────────
Ξ“ ⊒ ( toMap e : Tβ‚€ ) : List { mapKey : Text, mapValue : T₁ }


Ξ“ ⊒ toMap e : Tβ‚€   Tβ‚€ ≑ T₁
──────────────────────────
Ξ“ ⊒ ( toMap e : T₁ ) : Tβ‚€

You can complete a record literal using the record completion operator (T::r), which is syntactic sugar for (T.default β«½ r) : T.Type. The motivation for this operator is to easily create records without having to explicitly specify default-valued fields.

In other words, given a a record T containing the following fields:

  • A Type field with a record type
  • A default field with default fields for the record type

... then T::r creates a record of type T.Type by extending the default fields from T.default with the fields provided by r, overriding if necessary.

To type-check a record completion, desugar the operator and type-check the desugared form:

Ξ“ ⊒ ((T.default β«½ r) : T.Type) : U
──────────────────────────────────
Ξ“ ⊒ T::r : U

Unions

Union types are "anonymous", meaning that they are uniquely defined by the names and types of their alternatives.

An empty union is a Type:

─────────────
Ξ“ ⊒ <> : Type

A non-empty union can have alternatives of terms, types and kinds:

Ξ“ ⊒ T : tβ‚€   Ξ“ ⊒ < xs… > : t₁  tβ‚€ ⋁ t₁ = tβ‚‚
───────────────────────────────────────────  ; x βˆ‰ { xs… }
Ξ“ ⊒ < x : T | xs… > : tβ‚‚

A union type may contain alternatives without an explicit type label:

Ξ“ ⊒ < ts… > : c
───────────────────  ; x βˆ‰ < ts… >
Ξ“ ⊒ < x | ts… > : c

Note that the above rule allows storing values, types, and kinds in unions. However, if the type of the alternative is not Type, Kind, or Sort then that is a type error.

If two alternatives share the same name then that is a type error.

If a union alternative is non-empty, then the corresponding constructor is a function that wraps a value of the appropriate type:

Ξ“ ⊒ u : c   u β‡₯ < x : T | ts… >   ↑(1, x, 0, < x : T | ts… >) = U
─────────────────────────────────────────────────────────────────
Ξ“ ⊒ u.x : βˆ€(x : T) β†’ U

If a union alternative is empty, then the corresponding constructor's type is the same as the original union type:

Ξ“ ⊒ u : c   u β‡₯ < x | ts… >
───────────────────────────
Ξ“ ⊒ u.x : < x | ts… >

merge expressions

A merge expression is well-typed if there is a one-to-one correspondence between the fields of the handler record and the alternatives of the union:

Ξ“ ⊒ t : {}   Ξ“ ⊒ u : <>   Ξ“ ⊒ T : Type
──────────────────────────────────────
Ξ“ ⊒ (merge t u : T) : T


Ξ“ ⊒ merge t u : Tβ‚€   Tβ‚€ ≑ T₁
────────────────────────────
Ξ“ ⊒ (merge t u : T₁) : Tβ‚€

We use a trick to recursively check that all handlers have the same output type: Based on the types of the remaining fields and alternatives, we "invent" values (t₁ and u₁) from which we can create the smaller merge expression.

An implementation could simply loop over the inferred record type.

Ξ“ ⊒ tβ‚€ : { y : βˆ€(x : Aβ‚€) β†’ Tβ‚€, ts… }
Ξ“ ⊒ uβ‚€ : < y : A₁ | us… >
Aβ‚€ ≑ A₁
Ξ“ ⊒ t₁ : { ts… }
Ξ“ ⊒ u₁ : < us… >
↑(-1, x, 0, Tβ‚€) = T₁
Ξ“ ⊒ (merge t₁ u₁ : T₁) : Tβ‚‚
────────────────────────────────────  ; `x` not free in `Tβ‚€`
Ξ“ ⊒ merge tβ‚€ uβ‚€ : T₁


Ξ“ ⊒ tβ‚€ : { y : Tβ‚€, ts… }
Ξ“ ⊒ uβ‚€ : < y | us… >
Ξ“ ⊒ t₁ : { ts… }
Ξ“ ⊒ u₁ : < us… >
Ξ“ ⊒ (merge t₁ u₁ : Tβ‚€) : T₁
─────────────────────────────────────
Ξ“ ⊒ merge tβ‚€ uβ‚€ : Tβ‚€

Optionals can also be merged as if they had type < None | Some : A >:

Ξ“β‚€ ⊒ o : Optional A
↑(1, x, 0, (Ξ“β‚€, x : < None | Some : A >)) = Γ₁
Γ₁ ⊒ merge t x : T
──────────────────────────────────
Ξ“ ⊒ merge t o : T

If the first argument of a merge expression is not a record then that is a type error.

If the second argument of a merge expression is not a union or an Optional then that is a type error.

If you merge an empty union without a type annotation then that is a type error.

If the merge expression has a type annotation that is not a Type then that is a type error.

If there is a handler without a matching alternative then that is a type error.

If there is an alternative without a matching handler then that is a type error.

If a handler is not a function and the corresponding union alternative is non-empty, then that is a type error.

If the handler's input type does not match the corresponding alternative's type then that is a type error.

If there are two handlers with different output types then that is a type error.

If a merge expression has a type annotation that doesn't match every handler's output type then that is a type error.

showConstructor expressions

A showConstructor expression with a union or an Optional as its first argument has type Text.

Ξ“ ⊒ e : < ts… >
──────────────────────────────
Ξ“ ⊒ showConstructor e : Text


Ξ“ ⊒ o : Optional A
────────────────────────────
Ξ“ ⊒ showConstructor o : Text

If the first argument of a showConstructor expression is not a union or an Optional then that is a type error.

with expressions

A record update using the with keyword replaces a field:

Ξ“ ⊒ e : { k : T₁, ts… }
Ξ“ ⊒ v : Tβ‚‚
──────────────────────────────────
Ξ“ ⊒ e with k = v : { k : Tβ‚‚, ts… }


Ξ“ ⊒ e : { ts… }
Ξ“ ⊒ v : T
─────────────────────────────────  ; k βˆ‰ ts
Ξ“ ⊒ e with k = v : { k : T, ts… }

... and record updates can be nested, creating intermediate records if necessary:

Ξ“ ⊒ e : { kβ‚€ : Tβ‚€, ts… }
Ξ“ ⊒ e.kβ‚€ with k₁.ks… = v : T₁
───────────────────────────────────────────
Ξ“ ⊒ e with kβ‚€.k₁.ks… = v : { kβ‚€ : T₁, ts… }


Ξ“ ⊒ e : { ts… }
Ξ“ ⊒ {=} with k₁.ks… = v : T₁
───────────────────────────────────────────  ; kβ‚€ βˆ‰ ts
Ξ“ ⊒ e with kβ‚€.k₁.ks… = v : { kβ‚€ : T₁, ts… }

An Optional update using the with keyword must preserve the type of the "inner" value.

Ξ“ ⊒ e : Optional T
Ξ“ ⊒ v : T
──────────────────────────────────
Ξ“ ⊒ e with ?.ks… = v : Optional T

If the expression being updated (i.e. the e in e with ks… = v) is not a record or an Optional then that is a type error.

Integer

Integer is a type:

──────────────────
Ξ“ ⊒ Integer : Type

Integer literals have type Integer:

────────────────
Ξ“ ⊒ Β±n : Integer

The built-in functions on Integer have the following types:

─────────────────────────────────
Ξ“ ⊒ Integer/show : Integer β†’ Text


───────────────────────────────────────
Ξ“ ⊒ Integer/toDouble : Integer β†’ Double


──────────────────────────────────────
Ξ“ ⊒ Integer/negate : Integer β†’ Integer


─────────────────────────────────────
Ξ“ ⊒ Integer/clamp : Integer β†’ Natural

Double

Double is a type:

──────────────────
Ξ“ ⊒ Double : Type

Double literals have type Double:

────────────────
Ξ“ ⊒ n.n : Double

The built-in Double/show function has the following type:

───────────────────────────────
Ξ“ ⊒ Double/show : Double β†’ Text

Functions

A function type is only well-typed if the input and output type are well-typed and if the inferred input and output type are allowed by the function check:

Ξ“β‚€ ⊒ A : i   ↑(1, x, 0, (Ξ“β‚€, x : A)) = Γ₁   Γ₁ ⊒ B : o   i ↝ o : c
──────────────────────────────────────────────────────────────────
Ξ“β‚€ ⊒ βˆ€(x : A) β†’ B : c

If the input or output type is neither a Type, a Kind, nor a Sort then that is a type error.

An unquantified function type A β†’ B is a short-hand for βˆ€(_ : A) β†’ B. Note that the _ does not denote some unused type variable but rather denotes the specific variable named _ (which is a valid variable name and this variable named _ may in fact be present within B). For example, this is a well-typed judgment:

Ξ΅ ⊒ Type β†’ βˆ€(x : _) β†’ _ : Type

... because it is equivalent to:

Ξ΅ ⊒ βˆ€(_ : Type) β†’ βˆ€(x : _) β†’ _ : Type

The type of a Ξ»-expression is a function type whose input type (A) is the same as the type of the bound variable and whose output type (B) is the same as the inferred type of the body of the Ξ»-expression (b).

Ξ“β‚€ ⊒ Aβ‚€ : cβ‚€
Aβ‚€ β‡₯ A₁
↑(1, x, 0, (Ξ“β‚€, x : A₁)) = Γ₁
Γ₁ ⊒ b : B
Ξ“β‚€ ⊒ βˆ€(x : A₁) β†’ B : c₁
──────────────────────────────────
Ξ“β‚€ ⊒ Ξ»(x : Aβ‚€) β†’ b : βˆ€(x : A₁) β†’ B

Note that the above rule requires that the inferred function type must be well-typed.

The type system ensures that function application is well-typed, meaning that the input type that a function expects matches the inferred type of the function's argument:

Ξ“ ⊒ f : βˆ€(x : Aβ‚€) β†’ Bβ‚€
Ξ“ ⊒ aβ‚€ : A₁
Aβ‚€ ≑ A₁
↑(1, x, 0, aβ‚€) = a₁
Bβ‚€[x ≔ a₁] = B₁
↑(-1, x, 0, B₁) = Bβ‚‚
Bβ‚‚ β‡₯ B₃
──────────────────────
Ξ“ ⊒ f aβ‚€ : B₃

If the function does not have a function type, then that is a type error.

If the inferred input type of the function does not match the inferred type of the function argument then that is a type error.

let expressions

For the purposes of type-checking, an expression of the form:

let x : A = aβ‚€ in bβ‚€

... is not semantically identical to:

(Ξ»(x : A) β†’ bβ‚€) aβ‚€

let differs in behavior in order to support "type synonyms", such as:

let t : Type = Natural in 1 : t

If you were to desugar that to:

(Ξ»(t : Type) β†’ 1 : t) Natural

... then that would not be a well-typed expression, even though the let expression would be well-typed.

Ξ“ ⊒ aβ‚€ : A₁
Ξ“ ⊒ Aβ‚€ : i
Aβ‚€ ≑ A₁
aβ‚€ β‡₯ a₁
↑(1, x, 0, a₁) = aβ‚‚
bβ‚€[x ≔ aβ‚‚] = b₁
↑(-1, x, 0, b₁) = bβ‚‚
Ξ“ ⊒ bβ‚‚ : B
─────────────────────────────
Ξ“ ⊒ let x : Aβ‚€ = aβ‚€ in bβ‚€ : B


Ξ“ ⊒ aβ‚€ : A
aβ‚€ β‡₯ a₁
↑(1, x, 0, a₁) = aβ‚‚
bβ‚€[x ≔ aβ‚‚] = b₁
↑(-1, x, 0, b₁) = bβ‚‚
Ξ“ ⊒ bβ‚‚ : B
────────────────────────
Ξ“ ⊒ let x = aβ‚€ in bβ‚€ : B

If the let expression has a type annotation that doesn't match the type of the right-hand side of the assignment then that is a type error.

Type annotations

Type-checking an annotated expression verifies that the annotation matches the inferred type of the annotated expression, and returns the inferred type as the type of the whole expression:

Ξ“ ⊒ Tβ‚€ : i   Ξ“ ⊒ t : T₁   Tβ‚€ ≑ T₁
─────────────────────────────────
Ξ“ ⊒ (t : Tβ‚€) : T₁

Note that the above rule permits kind annotations, such as List : Type β†’ Type.

If the inferred type of the annotated expression does not match the type annotation then that is a type error.

Even though Sort is not a type-valid expression by itself, it is valid as a type annotation:

Ξ“ ⊒ t : Sort
─────────────────────
Ξ“ ⊒ (t : Sort) : Sort

Assertions

An assertion is equivalent to built-in language support for checking two expressions for judgmental equality, commonly invoked like this:

let example = assert : (2 + 2) === 4

in  …

An assertion checks that:

  • The type annotation is an equivalence
  • The two sides of the equivalence are in fact equivalent

... or in other words:

Ξ“ ⊒ T : Type
T β‡₯ x === y
x ≑ y
──────────────────────────
Ξ“ ⊒ (assert : T) : x === y

The inferred type of an assertion is the same as the provided annotation.

If the annotation is not an equivalence then that is a type error.

If the two sides of the equivalence are not equivalent then that is a type error.

To type-check an equivalence, verify that the two sides are terms:

Ξ“ ⊒ x : Aβ‚€
Ξ“ ⊒ y : A₁
Ξ“ ⊒ Aβ‚€ : Type
Ξ“ ⊒ A₁ : Type
Aβ‚€ ≑ A₁
──────────────────
Ξ“ ⊒ x === y : Type

If either side of the equivalence is not a term, then that is a type error.

If the inferred types do not match, then that is also a type error.

Imports

An expression with unresolved imports cannot be type-checked