1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Error reporting improvements for functions & overloads

This commit is contained in:
Étienne Fildadut 2024-01-06 15:14:15 +01:00
parent 7f71569e07
commit b004946266
20 changed files with 114 additions and 89 deletions

View file

@ -159,7 +159,7 @@ ArgumentTuple = ast.abstract.Node {
-- type check (assume ok for default values)
if param.type_check and arg ~= param.default then
local r = param.type_check:call(state, ArgumentTuple:new(arg))
if not r:truthy() then return false, ("type check failure for parameter %s in function %s"):format(param.identifier:format(state), params:format(state)) end
if not r:truthy() then return false, ("type check failure for parameter %s"):format(param.identifier:format(state)) end
if Number:is(r) then
specificity = specificity + r.number
else

View file

@ -19,6 +19,7 @@ local VariableMetadata = ast.abstract.Runtime {
if self.symbol.alias then
return v:call(state, ArgumentTuple:new())
else
v.from_symbol = self.symbol
return v
end
end,
@ -31,11 +32,11 @@ local VariableMetadata = ast.abstract.Runtime {
end,
set = function(self, state, value)
if self.symbol.constant then
error(("trying to change the value of constant %s"):format(self.symbol.string), 0)
error(("trying to change the value of constant %s"):format(self.symbol.string:format(state)), 0)
end
if self.symbol.type_check then
local r = self.symbol.type_check:call(state, ArgumentTuple:new(value))
if not r:truthy() then error(("type check failure for %s; %s does not satisfy %s"):format(self.symbol.string, value, self.symbol.type_check), 0) end
if not r:truthy() then error(("type check failure for %s; %s does not satisfy %s"):format(self.symbol.string:format(state), value, self.symbol.type_check:format(state)), 0) end
end
if self.symbol.alias then
local assign_args = ArgumentTuple:new()
@ -223,7 +224,9 @@ local Environment = ast.abstract.Runtime {
local e = self
while e.parent do
e = e.parent
d = d + 1
if not (e.partial or e.export) then -- only count full layers
d = d + 1
end
end
return d
end,

View file

@ -39,9 +39,9 @@ Function = Overloadable {
_format = function(self, ...)
if self.parameters.assignment then
return "$"..self.parameters:format(...).."; "..self.expression:format_right(...)
return "$"..self.parameters:format_short(...).."; "..self.expression:format_right(...)
else
return "$"..self.parameters:format(...).." "..self.expression:format_right(...)
return "$"..self.parameters:format_short(...).." "..self.expression:format_right(...)
end
end,
_format_priority = function(self)
@ -77,7 +77,7 @@ Function = Overloadable {
return args:match_parameter_tuple(state, self.parameters)
end,
format_signature = function(self, state)
return self.parameters:format(state)
return "$"..self.parameters:format_short(state)
end,
hash_signature = function(self)
return self.parameters:hash()

View file

@ -41,7 +41,7 @@ LuaFunction = ast.abstract.Runtime(Overloadable) {
return args:match_parameter_tuple(state, self.parameters)
end,
format_signature = function(self, state)
return self.parameters:format(state)
return "$"..self.parameters:format_short(state)
end,
hash_signature = function(self)
return self.parameters:hash()

View file

@ -23,12 +23,12 @@ Overload = ast.abstract.Node {
end,
_format = function(self, ...)
local s = "overload<"
local s = "overload(["
for i, e in ipairs(self.list) do
s = s .. e:format(...)
if i < #self.list then s = s .. ", " end
end
return s..">"
return s.."])"
end,
traverse = function(self, fn, ...)
@ -50,18 +50,18 @@ Overload = ast.abstract.Node {
if secondary_specificity > success_secondary_specificity then
success, success_specificity, success_secondary_specificity = fn, specificity, secondary_specificity
elseif secondary_specificity == success_secondary_specificity then
return nil, ("more than one function match %s, matching functions were at least (specificity %s.%s):\n\t• %s\n\t• %s"):format(args:format(state), specificity, secondary_specificity, fn:format_signature(state), success:format_signature(state))
return nil, ("more than one function match arguments %s, matching functions were at least (specificity %s.%s):\n\t• %s (from %s)\n\t• %s (from %s)"):format(args:format(state), specificity, secondary_specificity, fn:format_signature(state), fn.source, success:format_signature(state), success.source)
end
end
-- no need to add error message for less specific function since we already should have at least one success
elseif not success then
table.insert(failure, fn:format_signature(state) .. ": " .. secondary_specificity)
table.insert(failure, ("%s (from %s):\n\t\t%s"):format(fn:format_signature(state), fn.source, secondary_specificity))
end
end
if success then
return success, args
else
return nil, ("no function match %s, possible functions were:\n\t• %s"):format(args:format(state), table.concat(failure, "\n\t"))
return nil, ("no function match arguments %s, possible functions were:\n\t• %s"):format(args:format(state), table.concat(failure, "\n\t"))
end
end
}

View file

@ -66,7 +66,7 @@ Tuple = ast.abstract.Node {
if index > #self.list or index == 0 then error("tuple index out of bounds", 0) end
self.list[index] = value
end,
len = function(self)
len = function(self)
return #self.list
end,
iter = function(self)

View file

@ -134,7 +134,7 @@ Node = class {
end,
-- generate a list of translatable nodes that appear in this node
-- should only be called on non-runtime nodes
-- should only be called on non-evaluated nodes
-- if a node is translatable, redefine this to add it to the table - note that it shouldn't call :traverse or :list_translatable on its children, as nested translations should not be needed
list_translatable = function(self, t)
t = t or {}
@ -235,7 +235,7 @@ Node = class {
if dispatched then
return dispatched:call_dispatched(state, dispatched_arg)
else
error(("can't call %s %s: %s"):format(self.type, self:format(state), dispatched_arg), 0)
error(("can't call %s %s: %s"):format(self.type, self:format_short(state), dispatched_arg), 0)
end
end,
-- find a function that can be called with the given arguments
@ -300,21 +300,26 @@ Node = class {
end,
-- return a pretty string representation of the node.
-- for non-runtime nodes (what was generated by a parse without any evaluation), this should return valid Anselme code that is functionnally equivalent to the parsed code. note that it currently does not preserve comment.
-- for non-evaluated nodes, this should return valid Anselme code that is functionnally equivalent to the parsed code. note that it currently does not preserve comment.
-- assuming nothing was mutated in the node, the returned string should remain the same - so if make sure the function is deterministic, e.g. sort if you use pairs()
-- redefine _format, not this - note that _format is a mandary method for all nodes.
-- state is optional and should only be relevant for runtime nodes; if specified, only show what is relevant for the current branch.
-- state is optional and should only have an effect on evaluated nodes; if specified, only show what is relevant for the current branch.
-- indentation_level and parent_priority are optional value that respectively keep track in nester :format calls of the indentation level (number) and parent operator priority (number); if the node has a strictly lower priority than the parent node, parentheses will be added
-- also remember that execution is done left-to-right, so in case of priority equality, all is fine if the term appear left of the operator, but parentheses will need to be added if the term is right of the operator - so make sure to call :format_right for such cases
-- short_mode, if set to true, will use the identifier the expression was last retrieved from instead of calling :_format. Should have no effect on non-evaluated nodes.
-- (:format is not cached as even immutable nodes may contain mutable children)
format = function(self, state, parent_priority, indentation_level)
format = function(self, state, parent_priority, indentation_level, short_mode, right)
indentation_level = indentation_level or 0
parent_priority = parent_priority or 0
local s = self:_format(state, self:format_priority(), indentation_level)
if self:format_priority() < parent_priority then
s = ("(%s)"):format(s)
local s
if short_mode and self.from_symbol then
s = self.from_symbol.string
else
s = self:_format(state, self:format_priority(), indentation_level, short_mode)
if self:format_priority() < parent_priority or (right and self:format_priority() <= parent_priority) then
s = ("(%s)"):format(s)
end
end
local indentation = ("\t"):rep(indentation_level)
@ -323,23 +328,15 @@ Node = class {
return s
end,
-- same as :format, but should be called only for nodes right of the current operator
format_right = function(self, state, parent_priority, indentation_level)
indentation_level = indentation_level or 0
parent_priority = parent_priority or 0
local s = self:_format(state, self:format_priority(), indentation_level)
if self:format_priority() <= parent_priority then
s = ("(%s)"):format(s)
end
local indentation = (" "):rep(indentation_level)
s = indentation..s:gsub("\n", "\n"..indentation)
return s
format_right = function(self, state, parent_priority, indentation_level, short_mode)
return self:format(state, parent_priority, indentation_level, short_mode, true)
end,
-- same as :format, but enable short mode
format_short = function(self, state, parent_priority, indentation_level)
return self:format(state, parent_priority, indentation_level, true)
end,
-- redefine this to provide a custom :format. returns a string.
_format = function(self, state, self_priority, identation)
_format = function(self, state, self_priority, identation, short_mode)
error("format not implemented for "..self.type)
end,
-- compute the priority of the node that will be used in :format to add eventually needed parentheses.
@ -356,6 +353,7 @@ Node = class {
return math.huge -- by default, assumes primary node, i.e. never wrap in parentheses
end,
_format_priority_cache = nil, -- cached priority
from_symbol = nil, -- last symbol this node was retrived from
-- return Lua value
-- this should probably be only called on a Node that is already evaluated

View file

@ -12,11 +12,11 @@ return ast.abstract.Node {
error("not implemented for "..self.type)
end,
-- return string
-- return string, friendly representation of the function signature
format_signature = function(self, state)
error("not implemented for "..self.type)
end,
-- return string
-- return string, unique hash for this function signature
hash_signature = function(self)
error("not implemented for "..self.type)
end,

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast")
local Nil, Boolean, Definition, Return = ast.Nil, ast.Boolean, ast.Definition, ast.Return
local Nil, Boolean, Definition, Return, Overload, Overloadable = ast.Nil, ast.Boolean, ast.Definition, ast.Return, ast.Overload, ast.abstract.Overloadable
local assert0 = require("anselme.common").assert0
return {
@ -17,7 +17,19 @@ return {
},
{
"_._", "(c::function, s::string)",
"overload", "(l::is sequence)",
function(state, l)
local r = Overload:new()
for _, fn in l:iter(state) do
assert0(Overloadable:issub(fn), ("trying to add a non overloadable %s to an overload"):format(fn:format(state)))
r:insert(fn)
end
return r
end
},
{
"_._", "(c::is function, s::is string)",
function(state, c, s)
local identifier = s:to_identifier()
assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string))

View file

@ -5,7 +5,7 @@
--- text ---
| {}"" {}"42" {}"" |
--- error ---
identifier "z" is undefined in branch 7dfcaa5b-2163-4f36-116ed-c4ab6bc8d28b
identifier "z" is undefined in branch 46991f07-7340-4104-9f35-c1fb62e95088
↳ from test/tests/exported variable nested.ans:12:3 in identifier: z
↳ from test/tests/exported variable nested.ans:12:1 in text interpolation: | {z} |
↳ from ? in block: :f = ($() _)…

View file

@ -1,14 +1,18 @@
--# run #--
--- error ---
can't call overload overload<($(table::($(x) <lua function>)) _), ($(tuple::($(x) type(x) == "tuple" | type(x) == "list")) _), ($(range::($(x) type(x) == t)) _), ($(s::($(x) <lua function>)) <lua function>)>: no function match (42), possible functions were:
• (table::($(x) <lua function>)): type check failure for parameter table in function (table::($(x) <lua function>))
• (tuple::($(x) type(x) == "tuple" | type(x) == "list")): type check failure for parameter tuple in function (tuple::($(x) type(x) == "tuple" | type(x) == "list"))
• (range::($(x) type(x) == t)): type check failure for parameter range in function (range::($(x) type(x) == t))
• (s::($(x) <lua function>)): type check failure for parameter s in function (s::($(x) <lua function>))
↳ from for.ans:3:18 in call: iter(var)
↳ from for.ans:3:12 in definition: :iterator = iter(var)
↳ from for.ans:2:1 in block: :iterator = iter(var)…
↳ from for.ans:2:68 in call: _
can't call overload iter: no function match arguments (42), possible functions were:
• $(table::is table) (from stdlib/for.ans:46:1):
type check failure for parameter table
• $(tuple::is sequence) (from stdlib/for.ans:37:1):
type check failure for parameter tuple
• $(range::is range) (from stdlib/for.ans:19:1):
type check failure for parameter range
• $(s::is struct) (from stdlib/for.ans:3:14):
type check failure for parameter s
↳ from stdlib/for.ans:3:18 in call: iter(var)
↳ from stdlib/for.ans:3:12 in definition: :iterator = iter(var)
↳ from stdlib/for.ans:2:1 in block: :iterator = iter(var)…
↳ from stdlib/for.ans:2:71 in call: _
↳ from test/tests/for invalid iterator.ans:1:4 in call: for(:x, 42)
↳ from ? in block: for(:x, 42)…
--# saved #--

View file

@ -1,6 +1,6 @@
--# run #--
--- error ---
can't call function $(a, b) _: expected 2 arguments, received 1
can't call function f: expected 2 arguments, received 1
↳ from test/tests/function args arity check fail.ans:4:2 in call: f("ok")
↳ from ? in block: :f = ($(a, b) _)…
--# saved #--

View file

@ -1,6 +1,6 @@
--# run #--
--- error ---
a function with parameters (a, b) is already defined in the overload
a function with parameters $(a, b) is already defined in the overload
↳ from test/tests/function conflict.ans:5:1 in definition: :f = ($(a, b) 0)
↳ from ? in block: :f = ($(a, b) 0)…
--# saved #--

View file

@ -4,9 +4,11 @@
--- text ---
| {}"" {}"idk" {}" is esperanto" |
--- error ---
can't call overload overload<($(name::($(x) type(x) == t)) _), ($(name::($(x) type(x) == t)) _)>: no function match (type(5, "nope")), possible functions were:
• (name::($(x) type(x) == t)): type check failure for parameter name in function (name::($(x) type(x) == t))
• (name::($(x) type(x) == t)): type check failure for parameter name in function (name::($(x) type(x) == t))
can't call overload a: no function match arguments (type(5, "nope")), possible functions were:
• $(name::($(x) type(x) == t)) (from test/tests/function custom type dispatch error.ans:7:1):
type check failure for parameter name
• $(name::($(x) type(x) == t)) (from test/tests/function custom type dispatch error.ans:4:1):
type check failure for parameter name
↳ from test/tests/function custom type dispatch error.ans:14:2 in call: a(type(5, "nope"))
↳ from ? in block: :french name = "french name"…
--# saved #--

View file

@ -2,14 +2,14 @@
--- text ---
| {}"a" |
--- text ---
| {}"b" |
--- text ---
| {}"c" |
--- text ---
| {}"a" |
--- text ---
| {}"a" |
--- text ---
| {}"c" |
| {}"b" |
--- return ---
()
--# saved #--
{"a.checkpoint":false, "a.run":3, "c.checkpoint":false, "c.run":2}
{"a.checkpoint":false, "a.run":2, "b.checkpoint":false, "b.run":2, "c.checkpoint":false, "c.run":1}

View file

@ -1,6 +1,6 @@
--# run #--
--- error ---
identifier "b" is undefined in branch 7dfcaa5b-2163-4f36-116ed-c4ab6bc8d28b
identifier "b" is undefined in branch 46991f07-7340-4104-9f35-c1fb62e95088
↳ from test/tests/function scope wrong.ans:4:7 in identifier: b
↳ from test/tests/function scope wrong.ans:4:1 in text interpolation: | a: {b} |
↳ from ? in block: :a = ($() _)…

View file

@ -1,12 +1,18 @@
--# run #--
--- error ---
can't call overload overload<($(s::($(x) type(x) == t), k::($(x) <lua function>)) = val; _), ($(s::($(x) type(x) == t), k::($(x) <lua function>)) = val; _), ($(s::($(x) type(x) == t), k::($(x) <lua function>)) _), ($(c::($(x) <lua function>), s::($(x) <lua function>)) = v; <lua function>), ($(c::($(x) <lua function>), s::($(x) <lua function>)) = v; <lua function>), ($(c::($(x) <lua function>), s::($(x) <lua function>)) <lua function>)>: no function match (overload<($(b) _), ($(x) _), ($() _)>, "a"), possible functions were:
• (s::($(x) type(x) == t), k::($(x) <lua function>)) = val: expected 3 arguments, received 2
• (s::($(x) type(x) == t), k::($(x) <lua function>)) = val: expected 3 arguments, received 2
• (s::($(x) type(x) == t), k::($(x) <lua function>)): type check failure for parameter s in function (s::($(x) type(x) == t), k::($(x) <lua function>))
• (c::($(x) <lua function>), s::($(x) <lua function>)) = v: expected 3 arguments, received 2
• (c::($(x) <lua function>), s::($(x) <lua function>)) = v: expected 3 arguments, received 2
• (c::($(x) <lua function>), s::($(x) <lua function>)): type check failure for parameter c in function (c::($(x) <lua function>), s::($(x) <lua function>))
can't call overload _._: no function match arguments (overload([($(b) _), ($(x) _), ($() _)]), "a"), possible functions were:
• $(s::is script, k::is symbol) = val (from stdlib/script.ans:44:1):
expected 3 arguments, received 2
• $(s::is script, k::is string) = val (from stdlib/script.ans:42:1):
expected 3 arguments, received 2
• $(s::is script, k::is string) (from stdlib/script.ans:40:1):
type check failure for parameter s
• $(c::is function, s::is symbol) = v (from test/tests/function separate variable from variants.ans:10:4):
expected 3 arguments, received 2
• $(c::is function, s::is string) = v (from test/tests/function separate variable from variants.ans:10:4):
expected 3 arguments, received 2
• $(c::is function, s::is string) (from test/tests/function separate variable from variants.ans:10:4):
type check failure for parameter c
↳ from test/tests/function separate variable from variants.ans:10:4 in call: f . "a"
↳ from test/tests/function separate variable from variants.ans:10:1 in text interpolation: | {f . "a"} = 2 |
↳ from ? in block: :f = ($() _)…

View file

@ -1,9 +1,9 @@
--# run #--
--- error ---
can't call overload overload<($(a::($(x) <lua function>)) _), ($(x::($(x) <lua function>)) _)>: more than one function match (5), matching functions were at least (specificity 1.3):
(x::($(x) <lua function>))
(a::($(x) <lua function>))
can't call overload fn: more than one function match arguments (5), matching functions were at least (specificity 1.2):
$(x::is number) (from test/tests/function type dispatch ambigous.ans:1:1)
$(a::is number) (from test/tests/function type dispatch ambigous.ans:4:1)
↳ from test/tests/function type dispatch ambigous.ans:7:3 in call: fn(5)
↳ from ? in block: :fn = ($(x::number) _)…
↳ from ? in block: :fn = ($(x::is number) _)…
--# saved #--
{}

View file

@ -4,14 +4,14 @@
↳ from test/tests/merge nested mutable error bis.ans:14:7 in call: error("abort")
↳ from test/tests/merge nested mutable error bis.ans:3:1 in block: insert(a, b)…
↳ from test/tests/merge nested mutable error bis.ans:3:18 in call: _
↳ from script.ans:30:6 in call: fn!
↳ from script.ans:28:3 in block: resume target = ()…
↳ from script.ans:28:7 in call: else!
↳ from script.ans:24:2 in block: if(fn . "current checkpoint")…
↳ from script.ans:24:9 in call: _
↳ from script.ans:38:9 in call: value(s)!
↳ from script.ans:37:1 in block: value(s)!
↳ from script.ans:37:20 in call: _
↳ from stdlib/script.ans:30:6 in call: fn!
↳ from stdlib/script.ans:28:3 in block: resume target = ()…
↳ from stdlib/script.ans:28:7 in call: else!
↳ from stdlib/script.ans:24:2 in block: if(fn . "current checkpoint")…
↳ from stdlib/script.ans:24:9 in call: _
↳ from stdlib/script.ans:38:9 in call: value(s)!
↳ from stdlib/script.ans:37:1 in block: value(s)!
↳ from stdlib/script.ans:37:20 in call: _
↳ from test/tests/merge nested mutable error bis.ans:19:2 in call: f!
↳ from ? in block: :a = *[1]…
--# post run check #--

View file

@ -4,14 +4,14 @@
↳ from test/tests/merge nested mutable error.ans:14:7 in call: error("abort")
↳ from test/tests/merge nested mutable error.ans:3:1 in block: insert(a, b)…
↳ from test/tests/merge nested mutable error.ans:3:18 in call: _
↳ from script.ans:30:6 in call: fn!
↳ from script.ans:28:3 in block: resume target = ()…
↳ from script.ans:28:7 in call: else!
↳ from script.ans:24:2 in block: if(fn . "current checkpoint")…
↳ from script.ans:24:9 in call: _
↳ from script.ans:38:9 in call: value(s)!
↳ from script.ans:37:1 in block: value(s)!
↳ from script.ans:37:20 in call: _
↳ from stdlib/script.ans:30:6 in call: fn!
↳ from stdlib/script.ans:28:3 in block: resume target = ()…
↳ from stdlib/script.ans:28:7 in call: else!
↳ from stdlib/script.ans:24:2 in block: if(fn . "current checkpoint")…
↳ from stdlib/script.ans:24:9 in call: _
↳ from stdlib/script.ans:38:9 in call: value(s)!
↳ from stdlib/script.ans:37:1 in block: value(s)!
↳ from stdlib/script.ans:37:20 in call: _
↳ from test/tests/merge nested mutable error.ans:19:2 in call: f!
↳ from ? in block: :a = *[1]…
--# post run check #--