# `Feel.Type`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/type.ex#L1)

The FEEL type system (DMN 1.6, section 10.3.2.9).

Types form a lattice with `:any` on top and `:null` at the bottom. Apart from
the parameterised families (`list`, `range`, `context`, `function`), the
lattice is flat: the primitive types do not conform to each other, and in
particular `:date` does not conform to `:date_and_time`. There are no union
types, and no common supertype of the two duration types.

| `t:t/0`                           | FEEL                                   |
| --------------------------------- | -------------------------------------- |
| `:any`, `:null`                   | `Any`, `Null`                          |
| `:boolean`, `:number`, `:string`  | `boolean`, `number`, `string`          |
| `:date`, `:time`                  | `date`, `time`                         |
| `:date_and_time`                  | `date and time`                        |
| `:years_and_months_duration`      | `years and months duration`            |
| `:days_and_time_duration`         | `days and time duration`               |
| `{:list, type}`                   | `list<type>`                           |
| `{:range, type}`                  | `range<type>`                          |
| `{:context, %{"k" => type}}`      | `context<k: type>`                     |
| `{:function, [type], result}`     | `function<type> -> result`             |
| `{:recursive, name, graph}`       | guarded recursive item data             |
| `{:named, "Name"}`                | a model-defined type, by name          |
| `{:non_null, type}`               | `type!` (an extension, D-48)           |
| `{:not, type}`                    | `Any except type` (an extension, D-178) |
| `{:union, [type, ...]}`           | `type \| type` (an extension, D-178)    |

A context type lists its members by name; the order of members does not
matter. Parameter names are not part of a function type.

The last three go past the lattice: `T!` says that a name is never `null`, `Any except T` that it is not
a `T`, and `T | U` that it is one or the other. They are for what a host *declares* (`Feel.Scope`, the result type of an
expression, a type of the scope that an expression names), and `parse/2` reads them; the types in an expression (`instance of`, the
parameters of a function) are those of the specification and `T!`. See `Feel.Type.Composite` for what the last two mean.

## Types and values

This module describes types as data (`t:t/0`), for checking and inference. It
does not hold values. The values of each FEEL type, and how they are
represented, are in the modules under `Feel.Value`, such as `Feel.Value.Number`
and `Feel.Value.List`. Each of them has a `type/0` (or `type/1` for the
parameterised types) that returns its descriptor, so `Feel.Value.Number.type()`
is `:number`. `Feel.Value` itself is the type `Any`.

## What is here

  * **Relations between types**: `conforms?/2`, `equivalent?/2`, and `join/2`
    (see `Feel.Type.Conformance`).
  * **Relations between values and types**: `of/1`, `instance?/2`, and
    `value_conforms?/2` (see `Feel.Type.Instance`).
  * **Implicit conversion**: `coerce/3` (see `Feel.Type.Conversion`).
  * **Type expressions**: `parse/2` reads `list<number>` and the like, and
    `format/1` writes them. Types defined by a model, like the item definitions of
    a DMN model, are found in the `Feel.Scope`, and `resolve/2` replaces a
    `{:named, name}` by its definition.

The relations work on resolved types, with no `{:named, _}` left in them.

In these types `Null` is under every type, so `number` means "a number or `null`". The
analyzer needs to say that a result is never `null`, and uses `Feel.Type.Union`, in
which the null is an alternative of its own (D-39).

# `t`

```elixir
@type t() ::
  :any
  | :null
  | :boolean
  | :number
  | :string
  | :date
  | :time
  | :date_and_time
  | :years_and_months_duration
  | :days_and_time_duration
  | {:list, t()}
  | {:range, t()}
  | {:context, %{optional(String.t()) =&gt; t()}}
  | {:function, [t()], t()}
  | {:named, String.t()}
  | Feel.Type.Recursive.t()
  | {:non_null, t()}
  | {:not, t()}
  | {:union, [t(), ...]}
```

# `builtin`

```elixir
@spec builtin(Feel.Name.t()) :: {:ok, t()} | :error
```

Returns the built-in type with the given canonical name.

    iex> Feel.Type.builtin("date and time")
    {:ok, :date_and_time}
    iex> Feel.Type.builtin("Employee")
    :error

# `builtin_names`

```elixir
@spec builtin_names() :: [Feel.Name.t()]
```

Returns the names of the built-in types, sorted.

# `coerce`

```elixir
@spec coerce(Feel.Value.t(), t(), [{:compose, boolean()}]) ::
  {:ok, Feel.Value.t()} | :error
```

Converts a value to fit a type with the implicit conversions of section
10.3.2.9.4, such as wrapping a value in a singleton list. Returns `:error` when
no conversion applies. See `Feel.Type.Conversion`.

# `conforms?`

```elixir
@spec conforms?(t(), t()) :: boolean()
```

Returns `true` when the first type conforms to the second: an instance of the
first can be used wherever the second is expected. See `Feel.Type.Conformance`.

# `equivalent?`

```elixir
@spec equivalent?(t(), t()) :: boolean()
```

Returns `true` when two types are equivalent. See `Feel.Type.Conformance`.

# `format`

```elixir
@spec format(t()) :: String.t()
```

Formats a type as FEEL type syntax.

Context members are listed in key order, and an empty context type is written
`context<>`, which is not valid FEEL syntax.

    iex> Feel.Type.format({:list, :number})
    "list<number>"
    iex> Feel.Type.format({:function, [:number, :string], :boolean})
    "function<number, string> -> boolean"
    iex> Feel.Type.format({:context, %{"name" => :string, "age" => :number}})
    "context<age: number, name: string>"
    iex> Feel.Type.format({:non_null, {:list, :number}})
    "list<number>!"
    iex> Feel.Type.format({:union, [:number, {:not, :string}]})
    "number | (Any except string)"
    iex> Feel.Type.format({:non_null, {:not, {:union, [:number, :string]}}})
    "(Any except number | string)!"

