Feel.Analysis.Identity (feel v0.1.0)

Copy Markdown View Source

A value compared with itself (D-170): x = x is true and x != x is false, for every value of FEEL, null included.

Feel.Runtime.Compare.equal/2 has no shortcut for two values that are the same one: it walks lists and contexts element by element. A value that is built from itself ({a0: [1, 2], a1: [a0, a0], …, a22: [a21, a21]}) is 23 cells in the VM and 2^22 numbers when it is walked, so a22 = a22 was ten seconds of one step, that no limit saw. An operand that is read twice is the same value both times, so the answer is known without reading it. The order of <, <=, > and >= is not an identity (null < null is null, and true <= true is null), so it is not the constant: ordering/4 has the type that the same read gives for the kinds that have an order (D-170).

Two operands are the same read when they are the same node, but for its meta:

  • a constant that is the same term, or a name of the bindings ({:var, _, name}) with the same name;
  • a :local of the same name and the same depth (meta.id, D-193), which is the binding of one construct;
  • a selection by the same name (p.a, not p.(a)) from the same read;
  • a list of the same length of the same reads, if the run can make it (max_list_length);
  • a context literal with the same keys in the same order, of the same reads, that is not an error (D-58).

A call is never one, whatever it is called with: it is counted by max_calls and by the diagnostics, and now() and uuid() give another value the second time. Nor is anything that binds a name inside the operand (an iterator, a function), a filter, an item, a partial, a descendant, or an operation: when in doubt, the comparison is made.

And only for values of FEEL. A name of type Any can be bound to a term that is not one (a float, an atom, a decimal that is a NaN: Feel.Runtime.Kind.of/1 is :unknown for it, and x = x is null with a type mismatch), and the soundness sweeps run the analysis on such values (scripts/sweep/dispatch.exs). So the type of the operand has to say that every value of it is a value of FEEL: no Any, no :foreign, and no name of a model type in it, at any depth. A type that is too large to look through (a tower of contexts) is not looked through, and the comparison is made. A list and a context, open or closed, are values of FEEL whatever their elements and entries are: Compare.equal/2 answers true for a list or a context that is the same term before it looks at an element or an entry (D-168), so a p that is a declared context and has an entry of any kind, and an xs of list<Any> that holds a float, are equal to themselves (D-170). A range is walked, and its ends are looked at.

What the comparison tells depends on whether the reads can report:

  • silent: constants, locals, the lists and the contexts of them, and a selection from a silent read whose type has no null in it (a selection reports an error only where it gives null). The two reads settle nothing and report nothing, so the comparison is the constant ({:const, _, true}).
  • reads: a name of the bindings, which reports a name that is not bound (D-37) at each of the two reads, and what is built on it. The type of the comparison is true and the node stays, so that the run reports what it did (D-45, D-53).

What it does not see: two operands that are alike but are not the same read (a22 = b22 for two towers built apart, xs[1] = xs[1]), and the same value that comes from two calls. They are compared by the run.

iex> {:ok, ast} = Feel.parse("{a: [1, 2], b: [a, a], r: b = b}")
iex> {:ok, %{ir: {:context, _, entries}}} = Feel.Analysis.Analyzer.analyze(ast, [])
iex> {"r", {:const, %{type: type}, value}} = List.last(entries)
iex> {type, value}
{[{:boolean, true}], true}

Summary

Types

What the reads of two operands that are the same can report. :constant is for two constants only.

Functions

What left <operator> right is when the two operands are the same read: whether the reads can report, and the value of the comparison. :none when they are not, or the operator is not = or !=.

The type of left <operator> right for <, <=, > and >= when the two operands are the same read of a value of a kind that has an order (D-170): false for < and >, and true for <= and >=, and null where the value is, since an ordering with a null is null. It is not an identity as = is, because of that null, and so the node stays and the run makes it. :none when the operands are not the same read, or the type has a kind without an order, which is a mismatch (a boolean, a list, a context, a range, a function), or one that is not known (Any).

