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

The values that unary tests accept, as sets (D-39).

The allowed values of a variable are compiled to the IR of unary tests, in which each test is an
expression of the input `?` (`Feel.Analysis.AllowedValues`). `restrict/2` reads that IR and
narrows the type of the variable to the numbers, the strings, the booleans, the dates or the durations
the tests accept, so that `[0..150]` makes a number of `age` a number in `[0..150]`, and
`[@"2020-01-01"..@"2020-12-31"]` makes a `date` one in that year (D-109).

A date and a duration are sets of numbers too, of the numbers that stand for them (`Feel.Type.Image`,
D-109), and are ordered as the numbers are, so they are read exactly as a number is, with the image of
the other side of a comparison. A time and a date and time are sets of numbers too, of the second a value falls
in, but their order depends on a zone (D-54), so a value has the number of its clock and, if it has a zone, that of its
instant, and they are read by `Feel.Analysis.MomentTests` (D-109).

Only a safe reading is made. The set of a test is a superset of the values that make it true,
so a value that is accepted is always in the set, and a test that is not read (a name that is not
a constant, an expression of `?`, a `:match`) accepts everything. A set is therefore the values
that the tests *may* accept; within the representation budget it is exact for input entries:
comparisons of `?` with numbers or strings, intervals, lists of values, `-`, and `not(...)` of
those.

The result of a test has three values, `true`, `false` and `null`. `null` is neither accepted nor
rejected: `not(...)` accepts only values that make all of its tests `false`. So each test has two
sets: where it can be true, and where it can be false (both supersets), and `and` and `or`
combine them as the logic does: a conjunction is true where both are, and false where either is.

## A name of the type `Any`

`Any` is the union of all the kinds of value, so it is narrowed by the kinds that the tests can
accept, each with the set of its values the tests accept. A comparison of `?` with an operand of a
known kind is true or false only for a value of that kind: `"a" >= 0` is `null`, as is `[5] >= 0`
and `true < 1`, and so is `"a" = 0`, a type mismatch, and `null` is neither accepted nor rejected.
So `[0..10]` accepts only numbers, and an `Any` becomes `number in [0..10]`; `[0..10], true` becomes
`number in [0..10] | boolean = true`. This is read in the same way as the sets, as a pair of kinds for
each test (where it may be true, where it may be false), which `and` and `or` meet and join and
`not(...)` takes the second of.

Three things accept more kinds than the operand says: an operand that can be `null` (`x = null` is
`false`, and `x != null` is `true`, for every value that is not `null`), an operand of an unknown
type (`= x` for an `x` of the type `Any` accepts every kind, and `> x` every kind that has an order),
and a test that is not read. When the kinds are all of them the type stays `Any`. A function that may be
accepted is the kind `function`, a function of any parameters (D-39).

    iex> {:ok, ast} = Feel.parse("[0..150], > 200", entry: :unary_tests)
    iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
    iex> Feel.Analysis.TestSets.restrict([:number, :null], tests) |> Feel.Type.Union.format()
    "number in [0..150], (200..max] | Null"

# `strings`

```elixir
@type strings() :: Feel.Type.StringSet.set()
```

A set of strings (`Feel.Type.StringSet`): every string, a list, or one with intervals.

# `certain`

```elixir
@spec certain(Feel.Type.Union.t(), Feel.IR.t()) :: Feel.Type.Union.t() | []
```

The values of a type that the tests **certainly** accept: `restrict/2` is a superset of what they accept, and this is a subset,
so that the two together bound a rule of a decision table from outside and from inside.

A test is read only where its answer is known to be exact: `? op other` for an `other` that is one number, string, boolean,
date or duration (the sets of `compared_sets/3` are then the values for which the comparison is `true` and for which it is
`false`), joined by `and` and `or`. Anything else (an `other` that is a range of values or is unknown, a time, a test that is
not a comparison) accepts nothing for certain. The result has no `null`: whether a test accepts `null` is not a question about
a set, and is left to whoever runs it. It is `[]` when nothing is certain, and a type of the members that have values.

Numeric combinations keep inner bounds. String alternatives use bulk union, and negated lists use
bulk intersection, before the point/exclusion budget is checked. Interval ranges do not count
toward that budget; the bulk operations avoid rebuilding a growing range set for every entry.

    iex> {:ok, ast} = Feel.parse("[0..150], > 200", entry: :unary_tests)
    iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
    iex> Feel.Analysis.TestSets.certain([:number, :null], tests) |> Feel.Type.Union.format()
    "number in [0..150], (200..max]"
    iex> {:ok, ast} = Feel.parse("> x", entry: :unary_tests)
    iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
    iex> Feel.Analysis.TestSets.certain([:number, :null], tests)
    []

# `compared_sets`

```elixir
@spec compared_sets(
  :number | :string | :boolean | Feel.Type.Image.kind(),
  atom(),
  Feel.Type.Union.t()
) ::
  {term(), term()}
```

Returns `{where it can be true, where it can be false}` for `? operator other`, when `?` is a value of
a kind (`:number`, `:string`, `:boolean`, `:date` or a duration) and `other` is of the type, a union.

Both are supersets of the values of the kind for which the comparison is `true`, and `false`; it
is `null`, which is neither, for the values that `other` cannot be compared with. For a number, a date and
a duration they are `Feel.Type.NumberSet`s (of the images, for the last two, D-109), for a string a
`Feel.Type.StringSet` (a list, or `:all`), and for a boolean a list of `true` and `false`.
`Feel.Analysis.Narrowing` reads the types of the names in a branch from them.

    iex> five = [{:number, Feel.Type.NumberSet.point(Decimal.new(5))}]
    iex> {true_set, false_set} = Feel.Analysis.TestSets.compared_sets(:number, :gt, five)
    iex> {Feel.Type.NumberSet.format(true_set), Feel.Type.NumberSet.format(false_set)}
    {"(5..max]", "[-max..5]"}
    iex> Feel.Analysis.TestSets.compared_sets(:boolean, :eq, [{:boolean, true}])
    {[true], [false]}

# `restrict`

```elixir
@spec restrict(Feel.Type.Union.t(), Feel.IR.t()) :: Feel.Type.Union.t()
```

Narrows the number, the string or the boolean of a type to what the tests accept, and `Any` to the
kinds the tests can accept.

The type must have one alternative besides `null`, a `:number`, `:string`, `:boolean`, `:date`, a
duration, or `:any`, bare or with the set of its values; any other type is returned as it is.

# `restrict_elements`

```elixir
@spec restrict_elements(Feel.Type.Union.t(), Feel.IR.t()) :: Feel.Type.Union.t()
```

Narrows the elements of the lists of a type to what the tests accept, as `restrict/2` does for a
value. The tests are those of the elements (`each` of `Feel.Analysis.AllowedValues.compile/3`), and
a `null` element is exempt, so it stays if the elements could be `null`.

    iex> {:ok, ast} = Feel.parse("[0..150]", entry: :unary_tests)
    iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
    iex> Feel.Analysis.TestSets.restrict_elements([{:list, [:number, :null]}, :null], tests) |> Feel.Type.Union.format()
    "list<number in [0..150] | Null> | Null"

---

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