[feature] detect unused imports

This commit is contained in:
Araq
2019-07-17 22:36:23 +02:00
parent 44d80dd863
commit 4137a4dbf3
12 changed files with 62 additions and 33 deletions

View File

@@ -167,6 +167,7 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
message(c.config, n.info, warnDeprecated, result.name.s & " is deprecated")
suggestSym(c.config, n.info, result, c.graph.usageSym, false)
importStmtResult.add newSymNode(result, n.info)
c.unusedImports.add((result, n.info))
#newStrNode(toFullPath(c.config, f), n.info)
proc transformImportAs(c: PContext; n: PNode): PNode =

View File

@@ -38,7 +38,8 @@ type
warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed,
warnInconsistentSpacing, warnCaseTransition, warnUser,
hintSuccess, hintSuccessX, hintCC,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
hintLineTooLong, hintXDeclaredButNotUsed, hintUnusedModuleX,
hintConvToBaseNotNeeded,
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
hintConditionAlwaysTrue, hintConditionAlwaysFalse, hintName, hintPattern,
@@ -97,6 +98,7 @@ const
hintCC: "CC: \'$1\'", # unused
hintLineTooLong: "line too long",
hintXDeclaredButNotUsed: "'$1' is declared but not used",
hintUnusedModuleX: "unused module: '$1'",
hintConvToBaseNotNeeded: "conversion to base object is not needed",
hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless",
hintExprAlwaysX: "expression evaluates always to '$1'",
@@ -140,7 +142,8 @@ const
HintsToStr* = [
"Success", "SuccessX", "CC", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"XDeclaredButNotUsed", "UnusedModule",
"ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
"Path", "CondTrue", "CondFalse", "Name", "Pattern", "Exec", "Link", "Dependency",
"Source", "Performance", "StackTrace", "GCStats", "GlobalVar", "ExpandMacro",

View File

@@ -296,7 +296,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
# constraint not fulfilled:
if not ok: return nil
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
if ctx.subMatch:
assert m.len == 3
m.sons[1] = result

View File

@@ -450,7 +450,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
let info = getCallLineInfo(n)
markUsed(c.config, info, sym, c.graph.usageSym)
markUsed(c, info, sym, c.graph.usageSym)
onUse(info, sym)
if sym == c.p.owner:
globalError(c.config, info, "recursive dependency: '$1'" % sym.name.s)
@@ -618,12 +618,17 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
rod.storeNode(c.graph, c.module, result)
proc reportUnusedModules(c: PContext) =
for i in 0..high(c.unusedImports):
message(c.config, c.unusedImports[i][1], hintUnusedModuleX, c.unusedImports[i][0].name.s)
proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
var c = PContext(context)
if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
suggestSentinel(c)
closeScope(c) # close module's scope
rawCloseScope(c) # imported symbols; don't check for unused ones!
reportUnusedModules(c)
result = newNode(nkStmtList)
if n != nil:
internalError(c.config, n.info, "n is not nil") #result := n;

View File

@@ -474,7 +474,7 @@ proc semResolvedCall(c: PContext, x: TCandidate,
assert x.state == csMatch
var finalCallee = x.calleeSym
let info = getCallLineInfo(n)
markUsed(c.config, info, finalCallee, c.graph.usageSym)
markUsed(c, info, finalCallee, c.graph.usageSym)
onUse(info, finalCallee)
assert finalCallee.ast != nil
if x.hasFauxMatch:
@@ -584,7 +584,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
var newInst = generateInstance(c, s, m.bindings, n.info)
newInst.typ.flags.excl tfUnresolved
let info = getCallLineInfo(n)
markUsed(c.config, info, s, c.graph.usageSym)
markUsed(c, info, s, c.graph.usageSym)
onUse(info, s)
result = newSymNode(newInst, info)

View File

@@ -74,7 +74,8 @@ type
TExprFlags* = set[TExprFlag]
PContext* = ref TContext
TContext* = object of TPassContext # a context represents a module
TContext* = object of TPassContext # a context represents the module
# that is currently being compiled
enforceVoidContext*: PType
module*: PSym # the module sym belonging to the context
currentScope*: PScope # current scope
@@ -139,6 +140,7 @@ type
# the generic type has been constructed completely. See
# tests/destructor/topttree.nim for an example that
# would otherwise fail.
unusedImports*: seq[(PSym, TLineInfo)]
template config*(c: PContext): ConfigRef = c.graph.config

View File

@@ -25,7 +25,7 @@ const
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
flags: TExprFlags = {}): PNode =
let info = getCallLineInfo(n)
markUsed(c.config, info, s, c.graph.usageSym)
markUsed(c, info, s, c.graph.usageSym)
onUse(info, s)
# Note: This is n.info on purpose. It prevents template from creating an info
# context when called from an another template
@@ -305,7 +305,7 @@ proc semConv(c: PContext, n: PNode): PNode =
let it = op.sons[i]
let status = checkConvertible(c, result.typ, it)
if status in {convOK, convNotNeedeed}:
markUsed(c.config, n.info, it.sym, c.graph.usageSym)
markUsed(c, n.info, it.sym, c.graph.usageSym)
onUse(n.info, it.sym)
markIndirect(c, it.sym)
return it
@@ -1106,7 +1106,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
let s = getGenSym(c, sym)
case s.kind
of skConst:
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
let typ = skipTypes(s.typ, abstractInst-{tyTypeDesc})
case typ.kind
@@ -1138,7 +1138,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
of skMacro:
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
(n.kind notin nkCallKinds and s.requiredParams > 0):
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
result = symChoice(c, n, s, scClosed)
else:
@@ -1148,13 +1148,13 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
(n.kind notin nkCallKinds and s.requiredParams > 0) or
sfCustomPragma in sym.flags:
let info = getCallLineInfo(n)
markUsed(c.config, info, s, c.graph.usageSym)
markUsed(c, info, s, c.graph.usageSym)
onUse(info, s)
result = symChoice(c, n, s, scClosed)
else:
result = semTemplateExpr(c, n, s, flags)
of skParam:
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil:
# XXX see the hack in sigmatch.nim ...
@@ -1178,7 +1178,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
if s.magic == mNimvm:
localError(c.config, n.info, "illegal context for 'nimvm' magic")
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
result = newSymNode(s, n.info)
# We cannot check for access to outer vars for example because it's still
@@ -1196,7 +1196,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
n.typ = s.typ
return n
of skType:
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
if s.typ.kind == tyStatic and s.typ.base.kind != tyNone and s.typ.n != nil:
return s.typ.n
@@ -1218,7 +1218,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
if f != nil and fieldVisible(c, f):
# is the access to a public field or in the same module or in a friend?
doAssert f == s
markUsed(c.config, n.info, f, c.graph.usageSym)
markUsed(c, n.info, f, c.graph.usageSym)
onUse(n.info, f)
result = newNodeIT(nkDotExpr, n.info, f.typ)
result.add makeDeref(newSymNode(p.selfSym))
@@ -1231,12 +1231,12 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
if ty.sons[0] == nil: break
ty = skipTypes(ty.sons[0], skipPtrs)
# old code, not sure if it's live code:
markUsed(c.config, n.info, s, c.graph.usageSym)
markUsed(c, n.info, s, c.graph.usageSym)
onUse(n.info, s)
result = newSymNode(s, n.info)
else:
let info = getCallLineInfo(n)
markUsed(c.config, info, s, c.graph.usageSym)
markUsed(c, info, s, c.graph.usageSym)
onUse(info, s)
result = newSymNode(s, info)
@@ -1258,7 +1258,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
result = symChoice(c, n, s, scClosed)
if result.kind == nkSym: result = semSym(c, n, s, flags)
else:
markUsed(c.config, n.sons[1].info, s, c.graph.usageSym)
markUsed(c, n.sons[1].info, s, c.graph.usageSym)
result = semSym(c, n, s, flags)
onUse(n.sons[1].info, s)
return
@@ -1322,7 +1322,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
result = newSymNode(f)
result.info = n.info
result.typ = ty
markUsed(c.config, n.info, f, c.graph.usageSym)
markUsed(c, n.info, f, c.graph.usageSym)
onUse(n.info, f)
return
of tyObject, tyTuple:
@@ -1357,7 +1357,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
else: true
if not visibilityCheckNeeded or fieldVisible(c, f):
# is the access to a public field or in the same module or in a friend?
markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
markUsed(c, n.sons[1].info, f, c.graph.usageSym)
onUse(n.sons[1].info, f)
n.sons[0] = makeDeref(n.sons[0])
n.sons[1] = newSymNode(f) # we now have the correct field
@@ -1371,7 +1371,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
elif ty.kind == tyTuple and ty.n != nil:
f = getSymFromList(ty.n, i)
if f != nil:
markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
markUsed(c, n.sons[1].info, f, c.graph.usageSym)
onUse(n.sons[1].info, f)
n.sons[0] = makeDeref(n.sons[0])
n.sons[1] = newSymNode(f)
@@ -1902,7 +1902,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode =
if expandedSym.kind == skError: return n
macroCall.sons[0] = newSymNode(expandedSym, macroCall.info)
markUsed(c.config, n.info, expandedSym, c.graph.usageSym)
markUsed(c, n.info, expandedSym, c.graph.usageSym)
onUse(n.info, expandedSym)
if isCallExpr(macroCall):
@@ -1927,7 +1927,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode =
else:
let info = macroCall.sons[0].info
macroCall.sons[0] = newSymNode(cand, info)
markUsed(c.config, info, cand, c.graph.usageSym)
markUsed(c, info, cand, c.graph.usageSym)
onUse(info, cand)
# we just perform overloading resolution here:
@@ -2453,6 +2453,7 @@ proc semExportExcept(c: PContext, n: PNode): PNode =
s.name.id notin exceptSet:
strTableAdd(c.module.tab, s)
result.add newSymNode(s, n.info)
markUsed(c, n.info, s, c.graph.usageSym)
s = nextIter(i, exported.tab)
proc semExport(c: PContext, n: PNode): PNode =
@@ -2473,6 +2474,7 @@ proc semExport(c: PContext, n: PNode): PNode =
strTableAdd(c.module.tab, it)
result.add newSymNode(it, a.info)
it = nextIter(ti, s.tab)
markUsed(c, n.info, s, c.graph.usageSym)
else:
while s != nil:
if s.kind == skEnumField:
@@ -2481,6 +2483,7 @@ proc semExport(c: PContext, n: PNode): PNode =
if s.kind in ExportableSymKinds+{skModule}:
result.add(newSymNode(s, a.info))
strTableAdd(c.module.tab, s)
markUsed(c, n.info, s, c.graph.usageSym)
s = nextOverloadIter(o, c, a)
proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =

View File

@@ -812,7 +812,7 @@ proc handleCaseStmtMacro(c: PContext; n: PNode): PNode =
errors, false)
if r.state == csMatch:
var match = r.calleeSym
markUsed(c.config, n[0].info, match, c.graph.usageSym)
markUsed(c, n[0].info, match, c.graph.usageSym)
onUse(n[0].info, match)
# but pass 'n' to the 'match' macro, not 'n[0]':

