Skip to content

Commit

Permalink
add --spellsuggest to suggest symbols in scope with similar spellings…
Browse files Browse the repository at this point in the history
… on undefined symbol errors
  • Loading branch information
timotheecour committed Nov 20, 2020
1 parent c7d9b17 commit e09791f
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 28 deletions.
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@

- Added `--declaredlocs` to show symbol declaration location in messages.

- Added `--spellSuggest` to show spelling suggestions on typos.

- Source+Edit links now appear on top of every docgen'd page when
`nim doc --git.url:url ...` is given.

Expand Down
2 changes: 2 additions & 0 deletions compiler/commands.nim
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
processOnOffSwitchG(conf, {optStdout}, arg, pass, info)
of "listfullpaths":
processOnOffSwitchG(conf, {optListFullPaths}, arg, pass, info)
of "spellsuggest":
processOnOffSwitchG(conf, {optSpellSuggest}, arg, pass, info)
of "declaredlocs":
processOnOffSwitchG(conf, {optDeclaredLocs}, arg, pass, info)
of "dynliboverride":
Expand Down
80 changes: 52 additions & 28 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,48 @@ proc mergeShadowScope*(c: PContext) =
else:
c.addInterfaceDecl(sym)

proc altSpelling(x: PIdent): PIdent =
case x.s[0]
of 'A'..'Z': result = getIdent(toLowerAscii(x.s[0]) & x.s.substr(1))
of 'a'..'z': result = getIdent(toLowerAscii(x.s[0]) & x.s.substr(1))
else: result = x
when false:
proc altSpelling(c: PContext, x: PIdent): PIdent =
case x.s[0]
of 'A'..'Z': result = getIdent(c.cache, toLowerAscii(x.s[0]) & x.s.substr(1))
of 'a'..'z': result = getIdent(c.cache, toLowerAscii(x.s[0]) & x.s.substr(1))
else: result = x

proc fixSpelling(c: PContext, n: PNode, ident: PIdent) =
import std/editdistance
import std/heapqueue

proc fixSpelling(c: PContext, n: PNode, ident: PIdent, result: var string) =
## when we cannot find the identifier, retry with a changed identifier
if isDefined(c, "nimFixSpelling") or defined(nimfix):
let alt = ident.altSpelling
result = searchInScopes(c, alt).skipAlias(n)
if result != nil:
prettybase.replaceDeprecated(n.info, ident, alt)
return result
# xxx nimfix used to call: prettybase.replaceDeprecated(n.info, ident, alt)
# if not (isDefined(c.config, "nimFixSpelling") or defined(nimfix)): return
if optSpellSuggest notin c.config.globalOptions: return
type E = tuple[dist: int, depth: int, sym: PSym]
proc `<`(a, b: E): bool =
# favors nearby scopes
a.dist < b.dist or a.dist == b.dist and a.depth < b.depth
var list = initHeapQueue[E]()
let name0 = ident.s.nimIdentNormalize
var depth = 0
for scope in walkScopes(c.currentScope):
for h in 0..high(scope.symbols.data):
if scope.symbols.data[h] != nil:
let identi = scope.symbols.data[h]
let si = identi.name.s
let dist = editDistance(name0, si.nimIdentNormalize)
list.push (dist, depth, identi)
depth.inc

# xxx: items(list) should work for HeapQueue
if list.len == 0: return
let e0 = list[0]
# let dist0 = list[0].dist
for i in 0..<list.len:
let e = list[i]
if e0 < e: break
let (dist, depth, sym) = e
result.add "\n candidate misspelling: '" & sym.name.s & "'"
# TODO: .skipAlias(n, c.config) >
addDeclaredLocMaybe(result, c.config, sym)

proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
var err = "ambiguous identifier: '" & s.name.s & "'"
Expand All @@ -286,33 +314,33 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
inc i
localError(c.config, info, errGenerated, err)

proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) =
var err = "undeclared identifier: '" & name & "'"
proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string, extra = "") =
var err = "undeclared identifier: '" & name & "'" & extra
if c.recursiveDep.len > 0:
err.add "\nThis might be caused by a recursive module dependency:\n"
err.add c.recursiveDep
# prevent excessive errors for 'nim check'
c.recursiveDep = ""
localError(c.config, info, errGenerated, err)

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

proc lookUp*(c: PContext, n: PNode): PSym =
# Looks up a symbol. Generates an error in case of nil.
case n.kind
of nkIdent:
result = searchInScopes(c, n.ident).skipAlias(n, c.config)
if result == nil:
fixSpelling(n, n.ident, searchInScopes)
errorUndeclaredIdentifier(c, n.info, n.ident.s)
result = errorSym(c, n)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, n.ident)
of nkSym:
result = n.sym
of nkAccQuoted:
var ident = considerQuotedIdent(c, n)
result = searchInScopes(c, ident).skipAlias(n, c.config)
if result == nil:
fixSpelling(n, ident, searchInScopes)
errorUndeclaredIdentifier(c, n.info, ident.s)
result = errorSym(c, n)
if result == nil: result = errorUndeclaredIdentifierHint(c, n, ident)
else:
internalError(c.config, n.info, "lookUp")
return
Expand All @@ -337,9 +365,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
if result == nil and checkPureEnumFields in flags:
result = strTableGet(c.pureEnumFields, ident)
if result == nil and checkUndeclared in flags:
fixSpelling(n, ident, searchInScopes)
errorUndeclaredIdentifier(c, n.info, ident.s)
result = errorSym(c, n)
result = errorUndeclaredIdentifierHint(c, n, ident)
elif checkAmbiguity in flags and result != nil and result.id in c.ambiguousSymbols:
errorUseQualifier(c, n.info, result)
of nkSym:
Expand All @@ -361,9 +387,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
else:
result = strTableGet(m.tab, ident).skipAlias(n, c.config)
if result == nil and checkUndeclared in flags:
fixSpelling(n[1], ident, searchInScopes)
errorUndeclaredIdentifier(c, n[1].info, ident.s)
result = errorSym(c, n[1])
result = errorUndeclaredIdentifierHint(c, n[1], ident)
elif n[1].kind == nkSym:
result = n[1].sym
elif checkUndeclared in flags and
Expand Down
1 change: 1 addition & 0 deletions compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type # please make sure we have under 32 options
optMixedMode # true if some module triggered C++ codegen
optListFullPaths # use full paths in toMsgFilename
optDeclaredLocs # show declaration locations in messages
optSpellSuggest # spelling suggestions for typos
optNoNimblePath
optHotCodeReloading
optDynlibOverrideAll
Expand Down
1 change: 1 addition & 0 deletions doc/advopt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Advanced options:
--colors:on|off turn compiler messages coloring on|off
--listFullPaths:on|off list full paths in messages
--declaredLocs:on|off show declaration locations in messages
--spellSuggest:on|off show spelling suggestions on typos
-w:on|off|list, --warnings:on|off|list
turn all warnings on|off or list all available
--warning[X]:on|off turn specific warning X on|off
Expand Down

0 comments on commit e09791f

Please sign in to comment.