Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ambiguous identifier resolution #23123

Merged
merged 12 commits into from
Jan 1, 2024
Merged
64 changes: 36 additions & 28 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -303,21 +303,24 @@ proc isAmbiguous*(c: PContext, s: PIdent, filter: TSymKinds, sym: var PSym): boo
# imports had a candidate but wasn't ambiguous
return false

proc errorSym*(c: PContext, n: PNode): PSym =
proc errorSym*(c: PContext, ident: PIdent, info: TLineInfo): PSym =
## creates an error symbol to avoid cascading errors (for IDE support)
result = newSym(skError, ident, c.idgen, getCurrOwner(c), info, {})
result.typ = errorType(c)
incl(result.flags, sfDiscardable)
# pretend it's from the top level scope to prevent cascading errors:
if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
c.moduleScope.addSym(result)

proc errorSym*(c: PContext, n: PNode): PSym =
var m = n
# ensure that 'considerQuotedIdent' can't fail:
if m.kind == nkDotExpr: m = m[1]
let ident = if m.kind in {nkIdent, nkSym, nkAccQuoted}:
considerQuotedIdent(c, m)
else:
getIdent(c.cache, "err:" & renderTree(m))
result = newSym(skError, ident, c.idgen, getCurrOwner(c), n.info, {})
result.typ = errorType(c)
incl(result.flags, sfDiscardable)
# pretend it's from the top level scope to prevent cascading errors:
if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
c.moduleScope.addSym(result)
result = errorSym(c, ident, n.info)

type
TOverloadIterMode* = enum
Expand Down Expand Up @@ -499,7 +502,7 @@ proc mustFixSpelling(c: PContext): bool {.inline.} =
result = c.config.spellSuggestMax != 0 and c.compilesContextId == 0
# don't slowdown inside compiles()

proc fixSpelling(c: PContext, n: PNode, ident: PIdent, result: var string) =
proc fixSpelling(c: PContext, ident: PIdent, result: var string) =
## when we cannot find the identifier, suggest nearby spellings
var list = initHeapQueue[SpellCandidate]()
let name0 = ident.s.nimIdentNormalize
Expand Down Expand Up @@ -558,7 +561,7 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
var amb: bool
discard errorUseQualifier(c, info, s, amb)

proc errorUseQualifier(c: PContext; info: TLineInfo; candidates: seq[PSym]; prefix = "use one of") =
proc errorUseQualifier*(c: PContext; info: TLineInfo; candidates: seq[PSym]; prefix = "use one of") =
var err = "ambiguous identifier: '" & candidates[0].name.s & "'"
var i = 0
for candidate in candidates:
Expand Down Expand Up @@ -589,25 +592,25 @@ proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string, extr
c.recursiveDep = ""
localError(c.config, info, errGenerated, err)

proc errorUndeclaredIdentifierHint*(c: PContext; n: PNode, ident: PIdent): PSym =
proc errorUndeclaredIdentifierHint*(c: PContext; ident: PIdent; info: TLineInfo): PSym =
var extra = ""
if c.mustFixSpelling: fixSpelling(c, n, ident, extra)
errorUndeclaredIdentifier(c, n.info, ident.s, extra)
result = errorSym(c, n)
if c.mustFixSpelling: fixSpelling(c, ident, extra)
errorUndeclaredIdentifier(c, info, ident.s, extra)
result = errorSym(c, ident, info)

proc lookUp*(c: PContext, n: PNode): PSym =
# Looks up a symbol. Generates an error in case of nil.
var amb = false
case n.kind
of nkIdent:
result = searchInScopes(c, n.ident, amb)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, n.ident)
if result == nil: result = errorUndeclaredIdentifierHint(c, n.ident, n.info)
of nkSym:
result = n.sym
of nkAccQuoted:
var ident = considerQuotedIdent(c, n)
result = searchInScopes(c, ident, amb)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, ident)
if result == nil: result = errorUndeclaredIdentifierHint(c, ident, n.info)
else:
internalError(c.config, n.info, "lookUp")
return nil
Expand All @@ -621,33 +624,38 @@ type
TLookupFlag* = enum
checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields

const allExceptModule = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage}

proc lookUpCandidates*(c: PContext, ident: PIdent, filter: set[TSymKind]): seq[PSym] =
result = searchInScopesFilterBy(c, ident, filter)
if result.len == 0:
result.add allPureEnumFields(c, ident)

proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
const allExceptModule = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage}
case n.kind
of nkIdent, nkAccQuoted:
var amb = false
var ident = considerQuotedIdent(c, n)
if checkModule in flags:
result = searchInScopes(c, ident, amb)
if result == nil:
let candidates = allPureEnumFields(c, ident)
if candidates.len > 0:
result = candidates[0]
amb = candidates.len > 1
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)
else:
let candidates = searchInScopesFilterBy(c, ident, allExceptModule)
let candidates = lookUpCandidates(c, ident, allExceptModule)
if candidates.len > 0:
result = candidates[0]
amb = candidates.len > 1
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)
else:
result = nil
if result == nil:
let candidates = allPureEnumFields(c, ident)
if candidates.len > 0:
result = candidates[0]
amb = candidates.len > 1
if amb and checkAmbiguity in flags:
errorUseQualifier(c, n.info, candidates)

