Skip to content

Commit 71de7fc

Browse files
authored
handle explicit generic routine instantiations in sigmatch (#24010)
fixes #16376 The way the compiler handled generic proc instantiations in calls (like `foo[int](...)`) up to this point was to instantiate `foo[int]`, create a symbol for the instantiated proc (or a symchoice for multiple procs excluding ones with mismatching generic param counts), then perform overload resolution on this symbol/symchoice. The exception to this was when the called symbol was already a symchoice node, in which case it wasn't instantiated and overloading was called directly ([these lines](https://github.com/nim-lang/Nim/blob/b7b1313d21deb687adab2b4a162e716ba561a26b/compiler/semexprs.nim#L3366-L3371)). This has several problems: * Templates and macros can't create instantiated symbols, so they couldn't participate in overloaded explicit generic instantiations, causing the issue #16376. * Every single proc that can be instantiated with the given generic params is fully instantiated including the body. #9997 is about this but isn't fixed here since the instantiation isn't in a call. The way overload resolution handles explicit instantiations by itself is also buggy: * It doesn't check constraints. * It allows only partially providing the generic parameters, which makes sense for implicit generics, but can cause ambiguity in overloading. Here is how this PR deals with these problems: * Overload resolution now always handles explicit generic instantiations in calls, in `initCandidate`, as long as the symbol resolves to a routine symbol. * Overload resolution now checks the generic params for constraints and correct parameter count (ignoring implicit params). If these don't match, the entire overload is considered as not matching and not instantiated. * Special error messages are added for mismatching/missing/extra generic params. This is almost all of the diff in `semcall`. * Procs with matching generic parameters now instantiate only the type of the signature in overload resolution, not the proc itself, which also works for templates and macros. Unfortunately we can't entirely remove instantiations because overload resolution can't handle some cases with uninstantiated types even though it's resolved in the binding (see the last 2 blocks in `texplicitgenerics`). There are also some instantiation issues with default params that #24005 didn't fix but I didn't want this to become the 3rd huge generics PR in a row so I didn't dive too deep into trying to fix them. There is still a minor instantiation fix in `semtypinst` though for subscripts in calls. Additional changes: * Overloading of `[]` wasn't documented properly, it somewhat is now because we need to mention the limitation that it can't be done for generic procs/types. * Tests can now enable the new type mismatch errors with just `-d:testsConciseTypeMismatch` in the command. Package PRs: - using fork for now: [combparser](PMunch/combparser#7) (partial generic instantiation) - merged: [cligen](c-blake/cligen#233) (partial generic instantiation but non-overloaded + template) - merged: [neo](andreaferretti/neo#56) (trying to instantiate template with no generic param)
1 parent c8af099 commit 71de7fc

22 files changed

+436
-118
lines changed

compiler/ast.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,7 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
15111511

15121512
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} = dest.sons = sons
15131513
proc setSon*(dest: PType; son: sink PType) {.inline.} = dest.sons = @[son]
1514+
proc setSonsLen*(dest: PType; len: int) {.inline.} = setLen(dest.sons, len)
15141515

15151516
proc mergeLoc(a: var TLoc, b: TLoc) =
15161517
if a.k == low(typeof(a.k)): a.k = b.k

compiler/sem.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ proc preparePContext*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PCo
728728
result.semOverloadedCall = semOverloadedCall
729729
result.semInferredLambda = semInferredLambda
730730
result.semGenerateInstance = generateInstance
731+
result.instantiateOnlyProcType = instantiateOnlyProcType
731732
result.semTypeNode = semTypeNode
732733
result.instTypeBoundOp = sigmatch.instTypeBoundOp
733734
result.hasUnresolvedArgs = hasUnresolvedArgs

compiler/semcall.nim

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
249249
let nArg = if err.firstMismatch.arg < n.len: n[err.firstMismatch.arg] else: nil
250250
let nameParam = if err.firstMismatch.formal != nil: err.firstMismatch.formal.name.s else: ""
251251
if n.len > 1:
252+
const genericParamMismatches = {kGenericParamTypeMismatch, kExtraGenericParam, kMissingGenericParam}
252253
if verboseTypeMismatch notin c.config.legacyFeatures:
253254
case err.firstMismatch.kind
254255
of kUnknownNamedParam:
@@ -269,6 +270,12 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
269270
of kMissingParam:
270271
candidates.add(" missing parameter: " & nameParam)
271272
candidates.add "\n"
273+
of kExtraGenericParam:
274+
candidates.add(" extra generic param given")
275+
candidates.add "\n"
276+
of kMissingGenericParam:
277+
candidates.add(" missing generic parameter: " & nameParam)
278+
candidates.add "\n"
272279
of kVarNeeded:
273280
doAssert nArg != nil
274281
doAssert err.firstMismatch.formal != nil
@@ -292,9 +299,36 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
292299
candidates.addPragmaAndCallConvMismatch(wanted, got, c.config)
293300
effectProblem(wanted, got, candidates, c)
294301
candidates.add "\n"
302+
of kGenericParamTypeMismatch:
303+
let pos = err.firstMismatch.arg
304+
doAssert n[0].kind == nkBracketExpr and pos < n[0].len
305+
let arg = n[0][pos]
306+
doAssert arg != nil
307+
var wanted = err.firstMismatch.formal.typ
308+
if wanted.kind == tyGenericParam and wanted.genericParamHasConstraints:
309+
wanted = wanted.genericConstraint
310+
let got = arg.typ
311+
doAssert err.firstMismatch.formal != nil
312+
doAssert wanted != nil
313+
doAssert got != nil
314+
candidates.add " generic parameter mismatch, expected "
315+
candidates.addTypeDeclVerboseMaybe(c.config, wanted)
316+
candidates.add " but got '"
317+
candidates.add renderTree(arg)
318+
candidates.add "' of type: "
319+
candidates.addTypeDeclVerboseMaybe(c.config, got)
320+
if got != nil and got.kind == tyProc and wanted.kind == tyProc:
321+
# These are proc mismatches so,
322+
# add the extra explict detail of the mismatch
323+
candidates.addPragmaAndCallConvMismatch(wanted, got, c.config)
324+
if got != nil:
325+
effectProblem(wanted, got, candidates, c)
326+
candidates.add "\n"
295327
of kUnknown: discard "do not break 'nim check'"
296328
else:
297329
candidates.add(" first type mismatch at position: " & $err.firstMismatch.arg)
330+
if err.firstMismatch.kind in genericParamMismatches:
331+
candidates.add(" in generic parameters")
298332
# candidates.add "\n reason: " & $err.firstMismatch.kind # for debugging
299333
case err.firstMismatch.kind
300334
of kUnknownNamedParam:
@@ -306,20 +340,35 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
306340
of kPositionalAlreadyGiven: candidates.add("\n positional param was already given as named param")
307341
of kExtraArg: candidates.add("\n extra argument given")
308342
of kMissingParam: candidates.add("\n missing parameter: " & nameParam)
309-
of kTypeMismatch, kVarNeeded:
310-
doAssert nArg != nil
311-
let wanted = err.firstMismatch.formal.typ
343+
of kExtraGenericParam:
344+
candidates.add("\n extra generic param given")
345+
of kMissingGenericParam:
346+
candidates.add("\n missing generic parameter: " & nameParam)
347+
of kTypeMismatch, kGenericParamTypeMismatch, kVarNeeded:
348+
var arg: PNode = nArg
349+
let genericMismatch = err.firstMismatch.kind == kGenericParamTypeMismatch
350+
if genericMismatch:
351+
let pos = err.firstMismatch.arg
352+
doAssert n[0].kind == nkBracketExpr and pos < n[0].len
353+
arg = n[0][pos]
354+
else:
355+
arg = nArg
356+
doAssert arg != nil
357+
var wanted = err.firstMismatch.formal.typ
358+
if genericMismatch and wanted.kind == tyGenericParam and
359+
wanted.genericParamHasConstraints:
360+
wanted = wanted.genericConstraint
312361
doAssert err.firstMismatch.formal != nil
313362
candidates.add("\n required type for " & nameParam & ": ")
314363
candidates.addTypeDeclVerboseMaybe(c.config, wanted)
315364
candidates.add "\n but expression '"
316365
if err.firstMismatch.kind == kVarNeeded:
317-
candidates.add renderNotLValue(nArg)
366+
candidates.add renderNotLValue(arg)
318367
candidates.add "' is immutable, not 'var'"
319368
else:
320-
candidates.add renderTree(nArg)
369+
candidates.add renderTree(arg)
321370
candidates.add "' is of type: "
322-
let got = nArg.typ
371+
let got = arg.typ
323372
candidates.addTypeDeclVerboseMaybe(c.config, got)
324373
doAssert wanted != nil
325374
if got != nil:
@@ -331,8 +380,8 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
331380

332381
of kUnknown: discard "do not break 'nim check'"
333382
candidates.add "\n"
334-
if err.firstMismatch.arg == 1 and nArg.kind == nkTupleConstr and
335-
n.kind == nkCommand:
383+
if err.firstMismatch.arg == 1 and nArg != nil and
384+
nArg.kind == nkTupleConstr and n.kind == nkCommand:
336385
maybeWrongSpace = true
337386
for diag in err.diagnostics:
338387
candidates.add(diag & "\n")
@@ -780,21 +829,16 @@ proc explicitGenericInstError(c: PContext; n: PNode): PNode =
780829
result = n
781830