View File

@@ -64,7 +64,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode =
# (s.kind notin routineKinds or s.magic != mNone):
# for instance 'nextTry' is both in tables.nim and astalgo.nim ...
result = newSymNode(s, info)
markUsed(c.config, info, s, c.graph.usageSym)
markUsed(c, info, s, c.graph.usageSym)
onUse(info, s)
else:
# semantic checking requires a type; ``fitNode`` deals with it

View File

@@ -353,7 +353,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
if result.isNil:
result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared})
if result != nil:
markUsed(c.config, n.info, result, c.graph.usageSym)
markUsed(c, n.info, result, c.graph.usageSym)
onUse(n.info, result)
if result.kind == skParam and result.typ.kind == tyTypeDesc:
@@ -1063,7 +1063,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false))
of tyGenericParam:
markUsed(c.config, paramType.sym.info, paramType.sym, c.graph.usageSym)
markUsed(c, paramType.sym.info, paramType.sym, c.graph.usageSym)
onUse(paramType.sym.info, paramType.sym)
if tfWildcard in paramType.flags:
paramType.flags.excl tfWildcard
@@ -1751,7 +1751,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
else:
assignType(prev, t)
result = prev
markUsed(c.config, n.info, n.sym, c.graph.usageSym)
markUsed(c, n.info, n.sym, c.graph.usageSym)
onUse(n.info, n.sym)
else:
if s.kind != skError:

