Skip to content
Draft
3 changes: 1 addition & 2 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,7 @@ type
mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2,
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 @@ -133,3 +133,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasCustomLiterals")
defineSymbol("nimHasUnifiedTuple")
defineSymbol("nimHasIterable")
defineSymbol("himHasOverloadResolve")
6 changes: 3 additions & 3 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,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 @@ -520,7 +520,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)

if result == nil and checkUndeclared in flags:
if result == nil and checkUndeclared in flags and checkOverloadResolve notin flags:
result = errorUndeclaredIdentifierHint(c, n, ident)
elif checkAmbiguity in flags and result != nil and amb:
result = errorUseQualifier(c, n.info, result, amb)
Expand All @@ -541,7 +541,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n, c.config)
else:
result = someSym(c.graph, m, ident).skipAlias(n, c.config)
if result == nil and checkUndeclared in flags:
if result == nil and checkUndeclared in flags and checkOverloadResolve notin flags:
result = errorUndeclaredIdentifierHint(c, n[1], ident)
elif n[1].kind == nkSym:
result = n[1].sym
Expand Down
9 changes: 6 additions & 3 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ 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
# xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident)
localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
return
Expand Down Expand Up @@ -520,6 +521,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 @@ -568,6 +570,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
filter: TSymKinds, flags: TExprFlags): PNode {.nosinks.} =
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 @@ -595,14 +598,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 @@ -69,9 +69,11 @@ type
efWantStmt, efAllowStmt, efDetermineType, efExplain,
efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
efNoEvaluateGeneric, efInCall, efFromHlo, efNoSem2Check,
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
69 changes: 60 additions & 9 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
proc semExprCheck(c: PContext, n: PNode, flags: TExprFlags): PNode =
rejectEmptyNode(n)
result = semExpr(c, n, flags+{efWantValue})
if result == nil: return errorNode(c, n)

let
isEmpty = result.kind == nkEmpty
Expand Down Expand Up @@ -865,6 +866,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 @@ -947,7 +949,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 @@ -1031,6 +1034,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 @@ -1358,7 +1362,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 @@ -2174,11 +2185,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 @@ -2250,6 +2287,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 @@ -2694,15 +2734,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 @@ -2758,7 +2801,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 @@ -2779,7 +2827,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}
c.isAmbiguous = false
var s = qualifiedLookUp(c, n[0], mode)
if s != nil:
Expand Down Expand Up @@ -2815,8 +2863,11 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
result = semDirectOp(c, n, flags)
else:
result = semIndirectOp(c, n, flags)

if nfDefaultRefsParam in result.flags:
if result == nil:
# dbg "D20210413T120912" # PRTEMP
discard
elif nfDefaultRefsParam in result.flags:
# if nfDefaultRefsParam in result.flags:
result = result.copyTree #XXX: Figure out what causes default param nodes to be shared.. (sigmatch bug?)
# We've found a default value that references another param.
# See the notes in `hoistParamsUsedInDefault` for more details.
Expand Down
8 changes: 8 additions & 0 deletions lib/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,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

103 changes: 103 additions & 0 deletions tests/magics/mresolves.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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) # Error: nnkNilLit when couldn't resolve

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

template inspect2*(a: untyped): untyped = inspect(resolveSymbol(a))

macro fieldExistsImpl(a: typed): bool =
newLit(a.symKind == nskField)

template fieldExists*(a: untyped): untyped = fieldExistsImpl(resolveSymbol(a))

macro canImportImpl(a: typed): bool =
newLit(a.symKind == nskModule)

# can't work like that...
template canImport*(a: untyped): untyped = canImportImpl(resolveSymbol(a))

macro inspect3*(a: typed): untyped =
echo (a.repr, a.kind, a.typeKind)
echo a.symKind
var a2 = a.getImpl
echo a2.kind
# echo a.sym.kind

# macro getSymImpl(a: typed): NimSym =
type SymWrap = object
s: NimSym
type SymWrap2 = object
s: int
type SymWrap3[T] = object
s: NimSym
# macro getSymImpl(a: typed): NimSym =

type SymWrap4*[sym] = object

# type SymWrap4b*[sym] = object
# sym2: sym

type SymWrap4b* = object
# sym2: T
sym2: NimSym

macro getSymImpl(a: typed): untyped =
# NimSym
let x = a.symbol
result = quote do:
SymWrap4[`x`]

template getSym*(a: untyped): untyped = getSymImpl(resolveSymbol(a))

type SymInt* = distinct int
macro getSymImpl2(a: typed): untyped =
# NimSym
let x = cast[int](a.symbol)
result = quote do:
# SymWrap4b[`x`](sym2: `x`)
# SymWrap4b(sym2: `x`)
`x`.SymInt

template getSym2*(a: untyped): untyped = getSymImpl2(resolveSymbol(a))
Loading