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

implement named args #828

Merged
merged 3 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/nimony/sem.nim
Original file line number Diff line number Diff line change
Expand Up @@ -851,10 +851,10 @@ proc maybeAddConceptMethods(c: var SemContext; fn: StrId; typevar: SymId; cands:
cands.addUnique FnCandidate(kind: ConceptProcY, sym: prc.symId, typ: d)
skip ops

proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; candidates: FnCandidates; args: openArray[Item], genericArgs: Cursor) =
proc considerTypeboundOps(c: var SemContext; m: var seq[Match]; candidates: FnCandidates; args: openArray[Item], genericArgs: Cursor, hasNamedArgs: bool) =
for candidate in candidates.a:
m.add createMatch(addr c)
sigmatch(m[^1], candidate, args, genericArgs)
sigmatchNamedArgs(m[^1], candidate, args, genericArgs, hasNamedArgs)

proc requestRoutineInstance(c: var SemContext; origin: SymId;
typeArgs: TokenBuf;
Expand Down Expand Up @@ -955,7 +955,7 @@ type
callNode: PackedToken
dest, genericDest: TokenBuf
args: seq[Item]
hasGenericArgs: bool
hasGenericArgs, hasNamedArgs: bool
flags: set[SemFlag]
candidates: FnCandidates
source: TransformedCallSource
Expand Down Expand Up @@ -1292,11 +1292,11 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) =
if typ.typeKind == ParamsT:
let candidate = FnCandidate(kind: s.kind, sym: sym, typ: typ)
m.add createMatch(addr c)
sigmatch(m[^1], candidate, cs.args, genericArgs)
sigmatchNamedArgs(m[^1], candidate, cs.args, genericArgs, cs.hasNamedArgs)
else:
buildErr c, cs.fn.n.info, "`choice` node does not contain `symbol`"
inc f
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs)
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs)
if m.len == 0:
# symchoice contained no callable symbols and no typebound ops
assert cs.fnName != StrId(0)
Expand All @@ -1318,8 +1318,8 @@ proc resolveOverloads(c: var SemContext; it: var Item; cs: var CallState) =
if typ.typeKind == ParamsT:
let candidate = FnCandidate(kind: cs.fnKind, sym: sym, typ: typ)
m.add createMatch(addr c)
sigmatch(m[^1], candidate, cs.args, genericArgs)
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs)
sigmatchNamedArgs(m[^1], candidate, cs.args, genericArgs, cs.hasNamedArgs)
considerTypeboundOps(c, m, cs.candidates, cs.args, genericArgs, cs.hasNamedArgs)
elif sym != SymId(0):
# non-callable symbol, look up all overloads
assert cs.fnName != StrId(0)
Expand Down Expand Up @@ -1626,7 +1626,14 @@ proc semCall(c: var SemContext; it: var Item; flags: set[SemFlag]; source: Trans
while it.n.kind != ParRi:
var arg = Item(n: it.n, typ: c.types.autoType)
argIndexes.add c.dest.len
let named = arg.n.substructureKind == VvU
if named:
cs.hasNamedArgs = true
takeToken c, arg.n
takeTree c, arg.n
semExpr c, arg, {AllowEmpty}
if named:
takeParRi c, arg.n
if arg.typ.typeKind == UntypedT:
skipSemCheck = true
# scope extension: If the type is Typevar and it has attached
Expand Down
118 changes: 110 additions & 8 deletions src/nimony/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import std / [sets, tables, assertions]

import bitabs, nifreader, nifstreams, nifcursors, lineinfos

import nimony_model, decls, programs, semdata, typeprops, xints, builtintypes, renderer, symparser
import nimony_model, decls, programs, semdata, typeprops, xints, builtintypes, renderer, symparser, asthelpers
import ".." / models / tags

type
Expand Down Expand Up @@ -39,6 +39,8 @@ type
CouldNotInferTypeVar
TooManyArguments
TooFewArguments
NameNotFound
ParamAlreadyGiven

MatchError* = object
info: PackedLineInfo
Expand Down Expand Up @@ -135,6 +137,10 @@ proc getErrorMsg*(m: Match): string =
"too many arguments"
of TooFewArguments:
"too few arguments"
of NameNotFound:
"named argument not found"
of ParamAlreadyGiven:
"parameter already given"

proc addErrorMsg*(dest: var string; m: Match) =
assert m.err
Expand Down Expand Up @@ -988,9 +994,13 @@ proc sigmatchLoop(m: var Match; f: var Cursor; args: openArray[Item]) =
if i >= args.len: break
if args[i].n.kind == DotToken:
# default parameter
assert param.val.kind != DotToken
assert not isVarargs
m.args.add dotToken(param.val.info)
if param.val.kind != DotToken:
m.args.add dotToken(param.val.info)
else:
# can end up here after named param ordering which doesn't check if params have default values
# XXX error message should include param name
m.error0 TooFewArguments
break
else:
m.argInfo = args[i].n.info
singleArg m, ftyp, args[i]
Expand Down Expand Up @@ -1161,8 +1171,100 @@ proc cmpMatches*(a, b: Match): DisambiguationResult =
else:
result = NobodyWins

# How to implement named parameters: In a preprocessing step
# The signature is matched against the named parameters. The
# call is then reordered to `f`'s needs. This keeps the common case fast
# where no named parameters are used at all.
type ParamsInfo = object
len: int
names: Table[StrId, int]
isVarargs: seq[bool] # could also use a set or store the decls and check after

proc buildParamsInfo(params: Cursor): ParamsInfo =
result = ParamsInfo(names: initTable[StrId, int](), len: 0)
var f = params
assert f.isParamsTag
inc f # "params"
while f.kind != ParRi:
assert f.symKind == ParamY
var param = takeLocal(f, SkipFinalParRi)
let isVarargs = param.typ.tagEnum == VarargsTagId
result.isVarargs.add isVarargs
let name = getIdent(param.name)
result.names[name] = result.len
inc result.len

proc orderArgs(m: var Match; paramsCursor: Cursor; args: openArray[Item]): seq[Item] =
let params = buildParamsInfo(paramsCursor)
var positions = newSeq[int](params.len)
for i in 0 ..< positions.len: positions[i] = -1
var cont: seq[bool] = @[] # could be a set but uses less memory for most common arg counts
var inVarargs = false
var fi = 0
var ai = 0
while ai < args.len:
# original nim uses this for next positional argument regardless of named arg:
let nextFi = fi + 1
var n = args[ai].n
if n.substructureKind == VvU:
inc n
let name = getIdent(n)
if name in params.names:
fi = params.names[name]
inVarargs = false
else:
swap m.pos, ai
m.error0 NameNotFound
swap m.pos, ai
return
elif fi >= params.len:
swap m.pos, ai
m.error0 TooManyArguments
swap m.pos, ai
return

if inVarargs:
if cont.len == 0:
cont = newSeq[bool](args.len)
assert ai != 0
cont[ai - 1] = true
elif positions[fi] < 0:
positions[fi] = ai
else:
swap m.pos, ai
m.error0 ParamAlreadyGiven
swap m.pos, ai
return

if not params.isVarargs[fi]:
fi = nextFi # will be checked on the next arg if it went over
else:
inVarargs = true
inc ai

result = newSeqOfCap[Item](args.len)
fi = 0
while fi < params.len:
ai = positions[fi]
if ai < 0:
# does not fail early here for missing default value
m.insertedParam = true
result.add Item(n: emptyNode(m.context[]), typ: m.context.types.autoType)
else:
while true:
var arg = args[ai]
# remove name:
if arg.n.substructureKind == VvU:
inc arg.n
skip arg.n
result.add arg
if cont.len != 0 and cont[ai]:
inc ai
assert ai < args.len
else:
break
inc fi

proc sigmatchNamedArgs*(m: var Match; fn: FnCandidate; args: openArray[Item];
explicitTypeVars: Cursor;
hasNamedArgs: bool) =
if hasNamedArgs:
sigmatch m, fn, orderArgs(m, fn.typ, args), explicitTypeVars
else:
sigmatch m, fn, args, explicitTypeVars
24 changes: 24 additions & 0 deletions tests/nimony/overload/tnamedparams.nif
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(.nif24)
0,2,tests/nimony/overload/tnamedparams.nim(stmts
(proc 5 :callme.0.tna1v8dmd . . . 11
(params 1
(param :x.0 . . 6
(i -1) .) 4
(param :y.0 . . 3
(i -1) .) 12
(param :s.0 . . 3 string.0.sysvq0asl 12 "") 28
(param :c.0 . . 3
(c +8) .) 37
(param :b.0 . . 3
(bool) 10
(false))) . . . 67
(stmts
(discard .))) 6,2
(call ~6 callme.0.tna1v8dmd 1 +0 4 +1 7 "abc" 14 '\09' 20
(true)) 6,3
(call ~6 callme.0.tna1v8dmd 8 +0 3 +1 11 "abd" 18 '\09' 52,~3
(false)) 6,4
(call ~6 callme.0.tna1v8dmd 16 +0 11 +1 29,~4 "" 3 '\09' 52,~4
(false)) ,5
(cmd callme.0.tna1v8dmd 7 +0 10 +1 13 "abc" 20 '\09' 58,~5
(false)))
7 changes: 7 additions & 0 deletions tests/nimony/overload/tnamedparams.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# copied from manual:
proc callme(x, y: int, s: string = "", c: char, b: bool = false) = discard

callme(0, 1, "abc", '\t', true)
callme(y=1, x=0, "abd", '\t')
callme(c='\t', y=1, x=0)
callme 0, 1, "abc", '\t'