MLton 20210117

# TypeChecking

MLton’s type checker follows the Definition closely, so you may find differences between MLton and other SML compilers that do not follow the Definition so closely. In particular, SML/NJ has many deviations from the Definition — please see SMLNJDeviations for those that we are aware of.

In some respects MLton’s type checker is more powerful than other SML compilers, so there are programs that MLton accepts that are rejected by some other SML compilers. These kinds of programs fall into a few simple categories.

- MLton resolves flexible record patterns using a larger context than many other SML compilers. For example, MLton accepts the following.

      fun f {x, ...} = x
      val _ = f {x = 13, y = "foo"}

- MLton uses as large a context as possible to resolve the type of variables constrained by the value restriction to be monotypes. For example, MLton accepts the following.

      structure S:
         sig
            val f: int -> int
         end =
         struct
            val f = (fn x => x) (fn y => y)
         end

## Type error messages

To aid in the understanding of type errors, MLton’s type checker displays type errors differently than other SML compilers. In particular, when two types are different, it is important for the programmer to easily understand why they are different. So, MLton displays only the differences between two types that don’t match, using underscores for the parts that match. For example, if a function expects real * int but gets real * real, the type error message would look like

    expects: _ * [int]
    but got: _ * [real]

As another aid to spotting differences, MLton places brackets [] around the parts of the types that don’t match. A common situation is when a function receives a different number of arguments than it expects, in which case you might see an error like

    expects: [int * real]
    but got: [int * real * string]

The brackets make it easy to see that the problem is that the tuples have different numbers of components — not that the components don’t match. Contrast that with a case where a function receives the right number of arguments, but in the wrong order, in which case you might see an error like

    expects: [int] * [real]
    but got: [real] * [int]

Here the brackets make it easy to see that the components do not match.

We appreciate feedback on any type error messages that you find confusing, or suggestions you may have for improvements to error messages.

## The shortest/most-recent rule for type names

In a type error message, MLton often has a number of choices in deciding what name to use for a type. For example, in the following type-incorrect program

    type t = int
    fun f (x: t) = x
    val _ = f "foo"

MLton reports the error message

    Error: z.sml 3.9-3.15.
      Function applied to incorrect argument.
        expects: [t]
        but got: [string]
        in: f "foo"

MLton could have reported expects: [int] instead of expects: [t]. However, MLton uses the shortest/most-recent rule in order to decide what type name to display. This rule means that, at the point of the error, MLton first looks for the shortest name for a type in terms of number of structure identifiers (e.g. foobar is shorter than A.t). Next, if there are multiple names of the same length, then MLton uses the most recently defined name. It is this tiebreaker that causes MLton to prefer t to int in the above example.

In signature matching, most recently defined is not taken to include all of the definitions introduced by the structure (since the matching takes place outside the structure and before it is defined). For example, in the following type-incorrect program

    structure S:
       sig
          val x: int
       end =
       struct
          type t = int
          val x = "foo"
       end

MLton reports the error message

    Error: z.sml 2.4-4.6.
      Variable in structure disagrees with signature (type): x.
        structure: val x: [string]
        defn at: z.sml 7.11-7.11
        signature: val x: [int]
        spec at: z.sml 3.11-3.11

If there is a type that only exists inside the structure being matched, then the prefix _str. is used. For example, in the following type-incorrect program

    structure S:
       sig
          val x: int
       end =
       struct
          datatype t = T
          val x = T
       end

MLton reports the error message

    Error: z.sml 2.4-4.6.
      Variable in structure disagrees with signature (type): x.
        structure: val x: [_str.t]
        defn at: z.sml 7.11-7.11
        signature: val x: [int]
        spec at: z.sml 3.11-3.11

in which the [_str.t] refers to the type defined in the structure.