diff --git a/changelog.md b/changelog.md index 4c18ddf1ef20a..33f70036c653e 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/compiler/commands.nim b/compiler/commands.nim index 1ad5ec64cb31d..1f3dc5080e6e9 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -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": diff --git a/compiler/lookups.nim b/compiler/lookups.nim index bc7432e1f9a4e..ebc4e51c14599 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -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.. + addDeclaredLocMaybe(result, c.config, sym) proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = var err = "ambiguous identifier: '" & s.name.s & "'" @@ -286,8 +314,8 @@ 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 @@ -295,24 +323,24 @@ proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) = 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 @@ -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: @@ -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 diff --git a/compiler/options.nim b/compiler/options.nim index 58d09938c4706..097d95f2adb1d 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -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 diff --git a/doc/advopt.txt b/doc/advopt.txt index 1fc0c946e84c9..c75cd2b98801c 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -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