The part of analyzing a call of a built-in function that does not need the analyzer (D-56).
Feel.Analysis.Analyzer analyzes the arguments, which takes the whole analysis, and asks this
module the rest:
callee/3says what the callee of a call is: a built-in function that is written, one that is not, or a value (D-82): a name that the scope or a context around has, which shadows the built-in of the same name (D-26), a name that is nothing, or any other expression.arrange/2puts the arguments in the order of the parameters, which is the form both backends take (Feel.Builtin). Positional arguments are in order already, and their number has to be one that a signature accepts. Named arguments are for the first signature that has a parameter of each name (with the signatures that differ from it only in types, which the names do not tell apart), and a parameter they leave out isnull:time(hour: 1, minute: 2, second: 3)istime(1, 2, 3, null), andis(value1: x)isis(x, null)(the TCK, 0103). Two arguments of one name, and a name no signature has, are errors, as is a wrong number of arguments.mismatch/3says whether the arguments, as the analyzer typed them, are ones that no signature that fits takes (D-56):context put(context: c, key: ["y", "a"], value: 2)has a list of two for akeythat is a string. Only a certain mismatch is one, so an argument that may benullor of any type is not. Nor do the signatures take arguments that have to be of one kind and cannot be (D-155):before("a", 2), ormax(d, 2)for a datedthat is notnull.result_type/3is the type of the result: what the signatures that the arguments may be for can give (abshas one for each kind it takes, andabs(d)of a duration is a duration), andnullunless the function never gives it.
An error is not an analysis error: the call is a null with that error when it runs, as the TCK has it
(day of year(value: @"1970-01-01") is null, with an error), and the analyzer warns.
Type mismatches keep the ordinary runtime call boundary, which checks actual argument values
and constructs its diagnostic independently of the analysis tier. Structural errors such as
invalid arity carry a fixed diagnostic on the call instead.
Summary
Types
An argument in the order of the parameters: an expression, or :missing for a parameter the call leaves out.
Functions
Puts the arguments of a call in the order of the parameters of the function, with the signatures that the
call fits. {:error, diagnostic} if it fits none.
What the callee of a call is.
Whether the arguments of a call, analyzed and in the order of the parameters, are ones that none of the
signatures it fits takes (D-56): the call is then null with an :argument_out_of_domain error, which is what
the function would say when it runs, but a function is given the values and not the signature that the names of
a call chose, so one that reads a list for a key as keys does not (context put, TCK 1146 nested008).
The places (from 0) of the arguments of a call that are read quietly (D-100): those where every signature that the call fits
has a quiet parameter (Feel.Builtin.Function.parameter/3).
The type of the result of a call: the types that the signatures give which the arguments may be for, and null too
unless the function is never null.
The one signature that a call by name has chosen, if these are its signatures: those that differ only in types are
one (Feel.Builtin.Function.merge_variants/1), so abs(n: x) has the signature of n that takes the kinds of
all three. nil if there are others, or none.
The expressions of the arguments as they are written, for a call that has to be null with its error:
they are analyzed all the same, so that their errors are reported.
Types
@type argument() :: Feel.AST.expression() | :missing
An argument in the order of the parameters: an expression, or :missing for a parameter the call leaves out.
Functions
@spec arrange(Feel.Builtin.Function.t(), Feel.AST.arguments()) :: {:ok, [argument()], [Feel.Builtin.Function.signature()]} | {:error, Feel.Diagnostic.t()}
Puts the arguments of a call in the order of the parameters of the function, with the signatures that the
call fits. {:error, diagnostic} if it fits none.
iex> {:ok, function} = Feel.Builtin.function("time")
iex> {:ok, ast} = Feel.parse("time(minute: 2, hour: 1, second: 3)")
iex> {:call, _meta, _callee, arguments} = ast
iex> {:ok, arranged, _signatures} = Feel.Analysis.Calls.arrange(function, arguments)
iex> Enum.map(arranged, fn :missing -> :missing; {:literal, _, value} -> Decimal.to_integer(value) end)
[1, 2, 3, :missing]
@spec callee( Feel.AST.expression(), Feel.Analysis.ScopeStack.t() | nil, Feel.Extensions.t() ) :: {:builtin, Feel.Builtin.Function.t()} | {:unwritten, Feel.Name.t()} | :value
What the callee of a call is.
iex> {:ok, ast} = Feel.parse(~s|date("2020-01-01")|)
iex> {:call, _meta, callee, _arguments} = ast
iex> {:builtin, function} = Feel.Analysis.Calls.callee(callee, nil)
iex> function.name
"date"
@spec mismatch(Feel.Builtin.Function.t(), [Feel.Builtin.Function.signature()], [ Feel.IR.t() ]) :: :ok | {:error, Feel.Diagnostic.t()}
Whether the arguments of a call, analyzed and in the order of the parameters, are ones that none of the
signatures it fits takes (D-56): the call is then null with an :argument_out_of_domain error, which is what
the function would say when it runs, but a function is given the values and not the signature that the names of
a call chose, so one that reads a list for a key as keys does not (context put, TCK 1146 nested008).
Only a certain mismatch is one. An argument that may be null or may be of any type is not, and the
conversions of section 10.3.2.9.4 are allowed: a list of one element is that element where a value is wanted,
and a value is the list of it where a list is wanted, and a date is a date and time where one is wanted (D-56). The length of a list is the one of the node that is a list
literal or a constant (D-193), and unknown for any other. A parameter that the registry types any takes every
argument, as does a signature the call is not the only one for.
A signature that says some arguments have to be of one kind (same_kind, D-155) rejects arguments whose kinds have
nothing in common, though each is taken by its parameter: a string and a number are two points, and before takes no
pair of them. The kinds an argument may be of are those of the members of its type that have an order (a number, a
string, a date, a time, a date and time, a duration of either kind); null, Any and the name of a model type
may be of every kind, as they are taken by every parameter, so one of them is never in disagreement. Nor is a
list that may have no element, whose elements are not compared when it has none (max([]) is null, with no
error); the items of a list literal or a constant are each one that the call has, and are compared. A range is
not looked at: its type holds the range with no endpoint, which can be related to a point of any kind.
iex> const = fn value -> {:const, %{span: {0, 0}, type: Feel.Type.Union.of_value(value)}, value} end
iex> {:ok, function} = Feel.Builtin.function("context put")
iex> [key_signature | _] = function.signatures
iex> arguments = [const.(%{"x" => Decimal.new(1)}), const.(["y", "a"]), const.(Decimal.new(2))]
iex> {:error, diagnostic} = Feel.Analysis.Calls.mismatch(function, [key_signature], arguments)
iex> diagnostic.message
"context put is not defined for context, list, number"
iex> arguments = [const.(%{"x" => Decimal.new(1)}), const.(["y"]), const.(Decimal.new(2))]
iex> Feel.Analysis.Calls.mismatch(function, [key_signature], arguments)
:ok
iex> {:ok, before} = Feel.Builtin.function("before")
iex> {:error, diagnostic} = Feel.Analysis.Calls.mismatch(before, before.signatures, [const.("a"), const.(Decimal.new(2))])
iex> diagnostic.message
"before is not defined for string, number"
iex> Feel.Analysis.Calls.mismatch(before, before.signatures, [const.("a"), const.("b")])
:ok
@spec quiet_places([Feel.Builtin.Function.signature()], non_neg_integer()) :: [ non_neg_integer() ]
The places (from 0) of the arguments of a call that are read quietly (D-100): those where every signature that the call fits
has a quiet parameter (Feel.Builtin.Function.parameter/3).
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> Feel.Analysis.Calls.quiet_places(abs.signatures, 1)
[]
@spec result_type( Feel.Builtin.Function.t(), [Feel.Builtin.Function.signature()], [Feel.IR.t()] | nil ) :: Feel.Type.Union.t()
The type of the result of a call: the types that the signatures give which the arguments may be for, and null too
unless the function is never null.
A signature that the arguments are certainly not for (mismatch/3 says it of all of them for a call that is null
with its error) is not one that the call can be answered by, so abs(duration("P1D")) is a duration, though abs has
signatures for numbers and for the other kind of duration. Only a certain mismatch leaves a signature out: an argument
that may be of any type is for every signature, and so is any argument of a function that does not check its arguments
(Feel.Builtin.Function). A null that an argument may be is not for every signature as it is in mismatch/3, since
what the call gives for a null is null, which its type has anyway, and the rest of the argument says which signature
gives the other results: abs(x) of a number that may be null is a number that may be null, and not a duration.
A parameter that takes null among its types takes it, and an argument that is only null is for every signature.
With no argument that tells them apart, the result is that of all the signatures. Without the list of the arguments at
all (nil, the default) the signatures alone are asked about: the types they declare for the result, and not those
that follow the arguments (result_from), and no rule of a family (Feel.Analysis.Images) is asked. A call with no
argument (now(), concatenate()) is the empty list, and a rule may answer it.
iex> {:ok, function} = Feel.Builtin.function("date")
iex> signatures = Feel.Builtin.Function.with_arity(function, 1)
iex> Feel.Analysis.Calls.result_type(function, signatures)
[:date, :null]
iex> {:ok, is} = Feel.Builtin.function("is")
iex> Feel.Analysis.Calls.result_type(is, is.signatures)
[:boolean]
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> Feel.Analysis.Calls.result_type(abs, abs.signatures)
[:number, :years_and_months_duration, :days_and_time_duration, :null]
iex> duration = {:var, %{span: {0, 0}, type: [:days_and_time_duration]}, "d"}
iex> abs |> Feel.Analysis.Calls.result_type(abs.signatures, [duration]) |> Feel.Type.Union.format()
"days and time duration in [PT0S..max]"
iex> maybe_number = {:var, %{span: {0, 0}, type: [:number, :null]}, "x"}
iex> abs |> Feel.Analysis.Calls.result_type(abs.signatures, [maybe_number]) |> Feel.Type.Union.format()
"number in [0..max] | Null"
@spec single([Feel.Builtin.Function.signature()]) :: Feel.Builtin.Function.signature() | nil
The one signature that a call by name has chosen, if these are its signatures: those that differ only in types are
one (Feel.Builtin.Function.merge_variants/1), so abs(n: x) has the signature of n that takes the kinds of
all three. nil if there are others, or none.
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> {:ok, _arranged, signatures} = Feel.Analysis.Calls.arrange(abs, {:named, [{["n"], {:literal, %{span: {0, 0}}, Decimal.new(1)}}]})
iex> length(signatures)
3
iex> Enum.map(Feel.Analysis.Calls.single(signatures).parameters, & &1.types)
[[:number, :days_and_time_duration, :years_and_months_duration]]
iex> Feel.Analysis.Calls.single([])
nil
@spec written(Feel.AST.arguments()) :: [Feel.AST.expression()]
The expressions of the arguments as they are written, for a call that has to be null with its error:
they are analyzed all the same, so that their errors are reported.