Skip to content
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

resolveSymbol(foo(args)) and overloadExists(foo(args)) to return symbol after overload resolution #12076

3 changes: 1 addition & 2 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,7 @@ type
mInstantiationInfo, mGetTypeInfo,
mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples,
mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf,
mSymIsInstantiationOf, mNodeId

mSymIsInstantiationOf, mNodeId, mOverloadResolve,

# things that we can evaluate safely at compile time, even if not asked for it:
const
Expand Down
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,4 @@ proc initDefines*(symbols: StringTableRef) =

defineSymbol("nimHasSinkInference")
defineSymbol("nimNewIntegerOps")
defineSymbol("himHasOverloadResolve")
6 changes: 3 additions & 3 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ proc lookUp*(c: PContext, n: PNode): PSym =

type
TLookupFlag* = enum
checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields
checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields, checkOverloadResolve

proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
const allExceptModule = {low(TSymKind)..high(TSymKind)}-{skModule,skPackage}
Expand All @@ -313,7 +313,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
result = searchInScopes(c, ident, allExceptModule).skipAlias(n, c.config)
if result == nil and checkPureEnumFields in flags:
result = strTableGet(c.pureEnumFields, ident)
if result == nil and checkUndeclared in flags:
if result == nil and checkUndeclared in flags and checkOverloadResolve notin flags:
fixSpelling(n, ident, searchInScopes)
errorUndeclaredIdentifier(c, n.info, ident.s)
result = errorSym(c, n)
Expand All @@ -338,7 +338,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n, c.config)
else:
result = strTableGet(m.tab, ident).skipAlias(n, c.config)
if result == nil and checkUndeclared in flags:
if result == nil and checkUndeclared in flags and checkOverloadResolve notin flags:
fixSpelling(n[1], ident, searchInScopes)
errorUndeclaredIdentifier(c, n[1].info, ident.s)
result = errorSym(c, n[1])
Expand Down
8 changes: 5 additions & 3 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
pickBest(callOp)

if overloadsState == csEmpty and result.state == csEmpty:
if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
if {efNoUndeclared, efOverloadResolve} * flags == {}: # for tests/pragmas/tcustom_pragma.nim
localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
return
elif result.state != csMatch:
Expand Down Expand Up @@ -488,6 +488,7 @@ proc semResolvedCall(c: PContext, x: TCandidate,
markUsed(c, info, finalCallee)
onUse(info, finalCallee)
assert finalCallee.ast != nil
if efOverloadResolve in flags: return newSymNode(finalCallee, info)
if x.hasFauxMatch:
result = x.call
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
Expand Down Expand Up @@ -533,6 +534,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
filter: TSymKinds, flags: TExprFlags): PNode =
var errors: CandidateErrors = @[] # if efExplain in flags: @[] else: nil
var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags)
template canError(): bool = {efNoUndeclared, efOverloadResolve} * flags == {}
if r.state == csMatch:
# this may be triggered, when the explain pragma is used
if errors.len > 0:
Expand Down Expand Up @@ -560,14 +562,14 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
# repeat the overload resolution,
# this time enabling all the diagnostic output (this should fail again)
discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
elif efNoUndeclared notin flags:
elif canError():
notFoundError(c, n, errors)
else:
if efExplain notin flags:
# repeat the overload resolution,
# this time enabling all the diagnostic output (this should fail again)
discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
elif efNoUndeclared notin flags:
elif canError():
notFoundError(c, n, errors)

proc explicitGenericInstError(c: PContext; n: PNode): PNode =
Expand Down
4 changes: 3 additions & 1 deletion compiler/semdata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ type
efWantStmt, efAllowStmt, efDetermineType, efExplain,
efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
efNoEvaluateGeneric, efInCall, efFromHlo,
efNoUndeclared
efNoUndeclared,
# Use this if undeclared identifiers should not raise an error during
# overload resolution.
efOverloadResolve,
# for `mOverloadResolve` evaluation, resolves `foo` in `foo(args)`

TExprFlags* = set[TExprFlag]

Expand Down
62 changes: 55 additions & 7 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
rejectEmptyNode(n)
result = semExpr(c, n, flags+{efWantValue})
if result == nil: return errorNode(c, n)
if result.kind == nkEmpty:
# do not produce another redundant error message:
result = errorNode(c, n)
Expand Down Expand Up @@ -832,6 +833,7 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
{skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}, flags)

