Skip to content

Commit fffc219

Browse files
authored
argument dependent lookup (#1112)
closes #1107 Same as the implementation in original Nim, if an argument has a nominal type and the nominal type is exported from a module, the toplevel symbols of the module the type is from are scanned if they are an attachable routine to the nominal type, in which case they are considered in overload resolution. This is done in the same place as concept procs, they are now built directly in `considerTypeboundOps` as well by iterating over `cs.args` rather than adding them when the arguments are typechecked. This is to simplify preventing symbols that were already considered in overloading from getting added, they now use the same marker set as the added typebound ops. Apparently the position of the argument is not considered in attachment, every parameter of the routine is checked for the nominal type. Presumably this is to simplify the logic for things like varargs and named arguments. It apparently also ignores parameters with default values. This logic is unchanged. Something to point out is that attached routines for types from the current semchecked module have to be special cased. One might assume they shouldn't be added at all since it would be redundant with regular lookup, but then the original test case in #1107 would not work without a fix like in #1110, as regular lookup with `OchoiceX` does not work as expected yet. Another problem with adding attached routines from the current module is that using a cache would not be reliable, and so it is not done here, but this means we have to search for attached routines every time for them. The original Nim implementation also disables type bound ops when the callee is a closed symbol/symchoice. This could be done here for open symchoices but it would be a little arbitrary to track the case where the callee is originally an identifier that gets turned into a symbol, maybe it can produce an open symchoice but this would complicate type conversions etc. So nothing is done for now.
1 parent fae14ba commit fffc219

File tree

8 files changed

+120
-21
lines changed

8 files changed

+120
-21
lines changed

src/nimony/programs.nim

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ proc loadVTable*(typ: SymId): seq[MethodIndexEntry] =
223223
return entry.methods
224224
return @[]
225225

226+
proc loadSyms*(suffix: string; identifier: StrId): seq[SymId] =
227+
# gives top level exported syms of a module
228+
result = @[]
229+
var m = load(suffix)
230+
for k, _ in m.index.public:
231+
var base = k
232+
extractBasename(base)
233+
let strId = pool.strings.getOrIncl(base)
234+
if strId == identifier:
235+
let symId = pool.syms.getOrIncl(k)
236+
result.add symId
237+
226238
proc registerHook*(suffix: string; typ: SymId; op: AttachedOp; hook: SymId; isGeneric: bool) =
227239
let m: NifModule
228240
if not prog.mods.hasKey(suffix):

src/nimony/sem.nim

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -856,10 +856,10 @@ proc sameIdent(a, b: SymId): bool {.used.} =
856856
type
857857
FnCandidates = object
858858
a: seq[FnCandidate]
859-
s: HashSet[SymId]
859+
marker: HashSet[SymId]
860860

861861
proc addUnique(c: var FnCandidates; x: FnCandidate) =
862-
if not containsOrIncl(c.s, x.sym):
862+
if not containsOrIncl(c.marker, x.sym):
863863
c.a.add x
864864

865865
iterator findConceptsInConstraint(typ: Cursor): Cursor =
@@ -905,10 +905,53 @@ proc maybeAddConceptMethods(c: var SemContext; fn: StrId; typevar: SymId; cands:
905905
cands.addUnique FnCandidate(kind: sk, sym: prc.symId, typ: d, fromConcept: true)
906906
skip ops
907907

908-
proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; candidates: FnCandidates; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) =
909-
for candidate in candidates.a:
910-
m.add createMatch(addr c)
911-
sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs)
908+
proc hasAttachedParam(params: Cursor; typ: SymId): bool =
909+
result = false
910+
var params = params
911+
assert params.substructureKind == ParamsU
912+
inc params
913+
while params.kind != ParRi:
914+
let param = takeLocal(params, SkipFinalParRi)
915+
let root = nominalRoot(param.typ)
916+
if root != SymId(0) and root == typ:
917+
return true
918+
919+
proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandidates) =
920+
let res = tryLoadSym(s)
921+
assert res.status == LacksNothing
922+
let decl = asTypeDecl(res.decl)
923+
if decl.kind == TypeY:
924+
let moduleSuffix = extractModule(pool.syms[s])
925+
if moduleSuffix == "":
926+
discard
927+
elif moduleSuffix == c.thisModuleSuffix:
928+
# XXX probably redundant over normal lookup but `OchoiceX` does not work yet
929+
# do not use cache, check symbols from toplevel scope:
930+
for topLevelSym in topLevelSyms(c, fn):
931+
let res = tryLoadSym(topLevelSym)
932+
assert res.status == LacksNothing
933+
let routine = asRoutine(res.decl)
934+
if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s):
935+
cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params)
936+
else:
937+
if (s, fn) in c.cachedTypeboundOps:
938+
for fnSym in c.cachedTypeboundOps[(s, fn)]:
939+
let res = tryLoadSym(fnSym)
940+
assert res.status == LacksNothing
941+
let routine = asRoutine(res.decl)
942+
cands.addUnique FnCandidate(kind: routine.kind, sym: fnSym, typ: routine.params)
943+
else:
944+
var ops: seq[SymId] = @[]
945+
for topLevelSym in loadSyms(moduleSuffix, fn):
946+
let res = tryLoadSym(topLevelSym)
947+
assert res.status == LacksNothing
948+
let routine = asRoutine(res.decl)
949+
if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s):
950+
ops.add topLevelSym
951+
cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params)
952+
c.cachedTypeboundOps[(s, fn)] = ops
953+
elif decl.kind == TypevarY:
954+
maybeAddConceptMethods c, fn, s, cands
912955

