From 3996c4c5fd7d5a012409b90616bef6493c107aa6 Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 18 May 2025 19:56:30 +0300 Subject: [PATCH 1/7] first implementation --- src/nimony/programs.nim | 11 ++++++ src/nimony/sem.nim | 45 +++++++++++++++++++++--- tests/nimony/lookups/deps/msandwich1.nim | 6 ++++ tests/nimony/lookups/deps/msandwich2.nim | 3 ++ tests/nimony/lookups/ttypeboundops.nim | 8 +++++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 tests/nimony/lookups/deps/msandwich1.nim create mode 100644 tests/nimony/lookups/deps/msandwich2.nim create mode 100644 tests/nimony/lookups/ttypeboundops.nim diff --git a/src/nimony/programs.nim b/src/nimony/programs.nim index 38ad2b688..863d92ad7 100644 --- a/src/nimony/programs.nim +++ b/src/nimony/programs.nim @@ -223,6 +223,17 @@ proc loadVTable*(typ: SymId): seq[MethodIndexEntry] = return entry.methods return @[] +proc loadSyms*(suffix: string; identifier: StrId): seq[SymId] = + result = @[] + var m = load(suffix) + for k, _ in m.index.public: + var base = k + extractBasename(base) + let strId = pool.strings.getOrIncl(base) + if strId == identifier: + let symId = pool.syms.getOrIncl(k) + result.add symId + proc registerHook*(suffix: string; typ: SymId; op: AttachedOp; hook: SymId; isGeneric: bool) = let m: NifModule if not prog.mods.hasKey(suffix): diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index 53d0a832e..ca3d1aa4e 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -905,10 +905,47 @@ proc maybeAddConceptMethods(c: var SemContext; fn: StrId; typevar: SymId; cands: cands.addUnique FnCandidate(kind: sk, sym: prc.symId, typ: d, fromConcept: true) skip ops +proc hasAttachedParam(params: Cursor; typ: SymId): bool = + result = false + var params = params + assert params.substructureKind == ParamsU + inc params + while params.kind != ParRi: + let param = takeLocal(params, SkipFinalParRi) + if param.val.kind != DotToken: + # original nim does not consider params with default values as attached for some reason + discard + else: + let root = nominalRoot(param.typ) + if root != SymId(0) and root == typ: + return true + +proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandidates) = + let res = tryLoadSym(s) + assert res.status == LacksNothing + let decl = asTypeDecl(res.decl) + if decl.kind == TypeY: + let moduleSuffix = extractModule(pool.syms[s]) + if moduleSuffix != "" and moduleSuffix != c.thisModuleSuffix and + decl.exported.kind != DotToken: + for topLevelSym in loadSyms(moduleSuffix, fn): + let res = tryLoadSym(topLevelSym) + assert res.status == LacksNothing + let routine = asRoutine(res.decl) + if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s): + cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params) + elif decl.kind == TypevarY: + maybeAddConceptMethods c, fn, s, cands + proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; candidates: FnCandidates; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) = + var alreadyMatched = initHashSet[SymId]() + for i in 0 ..< m.len: + if m[i].fn.sym != SymId(0): + alreadyMatched.incl m[i].fn.sym for candidate in candidates.a: - m.add createMatch(addr c) - sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs) + if candidate.sym notin alreadyMatched: + m.add createMatch(addr c) + sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs) proc requestRoutineInstance(c: var SemContext; origin: SymId; typeArgs: TokenBuf; @@ -1758,7 +1795,7 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans if cs.fnName != StrId(0): let root = nominalRoot(lhs.typ, allowTypevar = true) if root != SymId(0): - maybeAddConceptMethods c, cs.fnName, root, cs.candidates + addTypeboundOps c, cs.fnName, root, cs.candidates # lhs.n escapes here, but is not read and will be set by argIndexes: cs.args.add lhs else: @@ -1798,7 +1835,7 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans if cs.fnName != StrId(0): let root = nominalRoot(arg.typ, allowTypevar = true) if root != SymId(0): - maybeAddConceptMethods c, cs.fnName, root, cs.candidates + addTypeboundOps c, cs.fnName, root, cs.candidates it.n = arg.n cs.args.add arg when defined(debug): diff --git a/tests/nimony/lookups/deps/msandwich1.nim b/tests/nimony/lookups/deps/msandwich1.nim new file mode 100644 index 000000000..9215d9b83 --- /dev/null +++ b/tests/nimony/lookups/deps/msandwich1.nim @@ -0,0 +1,6 @@ +import std / syncio + +type + MyS* = distinct string + +proc write*(f: File; s: MyS) = write f, string(s) diff --git a/tests/nimony/lookups/deps/msandwich2.nim b/tests/nimony/lookups/deps/msandwich2.nim new file mode 100644 index 000000000..4ace7ce06 --- /dev/null +++ b/tests/nimony/lookups/deps/msandwich2.nim @@ -0,0 +1,3 @@ +import msandwich1 + +proc toMyS*(s: string): MyS = MyS(s) diff --git a/tests/nimony/lookups/ttypeboundops.nim b/tests/nimony/lookups/ttypeboundops.nim new file mode 100644 index 000000000..8a509711f --- /dev/null +++ b/tests/nimony/lookups/ttypeboundops.nim @@ -0,0 +1,8 @@ +import std / syncio +import deps / msandwich2 + +proc main = + var s90 = toMyS("abc") + echo s90 + +main() From cfe81152bcd1568ec8d83a29c0f1a1905712321a Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 18 May 2025 20:07:00 +0300 Subject: [PATCH 2/7] add cache --- src/nimony/sem.nim | 27 ++++++++++++++++++--------- src/nimony/semdata.nim | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index ca3d1aa4e..891c317e5 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -925,15 +925,24 @@ proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandid assert res.status == LacksNothing let decl = asTypeDecl(res.decl) if decl.kind == TypeY: - let moduleSuffix = extractModule(pool.syms[s]) - if moduleSuffix != "" and moduleSuffix != c.thisModuleSuffix and - decl.exported.kind != DotToken: - for topLevelSym in loadSyms(moduleSuffix, fn): - let res = tryLoadSym(topLevelSym) - assert res.status == LacksNothing - let routine = asRoutine(res.decl) - if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s): - cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params) + if (s, fn) notin c.cachedTypeboundOps: + var ops: seq[SymId] = @[] + let moduleSuffix = extractModule(pool.syms[s]) + # types from this module do not cache any ops since they will be looked up anyway: + if moduleSuffix != "" and moduleSuffix != c.thisModuleSuffix and + decl.exported.kind != DotToken: + for topLevelSym in loadSyms(moduleSuffix, fn): + let res = tryLoadSym(topLevelSym) + assert res.status == LacksNothing + let routine = asRoutine(res.decl) + if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s): + ops.add topLevelSym + c.cachedTypeboundOps[(s, fn)] = ops + for fnSym in c.cachedTypeboundOps[(s, fn)]: + let res = tryLoadSym(fnSym) + assert res.status == LacksNothing + let routine = asRoutine(res.decl) + cands.addUnique FnCandidate(kind: routine.kind, sym: fnSym, typ: routine.params) elif decl.kind == TypevarY: maybeAddConceptMethods c, fn, s, cands diff --git a/src/nimony/semdata.nim b/src/nimony/semdata.nim index c055f9bac..c1632940e 100644 --- a/src/nimony/semdata.nim +++ b/src/nimony/semdata.nim @@ -113,6 +113,7 @@ type pendingTypePlugins*: Table[SymId, StrId] pendingModulePlugins*: seq[StrId] pluginBlacklist*: HashSet[StrId] # make 1984 fiction again + cachedTypeboundOps*: Table[(SymId, StrId), seq[SymId]] proc typeToCanon*(buf: TokenBuf; start: int): string = result = "" From 5a9f988065822462d704f8f6e94b53f727c95962 Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 18 May 2025 21:05:30 +0300 Subject: [PATCH 3/7] add typebound ops directly in considerTypeboundOps --- src/nimony/sem.nim | 48 ++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index 891c317e5..ccb995438 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -856,10 +856,10 @@ proc sameIdent(a, b: SymId): bool {.used.} = type FnCandidates = object a: seq[FnCandidate] - s: HashSet[SymId] + marker: HashSet[SymId] proc addUnique(c: var FnCandidates; x: FnCandidate) = - if not containsOrIncl(c.s, x.sym): + if not containsOrIncl(c.marker, x.sym): c.a.add x iterator findConceptsInConstraint(typ: Cursor): Cursor = @@ -946,16 +946,6 @@ proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandid elif decl.kind == TypevarY: maybeAddConceptMethods c, fn, s, cands -proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; candidates: FnCandidates; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) = - var alreadyMatched = initHashSet[SymId]() - for i in 0 ..< m.len: - if m[i].fn.sym != SymId(0): - alreadyMatched.incl m[i].fn.sym - for candidate in candidates.a: - if candidate.sym notin alreadyMatched: - m.add createMatch(addr c) - sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs) - proc requestRoutineInstance(c: var SemContext; origin: SymId; typeArgs: TokenBuf; inferred: Table[SymId, Cursor]; @@ -1052,7 +1042,6 @@ type args: seq[Item] hasGenericArgs, hasNamedArgs: bool flags: set[SemFlag] - candidates: FnCandidates source: TransformedCallSource ## type of expression the call was transformed from @@ -1274,6 +1263,23 @@ proc buildCallSource(buf: var TokenBuf; cs: CallState) = proc semReturnType(c: var SemContext; n: var Cursor): TypeCursor = result = semLocalType(c, n, InReturnTypeDecl) +proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; fnName: StrId; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) = + # scope extension: If the type is Typevar and it has attached + # a concept, use the concepts symbols too: + if fnName != StrId(0): + var candidates = FnCandidates(marker: initHashSet[SymId]()) + for i in 0 ..< m.len: + if m[i].fn.sym != SymId(0): + # don't add already matched symbols + candidates.marker.incl m[i].fn.sym + for arg in args: + let root = nominalRoot(arg.typ, allowTypevar = true) + if root != SymId(0): + addTypeboundOps c, fnName, root, candidates + for candidate in candidates.a: + m.add createMatch(addr c) + sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs) + proc addArgsInstConverters(c: var SemContext; m: var Match; origArgs: openArray[Item]) = if not (m.genericConverter or m.checkEmptyArg or m.insertedParam): c.dest.add m.args @@ -1486,7 +1492,7 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) = else: buildErr c, cs.fn.n.info, "`choice` node does not contain `symbol`" inc f - considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs) + considerTypeboundOps(c, m, cs.fnName, cs.args, genericArgs, cs.hasNamedArgs) if m.len == 0: # symchoice contained no callable symbols and no typebound ops assert cs.fnName != StrId(0) @@ -1509,7 +1515,7 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) = let candidate = FnCandidate(kind: cs.fnKind, sym: sym, typ: typ) m.add createMatch(addr c) sigmatchNamedArgs(m[^1], candidate, cs.args, genericArgs, cs.hasNamedArgs) - considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs) + considerTypeboundOps(c, m, cs.fnName, cs.args, genericArgs, cs.hasNamedArgs) elif sym != SymId(0): # non-callable symbol, look up all overloads assert cs.fnName != StrId(0) @@ -1799,12 +1805,6 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans let lhsIndex = c.dest.len c.dest.addSubtree lhs.n argIndexes.add lhsIndex - # scope extension: If the type is Typevar and it has attached - # a concept, use the concepts symbols too: - if cs.fnName != StrId(0): - let root = nominalRoot(lhs.typ, allowTypevar = true) - if root != SymId(0): - addTypeboundOps c, cs.fnName, root, cs.candidates # lhs.n escapes here, but is not read and will be set by argIndexes: cs.args.add lhs else: @@ -1839,12 +1839,6 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans takeParRi c, arg.n if arg.typ.typeKind == UntypedT: skipSemCheck = true - # scope extension: If the type is Typevar and it has attached - # a concept, use the concepts symbols too: - if cs.fnName != StrId(0): - let root = nominalRoot(arg.typ, allowTypevar = true) - if root != SymId(0): - addTypeboundOps c, cs.fnName, root, cs.candidates it.n = arg.n cs.args.add arg when defined(debug): From d85a9c877e296e1dffdeaec2669b4865e1bfdfb3 Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 18 May 2025 22:29:26 +0300 Subject: [PATCH 4/7] hopefully a bit more palatable --- src/nimony/sem.nim | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index ccb995438..2bbc88c67 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -925,24 +925,29 @@ proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandid assert res.status == LacksNothing let decl = asTypeDecl(res.decl) if decl.kind == TypeY: - if (s, fn) notin c.cachedTypeboundOps: - var ops: seq[SymId] = @[] - let moduleSuffix = extractModule(pool.syms[s]) - # types from this module do not cache any ops since they will be looked up anyway: - if moduleSuffix != "" and moduleSuffix != c.thisModuleSuffix and - decl.exported.kind != DotToken: + let moduleSuffix = extractModule(pool.syms[s]) + if moduleSuffix == "": + discard + elif moduleSuffix == c.thisModuleSuffix: + # XXX assumes normal lookup is enough, but maybe should add symbols anyway + discard + else: + if (s, fn) in c.cachedTypeboundOps: + for fnSym in c.cachedTypeboundOps[(s, fn)]: + let res = tryLoadSym(fnSym) + assert res.status == LacksNothing + let routine = asRoutine(res.decl) + cands.addUnique FnCandidate(kind: routine.kind, sym: fnSym, typ: routine.params) + else: + var ops: seq[SymId] = @[] for topLevelSym in loadSyms(moduleSuffix, fn): let res = tryLoadSym(topLevelSym) assert res.status == LacksNothing let routine = asRoutine(res.decl) if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s): ops.add topLevelSym - c.cachedTypeboundOps[(s, fn)] = ops - for fnSym in c.cachedTypeboundOps[(s, fn)]: - let res = tryLoadSym(fnSym) - assert res.status == LacksNothing - let routine = asRoutine(res.decl) - cands.addUnique FnCandidate(kind: routine.kind, sym: fnSym, typ: routine.params) + cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params) + c.cachedTypeboundOps[(s, fn)] = ops elif decl.kind == TypevarY: maybeAddConceptMethods c, fn, s, cands From 8c8462b7f43f455111e66d8c4570484f5f444c6d Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 19 May 2025 16:01:38 +0300 Subject: [PATCH 5/7] add logic for same module anyway --- src/nimony/programs.nim | 1 + src/nimony/sem.nim | 18 ++++++++++++++---- src/nimony/sembasics.nim | 6 ++++++ tests/nimony/lookups/tcrossmoduleoverload.nim | 12 ++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/nimony/lookups/tcrossmoduleoverload.nim diff --git a/src/nimony/programs.nim b/src/nimony/programs.nim index 863d92ad7..7c99c3b35 100644 --- a/src/nimony/programs.nim +++ b/src/nimony/programs.nim @@ -224,6 +224,7 @@ proc loadVTable*(typ: SymId): seq[MethodIndexEntry] = return @[] proc loadSyms*(suffix: string; identifier: StrId): seq[SymId] = + # gives top level exported syms of a module result = @[] var m = load(suffix) for k, _ in m.index.public: diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index 2bbc88c67..734abac6b 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -929,8 +929,14 @@ proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandid if moduleSuffix == "": discard elif moduleSuffix == c.thisModuleSuffix: - # XXX assumes normal lookup is enough, but maybe should add symbols anyway - discard + # XXX probably redundant over normal lookup but `OchoiceX` does not work yet + # do not use cache, check symbols from toplevel scope: + for topLevelSym in topLevelSyms(c, fn): + let res = tryLoadSym(topLevelSym) + assert res.status == LacksNothing + let routine = asRoutine(res.decl) + if routine.kind in RoutineKinds and hasAttachedParam(routine.params, s): + cands.addUnique FnCandidate(kind: routine.kind, sym: topLevelSym, typ: routine.params) else: if (s, fn) in c.cachedTypeboundOps: for fnSym in c.cachedTypeboundOps[(s, fn)]: @@ -1269,18 +1275,22 @@ proc semReturnType(c: var SemContext; n: var Cursor): TypeCursor = result = semLocalType(c, n, InReturnTypeDecl) proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; fnName: StrId; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) = - # scope extension: If the type is Typevar and it has attached + # XXX maybe only trigger for open symchoice/ident callee, + # scope extension: procs attached to argument types are also considered + # If the type is Typevar and it has attached # a concept, use the concepts symbols too: if fnName != StrId(0): var candidates = FnCandidates(marker: initHashSet[SymId]()) + # mark already matched symbols so that they don't get added: for i in 0 ..< m.len: if m[i].fn.sym != SymId(0): - # don't add already matched symbols candidates.marker.incl m[i].fn.sym + # add attached ops for each arg: for arg in args: let root = nominalRoot(arg.typ, allowTypevar = true) if root != SymId(0): addTypeboundOps c, fnName, root, candidates + # now match them: for candidate in candidates.a: m.add createMatch(addr c) sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs) diff --git a/src/nimony/sembasics.nim b/src/nimony/sembasics.nim index 40bdf8625..499e7b83d 100644 --- a/src/nimony/sembasics.nim +++ b/src/nimony/sembasics.nim @@ -73,6 +73,12 @@ proc buildSymChoiceForSelfModule*(c: var SemContext; c.dest.shrink oldLen c.dest.add identToken(identifier, info) +iterator topLevelSyms*(c: var SemContext; identifier: StrId): SymId = + var it = c.currentScope + while it.up != nil: it = it.up + for sym in it.tab.getOrDefault(identifier): + yield sym.name + proc rawBuildSymChoiceForForeignModule(c: var SemContext; module: SymId; identifier: StrId; info: PackedLineInfo; marker: var HashSet[SymId]): int = diff --git a/tests/nimony/lookups/tcrossmoduleoverload.nim b/tests/nimony/lookups/tcrossmoduleoverload.nim new file mode 100644 index 000000000..10711d249 --- /dev/null +++ b/tests/nimony/lookups/tcrossmoduleoverload.nim @@ -0,0 +1,12 @@ +import std / syncio + +type + MyS = distinct string + +proc write*(f: File; s: MyS) = write f, string(s) + +proc main = + var s90 = MyS"abc" + echo s90 + +main() From 298f2f3164eeb2133b2bf2f98c2b9bceb5db3b96 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 19 May 2025 16:14:57 +0300 Subject: [PATCH 6/7] fix comment --- src/nimony/sem.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index 734abac6b..ac4e9bc4b 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -1275,11 +1275,11 @@ proc semReturnType(c: var SemContext; n: var Cursor): TypeCursor = result = semLocalType(c, n, InReturnTypeDecl) proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; fnName: StrId; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) = - # XXX maybe only trigger for open symchoice/ident callee, # scope extension: procs attached to argument types are also considered # If the type is Typevar and it has attached # a concept, use the concepts symbols too: if fnName != StrId(0): + # XXX maybe only trigger for open symchoice/ident callee, but the latter is not tracked var candidates = FnCandidates(marker: initHashSet[SymId]()) # mark already matched symbols so that they don't get added: for i in 0 ..< m.len: From 44e3b6fdd164d5c7ebc0f5961644b706f77467d5 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 20 May 2025 00:06:52 +0300 Subject: [PATCH 7/7] remove no default value requirement --- src/nimony/sem.nim | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index ac4e9bc4b..9a3dd9ae5 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -912,13 +912,9 @@ proc hasAttachedParam(params: Cursor; typ: SymId): bool = inc params while params.kind != ParRi: let param = takeLocal(params, SkipFinalParRi) - if param.val.kind != DotToken: - # original nim does not consider params with default values as attached for some reason - discard - else: - let root = nominalRoot(param.typ) - if root != SymId(0) and root == typ: - return true + let root = nominalRoot(param.typ) + if root != SymId(0) and root == typ: + return true proc addTypeboundOps(c: var SemContext; fn: StrId; s: SymId; cands: var FnCandidates) = let res = tryLoadSym(s)