IC: first steps towards 'nim check --def --ic:on' (#17714)

* IC: first steps towards 'nim check --def --ic:on'
* IC navigator: deduplicate output lines
* IC navigator: progress
* IC navigator: use a different nimcache entry
* IC navigator: special logic for templates/macros
* IC navigator: proper error messages
* IC navigator: prepare for testing code; document only what currently works somewhat
This commit is contained in:
Andreas Rumpf 2021-04-14 16:44:37 +02:00 • committed by GitHub
commit 67e28c07f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 211 additions and 12 deletions

View file

@ -89,14 +89,27 @@ proc rememberConfig(c: var PackedEncoder; m: var PackedModule; config: ConfigRef
#primConfigFields rem
m.cfg = pc
const
debugConfigDiff = defined(debugConfigDiff)
when debugConfigDiff:
import std / [hashes, tables, intsets, sha1, strutils, sets]
proc configIdentical(m: PackedModule; config: ConfigRef): bool =
result = m.definedSymbols == definedSymbolsAsString(config)
#if not result:
# echo "A ", m.definedSymbols, " ", definedSymbolsAsString(config)
when debugConfigDiff:
if not result:
var wordsA = m.definedSymbols.split(Whitespace).toHashSet()
var wordsB = definedSymbolsAsString(config).split(Whitespace).toHashSet()
for c in wordsA - wordsB:
echo "in A but not in B ", c
for c in wordsB - wordsA:
echo "in B but not in A ", c
template eq(x) =
result = result and m.cfg.x == config.x
#if not result:
# echo "B ", m.cfg.x, " ", config.x
when debugConfigDiff:
if m.cfg.x != config.x:
echo "B ", m.cfg.x, " ", config.x
primConfigFields eq
proc rememberStartupConfig*(dest: var PackedConfig, config: ConfigRef) =
@ -124,7 +137,7 @@ proc toLitId(x: FileIndex; c: var PackedEncoder; m: var PackedModule): LitId =
c.filenames[x] = result
c.lastFile = x
c.lastLit = result
assert result != LitId(0)
assert result != LitId(0)
proc toFileIndex*(x: LitId; m: PackedModule; config: ConfigRef): FileIndex =
result = msgs.fileInfoIdx(config, AbsoluteFile m.sh.strings[x])
@ -144,6 +157,8 @@ proc initEncoder*(c: var PackedEncoder; m: var PackedModule; moduleSym: PSym; co
m.bodies = newTreeFrom(m.topLevel)
m.toReplay = newTreeFrom(m.topLevel)
c.lastFile = FileIndex(-10)
let thisNimFile = FileIndex c.thisModule
var h = msgs.getHash(config, thisNimFile)
if h.len == 0:
@ -464,6 +479,9 @@ proc storeInstantiation*(c: var PackedEncoder; m: var PackedModule; s: PSym; i:
concreteTypes: t)
toPackedGeneratedProcDef(i.sym, c, m)
proc storeExpansion*(c: var PackedEncoder; m: var PackedModule; info: TLineInfo; s: PSym) =
toPackedNode(newSymNode(s, info), m.bodies, c, m)
proc loadError(err: RodFileError; filename: AbsoluteFile; config: ConfigRef;) =
case err
of cannotOpen:

147
compiler/ic/navigator.nim Normal file
View file

@ -0,0 +1,147 @@
#
#
# The Nim Compiler
# (c) Copyright 2021 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Supports the "nim check --ic:on --def --track:FILE,LINE,COL"
## IDE-like features. It uses the set of .rod files to accomplish
## its task. The set must cover a complete Nim project.
import std / sets
from os import nil
from std/private/miscdollars import toLocation
import ".." / [ast, modulegraphs, msgs, options]
import packed_ast, bitabs, ic
type
NavContext = object
g: ModuleGraph
thisModule: int32
trackPos: PackedLineInfo
alreadyEmitted: HashSet[string]
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
proc isTracked(current, trackPos: PackedLineInfo, tokenLen: int): bool =
if current.file == trackPos.file and current.line == trackPos.line:
let col = trackPos.col
if col >= current.col and col < current.col+tokenLen:
return true
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
result = s.name != LitId(0) and
isTracked(info, c.trackPos, c.g.packed[c.thisModule].fromDisk.sh.strings[s.name].len)
proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
let name = c.g.packed[s.module].fromDisk.sh.syms[s.item].name
result = name != LitId(0) and
isTracked(info, c.trackPos, c.g.packed[s.module].fromDisk.sh.strings[name].len)
const
EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
proc search(c: var NavContext; tree: PackedTree): ItemId =
# We use the linear representation here directly:
for i in 0..high(tree.nodes):
case tree.nodes[i].kind
of nkSym:
let item = tree.nodes[i].operand
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.sh.syms[item], tree.nodes[i].info):
return ItemId(module: c.thisModule, item: item)
of nkModuleRef:
if tree.nodes[i].info.line == c.trackPos.line and tree.nodes[i].info.file == c.trackPos.file:
let (n1, n2) = sons2(tree, NodePos i)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
if searchForeignSym(c, itemId, tree.nodes[i].info):
return itemId
else: discard
return EmptyItemId
proc isDecl(tree: PackedTree; n: NodePos): bool =
# XXX This is not correct yet.
const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
nkIdentDefs, nkEnumTy, nkVarTuple}
result = n.int >= 0 and tree[n.int].kind in declarativeNodes
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
var m = ""
var file = c.g.packed[c.thisModule].fromDisk.sh.strings[info.file]
if c.outputSep == ' ':
file = os.extractFilename file
toLocation(m, file, info.line.int, info.col.int + ColOffset)
if not c.alreadyEmitted.containsOrIncl(m):
echo (if isDecl: "def" else: "usage"), c.outputSep, m
proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
for i in 0..high(tree.nodes):
case tree.nodes[i].kind
of nkSym:
let item = tree.nodes[i].operand
if sym.item == item and sym.module == c.thisModule:
usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
of nkModuleRef:
let (n1, n2) = sons2(tree, NodePos i)
assert n1.kind == nkInt32Lit
assert n2.kind == nkInt32Lit
let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
if itemId.item == sym.item and sym.module == itemId.module:
usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
else: discard
proc nav(g: ModuleGraph) =
# translate the track position to a packed position:
let unpacked = g.config.m.trackPos
let mid = unpacked.fileIndex
let fileId = g.packed[int32 mid].fromDisk.sh.strings.getKeyId(toFullPath(g.config, mid))
if fileId == LitId(0):
internalError(g.config, unpacked, "cannot find a valid file ID")
return
var c = NavContext(
g: g,
thisModule: int32 mid,
trackPos: PackedLineInfo(line: unpacked.line, col: unpacked.col, file: fileId),
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
)
var symId = search(c, g.packed[int32 mid].fromDisk.topLevel)
if symId == EmptyItemId:
symId = search(c, g.packed[int32 mid].fromDisk.bodies)
if symId == EmptyItemId:
localError(g.config, unpacked, "no symbol at this position")
return
for i in 0..high(g.packed):
# case statement here to enforce exhaustive checks.
case g.packed[i].status
of undefined:
discard "nothing to do"
of loading:
assert false, "cannot check integrity: Module still loading"
of stored, storing, outdated, loaded:
c.thisModule = int32 i
list(c, g.packed[i].fromDisk.topLevel, symId)
list(c, g.packed[i].fromDisk.bodies, symId)
proc navDefinition*(g: ModuleGraph) = nav(g)
proc navUsages*(g: ModuleGraph) = nav(g)
proc navDefusages*(g: ModuleGraph) = nav(g)
proc writeRodFiles*(g: ModuleGraph) =
for i in 0..high(g.packed):
case g.packed[i].status
of undefined, loading, stored, loaded:
discard "nothing to do"
of storing, outdated:
closeRodFile(g, g.packed[i].module)

View file

@ -258,9 +258,9 @@ iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
proc parentImpl(tree: PackedTree; n: NodePos): NodePos =
# finding the parent of a node is rather easy:
var pos = n.int - 1
while pos >= 0 and isAtom(tree, pos) or (pos + tree.nodes[pos].operand - 1 < n.int):
while pos >= 0 and (isAtom(tree, pos) or (pos + tree.nodes[pos].operand - 1 < n.int)):
dec pos
assert pos >= 0, "node has no parent"
#assert pos >= 0, "node has no parent"
result = NodePos(pos)
template parent*(n: NodePos): NodePos = parentImpl(tree, n)