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

WIP resolveSymbol: fix https://github.com/nim-lang/RFCs/issues/164 D20190825T173945 #692

Draft
wants to merge 17 commits into
base: devel
Choose a base branch
from
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