if result == nil and checkUndeclared in flags:
result = errorUndeclaredIdentifierHint(c, n, ident)
result = errorUndeclaredIdentifierHint(c, ident, n.info)
elif checkAmbiguity in flags and result != nil and amb:
result = errorUseQualifier(c, n.info, result, amb)
c.isAmbiguous = amb
Expand All @@ -672,12 +680,12 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
else:
result = someSym(c.graph, m, ident)
if result == nil and checkUndeclared in flags:
result = errorUndeclaredIdentifierHint(c, n[1], ident)
result = errorUndeclaredIdentifierHint(c, ident, n[1].info)
elif n[1].kind == nkSym:
result = n[1].sym
if result.owner != nil and result.owner != m and checkUndeclared in flags:
# dotExpr in templates can end up here
result = errorUndeclaredIdentifierHint(c, n[1], considerQuotedIdent(c, n[1]))
result = errorUndeclaredIdentifierHint(c, result.name, n[1].info)
elif checkUndeclared in flags and
n[1].kind notin {nkOpenSymChoice, nkClosedSymChoice}:
localError(c.config, n[1].info, "identifier expected, but got: " &
Expand Down
109 changes: 76 additions & 33 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,13 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode
proc isSymChoice(n: PNode): bool {.inline.} =
result = n.kind in nkSymChoices

proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = n
proc resolveSymChoice(c: PContext, n: var PNode, flags: TExprFlags = {}, expectedType: PType = nil) =
## Attempts to resolve a symchoice `n`, `n` remains a symchoice if
## it cannot be resolved (this is the case even when `n.len == 1`).
if expectedType != nil:
result = fitNode(c, expectedType, result, n.info)
if isSymChoice(result) and efAllowSymChoice notin flags:
# resolve from type inference, see paramTypesMatch
n = fitNode(c, expectedType, n, n.info)
if isSymChoice(n) and efAllowSymChoice notin flags:
# some contexts might want sym choices preserved for later disambiguation
# in general though they are ambiguous
let first = n[0].sym
Expand All @@ -145,17 +147,24 @@ proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: P
foundSym == first:
# choose the first resolved enum field, i.e. the latest in scope
# to mirror behavior before overloadable enums
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, n.info, err)
n.typ = errorType(c)
result = n
n = n[0]

proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = n
resolveSymChoice(c, result, flags, expectedType)
if isSymChoice(result) and result.len == 1:
# resolveSymChoice can leave 1 sym
result = result[0]
if isSymChoice(result) and efAllowSymChoice notin flags:
var err = "ambiguous identifier: '" & result[0].sym.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, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)

Expand Down Expand Up @@ -2989,6 +2998,55 @@ proc semPragmaStmt(c: PContext; n: PNode) =
else:
pragma(c, c.p.owner, n, stmtPragmas, true)

proc resolveIdentToSym(c: PContext, n: PNode, resultNode: var PNode,
flags: TExprFlags, expectedType: PType): PSym =
# result is nil on error or if a node that can't produce a sym is resolved
let ident = considerQuotedIdent(c, n)
if expectedType != nil and (
let expected = expectedType.skipTypes(abstractRange-{tyDistinct});
expected.kind == tyEnum):
let nameId = ident.id
for f in expected.n:
if f.kind == nkSym and f.sym.name.id == nameId:
return f.sym
var filter = {low(TSymKind)..high(TSymKind)}
if efNoEvaluateGeneric in flags:
# `a[...]` where `a` is a module or package is not possible
filter.excl {skModule, skPackage}
let candidates = lookUpCandidates(c, ident, filter)
if candidates.len == 0:
result = errorUndeclaredIdentifierHint(c, ident, n.info)
elif candidates.len == 1 or {efNoEvaluateGeneric, efInCall} * flags != {}:
# unambiguous, or we don't care about ambiguity
result = candidates[0]
else:
# ambiguous symbols have 1 last chance as a symchoice,
# but type symbols cannot participate in symchoices
var choice = newNodeIT(nkClosedSymChoice, n.info, newTypeS(tyNone, c))
for c in candidates:
if c.kind notin {skType, skModule, skPackage}:
choice.add newSymNode(c, n.info)
if choice.len == 0:
# we know candidates.len > 1, we just couldn't put any in a symchoice
errorUseQualifier(c, n.info, candidates)
return nil
resolveSymChoice(c, choice, flags, expectedType)
# choice.len == 1 can be true here but as long as it's a symchoice
# it's still not resolved
if isSymChoice(choice):
result = nil
if efAllowSymChoice in flags:
resultNode = choice
else:
errorUseQualifier(c, n.info, candidates)
else:
if choice.kind == nkSym:
result = choice.sym
else:
# resolution could have generated nkHiddenStdConv etc
resultNode = semExpr(c, choice, flags, expectedType)
result = nil

proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
when defined(nimCompilerStacktraceHints):
setFrameMsg c.config$n.info & " " & $n.kind
Expand Down Expand Up @@ -3026,25 +3084,10 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
if nfSem in n.flags: return
case n.kind
of nkIdent, nkAccQuoted:
var s: PSym = nil
if expectedType != nil and (
let expected = expectedType.skipTypes(abstractRange-{tyDistinct});
expected.kind == tyEnum):
let nameId = considerQuotedIdent(c, n).id
for f in expected.n:
if f.kind == nkSym and f.sym.name.id == nameId:
s = f.sym
break
let s = resolveIdentToSym(c, n, result, flags, expectedType)
if s == nil:
let checks = if efNoEvaluateGeneric in flags:
{checkUndeclared, checkPureEnumFields}
elif efInCall in flags:
{checkUndeclared, checkModule, checkPureEnumFields}
else:
{checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields}
s = qualifiedLookUp(c, n, checks)
if s == nil:
return
# resolveIdentToSym either errored or gave a result node
return
if c.matchedConcept == nil: semCaptureSym(s, c.p.owner)
case s.kind
of skProc, skFunc, skMethod, skConverter, skIterator:
Expand Down
4 changes: 2 additions & 2 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2346,8 +2346,8 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
if arg == nil or arg.kind notin nkSymChoices:
result = paramTypesMatchAux(m, f, a, arg, argOrig)
else:
let matchSet = {skProc, skFunc, skMethod, skConverter,skIterator, skMacro,
skTemplate, skEnumField}
# symbol kinds that don't participate in symchoice type disambiguation:
let matchSet = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage, skType}

var best = -1
result = arg
Expand Down
4 changes: 2 additions & 2 deletions tests/enum/tambiguousoverloads.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ block: # bug #21887
EnumC = enum C

doAssert typeof(EnumC(A)) is EnumC #[tt.Error
^ ambiguous identifier 'A' -- use one of the following:
^ ambiguous identifier: 'A' -- use one of the following:
EnumA.A: EnumA
EnumB.A: EnumB]#

Expand All @@ -21,6 +21,6 @@ block: # issue #22598
red

let a = red #[tt.Error
^ ambiguous identifier 'red' -- use one of the following:
^ ambiguous identifier: 'red' -- use one of the following:
A.red: A
B.red: B]#
1 change: 1 addition & 0 deletions tests/enum/tpure_enums_conflict.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
discard """
disabled: true # pure enums behave like overloaded enums on ambiguity now which gives a different error message
errormsg: "ambiguous identifier: 'amb'"
line: 19
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/errmsgs/t8064.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ values

discard """
# either this or "expression has no type":
errormsg: "ambiguous identifier 'values' -- use one of the following:"
errormsg: "ambiguous identifier: 'values' -- use one of the following:"
"""
2 changes: 1 addition & 1 deletion tests/lookups/tambiguousemit.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ proc foo(x: int) = discard
proc foo(x: float) = discard

{.emit: ["// ", foo].} #[tt.Error
^ ambiguous identifier 'foo' -- use one of the following:
^ ambiguous identifier: 'foo' -- use one of the following:
tambiguousemit.foo: proc (x: int){.noSideEffect, gcsafe.}
tambiguousemit.foo: proc (x: float){.noSideEffect, gcsafe.}]#
4 changes: 2 additions & 2 deletions tests/lookups/tambprocvar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ discard """
action: reject
cmd: "nim check $file"
nimout: '''
tambprocvar.nim(15, 11) Error: ambiguous identifier 'foo' -- use one of the following:
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.}
'''
Expand All @@ -16,4 +16,4 @@ block:

block:
let x = `+` #[tt.Error
^ ambiguous identifier '+' -- use one of the following:]#
^ ambiguous identifier: '+' -- use one of the following:]#
2 changes: 1 addition & 1 deletion tests/lookups/tambsym3.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
discard """
errormsg: "ambiguous identifier 'mDec' -- use one of the following:"
errormsg: "ambiguous identifier: 'mDec' -- use one of the following:"
file: "tambsym3.nim"
line: 11
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/macros/t23032_2.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
discard """
action: "reject"
errormsg: "ambiguous identifier '%*'"
errormsg: "ambiguous identifier: '%*'"
"""
import std/macros

Expand Down
1 change: 1 addition & 0 deletions tests/pragmas/monoff1.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
proc on*() = discard
8 changes: 8 additions & 0 deletions tests/pragmas/tonoff1.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# issue #23002

import monoff1

proc test() =
{.warning[ProveInit]: on.}

test()
14 changes: 14 additions & 0 deletions tests/pragmas/tonoff2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
discard """
action: compile
"""

# issue #22841

import unittest

proc on() =
discard

suite "some suite":
test "some test":
discard
Loading