Skip to content

overloadExists(foo(args)) to tell whether an overload exists without compiling/running body via compiles #164

@timotheecour

Description

@timotheecour

(originally discussed here: nim-lang/Nim#11722 (comment) /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 whether foo(args) has a proper overload; it doesn't try to compile foo(args) ; in particular, the check succeeds even if foo(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 nim-lang/Nim#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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions