Skip to content

Commit

Permalink
fix error messages for wrongly typed generic param default values (#2…
Browse files Browse the repository at this point in the history
…4006)

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.
  • Loading branch information
metagn authored Aug 23, 2024
1 parent cb7bcae commit 446501b
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 7 deletions.
16 changes: 13 additions & 3 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions compiler/seminst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
10 changes: 6 additions & 4 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions tests/proc/twrongdefaultvalue.nim
Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit 446501b

Please sign in to comment.