913956
proc requestRoutineInstance(c: var SemContext; origin: SymId;
914957
typeArgs: TokenBuf;
@@ -1006,7 +1049,6 @@ type
10061049
args: seq[Item]
10071050
hasGenericArgs, hasNamedArgs: bool
10081051
flags: set[SemFlag]
1009-
candidates: FnCandidates
10101052
source: TransformedCallSource
10111053
## type of expression the call was transformed from
10121054

@@ -1228,6 +1270,27 @@ proc buildCallSource(buf: var TokenBuf; cs: CallState) =
12281270
proc semReturnType(c: var SemContext; n: var Cursor): TypeCursor =
12291271
result = semLocalType(c, n, InReturnTypeDecl)
12301272

1273+
proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; fnName: StrId; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) =
1274+
# scope extension: procs attached to argument types are also considered
1275+
# If the type is Typevar and it has attached
1276+
# a concept, use the concepts symbols too:
1277+
if fnName != StrId(0):
1278+
# XXX maybe only trigger for open symchoice/ident callee, but the latter is not tracked
1279+
var candidates = FnCandidates(marker: initHashSet[SymId]())
1280+
# mark already matched symbols so that they don't get added:
1281+
for i in 0 ..< m.len:
1282+
if m[i].fn.sym != SymId(0):
1283+
candidates.marker.incl m[i].fn.sym
1284+
# add attached ops for each arg:
1285+
for arg in args:
1286+
let root = nominalRoot(arg.typ, allowTypevar = true)
1287+
if root != SymId(0):
1288+
addTypeboundOps c, fnName, root, candidates
1289+
# now match them:
1290+
for candidate in candidates.a:
1291+
m.add createMatch(addr c)
1292+
sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs)
1293+
12311294
proc addArgsInstConverters(c: var SemContext; m: var Match; origArgs: openArray[Item]) =
12321295
if not (m.genericConverter or m.checkEmptyArg or m.insertedParam):
12331296
c.dest.add m.args
@@ -1440,7 +1503,7 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) =
14401503
else:
14411504
buildErr c, cs.fn.n.info, "`choice` node does not contain `symbol`"
14421505
inc f
1443-
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs)
1506+
considerTypeboundOps(c, m, cs.fnName, cs.args, genericArgs, cs.hasNamedArgs)
14441507
if m.len == 0:
14451508
# symchoice contained no callable symbols and no typebound ops
14461509
assert cs.fnName != StrId(0)
@@ -1463,7 +1526,7 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) =
14631526
let candidate = FnCandidate(kind: cs.fnKind, sym: sym, typ: typ)
14641527
m.add createMatch(addr c)
14651528
sigmatchNamedArgs(m[^1], candidate, cs.args, genericArgs, cs.hasNamedArgs)
1466-
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs)
1529+
considerTypeboundOps(c, m, cs.fnName, cs.args, genericArgs, cs.hasNamedArgs)
14671530
elif sym != SymId(0):
14681531
# non-callable symbol, look up all overloads
14691532
assert cs.fnName != StrId(0)
@@ -1753,12 +1816,6 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans
17531816
let lhsIndex = c.dest.len
17541817
c.dest.addSubtree lhs.n
17551818
argIndexes.add lhsIndex
1756-
# scope extension: If the type is Typevar and it has attached
1757-
# a concept, use the concepts symbols too:
1758-
if cs.fnName != StrId(0):
1759-
let root = nominalRoot(lhs.typ, allowTypevar = true)
1760-
if root != SymId(0):
1761-
maybeAddConceptMethods c, cs.fnName, root, cs.candidates
17621819
# lhs.n escapes here, but is not read and will be set by argIndexes:
17631820
cs.args.add lhs
17641821
else:
@@ -1793,12 +1850,6 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans
17931850
takeParRi c, arg.n
17941851
if arg.typ.typeKind == UntypedT:
17951852
skipSemCheck = true
1796-
# scope extension: If the type is Typevar and it has attached
1797-
# a concept, use the concepts symbols too:
1798-
if cs.fnName != StrId(0):
1799-
let root = nominalRoot(arg.typ, allowTypevar = true)
1800-
if root != SymId(0):
1801-
maybeAddConceptMethods c, cs.fnName, root, cs.candidates
18021853
it.n = arg.n
18031854
cs.args.add arg
18041855
when defined(debug):

