What the declarations of a scope are used for: the one option declarations: of Feel.prepare/2 (D-138).
A scope declares the type of a name and, for some names, its allowed values (Feel.Scope). Two things can be done with
a declaration, and they are separate (D-138). The run can look at a bound value that does not fit and do something about
it. The analysis can use the allowed values to narrow the type of the name: age declared with [0..150] is
number in [0..150], so age + 1 is number in [1..151], which lets the analysis prove things about it and the
optimizer take out checks that the types make useless. The declared type of a name is used whatever is chosen; it is
the narrowing by the allowed values that is chosen.
Narrowing is sound when the run makes the values what the types say, and it is the caller's word when it does not: a value that is not allowed after all makes the types wrong, and what the optimizer took out because of them. So the two are not chosen apart. The option is a stance on the declarations, one of six:
| Stance | A bound value that does not fit | The allowed values | age + 1, age in [0..150] |
|---|---|---|---|
:enforce (default) | is converted, or is null with an error | narrow the types | number in [1..151] | Null |
:enforce_wide | the same | do not narrow | number | Null |
:trust | is not looked at: the host guarantees the values | narrow the types | number in [1..151] | Null |
:audit | stays, and is reported as an error | narrow the types | number in [1..151] | Null |
:report | stays, and is reported: as an error for a declared type, as a warning for allowed values | do not narrow | number | Null |
:ignore | is not looked at | do not narrow | number | Null |
:enforce is what makes the types true, so it is the default. :trust is for a host that has looked at the values
already: the declarations are used and never checked, and nothing is put around the expression. :audit trusts in the
same way and says when it was wrong, as an error, which ends a strict run exactly where the types may be false; the
value stays, since an error that came with a null would be a different run. :report and :ignore rely on no allowed
value. The declared type is relied on in every stance, as a scope says it is the type of what the names are bound to; the
stances that look at the values (all but :trust and :ignore) differ in what they do about one that does not fit.
A name declared not null (number!, D-48) has no null in its type only under :enforce and :enforce_wide, where the
run ends if it would be null.
The tiers
Narrowing makes sets of values, which analysis: :core has none of (Feel.Analysis.Refinement, D-133). So there
:enforce narrows nothing, as :enforce_wide does not (a run gives the same values and diagnostics), and :trust and
:audit, which exist to narrow, raise ArgumentError.
What the analyzer is given
settings!/2 gives the analyzer the pair it works with, {verify, narrow}, from the stance and the tier. verify is
what the run does (:enforce, :report or false) and narrow whether the allowed values narrow the types. They
decide the nodes around the expression (Feel.Analysis.Analyzer), the severity of a report, and what the optimizer may
rely on, and are not options.
Summary
Types
A stance on the declarations of a scope: :enforce (the default), :enforce_wide, :trust, :audit, :report or
:ignore. The table is in the documentation of Feel.Analysis.Declarations.
What the run does with a bound value that does not fit its declaration.
Functions
Raises ArgumentError if the options have :verify or :narrow, the two options that :declarations replaced (D-138),
and names the stance to use for what was passed.
What the run does with a value and whether the allowed values narrow the types, for a stance in a tier of the analysis.
The stances, the default first.
Types
@type stance() :: :enforce | :enforce_wide | :trust | :audit | :report | :ignore
A stance on the declarations of a scope: :enforce (the default), :enforce_wide, :trust, :audit, :report or
:ignore. The table is in the documentation of Feel.Analysis.Declarations.
@type verify() :: :enforce | :report | false
What the run does with a bound value that does not fit its declaration.
Functions
@spec reject_removed!(keyword()) :: :ok
Raises ArgumentError if the options have :verify or :narrow, the two options that :declarations replaced (D-138),
and names the stance to use for what was passed.
iex> Feel.Analysis.Declarations.reject_removed!(declarations: :trust)
:ok
iex> Feel.Analysis.Declarations.reject_removed!(verify: false, narrow: true)
** (ArgumentError) the options :verify and :narrow are replaced by :declarations, which takes :enforce, :enforce_wide, :trust, :audit, :report or :ignore (see Feel.prepare/2): [verify: false, narrow: true] is declarations: :trust
@spec settings!(term(), Feel.Analysis.Refinement.tier()) :: {verify(), boolean()}
What the run does with a value and whether the allowed values narrow the types, for a stance in a tier of the analysis.
nil is the default stance, :enforce. Raises ArgumentError for anything that is not a stance, and for :trust and
:audit in the tier :core, which has no sets of values to narrow by.
iex> Feel.Analysis.Declarations.settings!(:enforce, :full)
{:enforce, true}
iex> Feel.Analysis.Declarations.settings!(:enforce, :core)
{:enforce, false}
iex> Feel.Analysis.Declarations.settings!(:trust, :full)
{false, true}
iex> Feel.Analysis.Declarations.settings!(:audit, :full)
{:report, true}
iex> Feel.Analysis.Declarations.settings!(:ignore, :core)
{false, false}
@spec stances() :: [stance(), ...]
The stances, the default first.