better procvar ambiguity errors, clean up after #20457 (#20932)

* better procvar ambiguity errors, clean up after #20457

fixes #6359, fixes #13849

* only trigger on closedsymchoice again

* new approach

* add manual entry for ambiguous enums too

* add indent [skip ci]

* move to proc
This commit is contained in:
metagn 2022-12-01 10:01:13 +03:00 • committed by GitHub
commit 2449c37137
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 36 deletions

View file

@ -70,10 +70,11 @@ type
efWantStmt, efAllowStmt, efDetermineType, efExplain, efWantStmt, efAllowStmt, efDetermineType, efExplain,
efWantValue, efOperand, efNoSemCheck, efWantValue, efOperand, efNoSemCheck,
efNoEvaluateGeneric, efInCall, efFromHlo, efNoSem2Check, efNoEvaluateGeneric, efInCall, efFromHlo, efNoSem2Check,
efNoUndeclared, efIsDotCall, efCannotBeDotCall efNoUndeclared, efIsDotCall, efCannotBeDotCall,
# Use this if undeclared identifiers should not raise an error during # Use this if undeclared identifiers should not raise an error during
# overload resolution. # overload resolution.
efNoDiagnostics efNoDiagnostics,
efTypeAllowed # typeAllowed will be called after
TExprFlags* = set[TExprFlag] TExprFlags* = set[TExprFlag]

View file