src/nimony/sembasics.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ proc buildSymChoiceForSelfModule*(c: var SemContext;
7373
c.dest.shrink oldLen
7474
c.dest.add identToken(identifier, info)
7575

76+
iterator topLevelSyms*(c: var SemContext; identifier: StrId): SymId =
77+
var it = c.currentScope
78+
while it.up != nil: it = it.up
79+
for sym in it.tab.getOrDefault(identifier):
80+
yield sym.name
81+
7682
proc rawBuildSymChoiceForForeignModule(c: var SemContext; module: SymId;
7783
identifier: StrId; info: PackedLineInfo;
7884
marker: var HashSet[SymId]): int =

src/nimony/semdata.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type
113113
pendingTypePlugins*: Table[SymId, StrId]
114114
pendingModulePlugins*: seq[StrId]
115115
pluginBlacklist*: HashSet[StrId] # make 1984 fiction again
116+
cachedTypeboundOps*: Table[(SymId, StrId), seq[SymId]]
116117

117118
proc typeToCanon*(buf: TokenBuf; start: int): string =
118119
result = ""
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import std / syncio
2+
3+
type
4+
MyS* = distinct string
5+
6+
proc write*(f: File; s: MyS) = write f, string(s)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import msandwich1
2+
3+
proc toMyS*(s: string): MyS = MyS(s)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import std / syncio
2+
3+
type
4+
MyS = distinct string
5+
6+
proc write*(f: File; s: MyS) = write f, string(s)
7+
8+
proc main =
9+
var s90 = MyS"abc"
10+
echo s90
11+
12+
main()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import std / syncio
2+
import deps / msandwich2
3+
4+
proc main =
5+
var s90 = toMyS("abc")
6+
echo s90
7+
8+
main()

0 commit comments

Comments
 (0)