fix error messages for wrongly typed generic param default values (#24006)

fixes #21258

When a generic proc is instantiated, if one of the default values
doesn't match the type of the parameter, `seminst` sets the default
parameter node to an `nkEmpty` node with `tyError` type. `sigmatch`
checks for this to give an error message if the default param is
actually used, but only while actively matching the proc signature,
before the proc is even instantiated. The error message also gives very
little information.

Now, we check for this in `updateDefaultParams` at the end of
`semResolvedCall`, after the proc has been instantiated. The `nkEmpty`
node also is given the original mismatching type instead rather than
`tyError`, only setting `tyError` after erroring to prevent cascading
errors. The error message is changed to the standard type mismatch error
also giving the instantiation info of the routine.
This commit is contained in:
metagn
2024-08-23 19:01:43 +03:00
committed by GitHub
parent cb7bcae7f7
commit 446501b53b
4 changed files with 50 additions and 7 deletions

View File

@@ -595,7 +595,7 @@ proc inferWithMetatype(c: PContext, formal: PType,
result = copyTree(arg)
result.typ = formal
proc updateDefaultParams(call: PNode) =
proc updateDefaultParams(c: PContext, call: PNode) =
# In generic procs, the default parameter may be unique for each
# instantiation (see tlateboundgenericparams).
# After a call is resolved, we need to re-assign any default value
@@ -605,8 +605,18 @@ proc updateDefaultParams(call: PNode) =
let calleeParams = call[0].sym.typ.n
for i in 1..<call.len:
if nfDefaultParam in call[i].flags:
let def = calleeParams[i].sym.ast
let formal = calleeParams[i].sym
let def = formal.ast
if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam
# mirrored with sigmatch:
if def.kind == nkEmpty:
# The default param value is set to empty in `instantiateProcType`
# when the type of the default expression doesn't match the type
# of the instantiated proc param:
pushInfoContext(c.config, call.info, call[0].sym.detailedInfo)
typeMismatch(c.config, def.info, formal.typ, def.typ, formal.ast)
popInfoContext(c.config)
def.typ = errorType(c)
call[i] = def
proc getCallLineInfo(n: PNode): TLineInfo =
@@ -727,7 +737,7 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
if finalCallee.magic notin {mArrGet, mArrPut}:
result.typ = finalCallee.typ.returnType
updateDefaultParams(result)
updateDefaultParams(c, result)
proc canDeref(n: PNode): bool {.inline.} =
result = n.len >= 2 and (let t = n[1].typ;

View File

@@ -253,9 +253,13 @@ proc instantiateProcType(c: PContext, pt: TypeMapping,
var typeToFit = resulti
let needsStaticSkipping = resulti.kind == tyFromExpr
let needsTypeDescSkipping = resulti.kind == tyTypeDesc and tfUnresolved in resulti.flags
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
@@ -292,6 +296,8 @@ proc instantiateProcType(c: PContext, pt: TypeMapping,
# 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]

View File

@@ -2790,14 +2790,16 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
m.firstMismatch.formal = formal
break
else:
# mirrored with updateDefaultParams:
if formal.ast.kind == nkEmpty:
# The default param value is set to empty in `instantiateProcType`
# when the type of the default expression doesn't match the type
# of the instantiated proc param:
localError(c.config, m.call.info,
("The default parameter '$1' has incompatible type " &
"with the explicitly requested proc instantiation") %
formal.name.s)
pushInfoContext(c.config, m.call.info,
if m.calleeSym != nil: m.calleeSym.detailedInfo else: "")
typeMismatch(c.config, formal.ast.info, formal.typ, formal.ast.typ, formal.ast)
popInfoContext(c.config)
formal.ast.typ = errorType(c)
if nfDefaultRefsParam in formal.ast.flags:
m.call.flags.incl nfDefaultRefsParam
var defaultValue = copyTree(formal.ast)

View File

@@ -0,0 +1,25 @@
discard """
cmd: "nim check $file"
action: reject
nimout: '''
twrongdefaultvalue.nim(20, 12) template/generic instantiation of `doit` from here
twrongdefaultvalue.nim(17, 37) Error: type mismatch: got <proc (p: int): Item[initItem.T]> but expected 'Item[system.string]'
twrongdefaultvalue.nim(25, 3) template/generic instantiation of `foo` from here
twrongdefaultvalue.nim(23, 33) Error: type mismatch: got <string> but expected 'int'
'''
"""
block: # issue #21258
type Item[T] = object
pos: int
proc initItem[T](p:int=10000) : Item[T] =
result = Item[T](p)
proc doit[T](x:Item[T], s:Item[T]=initItem) : string =
return $x.pos
let x = Item[string](pos:100)
echo doit(x)
block: # issue #21258, reduced case
proc foo[T](x: seq[T], y: T = "foo") =
discard
foo @[1, 2, 3]