-
Notifications
You must be signed in to change notification settings - Fork 16
Description
From the example in #1044:
template useCpuRegisters(body: untyped) =
var p {.inject.} = r.p
let eof {.inject.} = r.eof
body
r.p = p # store back
proc skipWhitespace(r: var Reader) =
useCpuRegisters:
while p < eof:
case ^p
When the call to useCpuRegisters
is being semchecked, the argument is semchecked by itself, which generates errors due to p
and eof
not being defined yet. This will not cause problems in overload resolution and the untyped
parameter will just accept it, however when the template is instantiated the error will remain in the AST and it won't compile.
I think for maximum simplicity while retaining most of the compatibility, instead of attempting to "walk back" the errors (which would either be done for typed
as well or have to specifically be differentiated), we can just store and pass the original argument AST before semchecking to the untyped
param rather than the semchecked AST which contains errors (which get ignored).
Then the only problem is that the semchecking of the untyped
param might have side effects. This is mostly a problem for macros but it means we need something like the "shadow scope" behavior from the original compiler for symbols defined in untyped args:
foo(
(let x = 123; 456),
x)
discard x
In original Nim the "shadow scope" is fully merged to the above scope on a successful match. This might cause a problem here if the first argument of foo
is untyped, it might not get added to the scope above, and the use of x
in the second argument might become invalid as well. We could maybe always open a new scope for call arguments to prevent this from being possible, or maybe a new scope for each argument. Both of these would break code but the first one would not break as much, using symbols defined in other arguments is probably more common than using a symbol defined in a call argument after the call, if either of these happen at all.
Edit: Maybe something like this is a common example:
if (let x = 123; x > 0) and (let y = 456; y > 0) and y > x:
...
# although you could also write this as: (let x = 123; x > 0 and (let y = 456; y > 0 and y > x))
If we open a scope inside a call but not for each individual argument, then this would break because it parses as (a and b) and c
, but it would work if it was written as a and (b and c)
. This is too weird imo. Not to mention we might want to use x
and y
later which is not possible at all with this.
Maybe we can track the introduced symbols in each argument, then only merge them to the upper scope if they didn't match as an untyped
param.
If side effects of semchecking still end up being too much of a problem, with type bound ops being added after matching as in #1112, the "lazy semchecking" behavior of the old compiler would not necessarily be incompatible here anymore. It could be added for compat mode, and there are still tweaks that could be made to it to mitigate the old issues with it (for example nim-lang/Nim#24555).