# `from_ast`

```elixir
@spec from_ast(Feel.AST.type_expression(), Feel.Scope.t()) ::
  {:ok, t()} | {:error, [Feel.Diagnostic.t(), ...]}
```

Turns a type node of the syntax tree (see `Feel.Syntax.TypeParser`) into a type.

Names are looked up among the built-in types and then in the scope. Returns the
diagnostics of the first problem: an unknown or self-referential type name, or a
context type with the same member twice.

# `instance?`

```elixir
@spec instance?(Feel.Value.t(), t()) :: boolean()
```

The `instance of` operator (Table 62): `null` is an instance of `:null` only, and
every other value must fit the type. See `Feel.Type.Instance`.

# `join`

```elixir
@spec join(t(), t()) :: t()
```

Returns a conservative common supertype that both types conform to. It need not
be the least expressible supertype for functions or composite declarations.
See `Feel.Type.Conformance`.

# `non_null`

```elixir
@spec non_null(t()) :: {:ok, t()} | :error
```

Marks a type as not `null` (D-48): the type of a name that must have a value.

A type that is already marked stays as it is. `Null!` has no values, so it is `:error`.

    iex> Feel.Type.non_null(:number)
    {:ok, {:non_null, :number}}
    iex> Feel.Type.non_null({:non_null, :number})
    {:ok, {:non_null, :number}}
    iex> Feel.Type.non_null(:null)
    :error

# `of`

```elixir
@spec of(Feel.Value.t()) :: t()
```

Returns the type of a value (Table 43). See `Feel.Type.Instance`.

# `parse`

```elixir
@spec parse(String.t(), [{:scope, Feel.Scope.t()}]) ::
  {:ok, t()} | {:error, Feel.Error.t()}
```

Parses a type expression such as `list<number>` or `context<a: string>`.

The names of types defined by a model are looked up in the `:scope` option, a
`t:Feel.Scope.t/0`, and replaced by their definitions. A name that is neither
built in nor in the scope is an error, as is a name that refers to itself. A
context member can be a name or a string, so `context<"first name": string>`
works.

This is the reading of a type that a host declares, and it goes past the types of an expression (D-178): `Any except T`
is `{:not, T}` and `T | U` is `{:union, [T, U]}`, and a type can be put in parentheses. `Any except` takes everything
to its right up to the end of the bracket it is in, so `Any except number | string` is neither, and
`(Any except number) | string` is `Any`. A `!` after a member of a union or after `except` is refused, since it is not
read; mark the whole type, `(Any except number)!`. The type of an expression (`instance of`, the parameters of a
function) is parsed by `Feel.Syntax.TypeParser` without these.

## Errors

The error has kind `:syntax` when the text is not a type expression, and
`:analysis` when it is one but names a type that does not exist.

    iex> Feel.Type.parse("list<date and time>")
    {:ok, {:list, :date_and_time}}
    iex> Feel.Type.parse("function<number, string> -> boolean")
    {:ok, {:function, [:number, :string], :boolean}}
    iex> Feel.Type.parse("list<number!>!")
    {:ok, {:non_null, {:list, {:non_null, :number}}}}
    iex> Feel.Type.parse("Any except number")
    {:ok, {:not, :number}}
    iex> Feel.Type.parse("number | list<string | boolean>")
    {:ok, {:union, [:number, {:list, {:union, [:string, :boolean]}}]}}
    iex> Feel.Type.parse("(Any except number | string)!")
    {:ok, {:non_null, {:not, {:union, [:number, :string]}}}}
    iex> {:error, error} = Feel.Type.parse("list<Employee>")
    iex> {error.kind, error.code}
    {:analysis, :invalid_type_expression}

# `parse_ast`

```elixir
@spec parse_ast(String.t(), [Feel.Name.t()]) ::
  {:ok, Feel.AST.type_expression()} | :error
```

Reads a type expression as the node of the syntax tree that `from_ast/2` takes, with the places of the text. `known` is
the set of the names of the types that exist besides the built-in ones. `:error` for a text that is not a type
expression, which `parse/2` says why.

    iex> {:ok, {:non_null_type, _, {:type_name, _, ["number"]}}} = Feel.Type.parse_ast("number!", [])
    iex> Feel.Type.parse_ast("number +", [])
    :error

# `resolve`

```elixir
@spec resolve(t(), Feel.Scope.t()) ::
  {:ok, t()} | {:error, {:unknown_type | :cyclic_type, Feel.Name.t()}}
```

Replaces every `{:named, name}` in a type by the definition found in the scope,
recursively.

Returns `{:error, {:unknown_type, name}}` for a name that is not in the scope,
and `{:error, {:cyclic_type, name}}` for a type whose definition leads back to
itself through unresolved named references. Guarded descriptors produced by
`Feel.ItemDefinitions.new/2` with `recursive: :guarded` are already resolved finite graphs;
they are retained without recursively expanding their back references.

    iex> scope = Feel.Scope.put_type(%Feel.Scope{}, "Address", {:context, %{"zip" => :string}})
    iex> Feel.Type.resolve({:list, {:named, "Address"}}, scope)
    {:ok, {:list, {:context, %{"zip" => :string}}}}
    iex> Feel.Type.resolve({:named, "Missing"}, scope)
    {:error, {:unknown_type, "Missing"}}

# `value_conforms?`

```elixir
@spec value_conforms?(Feel.Value.t(), t()) :: boolean()
```

Returns `true` when a value fits a type, where `null` fits every type. See
`Feel.Type.Instance`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
