Feel.Runtime.BindingTypes (feel v0.1.0)

Copy Markdown View Source

The conversion of bound values to the types that were declared for them (D-138).

A Feel.Scope says what type each name has. When a run starts, Feel.Analysis.Analyzer's :conform_bindings node has each value that is bound to a name the expression reads converted to its declared type by the implicit conversions of DMN 1.6, section 10.3.2.9.4, and verified to conform to it, as the result is (Feel.Runtime.ResultType, D-138). A backend calls conform_bindings/3. It is a function of the runtime, and not a part of a backend, because generated code may call only Feel.Runtime and both backends have to give the same result.

  • null conforms to every type, and is the value as it is. So does a name that is not bound, which is left for its read to report. A type marked ! (D-48) is the exception: null does not conform to number!, and neither does a name that has no value. In the mode :enforce that ends the run with an error of kind :input, in either mode of the run, because the type says that the value is there and the expression was analyzed on it. In the mode {:report, severity} it is reported with that severity and the value stays.
  • A value that conforms is the value. One that does after a conversion is converted (Feel.Type.Conversion.convert/3: conformance first, then a singleton list is unwrapped, a value is wrapped in a list, a date becomes a date and time), and the conversion is reported as an :info (:implicit_conversion, D-46), which does not end a strict run.
  • A value that cannot be made to conform is null, with an error (:type_mismatch) that ends a run in strict mode. For a type marked ! it cannot be null either, so it ends the run with an error of kind :input, as a null does.

This is what lets the type of a name be relied on: every read sees a value of its type, or null. That is the mode :enforce. In the mode {:report, severity} (D-138) nothing is converted and nothing becomes null: a value that does not conform as it is gets a :type_mismatch diagnostic with that severity, and stays. The type is then an assertion of the caller's, and the report says when it does not hold.

Summary

Types

What is done with a value that does not conform: :enforce converts it or makes it null, and {:report, severity} leaves it and reports it with that severity (D-138).

Functions

Converts the bound values of the names in conforms to their types.

Types

mode()

@type mode() :: :enforce | {:report, :error | :warning}

What is done with a value that does not conform: :enforce converts it or makes it null, and {:report, severity} leaves it and reports it with that severity (D-138).

Functions

conform_bindings(env, bindings, conforms, mode \\ :enforce)

Converts the bound values of the names in conforms to their types.

Each entry is the canonical name, the span of its first read in the expression, which goes on the error, and the declared type, with the names of types resolved. Returns the bindings that the expression is to run with, and the environment. mode is :enforce, the default, or {:report, severity}.

iex> env = Feel.Runtime.Env.new()
iex> conforms = [{"age", {0, 3}, :number}, {"tags", {4, 8}, {:list, :string}}]
iex> bindings = %{"age" => [Decimal.new(30)], "tags" => "a", "id" => 7}
iex> {converted, converted_env} = Feel.Runtime.BindingTypes.conform_bindings(env, bindings, conforms)
iex> converted
%{"age" => Decimal.new(30), "tags" => ["a"], "id" => 7}
iex> for d <- Feel.Runtime.Env.diagnostics(converted_env), do: {d.severity, d.code, d.span}
[{:info, :implicit_conversion, {0, 3}}, {:info, :implicit_conversion, {4, 8}}]
iex> {converted, env} = Feel.Runtime.BindingTypes.conform_bindings(env, %{"age" => "x"}, conforms)
iex> converted
%{"age" => nil}
iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
iex> {diagnostic.code, diagnostic.span, diagnostic.message}
{:type_mismatch, {0, 3}, "the value of \"age\" is a string, which cannot be converted to the declared type number"}