@ -83,18 +83,39 @@ proc semExprCheck(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType
# do not produce another redundant error message: # do not produce another redundant error message:
result = errorNode(c, n) result = errorNode(c, n)
proc ambiguousSymChoice(c: PContext, orig, n: PNode): PNode =
let first = n[0].sym
if first.kind == skEnumField:
# choose the first resolved enum field, i.e. the latest in scope
# to mirror behavior before overloadable enums
if hintAmbiguousEnum in c.config.notes:
var err = "ambiguous enum field '" & first.name.s &
"' assumed to be of type " & typeToString(first.typ) &
" -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s & "\n"
message(c.config, orig.info, hintAmbiguousEnum, err)
result = n[0]
else:
var err = "ambiguous identifier '" & first.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, orig.info, err)
n.typ = errorType(c)
result = n
proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode = proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = semExprCheck(c, n, flags, expectedType) result = semExprCheck(c, n, flags, expectedType)
if result.typ == nil and efInTypeof in flags: if result.typ == nil and efInTypeof in flags:
result.typ = c.voidType result.typ = c.voidType
elif (result.typ == nil or result.typ.kind == tyNone) and elif (result.typ == nil or result.typ.kind == tyNone) and
result.kind == nkClosedSymChoice and efTypeAllowed in flags and
result[0].sym.kind == skEnumField: result.kind == nkClosedSymChoice and result.len > 0:
# if overloaded enum field could not choose a type from a closed list, result = ambiguousSymChoice(c, n, result)
# choose the first resolved enum field, i.e. the latest in scope
# to mirror old behavior
msgSymChoiceUseQualifier(c, result, hintAmbiguousEnum)
result = result[0]
elif result.typ == nil or result.typ == c.enforceVoidContext: elif result.typ == nil or result.typ == c.enforceVoidContext:
localError(c.config, n.info, errExprXHasNoType % localError(c.config, n.info, errExprXHasNoType %
renderTree(result, {renderNoComments})) renderTree(result, {renderNoComments}))
@ -634,7 +655,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp
lastValidIndex = lastOrd(c.config, indexType) lastValidIndex = lastOrd(c.config, indexType)
x = x[1] x = x[1]
let yy = semExprWithType(c, x, expectedType = expectedElementType) let yy = semExprWithType(c, x, {efTypeAllowed}, expectedElementType)
var typ = yy.typ var typ = yy.typ
if expectedElementType == nil: if expectedElementType == nil:
expectedElementType = typ expectedElementType = typ
@ -655,7 +676,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp
localError(c.config, x.info, "invalid order in array constructor") localError(c.config, x.info, "invalid order in array constructor")
x = x[1] x = x[1]
let xx = semExprWithType(c, x, {}, expectedElementType) let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType)
result.add xx result.add xx
typ = commonType(c, typ, xx.typ) typ = commonType(c, typ, xx.typ)
#n[i] = semExprWithType(c, x, {}) #n[i] = semExprWithType(c, x, {})
@ -1806,7 +1827,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
renderTree(a, {renderNoComments})) renderTree(a, {renderNoComments}))
else: else:
let lhs = n[0] let lhs = n[0]
let rhs = semExprWithType(c, n[1], {}, le) let rhs = semExprWithType(c, n[1], {efTypeAllowed}, le)
if lhs.kind == nkSym and lhs.sym.kind == skResult: if lhs.kind == nkSym and lhs.sym.kind == skResult:
n.typ = c.enforceVoidContext n.typ = c.enforceVoidContext
if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ): if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ):
@ -2502,8 +2523,8 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode =
for i in 0..<n.len: for i in 0..<n.len:
if isRange(n[i]): if isRange(n[i]):
checkSonsLen(n[i], 3, c.config) checkSonsLen(n[i], 3, c.config)
n[i][1] = semExprWithType(c, n[i][1], {}, expectedElementType) n[i][1] = semExprWithType(c, n[i][1], {efTypeAllowed}, expectedElementType)
n[i][2] = semExprWithType(c, n[i][2], {}, expectedElementType) n[i][2] = semExprWithType(c, n[i][2], {efTypeAllowed}, expectedElementType)
if typ == nil: if typ == nil:
typ = skipTypes(n[i][1].typ, typ = skipTypes(n[i][1].typ,
{tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
@ -2518,7 +2539,7 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode =
if expectedElementType == nil: if expectedElementType == nil:
expectedElementType = typ expectedElementType = typ
else: else:
n[i] = semExprWithType(c, n[i], {}, expectedElementType) n[i] = semExprWithType(c, n[i], {efTypeAllowed}, expectedElementType)
if typ == nil: if typ == nil:
typ = skipTypes(n[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) typ = skipTypes(n[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
if expectedElementType == nil: if expectedElementType == nil:

View file

@ -585,22 +585,6 @@ proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode =
pragma(c, defs[lhsPos][namePos].sym, defs[lhsPos][pragmaPos], validPragmas) pragma(c, defs[lhsPos][namePos].sym, defs[lhsPos][pragmaPos], validPragmas)
return result return result
proc msgSymChoiceUseQualifier(c: PContext; n: PNode; note = errGenerated) =
assert n.kind in nkSymChoices
var err =
if note == hintAmbiguousEnum:
"ambiguous enum field '$1' assumed to be of type $2, this will become an error in the future" % [$n[0], typeToString(n[0].typ)]
else:
"ambiguous identifier: '" & $n[0] & "'"
var i = 0
for child in n:
let candidate = child.sym
if i == 0: err.add " -- use one of the following:\n"
else: err.add "\n"
err.add " " & candidate.owner.name.s & "." & candidate.name.s
inc i
message(c.config, n.info, note, err)
template isLocalVarSym(n: PNode): bool = template isLocalVarSym(n: PNode): bool =
n.kind == nkSym and n.kind == nkSym and
(n.sym.kind in {skVar, skLet} and not (n.sym.kind in {skVar, skLet} and not
@ -645,11 +629,9 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
var def: PNode = c.graph.emptyNode var def: PNode = c.graph.emptyNode
if a[^1].kind != nkEmpty: if a[^1].kind != nkEmpty:
def = semExprWithType(c, a[^1], {}, typ) def = semExprWithType(c, a[^1], {efTypeAllowed}, typ)
if def.kind in nkSymChoices and def[0].sym.kind == skEnumField: if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}:
msgSymChoiceUseQualifier(c, def, errGenerated)
elif def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}:
typFlags.incl taIsTemplateOrMacro typFlags.incl taIsTemplateOrMacro
elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro: elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
typFlags.incl taProcContextIsNotMacro typFlags.incl taProcContextIsNotMacro
@ -799,7 +781,7 @@ proc semConst(c: PContext, n: PNode): PNode =
var typFlags: TTypeAllowedFlags var typFlags: TTypeAllowedFlags
# don't evaluate here since the type compatibility check below may add a converter # don't evaluate here since the type compatibility check below may add a converter
var def = semExprWithType(c, a[^1], {}, typ) var def = semExprWithType(c, a[^1], {efTypeAllowed}, typ)
if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}: if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}:
typFlags.incl taIsTemplateOrMacro typFlags.incl taIsTemplateOrMacro

View file

@ -1365,6 +1365,23 @@ ambiguous, a static error will be produced.
p value2 p value2
``` ```
In some cases, ambiguity of enums is resolved depending on the relation
between the current scope and the scope the enums were defined in.
```nim
# a.nim
type Foo* = enum abc
# b.nim
import a
type Bar = enum abc
echo abc is Bar # true
block:
type Baz = enum abc
echo abc is Baz # true
```
To implement bit fields with enums see [Bit fields]. To implement bit fields with enums see [Bit fields].

View file

@ -0,0 +1,19 @@
discard """
action: reject
cmd: "nim check $file"
nimout: '''
tambprocvar.nim(15, 11) Error: ambiguous identifier 'foo' -- use one of the following:
tambprocvar.foo: proc (x: int){.noSideEffect, gcsafe.}
tambprocvar.foo: proc (x: float){.noSideEffect, gcsafe.}
'''
"""
block:
proc foo(x: int) = discard
proc foo(x: float) = discard
let x = foo
block:
let x = `+` #[tt.Error
^ ambiguous identifier '+' -- use one of the following:]#