Whether two operands are one read of a name, or of a member of one (p.age), so that they are the same value (D-170): the operands of x - x, which is 0 for every x, and not the difference of two numbers of the type of x. The same reads as comparison/4 takes, but for the ones that are made of other things than names: a constant, a list and a context are not here, since arithmetic on them is folded or a mismatch, and a list is dear to look through.

Types

reads()

@type reads() :: :constant | :silent | :reads

What the reads of two operands that are the same can report. :constant is for two constants only.

Functions

comparison(operator, left, right, limits)

@spec comparison(atom(), Feel.IR.t(), Feel.IR.t(), Feel.Limits.t()) ::
  {:silent | :reads, boolean()} | :none

What left <operator> right is when the two operands are the same read: whether the reads can report, and the value of the comparison. :none when they are not, or the operator is not = or !=.

iex> local = fn -> {:local, %{span: {0, 1}, type: [:number], id: 1}, "a"} end
iex> Feel.Analysis.Identity.comparison(:eq, local.(), local.(), Feel.Limits.default())
{:silent, true}
iex> var = fn -> {:var, %{span: {0, 1}, type: [:number]}, "x"} end
iex> Feel.Analysis.Identity.comparison(:neq, var.(), var.(), Feel.Limits.default())
{:reads, false}
iex> Feel.Analysis.Identity.comparison(:lte, var.(), var.(), Feel.Limits.default())
:none

ordering(operator, left, right, limits)

@spec ordering(atom(), Feel.IR.t(), Feel.IR.t(), Feel.Limits.t()) ::
  {:ok, Feel.Type.Union.t()} | :none

The type of left <operator> right for <, <=, > and >= when the two operands are the same read of a value of a kind that has an order (D-170): false for < and >, and true for <= and >=, and null where the value is, since an ordering with a null is null. It is not an identity as = is, because of that null, and so the node stays and the run makes it. :none when the operands are not the same read, or the type has a kind without an order, which is a mismatch (a boolean, a list, a context, a range, a function), or one that is not known (Any).

The two reads have to have one type, as they have in one place.

iex> var = fn type -> {:var, %{span: {0, 1}, type: type}, "x"} end
iex> Feel.Analysis.Identity.ordering(:lt, var.([:number, :null]), var.([:number, :null]), Feel.Limits.default())
{:ok, [{:boolean, false}, :null]}
iex> Feel.Analysis.Identity.ordering(:gte, var.([:date]), var.([:date]), Feel.Limits.default())
{:ok, [{:boolean, true}]}
iex> Feel.Analysis.Identity.ordering(:lte, var.([:boolean]), var.([:boolean]), Feel.Limits.default())
:none
iex> Feel.Analysis.Identity.ordering(:lt, var.([:any]), var.([:any]), Feel.Limits.default())
:none

same_value?(left, right, limits)

@spec same_value?(Feel.IR.t(), Feel.IR.t(), Feel.Limits.t()) :: boolean()

Whether two operands are one read of a name, or of a member of one (p.age), so that they are the same value (D-170): the operands of x - x, which is 0 for every x, and not the difference of two numbers of the type of x. The same reads as comparison/4 takes, but for the ones that are made of other things than names: a constant, a list and a context are not here, since arithmetic on them is folded or a mismatch, and a list is dear to look through.

iex> var = fn name -> {:var, %{span: {0, 1}, type: [:number]}, name} end
iex> Feel.Analysis.Identity.same_value?(var.("x"), var.("x"), Feel.Limits.default())
true
iex> Feel.Analysis.Identity.same_value?(var.("x"), var.("y"), Feel.Limits.default())
false
iex> path = fn name -> {:path, %{span: {0, 1}, type: [:number]}, var.("p"), name} end
iex> Feel.Analysis.Identity.same_value?(path.("age"), path.("age"), Feel.Limits.default())
true