782831
proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
832+
if s.kind in {skTemplate, skMacro}:
833+
internalError c.config, n.info, "cannot get explicitly instantiated symbol of " &
834+
(if s.kind == skTemplate: "template" else: "macro")
783835
# binding has to stay 'nil' for this to work!
784836
var m = newCandidate(c, s, nil)
785-
786-
for i in 1..<n.len:
787-
let formal = s.ast[genericParamsPos][i-1].typ
788-
var arg = n[i].typ
789-
# try transforming the argument into a static one before feeding it into
790-
# typeRel
791-
if formal.kind == tyStatic and arg.kind != tyStatic:
792-
let evaluated = c.semTryConstExpr(c, n[i], n[i].typ)
793-
if evaluated != nil:
794-
arg = newTypeS(tyStatic, c, son = evaluated.typ)
795-
arg.n = evaluated
796-
let tm = typeRel(m, formal, arg)
797-
if tm in {isNone, isConvertible}: return nil
837+
matchGenericParams(m, n, s)
838+
if m.state != csMatch:
839+
# state is csMatch only if *all* generic params were matched,
840+
# including implicit parameters
841+
return nil
798842
var newInst = generateInstance(c, s, m.bindings, n.info)
799843
newInst.typ.flags.excl tfUnresolved
800844
let info = getCallLineInfo(n)

compiler/semdata.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ type
139139
semInferredLambda*: proc(c: PContext, pt: Table[ItemId, PType], n: PNode): PNode
140140
semGenerateInstance*: proc (c: PContext, fn: PSym, pt: Table[ItemId, PType],
141141
info: TLineInfo): PSym
142+
instantiateOnlyProcType*: proc (c: PContext, pt: TypeMapping,
143+
prc: PSym, info: TLineInfo): PType
144+
# used by sigmatch for explicit generic instantiations
142145
includedFiles*: IntSet # used to detect recursive include files
143146
pureEnumFields*: TStrTable # pure enum fields that can be used unambiguously
144147
userPragmas*: TStrTable

