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
27 changes: 16 additions & 11 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,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 @@ -621,31 +621,36 @@ 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)
elif checkAmbiguity in flags and result != nil and amb:
Expand Down
85 changes: 61 additions & 24 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,23 @@ 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:
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 @@ -3026,25 +3034,54 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
if nfSem in n.flags: return
case n.kind
of nkIdent, nkAccQuoted:
let ident = considerQuotedIdent(c, n)
var s: PSym = nil
if expectedType != nil and (
let expected = expectedType.skipTypes(abstractRange-{tyDistinct});
expected.kind == tyEnum):
let nameId = considerQuotedIdent(c, n).id
let nameId = ident.id
for f in expected.n:
if f.kind == nkSym and f.sym.name.id == nameId:
s = f.sym
break
if s == nil:
let checks = if efNoEvaluateGeneric in flags:
{checkUndeclared, checkPureEnumFields}
elif efInCall in flags:
{checkUndeclared, checkModule, checkPureEnumFields}
var filter = {low(TSymKind)..high(TSymKind)}
metagn marked this conversation as resolved.
Show resolved Hide resolved
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:
s = errorUndeclaredIdentifierHint(c, n, ident)
elif candidates.len == 1 or {efNoEvaluateGeneric, efInCall} * flags != {}:
# unambiguous, or we don't care about ambiguity
s = 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)
else:
{checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields}
s = qualifiedLookUp(c, n, checks)
if s == nil:
return
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):
if efAllowSymChoice in flags:
result = choice
else:
errorUseQualifier(c, n.info, candidates)
else:
if choice.kind == nkSym:
s = choice.sym
else:
# resolution could have generated nkHiddenStdConv etc
result = semExpr(c, choice, flags, expectedType)
if s == nil:
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 overloadable enums now which give 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