Files
Nim/compiler/seminst.nim
metagn b5f2eafed1 don't match arguments with typeclass type in generics (#24123)
fixes #24121

Proc arguments can have typeclass type like `Foo | Bar` that `sigmatch`
handles specially before matching them to the param type, because they
wouldn't match otherwise. Not exactly sure why this existed but matching
any typeclass or unresolved type in generic contexts now fails the match
so typing the call is delayed until instantiation.

Also it turns out default values with `tyFromExpr` type depending on
other parameters was never tested, this also needs a patch to make the
`tyFromExpr` type `tfNonConstExpr` so it doesn't try to evaluate the
other parameter at compile time.
2024-09-17 06:22:45 +02:00

473 lines
18 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements the instantiation of generic procs.
# included from sem.nim
proc addObjFieldsToLocalScope(c: PContext; n: PNode) =
template rec(n) = addObjFieldsToLocalScope(c, n)
case n.kind
of nkRecList:
for i in 0..<n.len:
rec n[i]
of nkRecCase:
if n.len > 0: rec n[0]
for i in 1..<n.len:
if n[i].kind in {nkOfBranch, nkElse}: rec lastSon(n[i])
of nkSym:
let f = n.sym
if f.kind == skField and fieldVisible(c, f):
c.currentScope.symbols.strTableIncl(f, onConflictKeepOld=true)
incl(f.flags, sfUsed)
# it is not an error to shadow fields via parameters
else: discard
proc pushProcCon*(c: PContext; owner: PSym) =
c.p = PProcCon(owner: owner, next: c.p)
const
errCannotInstantiateX = "cannot instantiate: '$1'"
iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TypeMapping): PSym =
internalAssert c.config, n.kind == nkGenericParams
for a in n.items:
internalAssert c.config, a.kind == nkSym
var q = a.sym
if q.typ.kind in {tyTypeDesc, tyGenericParam, tyStatic, tyConcept}+tyTypeClasses:
let symKind = if q.typ.kind == tyStatic: skConst else: skType
var s = newSym(symKind, q.name, c.idgen, getCurrOwner(c), q.info)
s.flags.incl {sfUsed, sfFromGeneric}
var t = idTableGet(pt, q.typ)
if t == nil:
if tfRetType in q.typ.flags:
# keep the generic type and allow the return type to be bound
# later by semAsgn in return type inference scenario
t = q.typ
else:
if q.typ.kind != tyCompositeTypeClass:
localError(c.config, a.info, errCannotInstantiateX % s.name.s)
t = errorType(c)
elif t.kind in {tyGenericParam, tyConcept, tyFromExpr}:
localError(c.config, a.info, errCannotInstantiateX % q.name.s)
t = errorType(c)
elif isUnresolvedStatic(t) and (q.typ.kind == tyStatic or
(q.typ.kind == tyGenericParam and
q.typ.genericParamHasConstraints and
q.typ.genericConstraint.kind == tyStatic)) and
c.inGenericContext == 0 and c.matchedConcept == nil:
# generic/concept type bodies will try to instantiate static values but
# won't actually use them
localError(c.config, a.info, errCannotInstantiateX % q.name.s)
t = errorType(c)
elif t.kind == tyGenericInvocation:
#t = instGenericContainer(c, a, t)
t = generateTypeInstance(c, pt, a, t)
#t = ReplaceTypeVarsT(cl, t)
s.typ = t
if t.kind == tyStatic: s.ast = t.n
yield s
proc sameInstantiation(a, b: TInstantiation): bool =
if a.concreteTypes.len == b.concreteTypes.len:
for i in 0..a.concreteTypes.high:
if not compareTypes(a.concreteTypes[i], b.concreteTypes[i],
flags = {ExactTypeDescValues,
ExactGcSafety,
PickyCAliases}): return
result = true
else:
result = false
proc genericCacheGet(g: ModuleGraph; genericSym: PSym, entry: TInstantiation;
id: CompilesId): PSym =
result = nil
for inst in procInstCacheItems(g, genericSym):
if (inst.compilesId == 0 or inst.compilesId == id) and sameInstantiation(entry, inst[]):
return inst.sym
when false:
proc `$`(x: PSym): string =
result = x.name.s & " " & " id " & $x.id
proc freshGenSyms(c: PContext; n: PNode, owner, orig: PSym, symMap: var SymMapping) =
# we need to create a fresh set of gensym'ed symbols:
#if n.kind == nkSym and sfGenSym in n.sym.flags:
# if n.sym.owner != orig:
# echo "symbol ", n.sym.name.s, " orig ", orig, " owner ", n.sym.owner
if n.kind == nkSym and sfGenSym in n.sym.flags: # and
# (n.sym.owner == orig or n.sym.owner.kind in {skPackage}):
let s = n.sym
var x = idTableGet(symMap, s)
if x != nil:
n.sym = x
elif s.owner == nil or s.owner.kind == skPackage:
#echo "copied this ", s.name.s
x = copySym(s, c.idgen)
x.owner = owner
idTablePut(symMap, s, x)
n.sym = x
else:
for i in 0..<n.safeLen: freshGenSyms(c, n[i], owner, orig, symMap)
proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind)
proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
if n[bodyPos].kind != nkEmpty:
let procParams = result.typ.n
for i in 1..<procParams.len:
addDecl(c, procParams[i].sym)
maybeAddResult(c, result, result.ast)
inc c.inGenericInst
# add it here, so that recursive generic procs are possible:
var b = n[bodyPos]
var symMap = initSymMapping()
if params != nil:
for i in 1..<params.len:
let param = params[i].sym
if sfGenSym in param.flags:
idTablePut(symMap, params[i].sym, result.typ.n[param.position+1].sym)
freshGenSyms(c, b, result, orig, symMap)
if sfBorrow notin orig.flags:
# We do not want to generate a body for generic borrowed procs.
# As body is a sym to the borrowed proc.
let resultType = # todo probably refactor it into a function
if result.kind == skMacro:
sysTypeFromName(c.graph, n.info, "NimNode")
elif not isInlineIterator(result.typ):
result.typ.returnType
else:
nil
b = semProcBody(c, b, resultType)
result.ast[bodyPos] = hloBody(c, b)
excl(result.flags, sfForward)
trackProc(c, result, result.ast[bodyPos])
dec c.inGenericInst
proc fixupInstantiatedSymbols(c: PContext, s: PSym) =
for i in 0..<c.generics.len:
if c.generics[i].genericSym.id == s.id:
var oldPrc = c.generics[i].inst.sym
pushProcCon(c, oldPrc)
pushOwner(c, oldPrc)
pushInfoContext(c.config, oldPrc.info)
openScope(c)
var n = oldPrc.ast
n[bodyPos] = copyTree(getBody(c.graph, s))
instantiateBody(c, n, oldPrc.typ.n, oldPrc, s)
closeScope(c)
popInfoContext(c.config)
popOwner(c)
popProcCon(c)
proc sideEffectsCheck(c: PContext, s: PSym) =
when false:
if {sfNoSideEffect, sfSideEffect} * s.flags ==
{sfNoSideEffect, sfSideEffect}:
localError(s.info, errXhasSideEffects, s.name.s)
proc instGenericContainer(c: PContext, info: TLineInfo, header: PType,
allowMetaTypes = false): PType =
internalAssert c.config, header.kind == tyGenericInvocation
var cl: TReplTypeVars = TReplTypeVars(symMap: initSymMapping(),
localCache: initTypeMapping(), typeMap: LayeredIdTable(),
info: info, c: c, allowMetaTypes: allowMetaTypes
)
cl.typeMap.topLayer = initTypeMapping()
# We must add all generic params in scope, because the generic body
# may include tyFromExpr nodes depending on these generic params.
# XXX: This looks quite similar to the code in matchUserTypeClass,
# perhaps the code can be extracted in a shared function.
openScope(c)
let genericTyp = header.base
for i, genParam in genericBodyParams(genericTyp):
var param: PSym
template paramSym(kind): untyped =
newSym(kind, genParam.sym.name, c.idgen, genericTyp.sym, genParam.sym.info)
if genParam.kind == tyStatic:
param = paramSym skConst
param.ast = header[i+1].n
param.typ = header[i+1]
else:
param = paramSym skType
param.typ = makeTypeDesc(c, header[i+1])
# this scope was not created by the user,
# unused params shouldn't be reported.
param.flags.incl sfUsed
addDecl(c, param)
result = replaceTypeVarsT(cl, header)
closeScope(c)
proc referencesAnotherParam(n: PNode, p: PSym): bool =
if n.kind == nkSym:
return n.sym.kind == skParam and n.sym.owner == p
else:
for i in 0..<n.safeLen:
if referencesAnotherParam(n[i], p): return true
return false
proc instantiateProcType(c: PContext, pt: TypeMapping,
prc: PSym, info: TLineInfo) =
# XXX: Instantiates a generic proc signature, while at the same
# time adding the instantiated proc params into the current scope.
# This is necessary, because the instantiation process may refer to
# these params in situations like this:
# proc foo[Container](a: Container, b: a.type.Item): typeof(b.x)
#
# Alas, doing this here is probably not enough, because another
# proc signature could appear in the params:
# proc foo[T](a: proc (x: T, b: typeof(x.y))
#
# The solution would be to move this logic into semtypinst, but
# at this point semtypinst have to become part of sem, because it
# will need to use openScope, addDecl, etc.
#addDecl(c, prc)
pushInfoContext(c.config, info)
var typeMap = initLayeredTypeMap(pt)
var cl = initTypeVars(c, typeMap, info, nil)
var result = instCopyType(cl, prc.typ)
let originalParams = result.n
result.n = originalParams.shallowCopy
for i, resulti in paramTypes(result):
# twrong_field_caching requires these 'resetIdTable' calls:
if i > FirstParamAt:
resetIdTable(cl.symMap)
resetIdTable(cl.localCache)
# take a note of the original type. If't a free type or static parameter
# we'll need to keep it unbound for the `fitNode` operation below...
var typeToFit = resulti
let needsStaticSkipping = resulti.kind == tyFromExpr
let needsTypeDescSkipping = resulti.kind == tyTypeDesc and tfUnresolved in resulti.flags
if resulti.kind == tyFromExpr:
resulti.flags.incl tfNonConstExpr
result[i] = replaceTypeVarsT(cl, resulti)
if needsStaticSkipping:
result[i] = result[i].skipTypes({tyStatic})
if needsTypeDescSkipping:
result[i] = result[i].skipTypes({tyTypeDesc})
typeToFit = result[i]
# ...otherwise, we use the instantiated type in `fitNode`
if (typeToFit.kind != tyTypeDesc or typeToFit.base.kind != tyNone) and
(typeToFit.kind != tyStatic):
typeToFit = result[i]
internalAssert c.config, originalParams[i].kind == nkSym
let oldParam = originalParams[i].sym
let param = copySym(oldParam, c.idgen)
param.owner = prc
param.typ = result[i]
# The default value is instantiated and fitted against the final
# concrete param type. We avoid calling `replaceTypeVarsN` on the
# call head symbol, because this leads to infinite recursion.
if oldParam.ast != nil:
var def = oldParam.ast.copyTree
if def.typ.kind == tyFromExpr:
def.typ.flags.incl tfNonConstExpr
if not isIntLit(def.typ):
def = prepareNode(cl, def)
# allow symchoice since node will be fit later
# although expectedType should cover it
def = semExprWithType(c, def, {efAllowSymChoice}, typeToFit)
if def.referencesAnotherParam(getCurrOwner(c)):
def.flags.incl nfDefaultRefsParam
var converted = indexTypesMatch(c, typeToFit, def.typ, def)
if converted == nil:
# The default value doesn't match the final instantiated type.
# As an example of this, see:
# https://github.com/nim-lang/Nim/issues/1201
# We are replacing the default value with an error node in case
# the user calls an explicit instantiation of the proc (this is
# the only way the default value might be inserted).
param.ast = errorNode(c, def)
# we know the node is empty, we need the actual type for error message
param.ast.typ = def.typ
else:
param.ast = fitNodePostMatch(c, typeToFit, converted)
param.typ = result[i]
result.n[i] = newSymNode(param)
propagateToOwner(result, result[i])
addDecl(c, param)
resetIdTable(cl.symMap)
resetIdTable(cl.localCache)
cl.isReturnType = true
result.setReturnType replaceTypeVarsT(cl, result.returnType)
cl.isReturnType = false
result.n[0] = originalParams[0].copyTree
if result[0] != nil:
propagateToOwner(result, result[0])
eraseVoidParams(result)
skipIntLiteralParams(result, c.idgen)
prc.typ = result
popInfoContext(c.config)
proc instantiateOnlyProcType(c: PContext, pt: TypeMapping, prc: PSym, info: TLineInfo): PType =
# instantiates only the type of a given proc symbol
# used by sigmatch for explicit generics
# wouldn't be needed if sigmatch could handle complex cases,
# examples are in texplicitgenerics
# might be buggy, see rest of generateInstance if problems occur
let fakeSym = copySym(prc, c.idgen)
incl(fakeSym.flags, sfFromGeneric)
fakeSym.instantiatedFrom = prc
openScope(c)
for s in instantiateGenericParamList(c, prc.ast[genericParamsPos], pt):
addDecl(c, s)
instantiateProcType(c, pt, fakeSym, info)
closeScope(c)
result = fakeSym.typ
proc fillMixinScope(c: PContext) =
var p = c.p
while p != nil:
for bnd in p.localBindStmts:
for n in bnd:
addSym(c.currentScope, n.sym)
p = p.next
proc getLocalPassC(c: PContext, s: PSym): string =
when defined(nimsuggest): return ""
if s.ast == nil or s.ast.len == 0: return ""
result = ""
template extractPassc(p: PNode) =
if p.kind == nkPragma and p[0][0].ident == c.cache.getIdent"localpassc":
return p[0][1].strVal
extractPassc(s.ast[0]) #it is set via appendToModule in pragmas (fast access)
for n in s.ast:
for p in n:
extractPassc(p)
proc generateInstance(c: PContext, fn: PSym, pt: TypeMapping,
info: TLineInfo): PSym =
## Generates a new instance of a generic procedure.
## The `pt` parameter is a type-unsafe mapping table used to link generic
## parameters to their concrete types within the generic instance.
# no need to instantiate generic templates/macros:
internalAssert c.config, fn.kind notin {skMacro, skTemplate}
# generates an instantiated proc
if c.instCounter > 50:
globalError(c.config, info, "generic instantiation too nested")
inc c.instCounter
defer: dec c.instCounter
# careful! we copy the whole AST including the possibly nil body!
var n = copyTree(fn.ast)
# NOTE: for access of private fields within generics from a different module
# we set the friend module:
let producer = getModule(fn)
c.friendModules.add(producer)
let oldMatchedConcept = c.matchedConcept
c.matchedConcept = nil
let oldScope = c.currentScope
while not isTopLevel(c): c.currentScope = c.currentScope.parent
result = copySym(fn, c.idgen)
incl(result.flags, sfFromGeneric)
result.instantiatedFrom = fn
if sfGlobal in result.flags and c.config.symbolFiles != disabledSf:
let passc = getLocalPassC(c, producer)
if passc != "": #pass the local compiler options to the consumer module too
extccomp.addLocalCompileOption(c.config, passc, toFullPathConsiderDirty(c.config, c.module.info.fileIndex))
result.owner = c.module
else:
result.owner = fn
result.ast = n
pushOwner(c, result)
# mixin scope:
openScope(c)
fillMixinScope(c)
openScope(c)
let gp = n[genericParamsPos]
if gp.kind != nkGenericParams:
# bug #22137
globalError(c.config, info, "generic instantiation too nested")
n[namePos] = newSymNode(result)
pushInfoContext(c.config, info, fn.detailedInfo)
var entry = TInstantiation.new
entry.sym = result
# we need to compare both the generic types and the concrete types:
# generic[void](), generic[int]()
# see ttypeor.nim test.
var i = 0
newSeq(entry.concreteTypes, fn.typ.paramsLen+gp.len)
# let param instantiation know we are in a concept for unresolved statics:
c.matchedConcept = oldMatchedConcept
for s in instantiateGenericParamList(c, gp, pt):
addDecl(c, s)
entry.concreteTypes[i] = s.typ
inc i
c.matchedConcept = nil
pushProcCon(c, result)
instantiateProcType(c, pt, result, info)
for _, param in paramTypes(result.typ):
entry.concreteTypes[i] = param
inc i
#echo "INSTAN ", fn.name.s, " ", typeToString(result.typ), " ", entry.concreteTypes.len
if tfTriggersCompileTime in result.typ.flags:
incl(result.flags, sfCompileTime)
n[genericParamsPos] = c.graph.emptyNode
var oldPrc = genericCacheGet(c.graph, fn, entry[], c.compilesContextId)
if oldPrc == nil:
# we MUST not add potentially wrong instantiations to the caching mechanism.
# This means recursive instantiations behave differently when in
# a ``compiles`` context but this is the lesser evil. See
# bug #1055 (tevilcompiles).
#if c.compilesContextId == 0:
entry.compilesId = c.compilesContextId
addToGenericProcCache(c, fn, entry)
c.generics.add(makeInstPair(fn, entry))
# bug #12985 bug #22913
# TODO: use the context of the declaration of generic functions instead
# TODO: consider fixing options as well
let otherPragmas = c.optionStack[^1].otherPragmas
c.optionStack[^1].otherPragmas = nil
if n[pragmasPos].kind != nkEmpty:
pragma(c, result, n[pragmasPos], allRoutinePragmas)
if isNil(n[bodyPos]):
n[bodyPos] = copyTree(getBody(c.graph, fn))
instantiateBody(c, n, fn.typ.n, result, fn)
c.optionStack[^1].otherPragmas = otherPragmas
sideEffectsCheck(c, result)
if result.magic notin {mSlice, mTypeOf}:
# 'toOpenArray' is special and it is allowed to return 'openArray':
paramsTypeCheck(c, result.typ)
#echo "INSTAN ", fn.name.s, " ", typeToString(result.typ), " <-- NEW PROC!", " ", entry.concreteTypes.len
else:
#echo "INSTAN ", fn.name.s, " ", typeToString(result.typ), " <-- CACHED! ", typeToString(oldPrc.typ), " ", entry.concreteTypes.len
result = oldPrc
popProcCon(c)
popInfoContext(c.config)
closeScope(c) # close scope for parameters
closeScope(c) # close scope for 'mixin' declarations
popOwner(c)
c.currentScope = oldScope
discard c.friendModules.pop()
c.matchedConcept = oldMatchedConcept
if result.kind == skMethod: finishMethod(c, result)
# inform IC of the generic
#addGeneric(c.ic, result, entry.concreteTypes)