compiler/semexprs.nim

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,14 +1070,6 @@ proc resolveIndirectCall(c: PContext; n, nOrig: PNode;
10701070
result = initCandidate(c, t)
10711071
matches(c, n, nOrig, result)
10721072

1073-
proc bracketedMacro(n: PNode): PSym =
1074-
if n.len >= 1 and n[0].kind == nkSym:
1075-
result = n[0].sym
1076-
if result.kind notin {skMacro, skTemplate}:
1077-
result = nil
1078-
else:
1079-
result = nil
1080-
10811073
proc finishOperand(c: PContext, a: PNode): PNode =
10821074
if a.typ.isNil:
10831075
result = c.semOperand(c, a, {efDetermineType})
@@ -1167,11 +1159,6 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
11671159
let t = n[0].typ
11681160
if t != nil and t.kind in {tyVar, tyLent}:
11691161
n[0] = newDeref(n[0])
1170-
elif n[0].kind == nkBracketExpr:
1171-
let s = bracketedMacro(n[0])
1172-
if s != nil:
1173-
setGenericParams(c, n[0], s.ast[genericParamsPos])
1174-
return semDirectOp(c, n, flags, expectedType)
11751162
elif isSymChoice(n[0]) and nfDotField notin n.flags:
11761163
# overloaded generic procs e.g. newSeq[int] can end up here
11771164
return semDirectOp(c, n, flags, expectedType)
@@ -1721,8 +1708,6 @@ proc maybeInstantiateGeneric(c: PContext, n: PNode, s: PSym): PNode =
17211708
result = explicitGenericInstantiation(c, n, s)
17221709
if result == n:
17231710
n[0] = copyTree(result[0])
1724-
else:
1725-
n[0] = result
17261711

17271712
proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
17281713
## returns nil if not a built-in subscript operator; also called for the
@@ -3013,19 +2998,42 @@ proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp
30132998
else:
30142999
result = tupexp
30153000

3016-
proc shouldBeBracketExpr(n: PNode): bool =
3017-
result = false
3001+
proc isExplicitGenericCall(c: PContext, n: PNode): bool =
3002+
## checks if a call node `n` is a routine call with explicit generic params
3003+
##
3004+
## the callee node needs to be either an nkBracketExpr or a call to a
3005+
## symchoice of `[]` in which case it will be transformed into nkBracketExpr
3006+
##
3007+
## the LHS of the bracket expr has to either be a symchoice or resolve to
3008+
## a routine symbol
3009+
template checkCallee(n: PNode) =
3010+
# check subscript LHS, `n` must be mutable
3011+
if isSymChoice(n):
3012+
result = true
3013+
else:
3014+
let s = qualifiedLookUp(c, n, {})
3015+
if s != nil and s.kind in routineKinds:
3016+
result = true
3017+
n = semSymGenericInstantiation(c, n, s)
30183018
assert n.kind in nkCallKinds
3019+
result = false
30193020
let a = n[0]
3020-
if a.kind in nkCallKinds:
3021+
case a.kind
3022+
of nkBracketExpr:
3023+
checkCallee(a[0])
3024+
of nkCallKinds:
30213025
let b = a[0]
30223026
if b.kind in nkSymChoices:
3023-
for i in 0..<b.len:
3024-
if b[i].kind == nkSym and b[i].sym.magic == mArrGet:
3025-
let be = newNodeI(nkBracketExpr, n.info)
3027+
let name = b.getPIdent
3028+
if name != nil and name.s == "[]":
3029+
checkCallee(a[1])
3030+
if result:
3031+
# transform callee into normal bracket expr, only on success
3032+
let be = newNodeI(nkBracketExpr, a.info)
30263033
for i in 1..<a.len: be.add(a[i])
30273034
n[0] = be
3028-
return true
3035+
else:
3036+
result = false
30293037

30303038
proc asBracketExpr(c: PContext; n: PNode): PNode =
30313039
proc isGeneric(c: PContext; n: PNode): bool =
@@ -3365,11 +3373,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
33653373
else:
33663374
#liMessage(n.info, warnUser, renderTree(n));
33673375
result = semIndirectOp(c, n, flags, expectedType)
3368-
elif (n[0].kind == nkBracketExpr or shouldBeBracketExpr(n)) and
3369-
isSymChoice(n[0][0]):
3370-
# indirectOp can deal with explicit instantiations; the fixes
3371-
# the 'newSeq[T](x)' bug
3372-
setGenericParams(c, n[0], nil)
3376+
elif isExplicitGenericCall(c, n): # this modifies `n` if true
33733377
result = semDirectOp(c, n, flags, expectedType)
33743378
elif nfDotField in n.flags:
33753379
result = semDirectOp(c, n, flags, expectedType)

compiler/seminst.nim

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,22 @@ proc instantiateProcType(c: PContext, pt: TypeMapping,
321321
prc.typ = result
322322
popInfoContext(c.config)
323323

324+
proc instantiateOnlyProcType(c: PContext, pt: TypeMapping, prc: PSym, info: TLineInfo): PType =
325+
# instantiates only the type of a given proc symbol
326+
# used by sigmatch for explicit generics
327+
# wouldn't be needed if sigmatch could handle complex cases,
328+
# examples are in texplicitgenerics
329+
# might be buggy, see rest of generateInstance if problems occur
330+
let fakeSym = copySym(prc, c.idgen)
331+
incl(fakeSym.flags, sfFromGeneric)
332+
fakeSym.instantiatedFrom = prc
333+
openScope(c)
334+
for s in instantiateGenericParamList(c, prc.ast[genericParamsPos], pt):
335+
addDecl(c, s)
336+
instantiateProcType(c, pt, fakeSym, info)
337+
closeScope(c)
338+
result = fakeSym.typ
339+
324340
proc fillMixinScope(c: PContext) =
325341
var p = c.p
326342
while p != nil:

compiler/semtypinst.nim

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,14 @@ proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode =
147147
# exception exists for the call name being a dot expression since
148148
# dot expressions need their LHS instantiated
149149
assert n.len != 0
150-
let ignoreFirst = n[0].kind != nkDotExpr
150+
# avoid instantiating generic proc symbols, refine condition if needed:
151+
let ignoreFirst = n[0].kind notin {nkDotExpr, nkBracketExpr} + nkCallKinds
151152
let name = n[0].getPIdent
152153
let ignoreSecond = name != nil and name.s == "[]" and n.len > 1 and
153-
(n[1].typ != nil and n[1].typ.kind == tyTypeDesc)
154+
# generic type instantiation:
155+
((n[1].typ != nil and n[1].typ.kind == tyTypeDesc) or
156+
# generic proc instantiation:
157+
(n[1].kind == nkSym and n[1].sym.isGenericRoutineStrict))
154158
if ignoreFirst:
155159
result.add(n[0])
156160
else:
@@ -168,7 +172,10 @@ proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode =
168172
# dot expressions need their LHS instantiated
169173
assert n.len != 0
170174
let ignoreFirst = n[0].kind != nkDotExpr and
171-
n[0].typ != nil and n[0].typ.kind == tyTypeDesc
175+
# generic type instantiation:
176+
((n[0].typ != nil and n[0].typ.kind == tyTypeDesc) or
177+
# generic proc instantiation:
178+
(n[0].kind == nkSym and n[0].sym.isGenericRoutineStrict))
172179
if ignoreFirst:
173180
result.add(n[0])
174181
else:

0 commit comments

Comments
 (0)