Feel.Paranoid (feel v0.1.0)

Copy Markdown View Source

Inspection for trusted fixtures; execution and monitoring run in the caller. Use the ordinary Feel APIs for default containment of untrusted input.

The paranoid run (D-159): the proof that the analysis made of an expression, checked on the values of one run.

The analysis gives every node of the IR a type, and the optimizer takes out checks that those types make unneeded. A mistake in a type is a wrong answer and not a missed warning, and the sweeps (scripts/sweep/) only look at the type of the root of an expression. run/3 runs the IR as the analyzer made it (Feel.Expression.unoptimized_ir/1, with nothing taken out) on the interpreter, and for every node checks that the value it made is in its type with the exact Feel.Type.Union.contains?/2. It also runs the expression as it is prepared (optimized, on its own backend) and checks that the value and the diagnostics are the same.

It returns the result and the violations: a node whose value is not in its type (:type), or an optimized run that gave another result (:optimizer). A violation is a bug of the analysis or the optimizer. An empty list is no proof, and a run that has none has shown that this input is not a counterexample.

It runs at the speed of the interpreter and of a check at each node, so it is for tests and for finding out why an answer is wrong. Nothing else reads the monitor: it is told by Feel.Backend.Interpreter.Monitored, a walk of the nodes of its own, so a run without one (Feel.Backend.Interpreter.Plain) pays nothing for it (D-159).

iex> {:ok, expression} = Feel.prepare("for x in [1, 2, 3] return x * 2")
iex> {{:ok, _value, []}, []} = Feel.Paranoid.run(expression)

Summary

Types

A node whose value is not in its type, or is not a multiple of the grain the node says it has (D-161), or an optimized run that disagrees with the one without the optimizer.

Functions

Runs a prepared expression as the interpreter runs the IR the analyzer made, and returns the result with the violations.

Whether two results of Feel.run/3 are the same: the same value (a number at any scale is the same number, a function of FEEL is the same when its parameters and its result type are) and matching diagnostics, or the same error. Diagnostic fields are compared recursively, using value equality for embedded numbers and functions. Function comparison checks signature metadata, not behavior; test returned functions by calling them. The optimized run and the run of the IR as the analyzer made it are held to this; so is any other pair of runs of one expression.

Types

violation()

@type violation() ::
  %{
    kind: :type,
    node: atom(),
    span: Feel.Diagnostic.span() | nil,
    type: Feel.Type.Union.t(),
    value: term()
  }
  | %{
      kind: :grain,
      node: atom(),
      span: Feel.Diagnostic.span() | nil,
      grain: integer(),
      value: Decimal.t()
    }
  | %{kind: :optimizer, optimized: term(), unoptimized: term()}

A node whose value is not in its type, or is not a multiple of the grain the node says it has (D-161), or an optimized run that disagrees with the one without the optimizer.

Functions

run(expression, bindings \\ %{}, options \\ [])

Runs a prepared expression as the interpreter runs the IR the analyzer made, and returns the result with the violations.

Takes the bindings and the run options of Feel.run/3.

same?(arg1, arg2)

Whether two results of Feel.run/3 are the same: the same value (a number at any scale is the same number, a function of FEEL is the same when its parameters and its result type are) and matching diagnostics, or the same error. Diagnostic fields are compared recursively, using value equality for embedded numbers and functions. Function comparison checks signature metadata, not behavior; test returned functions by calling them. The optimized run and the run of the IR as the analyzer made it are held to this; so is any other pair of runs of one expression.

iex> Feel.Paranoid.same?({:ok, Decimal.new("4.0"), []}, {:ok, Decimal.new("4"), []})
true
iex> Feel.Paranoid.same?({:ok, Decimal.new("4"), []}, {:ok, nil, []})
false