Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Semester Project] Add new front-end phase for unused entities and add support for unused imports #16157

Merged
merged 54 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
27bfda8
Report simple unused import
PaulCoral Sep 30, 2022
93ba6c9
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Sep 30, 2022
f2c1551
Add warnings for unused wildcard imports
PaulCoral Oct 4, 2022
d6fa4cb
Add tests and fixes for unused warning
PaulCoral Oct 8, 2022
9f75334
Add warning unused given imports
PaulCoral Oct 9, 2022
b131601
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Oct 9, 2022
e37c2f9
Add tests for inline method
PaulCoral Oct 10, 2022
2d6da62
Add an `isRunnable` for `CheckUnused`
PaulCoral Oct 11, 2022
617155d
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Oct 14, 2022
0801dd6
Ignore exclusion when unused import
PaulCoral Oct 14, 2022
a6e7d05
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Oct 23, 2022
e1188b6
Add "-Wunused:locals" warning support
PaulCoral Oct 23, 2022
5447cdb
Add "-Wunused:privates" warning option
PaulCoral Oct 24, 2022
950e7de
Fix CheckUnused phasename
PaulCoral Oct 25, 2022
9ad1daa
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Oct 28, 2022
5ecb0eb
fix overlapping warning
PaulCoral Oct 28, 2022
a1c2bae
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Oct 31, 2022
f993782
Fix warning location
PaulCoral Oct 31, 2022
b2fd8cb
Add -Wunused:params,explicits,implicits,patvars
PaulCoral Nov 6, 2022
368748a
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Nov 6, 2022
e69c4a3
Merge branch 'feature/linter/unused' of github.com:PaulCoral/dotty in…
PaulCoral Nov 6, 2022
8ac97b3
Fix requested changes, add new tests
PaulCoral Nov 16, 2022
892621b
Annotation usage, allow unsed Self, add tests
PaulCoral Nov 17, 2022
6d8d20d
Refactor CheckUnused, fix pkg false negative
PaulCoral Nov 17, 2022
412412c
No -Wunused:imports for language imports
PaulCoral Nov 17, 2022
f354b96
Clean CheckUnused phase code, add doc+comments
PaulCoral Nov 18, 2022
d41c6dd
Remove report unused implicit imports
PaulCoral Nov 23, 2022
95c8a40
Revert to unused implicit check, also synthetics
PaulCoral Nov 24, 2022
b2f0fa1
Add warn Annotated Types and "new" kw
PaulCoral Dec 5, 2022
82290c8
Fix unused imports in import stmt
PaulCoral Dec 5, 2022
5a755dd
Reports more import alternatives
PaulCoral Dec 5, 2022
2ed1f1f
Warn unused Imports methods overload
PaulCoral Dec 5, 2022
8a91af1
Add an helpful message for `-W` option
PaulCoral Dec 12, 2022
ca8dbaf
Merge branch 'feature/linter/unused' of github.com:PaulCoral/dotty in…
PaulCoral Dec 12, 2022
426d34a
Fix typo in help -W
PaulCoral Dec 12, 2022
5ab55f8
Fix isBlank not member of String on CI
PaulCoral Dec 12, 2022
0cb2af0
Add `-Wunused:strict-no-implicit-warn` warning option
PaulCoral Dec 13, 2022
d1f9441
Fix test i15503j
PaulCoral Dec 13, 2022
e158a9f
Add better support for unused renamed import
PaulCoral Dec 13, 2022
e2b6b61
Clean, refine scope, add comments
PaulCoral Dec 14, 2022
b0790d1
Refactor CheckUnused into a MiniPhase
PaulCoral Dec 16, 2022
7f04ce3
Add support for @annotation.unused
PaulCoral Dec 26, 2022
779ec7d
Add support for trivial body
PaulCoral Dec 26, 2022
d37d99e
Fix the import precedence in -Wunused:imports
PaulCoral Dec 27, 2022
ae24d64
Improve -Wunused:locals
PaulCoral Dec 27, 2022
527aa31
Fix -Wunused:privates trait accessor
PaulCoral Dec 27, 2022
bfa20a1
Fix -Wunused:locals,privates with recursive
PaulCoral Dec 27, 2022
c70fa68
Fixes for -Wunused:params
PaulCoral Dec 29, 2022
7de90b3
Add better support for trivial methods -Wunused
PaulCoral Jan 3, 2023
c89e27c
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Jan 3, 2023
422ecb8
Merge branch 'lampepfl:main' into feature/linter/unused
PaulCoral Jan 4, 2023
4a06bc6
Remove unused method in CheckUnused
PaulCoral Jan 5, 2023
1db9040
Merge branch 'feature/linter/unused' of github.com:PaulCoral/dotty in…
PaulCoral Jan 5, 2023
08f807c
Make -Wunused:patvars to unsafe
PaulCoral Jan 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Compiler {
protected def frontendPhases: List[List[Phase]] =
List(new Parser) :: // Compiler frontend: scanner, parser
List(new TyperPhase) :: // Compiler frontend: namer, typer
List(new CheckUnused) :: // Check for unused elements
List(new YCheckPositions) :: // YCheck positions
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,13 @@ private sealed trait WarningSettings:
name = "-Wunused",
helpArg = "warning",
descr = "Enable or disable specific `unused` warnings",
choices = List("nowarn", "all"),
choices = List("nowarn", "all", "imports"),
default = Nil
)
object WunusedHas:
def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s))
def nowarn(using Context) = allOr("nowarn")
def imports(using Context) = allOr("imports")

