Merge pull request #11766 from nim-lang/araq-detect-unused-imports

[feature] detect unused imports
This commit is contained in:
Andreas Rumpf
2019-07-19 01:06:45 +02:00
committed by GitHub
12 changed files with 66 additions and 35 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

@@ -33,12 +33,14 @@ type
warnFieldXNotSupported, warnCommentXIgnored,
warnTypelessParam,
warnUseBase, warnWriteToForeignHeap, warnUnsafeCode,
warnUnusedImportX,
warnEachIdentIsTuple,
warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2,
warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed,
warnInconsistentSpacing, warnCaseTransition, warnUser,
hintSuccess, hintSuccessX, hintCC,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
hintLineTooLong, hintXDeclaredButNotUsed,
hintConvToBaseNotNeeded,
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
hintConditionAlwaysTrue, hintConditionAlwaysFalse, hintName, hintPattern,
@@ -78,6 +80,7 @@ const
warnUseBase: "use {.base.} for base methods; baseless methods are deprecated",
warnWriteToForeignHeap: "write to foreign heap",
warnUnsafeCode: "unsafe code: '$1'",
warnUnusedImportX: "imported and not used: '$1'",
warnEachIdentIsTuple: "each identifier is a tuple",
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.",
warnProveField: "cannot prove that field '$1' is accessible",
@@ -133,6 +136,7 @@ const
"LanguageXNotSupported", "FieldXNotSupported",
"CommentXIgnored",
"TypelessParam", "UseBase", "WriteToForeignHeap",
"UnusedModule",
"UnsafeCode", "EachIdentIsTuple",
"ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
"GcMem", "Destructor", "LockLevel", "ResultShadowed",
@@ -140,7 +144,8 @@ const
HintsToStr* = [
"Success", "SuccessX", "CC", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"XDeclaredButNotUsed",
"ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
"Path", "CondTrue", "CondFalse", "Name", "Pattern", "Exec", "Link", "Dependency",
"Source", "Performance", "StackTrace", "GCStats", "GlobalVar", "ExpandMacro",

View File

@@ -295,7 +295,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], warnUnusedImportX, 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

@@ -71,7 +71,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
@@ -136,6 +137,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)
@@ -1905,7 +1905,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):
@@ -1930,7 +1930,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,9 +2453,10 @@ proc semExportExcept(c: PContext, n: PNode): PNode =
var s = initTabIter(i, exported.tab)
while s != nil:
if s.kind in ExportableSymKinds+{skModule} and
s.name.id notin exceptSet:
s.name.id notin exceptSet and sfError notin s.flags:
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 =
@@ -2476,14 +2477,16 @@ 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:
localError(c.config, a.info, errGenerated, "cannot export: " & renderTree(a) &
"; enum field cannot be exported individually")
if s.kind in ExportableSymKinds+{skModule}:
if s.kind in ExportableSymKinds+{skModule} and sfError notin s.flags:
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

@@ -107,7 +107,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
@@ -1891,7 +1891,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
@@ -2220,7 +2220,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!