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

Declared types that go past the specification's lattice: `{:not, type}` and `{:union, [type, ...]}` (D-178).

The lattice has no union and no negation, and the internal type (`Feel.Type.Union`) has both: `Any except number` is what is
left of a value of `v instance of number` in the `else` branch, `number | string` is a join the analysis makes at every `if`.
A host that knows more of a name than the lattice can say, that it is anything but a number or that it is a number or a string, says
it with these two, as it says "never `null`" with `{:non_null, type}` (D-48):

| `t:Feel.Type.t/0`              | Written                       | The values                                             |
| ------------------------------ | ----------------------------- | ------------------------------------------------------ |
| `{:not, :number}`              | `Any except number`           | every value that is not a number                       |
| `{:union, [:number, :string]}` | `number \| string`            | a number or a string                                   |
| `{:not, {:union, [:number, {:list, :any}]}}` | `Any except number \| list<Any>` | neither a number nor a list         |

## What they mean

The union translation (`to_union/1`) supplies the analysis representation. Runtime membership
evaluates the union or negation directly with the original component declarations. Recursion may
occur in a value's retained function signature even when the target declaration has none, so an
analysis projection cannot replace the runtime check. In particular, negating a recursive
declaration projects to `Any | Null` without making runtime negation accept every value.

  * **`null`** is in the type as it is in every other type of a declaration: the run exempts `null` from a declared type
    (D-138), so an analysis that left it out would be wrong. Only a mark on the whole type, `{:non_null, type}` or `(Any except number)!`,
    takes it out. A mark on a member of a union, or on the type after `except`, is ignored by `to_union/1` and refused by
    `Feel.Type.parse/2`: `Any except number!` would read as "not a number, and not null", and it is not.
  * **`{:not, type}`** is `Any` less the values of `type` that are not `null`. `Null` is not a value that can be taken out
    (`null` is under every type), so `{:not, :null}` is `Any`, and `{:not, :any}` is `Null`.
  * **`{:union, types}`** is the join of the types, and has at least one. A member that is a composite type is flattened by the
    union's normal form, so `number | (string | boolean)` is `number | string | boolean`.
  * **Conversion** is the one of the specification, asked of the union: a singleton list is unwrapped when its element is in the
    type and the list is not (`Feel.Type.Conversion`). A value is not wrapped in a list, and a date is not made a date and time, to
    reach a member of a union (`3` is not `list<number> | string`, and is not made `[3]`), since the specification's conversions are
    for one type that is expected, and a union is not one. A list whose element type is a composite type is wrapped as any list is.
  * **A function type** with a composite type in its parameters or its result is compared by the algebra of arrows
    (`Feel.Type.Arrows`), not by the lattice. Composite conformance involving recursive descriptors
    conservatively accepts identical declarations; it does not prove recursive relations from projections.

`Feel.type/1` is a type of the specification, so for a name declared with one it is wider than the declaration: `Any` for `Any except
number` and for `number | string`, `list<Any>` for `list<number> | list<string>`. `Feel.IR.type/1` retains
the internal representation, including conservative upper approximations where required.

# `composite?`

```elixir
@spec composite?(Feel.Type.t()) :: boolean()
```

Whether a type is one of the two, at its outermost level. A composite type inside a list, a context or a function type is reached
by the code that walks the type.

    iex> Feel.Type.Composite.composite?({:not, :number})
    true
    iex> Feel.Type.Composite.composite?({:list, {:not, :number}})
    false

# `conforms?`

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

Whether a type is proved within another when either is composite. Non-recursive declarations
compare their analysis unions. If either contains guarded recursive descriptors, only identical
declarations are accepted here; a negative answer need not establish semantic non-inclusion.

    iex> Feel.Type.Composite.conforms?(:number, {:union, [:number, :string]})
    true
    iex> Feel.Type.Composite.conforms?({:not, :number}, :any)
    true
    iex> Feel.Type.Composite.conforms?({:union, [:number, :string]}, :number)
    false

# `to_union`

```elixir
@spec to_union(Feel.Type.t()) :: Feel.Type.Union.t()
```

The analysis union of a composite declaration, `null` included. Recursive declarations may give
an upper approximation rather than exact runtime membership. Raises `ArgumentError` for a union of no type.

    iex> Feel.Type.Composite.to_union({:not, :number}) |> Feel.Type.Union.format()
    "Any except number | Null"
    iex> Feel.Type.Composite.to_union({:union, [:number, :string]}) |> Feel.Type.Union.format()
    "number | string | Null"
    iex> Feel.Type.Composite.to_union({:not, {:union, [:number, :string]}}) |> Feel.Type.Union.format()
    "Any except number, string | Null"
    iex> Feel.Type.Composite.to_union({:not, {:list, :number}}) |> Feel.Type.Union.format()
    "Any except list<number | Null> | Null"
    iex> Feel.Type.Composite.to_union({:union, []})
    ** (ArgumentError) a union type needs a member

# `value_conforms?`

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

Whether a value is in a composite type. `null` is exempt; each component is checked directly
against the original declaration, including retained function signatures.

    iex> Feel.Type.Composite.value_conforms?(Decimal.new(1), {:not, :number})
    false
    iex> Feel.Type.Composite.value_conforms?("a", {:not, :number})
    true
    iex> Feel.Type.Composite.value_conforms?(nil, {:not, :number})
    true

---

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