View File

@@ -99,7 +99,7 @@ type
const
isNilConversion = isConvertible # maybe 'isIntConv' fits better?
proc markUsed*(conf: ConfigRef; info: TLineInfo, s: PSym; usageSym: var PSym)
proc markUsed*(c: PContext; info: TLineInfo, s: PSym; usageSym: var PSym)
template hasFauxMatch*(c: TCandidate): bool = c.fauxMatch != tyNone
@@ -1882,7 +1882,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
dest = generateTypeInstance(c, m.bindings, arg, dest)
let fdest = typeRel(m, f, dest)
if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind == tyVar):
markUsed(c.config, arg.info, c.converters[i], c.graph.usageSym)
markUsed(c, arg.info, c.converters[i], c.graph.usageSym)
var s = newSymNode(c.converters[i])
s.typ = c.converters[i].typ
s.info = arg.info
@@ -2211,7 +2211,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
else: result = nil
else:
# only one valid interpretation found:
markUsed(m.c.config, arg.info, arg.sons[best].sym, m.c.graph.usageSym)
markUsed(m.c, arg.info, arg.sons[best].sym, m.c.graph.usageSym)
onUse(arg.info, arg.sons[best].sym)
result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best],
argOrig)

View File

@@ -528,7 +528,21 @@ proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) =
return
localError(conf, info, "usage of '$1' is a user-defined error" % s.name.s)
proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) =
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):
if c.unusedImports[i][0] == module:
# mark it as used:
c.unusedImports.del(i)
else:
inc i
proc markUsed(c: PContext; info: TLineInfo; s: PSym; usageSym: var PSym) =
let conf = c.config
incl(s.flags, sfUsed)
if s.kind == skEnumField and s.owner != nil:
incl(s.owner.flags, sfUsed)
@@ -541,6 +555,7 @@ proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) =
suggestSym(conf, info, s, usageSym, false)
if {optStyleHint, optStyleError} * conf.globalOptions != {}:
styleCheckUse(conf, info, s)
markOwnerModuleAsUsed(c, s)
proc safeSemExpr*(c: PContext, n: PNode): PNode =
# use only for idetools support!