RFCs
RFCs copied to clipboard
`overloadExists(foo(args))` to tell whether an overload exists without compiling/running body via `compiles`
(originally discussed here: https://github.com/nim-lang/Nim/pull/11722#discussion_r317373944 /cc @krux02 )
proposal1
proc overloadExists(expr: untyped): bool {.magic:"overloadExists".} # in system.nim (needed for `untyped` in proc)
overloadExists(foo(args))compiler magic that checks whetherfoo(args)has a proper overload; it doesn't try to compilefoo(args); in particular, the check succeeds even iffoo(args)has a fwd declaration and no definition.
examples:
doAssert overloadExists(toUpper("abc"))
doAssert not overloadExists(toUpper(12))
doAssert overloadExists(1+1)
doAssert not overloadExists(1+1.2)
doAssert not overloadExists(nonexistant(1))
proposal2
independent / orthogonal to proposal1. This selects a particular overload via overloadResolve. Works with any routine (template/macro/iterator/generics, regardless of all optional params/varargs)
const fooParticularOverload = overloadResolve foo("1", true)
echo fooParticularOverload("bar", false)
# this is helpful for debugging among other use cases:
echo inspect(fooParticularOverload) # can be done via https://github.com/nim-lang/Nim/pull/11754 or even just #11992
# would print:
template fooParticularOverload(x: string|cstring, y = false, z = 0): untyped declared at foo/bar.nim(12,14)
this 2nd proposal requires https://github.com/nim-lang/Nim/pull/11992
rationale
depending on compiles(foo(args)) can be problematic for a number of reasons:
- compiles causes CT overhead, which can be significant depending on what's being compiled; eg:
when compiles(expensiveCalculation(args)): # foo(args) executed here
expensiveCalculation(args) # executed here again
else: workaround(args)
- compiles can have side effects, eg:
proc foo() =
const s = gorge("echo bar > /tmp/z01.txt")
echo s
when compiles(foo()): echo "ok1"
else: echo "ok2"
The side effect can even be executed when the compiles() eventually fails after the side effect
- the
compiles(foo(args))can fail for a reason the user did not expect, eg a bug in proc body; that bug may end up not getting caught; eg:
proc fun[T: Ordinal | Foo1 | Foo2](a: T) = # contains a bug that affects some `T`
proc workaround[T](a: T) = discard
when compiles(fun(args)): # fails for some T in `Ordinal | Foo1 | Foo2`
fun(args)
else: workaround(args)
here user expects fun(args) to compile for every T in Ordinal | Foo1 | Foo2 but in fact some cases may trigger the other branch to be taken due to a bug inside fun
Does it need to be magic? Or can we lift something already implemented in sigmatch? I think it should be in typetraits, typetraits is used in system.nim for deepCopy/supportsCopyMem.
If it helps I wrote my own overload resolution macro for my needs though it requires a typed input
Does it need to be magic?
I don't see how that could work without relying on a {.magic.}?
Or can we lift something already implemented in sigmatch?
yes, it would leverage sigmatch, the basic functionality to implement this is already there ; unless I'm missing something, a PR implementing proposal1 shouldn't be hard
If it helps I wrote my own overload resolution macro for my needs though it requires a typed input
well typed input would be "too late", since we don't know whether overload exists
I think it should be in typetraits, typetraits is used in system.nim
yes, that's the other sensible module. it would logically group well with system.declared, system.compiles, but I understand we want to avoid growing system.
system.compiles is incredibly underspecified and needs a spec and needs to disallow some of its wild west usages. After we have done that, I'm not sure we need more builtins with overlapping functionality.
Funny hack
proc plusExists[T]: bool =
type Foo = concept
proc `+`(x, y: T): T
result = void is Foo
echo plusExists[int]()
echo plusExists[string]()