if result != nil:
if efOverloadResolve in flags: return
if result[0].kind != nkSym:
internalError(c.config, "semOverloadedCallAnalyseEffects")
return
Expand Down Expand Up @@ -906,7 +908,8 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
else:
n[0] = n0
else:
n[0] = semExpr(c, n[0], {efInCall})
n[0] = semExpr(c, n[0], {efInCall} + flags * {efOverloadResolve})
if n[0] == nil and efOverloadResolve in flags: return errorNode(c, n)
let t = n[0].typ
if t != nil and t.kind in {tyVar, tyLent}:
n[0] = newDeref(n[0])
Expand Down Expand Up @@ -980,6 +983,7 @@ proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
let nOrig = n.copyTree
#semLazyOpAux(c, n)
result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags)
if efOverloadResolve in flags: return
if result != nil: result = afterCallActions(c, result, nOrig, flags)
else: result = errorNode(c, n)

Expand Down Expand Up @@ -1270,7 +1274,14 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
suggestExpr(c, n)
if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)

var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule})
var flags2 = {checkAmbiguity, checkUndeclared, checkModule}
if efOverloadResolve in flags: flags2.incl checkOverloadResolve
var s = qualifiedLookUp(c, n, flags2)
if efOverloadResolve in flags and n.kind == nkDotExpr:
var m = qualifiedLookUp(c, n[0], (flags2*{checkUndeclared})+{checkModule})
if m != nil and m.kind == skModule: # got `mymodule.someident`
if s == nil: return nil
else: return symChoice(c, n, s, scClosed)
if s != nil:
if s.kind in OverloadableSyms:
result = symChoice(c, n, s, scClosed)
Expand Down Expand Up @@ -2098,11 +2109,37 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
# we replace this node by a 'true' or 'false' node:
if n.len != 2: return semDirectOp(c, n, flags)

result = newIntNode(nkIntLit, ord(tryExpr(c, n[1], flags) != nil))
result.info = n.info
result.typ = getSysType(c.graph, n.info, tyBool)

proc semOverloadResolve(c: PContext, n: PNode, flags: TExprFlags, isTopLevel: bool): PNode =
var n = n
if isTopLevel:
if n.len != 2:
localError(c.config, n.info, "semOverloadResolve: got" & $n.len)
return
n = n[1]
if n.kind notin {nkIdent,nkDotExpr,nkAccQuoted} + nkCallKinds - {nkHiddenCallConv}:
localError(c.config, n.info, "expected routine, got " & $n.kind)
return errorNode(c, n)
if n.kind == nkDotExpr:
# so that this doesn't compile: `overloadExists(nonexistant().foo)`
n[0] = semExpr(c, n[0], flags)
let flags = flags + {efWantIterator, efOverloadResolve}
result = semExpr(c, n, flags)
if result == nil or result.kind == nkEmpty:
if isTopLevel:
result = newNodeIT(nkNilLit, n.info, getSysType(c.graph, n.info, tyNil))
elif result.kind == nkClosedSymChoice:
# avoids degenerating symchoice to a sym
let typ = newTypeS(tyTuple, c)
let result0 = result
result = newNodeIT(nkTupleConstr, n.info, typ)
result.add result0
else:
doAssert result.kind == nkSym, $result.kind

proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode =
if n.len == 3:
# XXX ugh this is really a hack: shallowCopy() can be overloaded only
Expand Down Expand Up @@ -2171,6 +2208,9 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
of mCompiles:
markUsed(c, n.info, s)
result = semCompiles(c, setMs(n, s), flags)
of mOverloadResolve:
markUsed(c, n.info, s)
result = semOverloadResolve(c, setMs(n, s), flags, isTopLevel = true)
of mIs:
markUsed(c, n.info, s)
result = semIs(c, setMs(n, s), flags)
Expand Down Expand Up @@ -2560,15 +2600,18 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
if nfSem in n.flags: return
case n.kind
of nkIdent, nkAccQuoted:
let checks = if efNoEvaluateGeneric in flags:
var checks = if efNoEvaluateGeneric in flags:
{checkUndeclared, checkPureEnumFields}
elif efInCall in flags:
{checkUndeclared, checkModule, checkPureEnumFields}
else:
{checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields}
if efOverloadResolve in flags: checks.incl checkOverloadResolve
var s = qualifiedLookUp(c, n, checks)
if efOverloadResolve in flags and s == nil: return nil
if c.matchedConcept == nil: semCaptureSym(s, c.p.owner)
if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}:
if efOverloadResolve in flags: result = symChoice(c, n, s, scClosed)
elif s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}:
#performProcvarCheck(c, n, s)
result = symChoice(c, n, s, scClosed)
if result.kind == nkSym:
Expand Down Expand Up @@ -2624,7 +2667,12 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
result = semFieldAccess(c, n, flags)
if result.kind == nkDotCall:
result.transitionSonsKind(nkCall)
result = semExpr(c, result, flags)
if efOverloadResolve in flags:
result = semOverloadResolve(c, result, flags, isTopLevel = false)
else:
result = semExpr(c, result, flags)
elif result.kind == nkDotExpr and efOverloadResolve in flags:
result = result[1]
of nkBind:
message(c.config, n.info, warnDeprecated, "bind is deprecated")
result = semExpr(c, n[0], flags)
Expand All @@ -2642,7 +2690,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
checkMinSonsLen(n, 1, c.config)
#when defined(nimsuggest):
# if gIdeCmd == ideCon and c.config.m.trackPos == n.info: suggestExprNoCheck(c, n)
let mode = if nfDotField in n.flags: {} else: {checkUndeclared}
let mode = if nfDotField in n.flags or efOverloadResolve in flags: {} else: {checkUndeclared}
var s = qualifiedLookUp(c, n[0], mode)
if s != nil:
#if c.config.cmd == cmdPretty and n[0].kind == nkDotExpr:
Expand Down
8 changes: 8 additions & 0 deletions lib/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,14 @@ type
NimNode* {.magic: "PNimrodNode".} = ref NimNodeObj
## Represents a Nim AST node. Macros operate on this type.

when defined(himHasOverloadResolve):
proc resolveSymbol*(x: untyped): NimNode {.magic: "OverloadResolve", noSideEffect, compileTime.} =
## resolves a symbol given an expression, eg: in `resolveSymbol(foo(args))`
## it will find the symbol that would be called after overload resolution,
## without calling it. Unlike `compiles(foo(args))`, the body is not analyzed.
## Also works with `compiles(mymod.mysym)` to return the symChoice overload
## set.

when defined(nimV2):
import system/repr_v2
export repr_v2
Expand Down
8 changes: 8 additions & 0 deletions tests/magics/mresolve_overloads.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
let mfoo1* = [1,2] ## c1
var mfoo2* = "asdf" ## c2
const mfoo3* = 'a' ## c3

proc `@@@`*(a: int) = discard
proc `@@@`*(a: float) = discard
proc `@@@`*[T: Ordinal](a: T) = discard

46 changes: 46 additions & 0 deletions tests/magics/mresolves.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import std/macros

macro overloadExistsImpl(x: typed): bool =
newLit(x != nil)

template overloadExists*(a: untyped): bool =
overloadExistsImpl(resolveSymbol(a))

type InstantiationInfo = type(instantiationInfo())

proc toStr(info: InstantiationInfo | LineInfo): string =
const offset = 1
result = info.filename & ":" & $info.line & ":" & $(info.column + offset)

proc inspectImpl*(s: var string, a: NimNode, resolveLet: bool) =
var a = a
if resolveLet:
a = a.getImpl
a = a[2]
case a.kind
of nnkClosedSymChoice:
s.add "closedSymChoice:"
for ai in a:
s.add "\n "
inspectImpl(s, ai, false)
of nnkSym:
var a2 = a.getImpl
const callables = {nnkProcDef, nnkMethodDef, nnkConverterDef, nnkMacroDef, nnkTemplateDef, nnkIteratorDef}
if a2.kind in callables:
let a20=a2
a2 = newTree(a20.kind)
for i, ai in a20:
a2.add if i notin [6]: ai else: newEmptyNode()
s.add a2.lineInfoObj.toStr & " " & a2.repr
else: error($a.kind, a)

macro inspect*(a: typed, resolveLet: static bool = false): untyped =
var a = a
if a.kind == nnkTupleConstr:
a = a[0]
var s: string
s.add a.lineInfoObj.toStr & ": "
s.add a.repr & " = "
inspectImpl(s, a, resolveLet)
when defined(nimTestsResolvesDebug):
echo s
Loading