val Wconf: Setting[List[String]] = MultiStringSetting(
"-Wconf",
Expand Down
177 changes: 177 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package dotty.tools.dotc.transform

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.tpd.TreeTraverser
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.ast.untpd.ImportSelector
import dotty.tools.dotc.config.ScalaSettings
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators.i
import dotty.tools.dotc.core.Flags.Given
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.StdNames
import dotty.tools.dotc.report
import dotty.tools.dotc.reporting.Message
import dotty.tools.dotc.typer.ImportInfo
import dotty.tools.dotc.util.Property

/**
* A compiler phase that checks for unused imports or definitions
*
* Basically, it gathers definition/imports and their usage. If a
* definition/imports does not have any usage, then it is reported.
*/
class CheckUnused extends Phase:
PaulCoral marked this conversation as resolved.
Show resolved Hide resolved
import CheckUnused.UnusedData

private val _key = Property.Key[UnusedData]

override def phaseName: String = CheckUnused.phaseName

override def description: String = CheckUnused.description

override def run(using Context): Unit =
val tree = ctx.compilationUnit.tpdTree
val data = UnusedData(ctx)
if data.neededChecks.nonEmpty then
PaulCoral marked this conversation as resolved.
Show resolved Hide resolved
// Execute checks if there exists at lease one config
val fresh = ctx.fresh.setProperty(_key, data)
traverser.traverse(tree)(using fresh)
reportUnusedImport(data.getUnused)

/**
* This traverse is the **main** component of this phase
*
* It traverse the tree the tree and gather the data in the
* corresponding context property
*/
private def traverser = new TreeTraverser {
import tpd._

override def traverse(tree: tpd.Tree)(using Context): Unit = tree match
case imp@Import(_, sels) => sels.foreach { s =>
ctx.property(_key).foreach(_.registerImport(imp))
}
case ident: Ident =>
val id = ident.symbol.id
PaulCoral marked this conversation as resolved.
Show resolved Hide resolved
ctx.property(_key).foreach(_.registerUsed(id))
traverseChildren(tree)
case sel: Select =>
val id = sel.symbol.id
ctx.property(_key).foreach(_.registerUsed(id))
traverseChildren(tree)
case tpd.Block(_,_) | tpd.Template(_,_,_,_) =>
ctx.property(_key).foreach(_.pushScope())
traverseChildren(tree)
ctx.property(_key).foreach(_.popScope())
case _ => traverseChildren(tree)

}

private def reportUnusedImport(sels: List[ImportSelector])(using Context) =
if ctx.settings.WunusedHas.imports then
sels.foreach { s =>
report.warning(i"unused import", s.srcPos)
}
end CheckUnused

object CheckUnused:
val phaseName: String = "check unused"
PaulCoral marked this conversation as resolved.
Show resolved Hide resolved
val description: String = "check for unused elements"

/**
* Various supported configuration chosen by -Wunused:<config>
*/
private enum UnusedConfig:
case UnusedImports
// TODO : handle other cases like unused local def

/**
* A stateful class gathering the infos on :
* - imports
* - definitions
* - usage
*/
private class UnusedData(initctx: Context):
import collection.mutable.{Set => MutSet, Map => MutMap, Stack, ListBuffer}

val neededChecks =
import UnusedConfig._
val hasConfig = initctx.settings.WunusedHas
val mut = MutSet[UnusedConfig]()
if hasConfig.imports(using initctx) then
mut += UnusedImports
mut.toSet

private val used = Stack(MutSet[Int]())
private val impInScope = Stack(MutMap[Int, ListBuffer[ImportSelector]]())
private val unused = ListBuffer[ImportSelector]()

private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match
case [email protected](name) => name == StdNames.nme.WILDCARD
case _ => false

/** Register the id of a found (used) symbol */
def registerUsed(id: Int): Unit =
used.top += id

/** Register an import */
def registerImport(imp: tpd.Import)(using Context): Unit =
val tpd.Import(tree, sels) = imp
val map = impInScope.top
val entries = sels.flatMap{ s =>
if s.isWildcard then
tree.tpe.allMembers
.filter(m => m.symbol.is(Given) == s.isGiven) // given imports
.map(_.symbol.id -> s)
else
val id = tree.tpe.member(s.name.toTermName).symbol.id
val typeId = tree.tpe.member(s.name.toTypeName).symbol.id
List(id -> s, typeId -> s)
}
entries.foreach{(id, sel) =>
map.get(id) match
case None => map.put(id, ListBuffer(sel))
case Some(value) => value += sel
}

/** enter a new scope */
def pushScope(): Unit =
used.push(MutSet())
impInScope.push(MutMap())

/** leave the current scope */
def popScope(): Unit =
val usedImp = MutSet[ImportSelector]()
val poppedImp = impInScope.pop()
val notDefined = used.pop().filter{id =>
poppedImp.remove(id) match
case None => true
case Some(value) =>
usedImp.addAll(value)
false
}
if used.size > 0 then
used.top.addAll(notDefined)
poppedImp.values.flatten.foreach{ sel =>
// If **any** of the entities used by the import is used,
// do not add to the `unused` Set
if !usedImp(sel) then
unused += sel
}

/**
* Leave the scope and return a `List` of unused `ImportSelector`s
*
* The given `List` is sorted by line and then column of the position
*/
def getUnused(using Context): List[ImportSelector] =
popScope()
unused.toList.sortBy{ sel =>
val pos = sel.srcPos.sourcePos
(pos.line, pos.column)
}

end UnusedData
end CheckUnused

87 changes: 87 additions & 0 deletions tests/neg-custom-args/fatal-warnings/i15503/i15503a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// scalac: -Wunused:imports


object FooUnused:
import collection.mutable.Set // error
import collection.mutable.{Map => MutMap} // error
import collection.mutable._ // error

object FooWildcardUnused:
import collection.mutable._ // error

object Foo:
import collection.mutable.Set // OK
import collection.mutable.{Map => MutMap} // OK

val bar = Set() // OK
val baz = MutMap() // OK

object FooWildcard:
import collection.mutable._ // OK

val bar = Set() // OK

object FooNestedUnused:
import collection.mutable.Set // error
object Nested:
def hello = 1

object FooNested:
import collection.mutable.Set // OK
object Nested:
def hello = Set()

object FooGivenUnused:
import SomeGivenImports.given // error

object FooGiven:
import SomeGivenImports.given // OK
import SomeGivenImports._ // error

val foo = summon[Int]

/**
* Import used as type name are considered
* as used.
*
* Import here are only used as types, not as
* Term
*/
object FooTypeName:
import collection.mutable.Set // OK
import collection.mutable.Map // OK
import collection.mutable.Seq // OK
import collection.mutable.ArrayBuilder // OK
import collection.mutable.ListBuffer // error

def checkImplicit[A](using Set[A]) = ()
def checkParamType[B](a: Map[B,B]): Seq[B] = ???
def checkTypeParam[A] = ()

checkTypeParam[ArrayBuilder[Int]]


object InlineChecks:
object InlineFoo:
import collection.mutable.Set // OK
import collection.mutable.Map // error
inline def getSet = Set(1)

object InlinedBar:
import collection.mutable.Set // error
import collection.mutable.Map // error
val a = InlineFoo.getSet

object MacroChecks:
object StringInterpol:
import collection.mutable.Set // OK
import collection.mutable.Map // OK
println(s"This is a mutableSet : ${Set[Map[Int,Int]]()}")

/**
* Some given values for the test
*/
object SomeGivenImports:
given Int = 0
given String = "foo"