mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-30 18:02:05 +00:00
fixes #22639 A `tyGenericInst` has its last son as the instantiated body of the original generic type. However this type keeps its original `sym` field from the original generic types, which means the sym's type is uninstantiated. This causes problems in the implementation of `getType`, where it uses the `sym` fields of types to represent them in AST, the relevant example for the issue being [here](d13aab50cf/compiler/vmdeps.nim (L191)) called from [here](d13aab50cf/compiler/vmdeps.nim (L143)). To fix this, create a new symbol from the original symbol for the instantiated body during the creation of `tyGenericInst`s with the appropriate type. Normally `replaceTypeVarsS` would be used for this, but here it seems to cause some recursion issue (immediately gives an error like "cannot instantiate HSlice[T, U]"), so we directly set the symbol's type to the instantiated type. Avoiding recursion means we also cannot use `replaceTypeVarsN` for the symbol AST, and the symbol not having any AST crashes the implementation of `getType` again [here](d13aab50cf/compiler/vmdeps.nim (L167)), so the symbol AST is set to the original generic type AST for now which is what it was before anyway. Not sure about this because not sure why the recursion issue is happening, putting it at the end of the proc doesn't help either. Also not sure if the `cl.owner != nil and s.owner != cl.owner` condition from `replaceTypeVarsS` is relevant here. This might also break some code if it depended on the original generic type symbol being given.
737 lines
27 KiB
Nim
737 lines
27 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2015 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## This file implements features required for IDE support.
|
|
##
|
|
## Due to Nim's nature and the fact that ``system.nim`` is always imported,
|
|
## there are lots of potential symbols. Furthermore thanks to templates and
|
|
## macros even context based analysis does not help much: In a context like
|
|
## ``let x: |`` where a type has to follow, that type might be constructed from
|
|
## a template like ``extractField(MyObject, fieldName)``. We deal with this
|
|
## problem by smart sorting so that the likely symbols come first. This sorting
|
|
## is done this way:
|
|
##
|
|
## - If there is a prefix (foo|), symbols starting with this prefix come first.
|
|
## - If the prefix is part of the name (but the name doesn't start with it),
|
|
## these symbols come second.
|
|
## - If we have a prefix, only symbols matching this prefix are returned and
|
|
## nothing else.
|
|
## - If we have no prefix, consider the context. We currently distinguish
|
|
## between type and non-type contexts.
|
|
## - Finally, sort matches by relevance. The relevance is determined by the
|
|
## number of usages, so ``strutils.replace`` comes before
|
|
## ``strutils.wordWrap``.
|
|
## - In any case, sorting also considers scoping information. Local variables
|
|
## get high priority.
|
|
|
|
# included from sigmatch.nim
|
|
|
|
import algorithm, sets, prefixmatches, parseutils, tables
|
|
from wordrecg import wDeprecated, wError, wAddr, wYield
|
|
|
|
when defined(nimsuggest):
|
|
import tables, pathutils # importer
|
|
|
|
const
|
|
sep = '\t'
|
|
|
|
#template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n"
|
|
|
|
template origModuleName(m: PSym): string = m.name.s
|
|
|
|
proc findDocComment(n: PNode): PNode =
|
|
if n == nil: return nil
|
|
if n.comment.len > 0: return n
|
|
if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0:
|
|
result = findDocComment(n[0])
|
|
if result != nil: return
|
|
if n.len > 1:
|
|
result = findDocComment(n[1])
|
|
elif n.kind in {nkAsgn, nkFastAsgn, nkSinkAsgn} and n.len == 2:
|
|
result = findDocComment(n[1])
|
|
else:
|
|
result = nil
|
|
|
|
proc extractDocComment(g: ModuleGraph; s: PSym): string =
|
|
var n = findDocComment(s.ast)
|
|
if n.isNil and s.kind in routineKinds and s.ast != nil:
|
|
n = findDocComment(getBody(g, s))
|
|
if not n.isNil:
|
|
result = n.comment.replace("\n##", "\n").strip
|
|
else:
|
|
result = ""
|
|
|
|
proc cmpSuggestions(a, b: Suggest): int =
|
|
template cf(field) {.dirty.} =
|
|
result = b.field.int - a.field.int
|
|
if result != 0: return result
|
|
|
|
cf prefix
|
|
cf contextFits
|
|
cf scope
|
|
# when the first type matches, it's better when it's a generic match:
|
|
cf quality
|
|
cf localUsages
|
|
cf globalUsages
|
|
# if all is equal, sort alphabetically for deterministic output,
|
|
# independent of hashing order:
|
|
result = cmp(a.name[], b.name[])
|
|
|
|
proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int =
|
|
let
|
|
line = sourceLine(conf, info)
|
|
column = toColumn(info)
|
|
|
|
proc isOpeningBacktick(col: int): bool =
|
|
if col >= 0 and col < line.len:
|
|
if line[col] == '`':
|
|
not isOpeningBacktick(col - 1)
|
|
else:
|
|
isOpeningBacktick(col - 1)
|
|
else:
|
|
false
|
|
|
|
if column > line.len:
|
|
result = 0
|
|
elif column > 0 and line[column - 1] == '`' and isOpeningBacktick(column - 1):
|
|
result = skipUntil(line, '`', column)
|
|
if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
|
|
result = 0
|
|
elif ident[0] in linter.Letters and ident[^1] != '=':
|
|
result = identLen(line, column)
|
|
if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
|
|
result = 0
|
|
else:
|
|
var sourceIdent: string = ""
|
|
result = parseWhile(line, sourceIdent,
|
|
OpChars + {'[', '(', '{', ']', ')', '}'}, column)
|
|
if ident[^1] == '=' and ident[0] in linter.Letters:
|
|
if sourceIdent != "=":
|
|
result = 0
|
|
elif sourceIdent.len > ident.len and sourceIdent[0..ident.high] == ident:
|
|
result = ident.len
|
|
elif sourceIdent != ident:
|
|
result = 0
|
|
|
|
proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
|
|
quality: range[0..100]; prefix: PrefixMatch;
|
|
inTypeContext: bool; scope: int;
|
|
useSuppliedInfo = false,
|
|
endLine: uint16 = 0,
|
|
endCol = 0): Suggest =
|
|
new(result)
|
|
result.section = section
|
|
result.quality = quality
|
|
result.isGlobal = sfGlobal in s.flags
|
|
result.prefix = prefix
|
|
result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam})
|
|
result.scope = scope
|
|
result.name = addr s.name.s
|
|
when defined(nimsuggest):
|
|
result.globalUsages = s.allUsages.len
|
|
var c = 0
|
|
for u in s.allUsages:
|
|
if u.fileIndex == info.fileIndex: inc c
|
|
result.localUsages = c
|
|
result.symkind = byte s.kind
|
|
if optIdeTerse notin g.config.globalOptions:
|
|
result.qualifiedPath = @[]
|
|
if not isLocal and s.kind != skModule:
|
|
let ow = s.owner
|
|
if ow != nil and ow.kind != skModule and ow.owner != nil:
|
|
let ow2 = ow.owner
|
|
result.qualifiedPath.add(ow2.origModuleName)
|
|
if ow != nil:
|
|
result.qualifiedPath.add(ow.origModuleName)
|
|
if s.name.s[0] in OpChars + {'[', '{', '('} or
|
|
s.name.id in ord(wAddr)..ord(wYield):
|
|
result.qualifiedPath.add('`' & s.name.s & '`')
|
|
else:
|
|
result.qualifiedPath.add(s.name.s)
|
|
|
|
if s.typ != nil:
|
|
result.forth = typeToString(s.typ)
|
|
else:
|
|
result.forth = ""
|
|
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
|
|
result.doc = extractDocComment(g, s)
|
|
if s.kind == skModule and s.ast.len != 0 and section != ideHighlight:
|
|
result.filePath = toFullPath(g.config, s.ast[0].info)
|
|
result.line = 1
|
|
result.column = 0
|
|
result.tokenLen = 0
|
|
else:
|
|
let infox =
|
|
if useSuppliedInfo or section in {ideUse, ideHighlight, ideOutline, ideDeclaration}:
|
|
info
|
|
else:
|
|
s.info
|
|
result.filePath = toFullPath(g.config, infox)
|
|
result.line = toLinenumber(infox)
|
|
result.column = toColumn(infox)
|
|
result.tokenLen = if section != ideHighlight:
|
|
s.name.s.len
|
|
else:
|
|
getTokenLenFromSource(g.config, s.name.s, infox)
|
|
result.version = g.config.suggestVersion
|
|
result.endLine = endLine
|
|
result.endCol = endCol
|
|
|
|
proc `$`*(suggest: Suggest): string =
|
|
result = $suggest.section
|
|
result.add(sep)
|
|
if suggest.section == ideHighlight:
|
|
if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
|
|
result.add("skGlobalVar")
|
|
elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
|
|
result.add("skGlobalLet")
|
|
else:
|
|
result.add($suggest.symkind.TSymKind)
|
|
result.add(sep)
|
|
result.add($suggest.line)
|
|
result.add(sep)
|
|
result.add($suggest.column)
|
|
result.add(sep)
|
|
result.add($suggest.tokenLen)
|
|
else:
|
|
result.add($suggest.symkind.TSymKind)
|
|
result.add(sep)
|
|
if suggest.qualifiedPath.len != 0:
|
|
result.add(suggest.qualifiedPath.join("."))
|
|
result.add(sep)
|
|
result.add(suggest.forth)
|
|
result.add(sep)
|
|
result.add(suggest.filePath)
|
|
result.add(sep)
|
|
result.add($suggest.line)
|
|
result.add(sep)
|
|
result.add($suggest.column)
|
|
result.add(sep)
|
|
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
|
|
result.add(suggest.doc.escape)
|
|
if suggest.version == 0 or suggest.version == 3:
|
|
result.add(sep)
|
|
result.add($suggest.quality)
|
|
if suggest.section == ideSug:
|
|
result.add(sep)
|
|
result.add($suggest.prefix)
|
|
|
|
if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
|
|
result.add(sep)
|
|
result.add($suggest.endLine)
|
|
result.add(sep)
|
|
result.add($suggest.endCol)
|
|
|
|
proc suggestResult*(conf: ConfigRef; s: Suggest) =
|
|
if not isNil(conf.suggestionResultHook):
|
|
conf.suggestionResultHook(s)
|
|
else:
|
|
conf.suggestWriteln($s)
|
|
|
|
proc produceOutput(a: var Suggestions; conf: ConfigRef) =
|
|
if conf.ideCmd in {ideSug, ideCon}:
|
|
a.sort cmpSuggestions
|
|
when defined(debug):
|
|
# debug code
|
|
writeStackTrace()
|
|
if a.len > conf.suggestMaxResults: a.setLen(conf.suggestMaxResults)
|
|
if not isNil(conf.suggestionResultHook):
|
|
for s in a:
|
|
conf.suggestionResultHook(s)
|
|
else:
|
|
for s in a:
|
|
conf.suggestWriteln($s)
|
|
|
|
proc filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
|
|
proc prefixMatch(s: PSym; n: PNode): PrefixMatch =
|
|
case n.kind
|
|
of nkIdent: result = n.ident.s.prefixMatch(s.name.s)
|
|
of nkSym: result = n.sym.name.s.prefixMatch(s.name.s)
|
|
of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
|
|
if n.len > 0:
|
|
result = prefixMatch(s, n[0])
|
|
else:
|
|
result = default(PrefixMatch)
|
|
else: result = default(PrefixMatch)
|
|
if s.kind != skModule:
|
|
if prefix != nil:
|
|
res = prefixMatch(s, prefix)
|
|
result = res != PrefixMatch.None
|
|
else:
|
|
result = true
|
|
else:
|
|
result = false
|
|
|
|
proc filterSymNoOpr(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
|
|
result = filterSym(s, prefix, res) and s.name.s[0] in lexer.SymChars and
|
|
not isKeyword(s.name)
|
|
|
|
proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
|
|
let fmoduleId = getModule(f).id
|
|
result = sfExported in f.flags or fmoduleId == c.module.id
|
|
|
|
if not result:
|
|
for module in c.friendModules:
|
|
if fmoduleId == module.id: return true
|
|
if f.kind == skField:
|
|
var symObj = f.owner.typ.toObjectFromRefPtrGeneric.sym
|
|
assert symObj != nil
|
|
for scope in allScopes(c.currentScope):
|
|
for sym in scope.allowPrivateAccess:
|
|
if symObj.id == sym.id: return true
|
|
|
|
proc getQuality(s: PSym): range[0..100] =
|
|
result = 100
|
|
if s.typ != nil and s.typ.len > 1:
|
|
var exp = s.typ[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink})
|
|
if exp.kind == tyVarargs: exp = elemType(exp)
|
|
if exp.kind in {tyUntyped, tyTyped, tyGenericParam, tyAnything}: result = 50
|
|
|
|
# penalize deprecated symbols
|
|
if sfDeprecated in s.flags:
|
|
result = result - 5
|
|
|
|
proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) =
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
if filterSym(s, f, pm) and fieldVisible(c, s):
|
|
outputs.add(symToSuggest(c.graph, s, isLocal=true, ideSug, info,
|
|
s.getQuality, pm, c.inTypeContext > 0, 0))
|
|
|
|
template wholeSymTab(cond, section: untyped) {.dirty.} =
|
|
for (item, scopeN, isLocal) in uniqueSyms(c):
|
|
let it = item
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
if cond:
|
|
outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, section, info, getQuality(it),
|
|
pm, c.inTypeContext > 0, scopeN))
|
|
|
|
proc suggestSymList(c: PContext, list, f: PNode; info: TLineInfo, outputs: var Suggestions) =
|
|
for i in 0..<list.len:
|
|
if list[i].kind == nkSym:
|
|
suggestField(c, list[i].sym, f, info, outputs)
|
|
#else: InternalError(list.info, "getSymFromList")
|
|
|
|
proc suggestObject(c: PContext, n, f: PNode; info: TLineInfo, outputs: var Suggestions) =
|
|
case n.kind
|
|
of nkRecList:
|
|
for i in 0..<n.len: suggestObject(c, n[i], f, info, outputs)
|
|
of nkRecCase:
|
|
if n.len > 0:
|
|
suggestObject(c, n[0], f, info, outputs)
|
|
for i in 1..<n.len: suggestObject(c, lastSon(n[i]), f, info, outputs)
|
|
of nkSym: suggestField(c, n.sym, f, info, outputs)
|
|
else: discard
|
|
|
|
proc nameFits(c: PContext, s: PSym, n: PNode): bool =
|
|
var op = if n.kind in nkCallKinds: n[0] else: n
|
|
if op.kind in {nkOpenSymChoice, nkClosedSymChoice}: op = op[0]
|
|
if op.kind == nkDotExpr: op = op[1]
|
|
var opr: PIdent
|
|
case op.kind
|
|
of nkSym: opr = op.sym.name
|
|
of nkIdent: opr = op.ident
|
|
else: return false
|
|
result = opr.id == s.name.id
|
|
|
|
proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool =
|
|
case candidate.kind
|
|
of OverloadableSyms:
|
|
var m = newCandidate(c, candidate, nil)
|
|
sigmatch.partialMatch(c, n, nOrig, m)
|
|
result = m.state != csNoMatch
|
|
else:
|
|
result = false
|
|
|
|
proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var Suggestions) =
|
|
let info = n.info
|
|
wholeSymTab(filterSym(it, nil, pm) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
|
|
ideCon)
|
|
|
|
proc suggestVar(c: PContext, n: PNode, outputs: var Suggestions) =
|
|
let info = n.info
|
|
wholeSymTab(nameFits(c, it, n), ideCon)
|
|
|
|
proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
|
|
if s.typ != nil and s.typ.len > 1 and s.typ[1] != nil:
|
|
# special rule: if system and some weird generic match via 'tyUntyped'
|
|
# or 'tyGenericParam' we won't list it either to reduce the noise (nobody
|
|
# wants 'system.`-|` as suggestion
|
|
let m = s.getModule()
|
|
if m != nil and sfSystemModule in m.flags:
|
|
if s.kind == skType: return
|
|
var exp = s.typ[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink})
|
|
if exp.kind == tyVarargs: exp = elemType(exp)
|
|
if exp.kind in {tyUntyped, tyTyped, tyGenericParam, tyAnything}: return
|
|
result = sigmatch.argtypeMatches(c, s.typ[1], firstArg)
|
|
else:
|
|
result = false
|
|
|
|
proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var Suggestions) =
|
|
assert typ != nil
|
|
let info = n.info
|
|
wholeSymTab(filterSymNoOpr(it, f, pm) and typeFits(c, it, typ), ideSug)
|
|
|
|
proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) =
|
|
# do not produce too many symbols:
|
|
for (it, scopeN, isLocal) in uniqueSyms(c):
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
if filterSym(it, f, pm):
|
|
outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, ideSug, n.info,
|
|
it.getQuality, pm, c.inTypeContext > 0, scopeN))
|
|
|
|
proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) =
|
|
# special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but
|
|
# ``myObj``.
|
|
var typ = n.typ
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
when defined(nimsuggest):
|
|
if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0:
|
|
# consider 'foo.|' where 'foo' is some not imported module.
|
|
let fullPath = findModule(c.config, n.sym.name.s, toFullPath(c.config, n.info))
|
|
if fullPath.isEmpty:
|
|
# error: no known module name:
|
|
typ = nil
|
|
else:
|
|
let m = c.graph.importModuleCallback(c.graph, c.module, fileInfoIdx(c.config, fullPath))
|
|
if m == nil: typ = nil
|
|
else:
|
|
for it in allSyms(c.graph, n.sym):
|
|
if filterSym(it, field, pm):
|
|
outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug,
|
|
n.info, it.getQuality, pm,
|
|
c.inTypeContext > 0, -100))
|
|
outputs.add(symToSuggest(c.graph, m, isLocal=false, ideMod, n.info,
|
|
100, PrefixMatch.None, c.inTypeContext > 0,
|
|
-99))
|
|
|
|
if typ == nil:
|
|
# a module symbol has no type for example:
|
|
if n.kind == nkSym and n.sym.kind == skModule:
|
|
if n.sym == c.module:
|
|
# all symbols accessible, because we are in the current module:
|
|
for it in items(c.topLevelScope.symbols):
|
|
if filterSym(it, field, pm):
|
|
outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug,
|
|
n.info, it.getQuality, pm,
|
|
c.inTypeContext > 0, -99))
|
|
else:
|
|
for it in allSyms(c.graph, n.sym):
|
|
if filterSym(it, field, pm):
|
|
outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug,
|
|
n.info, it.getQuality, pm,
|
|
c.inTypeContext > 0, -99))
|
|
else:
|
|
# fallback:
|
|
suggestEverything(c, n, field, outputs)
|
|
else:
|
|
let orig = typ
|
|
typ = skipTypes(orig, {tyTypeDesc, tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink, tyOwned})
|
|
|
|
if typ.kind == tyEnum and n.kind == nkSym and n.sym.kind == skType:
|
|
# look up if the identifier belongs to the enum:
|
|
var t = typ
|
|
while t != nil:
|
|
suggestSymList(c, t.n, field, n.info, outputs)
|
|
t = t[0]
|
|
elif typ.kind == tyObject:
|
|
var t = typ
|
|
while true:
|
|
suggestObject(c, t.n, field, n.info, outputs)
|
|
if t[0] == nil: break
|
|
t = skipTypes(t[0], skipPtrs)
|
|
elif typ.kind == tyTuple and typ.n != nil:
|
|
# All tuple fields are in scope
|
|
# So go through each field and add it to the suggestions (If it passes the filter)
|
|
for node in typ.n:
|
|
if node.kind == nkSym:
|
|
let s = node.sym
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
if filterSym(s, field, pm):
|
|
outputs.add(symToSuggest(c.graph, s, isLocal=true, ideSug, n.info,
|
|
s.getQuality, pm, c.inTypeContext > 0, 0))
|
|
|
|
suggestOperations(c, n, field, orig, outputs)
|
|
if typ != orig:
|
|
suggestOperations(c, n, field, typ, outputs)
|
|
|
|
type
|
|
TCheckPointResult* = enum
|
|
cpNone, cpFuzzy, cpExact
|
|
|
|
proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult =
|
|
if current.fileIndex == trackPos.fileIndex:
|
|
result = cpNone
|
|
if current.line == trackPos.line and
|
|
abs(current.col-trackPos.col) < 4:
|
|
return cpExact
|
|
if current.line >= trackPos.line:
|
|
return cpFuzzy
|
|
else:
|
|
result = cpNone
|
|
|
|
proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool =
|
|
if current.fileIndex==trackPos.fileIndex and current.line==trackPos.line:
|
|
let col = trackPos.col
|
|
if col >= current.col and col <= current.col+tokenLen-1:
|
|
result = true
|
|
else:
|
|
result = false
|
|
else:
|
|
result = false
|
|
|
|
when defined(nimsuggest):
|
|
# Since TLineInfo defined a == operator that doesn't include the column,
|
|
# we map TLineInfo to a unique int here for this lookup table:
|
|
proc infoToInt(info: TLineInfo): int64 =
|
|
info.fileIndex.int64 + info.line.int64 shl 32 + info.col.int64 shl 48
|
|
|
|
proc addNoDup(s: PSym; info: TLineInfo) =
|
|
# ensure nothing gets too slow:
|
|
if s.allUsages.len > 500: return
|
|
let infoAsInt = info.infoToInt
|
|
for infoB in s.allUsages:
|
|
if infoB.infoToInt == infoAsInt: return
|
|
s.allUsages.add(info)
|
|
|
|
proc findUsages(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym) =
|
|
if g.config.suggestVersion == 1:
|
|
if usageSym == nil and isTracked(info, g.config.m.trackPos, s.name.s.len):
|
|
usageSym = s
|
|
suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
|
|
elif s == usageSym:
|
|
if g.config.lastLineInfo != info:
|
|
suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
|
|
g.config.lastLineInfo = info
|
|
|
|
when defined(nimsuggest):
|
|
proc listUsages*(g: ModuleGraph; s: PSym) =
|
|
#echo "usages ", s.allUsages.len
|
|
for info in s.allUsages:
|
|
let x = if info == s.info and info.col == s.info.col: ideDef else: ideUse
|
|
suggestResult(g.config, symToSuggest(g, s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0))
|
|
|
|
proc findDefinition(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym) =
|
|
if s.isNil: return
|
|
if isTracked(info, g.config.m.trackPos, s.name.s.len) or (s == usageSym and sfForward notin s.flags):
|
|
suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0, useSuppliedInfo = s == usageSym))
|
|
if sfForward notin s.flags and g.config.suggestVersion != 3:
|
|
suggestQuit()
|
|
else:
|
|
usageSym = s
|
|
|
|
proc ensureIdx[T](x: var T, y: int) =
|
|
if x.len <= y: x.setLen(y+1)
|
|
|
|
proc ensureSeq[T](x: var seq[T]) =
|
|
if x == nil: newSeq(x, 0)
|
|
|
|
proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} =
|
|
## misnamed: should be 'symDeclared'
|
|
let conf = g.config
|
|
when defined(nimsuggest):
|
|
g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info)
|
|
|
|
if conf.suggestVersion == 0:
|
|
if s.allUsages.len == 0:
|
|
s.allUsages = @[info]
|
|
else:
|
|
s.addNoDup(info)
|
|
|
|
if conf.ideCmd == ideUse:
|
|
findUsages(g, info, s, usageSym)
|
|
elif conf.ideCmd == ideDef:
|
|
findDefinition(g, info, s, usageSym)
|
|
elif conf.ideCmd == ideDus and s != nil:
|
|
if isTracked(info, conf.m.trackPos, s.name.s.len):
|
|
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
|
|
findUsages(g, info, s, usageSym)
|
|
elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex:
|
|
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0))
|
|
elif conf.ideCmd == ideOutline and isDecl:
|
|
# if a module is included then the info we have is inside the include and
|
|
# we need to walk up the owners until we find the outer most module,
|
|
# which will be the last skModule prior to an skPackage.
|
|
var
|
|
parentFileIndex = info.fileIndex # assume we're in the correct module
|
|
parentModule = s.owner
|
|
while parentModule != nil and parentModule.kind == skModule:
|
|
parentFileIndex = parentModule.info.fileIndex
|
|
parentModule = parentModule.owner
|
|
|
|
if parentFileIndex == conf.m.trackPos.fileIndex:
|
|
suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0))
|
|
|
|
proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) =
|
|
var pragmaNode: PNode
|
|
pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s)
|
|
let name =
|
|
if s.kind == skEnumField and sfDeprecated notin s.flags: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'"
|
|
else: s.name.s
|
|
if pragmaNode != nil:
|
|
for it in pragmaNode:
|
|
if whichPragma(it) == wDeprecated and it.safeLen == 2 and
|
|
it[1].kind in {nkStrLit..nkTripleStrLit}:
|
|
message(conf, info, warnDeprecated, it[1].strVal & "; " & name & " is deprecated")
|
|
return
|
|
message(conf, info, warnDeprecated, name & " is deprecated")
|
|
|
|
proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) =
|
|
let pragmaNode = extractPragma(s)
|
|
template bail(prefix: string) =
|
|
localError(conf, info, "$1usage of '$2' is an {.error.} defined at $3" %
|
|
[prefix, s.name.s, toFileLineCol(conf, s.ast.info)])
|
|
if pragmaNode != nil:
|
|
for it in pragmaNode:
|
|
if whichPragma(it) == wError and it.safeLen == 2 and
|
|
it[1].kind in {nkStrLit..nkTripleStrLit}:
|
|
bail(it[1].strVal & "; ")
|
|
return
|
|
bail("")
|
|
|
|
proc markOwnerModuleAsUsed(c: PContext; s: PSym) =
|
|
var module = s
|
|
while module != nil and module.kind != skModule:
|
|
module = module.owner
|
|
if module != nil and module != c.module:
|
|
var i = 0
|
|
while i <= high(c.unusedImports):
|
|
let candidate = c.unusedImports[i][0]
|
|
if candidate == module or c.importModuleMap.getOrDefault(candidate.id, int.low) == module.id or
|
|
c.exportIndirections.contains((candidate.id, s.id)):
|
|
# mark it as used:
|
|
c.unusedImports.del(i)
|
|
else:
|
|
inc i
|
|
|
|
proc markUsed(c: PContext; info: TLineInfo; s: PSym) =
|
|
let conf = c.config
|
|
incl(s.flags, sfUsed)
|
|
if s.kind == skEnumField and s.owner != nil:
|
|
incl(s.owner.flags, sfUsed)
|
|
if sfDeprecated in s.owner.flags:
|
|
warnAboutDeprecated(conf, info, s)
|
|
if {sfDeprecated, sfError} * s.flags != {}:
|
|
if sfDeprecated in s.flags:
|
|
if not (c.lastTLineInfo.line == info.line and
|
|
c.lastTLineInfo.col == info.col):
|
|
warnAboutDeprecated(conf, info, s)
|
|
c.lastTLineInfo = info
|
|
|
|
if sfError in s.flags: userError(conf, info, s)
|
|
when defined(nimsuggest):
|
|
suggestSym(c.graph, info, s, c.graph.usageSym, false)
|
|
styleCheckUse(c, info, s)
|
|
markOwnerModuleAsUsed(c, s)
|
|
|
|
proc safeSemExpr*(c: PContext, n: PNode): PNode =
|
|
# use only for idetools support!
|
|
try:
|
|
result = c.semExpr(c, n)
|
|
except ERecoverableError:
|
|
result = c.graph.emptyNode
|
|
|
|
proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) =
|
|
if n.kind == nkDotExpr:
|
|
var obj = safeSemExpr(c, n[0])
|
|
# it can happen that errnously we have collected the fieldname
|
|
# of the next line, so we check the 'field' is actually on the same
|
|
# line as the object to prevent this from happening:
|
|
let prefix = if n.len == 2 and n[1].info.line == n[0].info.line and
|
|
not c.config.m.trackPosAttached: n[1] else: nil
|
|
suggestFieldAccess(c, obj, prefix, outputs)
|
|
|
|
#if optIdeDebug in gGlobalOptions:
|
|
# echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ)
|
|
#writeStackTrace()
|
|
elif n.kind == nkIdent:
|
|
let
|
|
prefix = if c.config.m.trackPosAttached: nil else: n
|
|
info = n.info
|
|
wholeSymTab(filterSym(it, prefix, pm), ideSug)
|
|
else:
|
|
let prefix = if c.config.m.trackPosAttached: nil else: n
|
|
suggestEverything(c, n, prefix, outputs)
|
|
|
|
proc suggestExprNoCheck*(c: PContext, n: PNode) =
|
|
# This keeps semExpr() from coming here recursively:
|
|
if c.compilesContextId > 0: return
|
|
inc(c.compilesContextId)
|
|
var outputs: Suggestions = @[]
|
|
if c.config.ideCmd == ideSug:
|
|
sugExpr(c, n, outputs)
|
|
elif c.config.ideCmd == ideCon:
|
|
if n.kind in nkCallKinds:
|
|
var a = copyNode(n)
|
|
var x = safeSemExpr(c, n[0])
|
|
if x.kind == nkEmpty or x.typ == nil: x = n[0]
|
|
a.add x
|
|
for i in 1..<n.len:
|
|
# use as many typed arguments as possible:
|
|
var x = safeSemExpr(c, n[i])
|
|
if x.kind == nkEmpty or x.typ == nil: break
|
|
a.add x
|
|
suggestCall(c, a, n, outputs)
|
|
elif n.kind in nkIdentKinds:
|
|
var x = safeSemExpr(c, n)
|
|
if x.kind == nkEmpty or x.typ == nil: x = n
|
|
suggestVar(c, x, outputs)
|
|
|
|
dec(c.compilesContextId)
|
|
if outputs.len > 0 and c.config.ideCmd in {ideSug, ideCon, ideDef}:
|
|
produceOutput(outputs, c.config)
|
|
suggestQuit()
|
|
|
|
proc suggestExpr*(c: PContext, n: PNode) =
|
|
if exactEquals(c.config.m.trackPos, n.info): suggestExprNoCheck(c, n)
|
|
|
|
proc suggestDecl*(c: PContext, n: PNode; s: PSym) =
|
|
let attached = c.config.m.trackPosAttached
|
|
if attached: inc(c.inTypeContext)
|
|
defer:
|
|
if attached: dec(c.inTypeContext)
|
|
suggestExpr(c, n)
|
|
|
|
proc suggestStmt*(c: PContext, n: PNode) =
|
|
suggestExpr(c, n)
|
|
|
|
proc suggestEnum*(c: PContext; n: PNode; t: PType) =
|
|
var outputs: Suggestions = @[]
|
|
suggestSymList(c, t.n, nil, n.info, outputs)
|
|
produceOutput(outputs, c.config)
|
|
if outputs.len > 0: suggestQuit()
|
|
|
|
proc suggestSentinel*(c: PContext) =
|
|
if c.config.ideCmd != ideSug or c.module.position != c.config.m.trackPos.fileIndex.int32: return
|
|
if c.compilesContextId > 0: return
|
|
inc(c.compilesContextId)
|
|
var outputs: Suggestions = @[]
|
|
# suggest everything:
|
|
for (it, scopeN, isLocal) in uniqueSyms(c):
|
|
var pm: PrefixMatch = default(PrefixMatch)
|
|
if filterSymNoOpr(it, nil, pm):
|
|
outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, ideSug,
|
|
newLineInfo(c.config.m.trackPos.fileIndex, 0, -1), it.getQuality,
|
|
PrefixMatch.None, false, scopeN))
|
|
|
|
dec(c.compilesContextId)
|
|
produceOutput(outputs, c.config)
|
|
|
|
when defined(nimsuggest):
|
|
proc onDef(graph: ModuleGraph, s: PSym, info: TLineInfo) =
|
|
if graph.config.suggestVersion == 3 and info.exactEquals(s.info):
|
|
suggestSym(graph, info, s, graph.usageSym)
|
|
|
|
template getPContext(): untyped =
|
|
when c is PContext: c
|
|
else: c.c
|
|
|
|
template onDef*(info: TLineInfo; s: PSym) =
|
|
let c = getPContext()
|
|
onDef(c.graph, s, info)
|