-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong symbol lookup in template expansion in generic function #22605
Comments
proc h(): string =
let x = f().valueOr:
return $error
"ok"
echo h() works as expected |
Minimized: type Xxx = enum
error = "bad"
template valueOr(self: int, def: untyped): untyped =
case false
of true: ""
of false:
template error: untyped {.used, inject.} = "good"
def
proc g(T: type): string =
let x = 123.valueOr:
return $error
"ok"
echo g(int)
Generic expansion captures enum field type Xxx = enum
error = "bad"
const error = "bad"
template error: untyped = "bad" |
can you think of any workaround? |
Making |
this works, but unfortunately it is not transparent to the user of
Can't seem to get these right, what am I missing? type Xxx* = enum
error
value
type
Result*[T, E] = object
when T is void:
when E is void:
oResultPrivate*: bool
else:
case oResultPrivate*: bool
of false:
eResultPrivate*: E
of true:
discard
else:
when E is void:
case oResultPrivate*: bool
of false:
discard
of true:
vResultPrivate*: T
else:
case oResultPrivate*: bool
of false:
eResultPrivate*: E
of true:
vResultPrivate*: T
template valueOr2*(self, def: untyped): untyped =
let s = (self) # TODO avoid copy
case s.oResultPrivate
of true:
s.vResultPrivate
of false:
when self.E isnot void:
template error: untyped {.used, inject.} = s.eResultPrivate
def
template valueOr(self, def: untyped): untyped =
const error = ()
valueOr2(self, def)
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
proc g(T: type): string =
let x = f().valueOr:
return $error
"ok"
echo g(int) |
The Doing both things, then using Fwiw this works: template valueOr(self, def: untyped): untyped =
const error {.inject.} = ()
valueOr2(self, def)
proc g(T: type): string =
let x = valueOr f():
return $error
"ok"
echo g(int) The original Edit: So the minimal working example is: type Xxx* = enum
error
value
type
Result*[T, E] = object
...
template valueOr*(self, def: untyped): untyped = # workaround part 1: make valueOr untyped
...
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
proc g(T: type): string =
let x = valueOr f(): # workaround part 2: use normal call syntax instead of method call syntax
return $error
"ok"
echo g(int) |
See also nim-lang/Nim#22605
Fwiw, the same issue exists with templates only ( template valueOr(self, def: untyped): untyped =
block:
template error: untyped {.used, inject.} = "good"
def
const error = "bad"
template g =
let x = 123.valueOr:
$error
echo x
g I don't think this is less than a language design problem, and a pretty old one at that. So can't really give any solutions. |
@metagn Interesting repro indeed! We've been aware of this problem in general for quite some time and implemented various random workarounds in "leaf" code like trying to come up with unique names for things - but this of course is not sustainable - specially not for "core infrastructure" like ie here's a good way to break any codebase - stick this in some commonly imported module: type Breakit* = enum
it |
|
Well it's not difficult to make this use case semantically legible, if we have template lambdas we can take a The problem here isn't with untyped parameters, it's with untyped bodies with speculative symbol resolution, which is not a necessary design for generics but is almost the entire reason templates are used. So we need to think about the template case, but it also doesn't hurt to have generics working before the typechecking is implemented. The template/generic body captures the It might be recency bias from the overloadable enum scope resolution issues but I think the simplest missing design is 2 dimensional symchoices, where the existing symchoices have 1 dimension where every choice has equal weight (since they are meant for routines which undergo overload resolution), but a second dimension of scope depth also exists in normal symbol resolution. Thankfully the way this dimension is used in symbol resolution is simple enough that we can probably represent this with just an optional "preferred" choice in the sym choice. In the example, |
Just to further confirm that it is not the injected template that fails, this also breaks for injected variables: template valueOr*[T: not void, E](self: Result[T, E], def: untyped): untyped =
let s = (self) # TODO avoid copy
case s.oResultPrivate
of true:
s.vResultPrivate
of false:
when E isnot void:
let error {.inject.} = s.eResultPrivate # def also fails to pick this up
def |
@metagn What would happen if all template symbols were open by default? What kinds of code would that break? |
The compiler just doesn't semcheck symchoices, they're only handled in routine overloading or when narrowing to a type. So just emitting open symbols doesn't work for cases like this issue where the symbol isn't overloadable, like a constant or a type. Example of nothing being done: import macros
const foo = 123
block:
const foo = 456
macro baz() =
# this just happens to create a symchoice currently:
result = bindSym("foo", brOpen)
echo result.treeRepr # OpenSymChoice 2 "foo"
let x = baz() # Error: expression has no type: foo While fixing this would need some sifting through the compiler to flesh out where to preserve or resolve symchoices, we also need some disambiguation mechanism; which routines naturally have because they get called and overloaded, but other symbols don't (hence recent overloadable enum issues). # a.nim
const foo* = 123
# b.nim
import a
const foo = 456
template bar*(): int =
mixin foo
foo
# c.nim
import b
echo bar() If we just emitted open symchoices,
The compiler can't disambiguate these when we call In the scope of the template declaration,
There's no # c.nim
import b
const foo = 789
echo bar() Being an open symchoice, it will check the local scope again for an unambiguous This is all wishful thinking, it needs a lot of cooperation with the rest of the compiler and a sound design, unfortunately I haven't attempted anything yet. |
Add a new node kind |
So what happens if there are 2+ overloads in the same module as the template? Will those be preferred over other overloads? |
No, neither will be preferred. If it's ambiguous at template scope, we can't mark any as preferred. |
Hm, then I still feel it's a tad arbitrary and inconsistent, and increases the language's complexity (adding yet another edge case). |
For the general case, there is a way simpler design, that multiple people have conceptually arrived at already. We can have The preferred stuff is a generalized version that happens to also solve another problem which is sym choices not retaining scope information in case they need to be disambiguated. This problem has technically always existed but is niche and only relevant now because of enums. I might have made a mistake in combining these problems, and we can probably have both solutions separately. The second problem is probably not nearly as important either, sorry for bringing it up here if it turns out to be separate. |
refs #22605 Sym choice nodes are now only allowed to pass through semchecking if contexts ask for them to (with `efAllowSymChoice`). Otherwise they are resolved or treated as ambiguous. The contexts that can receive symchoices in this PR are: * Call operands and addresses and emulations of such, which will subject them to overload resolution which will resolve them or fail. * Type conversion operands only for routine symchoices for type disambiguation syntax (like `(proc (x: int): int)(foo)`), which will resolve them or fail. * Proc parameter default values both at the declaration and during generic instantiation, which undergo type narrowing and so will resolve them or fail. This means unless these contexts mess up sym choice nodes should never leave the semchecking stage. This serves as a blueprint for future improvements to intermediate symbol resolution. Some tangential changes are also in this PR: 1. The `AmbiguousEnum` hint is removed, it was always disabled by default and since #22606 it only started getting emitted after the symchoice was soundly resolved. 2. Proc setter syntax (`a.b = c` becoming `` `b=`(a, c) ``) used to fully type check the RHS before passing the transformed call node to proc overloading. Now it just passes the original node directly so proc overloading can deal with its typechecking.
fixes nim-lang#11184, fixes nim-lang#22605, fixes nim-lang#20000
fixes nim-lang#11184, fixes nim-lang#22605, fixes nim-lang#20000
fixes nim-lang#22605, separated from nim-lang#22744
fixes #22605, separated from #22744 This marks symbol captures in macro calls in generic contexts as `nfOpenSym`, which means if there is a new symbol in the local instantiatied body during instantiation time, this symbol replaces the captured symbol. We have to be careful not to consider symbols outside of the instantiation body during instantiation, because this will leak symbols from the instantiation context scope rather than the original declaration scope. This is done by checking if the local context owner (maybe should be the symbol of the proc currently getting instantiated instead? not sure how to get this) is the same as or a parent owner of the owner of the replacement candidate symbol. This solution is distinct from the symchoice mechanisms which we originally assumed had to be related, if this assumption was wrong it would explain why this solution took so long to arrive at.
fixes #22605, separated from #22744 This marks symbol captures in macro calls in generic contexts as `nfOpenSym`, which means if there is a new symbol in the local instantiatied body during instantiation time, this symbol replaces the captured symbol. We have to be careful not to consider symbols outside of the instantiation body during instantiation, because this will leak symbols from the instantiation context scope rather than the original declaration scope. This is done by checking if the local context owner (maybe should be the symbol of the proc currently getting instantiated instead? not sure how to get this) is the same as or a parent owner of the owner of the replacement candidate symbol. This solution is distinct from the symchoice mechanisms which we originally assumed had to be related, if this assumption was wrong it would explain why this solution took so long to arrive at. (cherry picked from commit 9416595)
fixes #22605, separated from #22744 This marks symbol captures in macro calls in generic contexts as `nfOpenSym`, which means if there is a new symbol in the local instantiatied body during instantiation time, this symbol replaces the captured symbol. We have to be careful not to consider symbols outside of the instantiation body during instantiation, because this will leak symbols from the instantiation context scope rather than the original declaration scope. This is done by checking if the local context owner (maybe should be the symbol of the proc currently getting instantiated instead? not sure how to get this) is the same as or a parent owner of the owner of the replacement candidate symbol. This solution is distinct from the symchoice mechanisms which we originally assumed had to be related, if this assumption was wrong it would explain why this solution took so long to arrive at. (cherry picked from commit 9416595)
fixes #22605, separated from #22744 This marks symbol captures in macro calls in generic contexts as `nfOpenSym`, which means if there is a new symbol in the local instantiatied body during instantiation time, this symbol replaces the captured symbol. We have to be careful not to consider symbols outside of the instantiation body during instantiation, because this will leak symbols from the instantiation context scope rather than the original declaration scope. This is done by checking if the local context owner (maybe should be the symbol of the proc currently getting instantiated instead? not sure how to get this) is the same as or a parent owner of the owner of the replacement candidate symbol. This solution is distinct from the symchoice mechanisms which we originally assumed had to be related, if this assumption was wrong it would explain why this solution took so long to arrive at. (cherry picked from commit 9416595)
This PR adds a workaround for the case where a global symbol is matched instead of the local `error`/`value` template in `valueOr` and friends, when `valueOr` is being used in a generic context. Two options are added: * when using Nim version supporting the experimental `genericsOpenSym` [feature](nim-lang/Nim#23873), we use it * when not, a macro tries to replace all accesses in the given body with the correct symbol - this matching is done by name and some minimal heuristics which potentially could be wrong - the upstream solution is obviously preferable Both solutions can be disabled via compile-time options to retain the old behavior of matching the global symbol. See also nim-lang/Nim#22605 thanks to @Araq for the macro hack and @metagn for the language fix!
refs #22605 Sym choice nodes are now only allowed to pass through semchecking if contexts ask for them to (with `efAllowSymChoice`). Otherwise they are resolved or treated as ambiguous. The contexts that can receive symchoices in this PR are: * Call operands and addresses and emulations of such, which will subject them to overload resolution which will resolve them or fail. * Type conversion operands only for routine symchoices for type disambiguation syntax (like `(proc (x: int): int)(foo)`), which will resolve them or fail. * Proc parameter default values both at the declaration and during generic instantiation, which undergo type narrowing and so will resolve them or fail. This means unless these contexts mess up sym choice nodes should never leave the semchecking stage. This serves as a blueprint for future improvements to intermediate symbol resolution. Some tangential changes are also in this PR: 1. The `AmbiguousEnum` hint is removed, it was always disabled by default and since #22606 it only started getting emitted after the symchoice was soundly resolved. 2. Proc setter syntax (`a.b = c` becoming `` `b=`(a, c) ``) used to fully type check the RHS before passing the transformed call node to proc overloading. Now it just passes the original node directly so proc overloading can deal with its typechecking. (cherry picked from commit 5f9038a)
refs #22605 Sym choice nodes are now only allowed to pass through semchecking if contexts ask for them to (with `efAllowSymChoice`). Otherwise they are resolved or treated as ambiguous. The contexts that can receive symchoices in this PR are: * Call operands and addresses and emulations of such, which will subject them to overload resolution which will resolve them or fail. * Type conversion operands only for routine symchoices for type disambiguation syntax (like `(proc (x: int): int)(foo)`), which will resolve them or fail. * Proc parameter default values both at the declaration and during generic instantiation, which undergo type narrowing and so will resolve them or fail. This means unless these contexts mess up sym choice nodes should never leave the semchecking stage. This serves as a blueprint for future improvements to intermediate symbol resolution. Some tangential changes are also in this PR: 1. The `AmbiguousEnum` hint is removed, it was always disabled by default and since #22606 it only started getting emitted after the symchoice was soundly resolved. 2. Proc setter syntax (`a.b = c` becoming `` `b=`(a, c) ``) used to fully type check the RHS before passing the transformed call node to proc overloading. Now it just passes the original node directly so proc overloading can deal with its typechecking. (cherry picked from commit 5f9038a)
Description
Related to #22599 but this time
error
has its own scopeNim Version
1.6.14
Current Output
Expected Output
Possible Solution
No response
Additional Information
No response
edit: simplified
The text was updated successfully, but these errors were encountered: