Skip to content

Commit c043299

Browse files
authored
Backport "Make CheckUnused run both after Typer and Inlining" (#17279)
Backports #17206
2 parents 29bc3db + e369d90 commit c043299

File tree

5 files changed

+104
-37
lines changed

5 files changed

+104
-37
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Compiler {
3535
protected def frontendPhases: List[List[Phase]] =
3636
List(new Parser) :: // Compiler frontend: scanner, parser
3737
List(new TyperPhase) :: // Compiler frontend: namer, typer
38-
List(new CheckUnused) :: // Check for unused elements
38+
List(new CheckUnused.PostTyper) :: // Check for unused elements
3939
List(new YCheckPositions) :: // YCheck positions
4040
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4141
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
@@ -50,6 +50,7 @@ class Compiler {
5050
List(new Pickler) :: // Generate TASTY info
5151
List(new Inlining) :: // Inline and execute macros
5252
List(new PostInlining) :: // Add mirror support for inlined code
53+
List(new CheckUnused.PostInlining) :: // Check for unused elements
5354
List(new Staging) :: // Check staging levels and heal staged types
5455
List(new Splicing) :: // Replace level 1 splices with holes
5556
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

+66-34
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,23 @@ import dotty.tools.dotc.core.Definitions
2525
import dotty.tools.dotc.core.NameKinds.WildcardParamName
2626
import dotty.tools.dotc.core.Symbols.Symbol
2727
import dotty.tools.dotc.core.StdNames.nme
28-
28+
import scala.math.Ordering
2929

3030
/**
3131
* A compiler phase that checks for unused imports or definitions
3232
*
3333
* Basically, it gathers definition/imports and their usage. If a
3434
* definition/imports does not have any usage, then it is reported.
3535
*/
36-
class CheckUnused extends MiniPhase:
37-
import CheckUnused.UnusedData
38-
39-
/**
40-
* The key used to retrieve the "unused entity" analysis metadata,
41-
* from the compilation `Context`
42-
*/
43-
private val _key = Property.Key[UnusedData]
36+
class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _key: Property.Key[CheckUnused.UnusedData]) extends MiniPhase:
37+
import CheckUnused.*
38+
import UnusedData.*
4439

4540
private def unusedDataApply[U](f: UnusedData => U)(using Context): Context =
4641
ctx.property(_key).foreach(f)
4742
ctx
48-
private def getUnusedData(using Context): Option[UnusedData] =
49-
ctx.property(_key)
5043

51-
override def phaseName: String = CheckUnused.phaseName
44+
override def phaseName: String = CheckUnused.phaseNamePrefix + suffix
5245

5346
override def description: String = CheckUnused.description
5447

@@ -60,13 +53,21 @@ class CheckUnused extends MiniPhase:
6053

6154
override def prepareForUnit(tree: tpd.Tree)(using Context): Context =
6255
val data = UnusedData()
56+
tree.getAttachment(_key).foreach(oldData =>
57+
data.unusedAggregate = oldData.unusedAggregate
58+
)
6359
val fresh = ctx.fresh.setProperty(_key, data)
60+
tree.putAttachment(_key, data)
6461
fresh
6562

6663
// ========== END + REPORTING ==========
6764

6865
override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree =
69-
unusedDataApply(ud => reportUnused(ud.getUnused))
66+
unusedDataApply { ud =>
67+
ud.finishAggregation()
68+
if(phaseMode == PhaseMode.Report) then
69+
ud.unusedAggregate.foreach(reportUnused)
70+
}
7071
tree
7172

7273
// ========== MiniPhase Prepare ==========
@@ -252,31 +253,35 @@ class CheckUnused extends MiniPhase:
252253
private def traverseAnnotations(sym: Symbol)(using Context): Unit =
253254
sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree))
254255

256+
255257
/** Do the actual reporting given the result of the anaylsis */
256258
private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit =
257-
import CheckUnused.WarnTypes
258-
res.warnings.foreach { s =>
259+
res.warnings.toList.sortBy(_.pos.line)(using Ordering[Int]).foreach { s =>
259260
s match
260-
case (t, WarnTypes.Imports) =>
261+
case UnusedSymbol(t, _, WarnTypes.Imports) =>
261262
report.warning(s"unused import", t)
262-
case (t, WarnTypes.LocalDefs) =>
263+
case UnusedSymbol(t, _, WarnTypes.LocalDefs) =>
263264
report.warning(s"unused local definition", t)
264-
case (t, WarnTypes.ExplicitParams) =>
265+
case UnusedSymbol(t, _, WarnTypes.ExplicitParams) =>
265266
report.warning(s"unused explicit parameter", t)
266-
case (t, WarnTypes.ImplicitParams) =>
267+
case UnusedSymbol(t, _, WarnTypes.ImplicitParams) =>
267268
report.warning(s"unused implicit parameter", t)
268-
case (t, WarnTypes.PrivateMembers) =>
269+
case UnusedSymbol(t, _, WarnTypes.PrivateMembers) =>
269270
report.warning(s"unused private member", t)
270-
case (t, WarnTypes.PatVars) =>
271+
case UnusedSymbol(t, _, WarnTypes.PatVars) =>
271272
report.warning(s"unused pattern variable", t)
272273
}
273274

274275
end CheckUnused
275276

276277
object CheckUnused:
277-
val phaseName: String = "checkUnused"
278+
val phaseNamePrefix: String = "checkUnused"
278279
val description: String = "check for unused elements"
279280

281+
enum PhaseMode:
282+
case Aggregate
283+
case Report
284+
280285
private enum WarnTypes:
281286
case Imports
282287
case LocalDefs
@@ -285,19 +290,30 @@ object CheckUnused:
285290
case PrivateMembers
286291
case PatVars
287292

293+
/**
294+
* The key used to retrieve the "unused entity" analysis metadata,
295+
* from the compilation `Context`
296+
*/
297+
private val _key = Property.StickyKey[UnusedData]
298+
299+
class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key)
300+
301+
class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key)
302+
288303
/**
289304
* A stateful class gathering the infos on :
290305
* - imports
291306
* - definitions
292307
* - usage
293308
*/
294309
private class UnusedData:
295-
import dotty.tools.dotc.transform.CheckUnused.UnusedData.UnusedResult
296310
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack}
297-
import UnusedData.ScopeType
311+
import UnusedData.*
298312

299313
/** The current scope during the tree traversal */
300-
var currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other)
314+
val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other)
315+
316+
var unusedAggregate: Option[UnusedResult] = None
301317

302318
/* IMPORTS */
303319
private val impInScope = MutStack(MutSet[tpd.Import]())
@@ -344,6 +360,17 @@ object CheckUnused:
344360
execInNewScope
345361
popScope()
346362

363+
def finishAggregation(using Context)(): Unit =
364+
val unusedInThisStage = this.getUnused
365+
this.unusedAggregate match {
366+
case None =>
367+
this.unusedAggregate = Some(unusedInThisStage)
368+
case Some(prevUnused) =>
369+
val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings)
370+
this.unusedAggregate = Some(UnusedResult(intersection))
371+
}
372+
373+
347374
/**
348375
* Register a found (used) symbol along with its name
349376
*
@@ -453,12 +480,13 @@ object CheckUnused:
453480
*
454481
* The given `List` is sorted by line and then column of the position
455482
*/
483+
456484
def getUnused(using Context): UnusedResult =
457485
popScope()
458486

459487
val sortedImp =
460488
if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then
461-
unusedImport.map(d => d.srcPos -> WarnTypes.Imports).toList
489+
unusedImport.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
462490
else
463491
Nil
464492
val sortedLocalDefs =
@@ -467,7 +495,7 @@ object CheckUnused:
467495
.filterNot(d => d.symbol.usedDefContains)
468496
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
469497
.filterNot(d => containsSyntheticSuffix(d.symbol))
470-
.map(d => d.namePos -> WarnTypes.LocalDefs).toList
498+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
471499
else
472500
Nil
473501
val sortedExplicitParams =
@@ -476,23 +504,23 @@ object CheckUnused:
476504
.filterNot(d => d.symbol.usedDefContains)
477505
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
478506
.filterNot(d => containsSyntheticSuffix(d.symbol))
479-
.map(d => d.namePos -> WarnTypes.ExplicitParams).toList
507+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)).toList
480508
else
481509
Nil
482510
val sortedImplicitParams =
483511
if ctx.settings.WunusedHas.implicits then
484512
implicitParamInScope
485513
.filterNot(d => d.symbol.usedDefContains)
486514
.filterNot(d => containsSyntheticSuffix(d.symbol))
487-
.map(d => d.namePos -> WarnTypes.ImplicitParams).toList
515+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)).toList
488516
else
489517
Nil
490518
val sortedPrivateDefs =
491519
if ctx.settings.WunusedHas.privates then
492520
privateDefInScope
493521
.filterNot(d => d.symbol.usedDefContains)
494522
.filterNot(d => containsSyntheticSuffix(d.symbol))
495-
.map(d => d.namePos -> WarnTypes.PrivateMembers).toList
523+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
496524
else
497525
Nil
498526
val sortedPatVars =
@@ -501,14 +529,14 @@ object CheckUnused:
501529
.filterNot(d => d.symbol.usedDefContains)
502530
.filterNot(d => containsSyntheticSuffix(d.symbol))
503531
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
504-
.map(d => d.namePos -> WarnTypes.PatVars).toList
532+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)).toList
505533
else
506534
Nil
507535
val warnings = List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
508-
val pos = s._1.sourcePos
536+
val pos = s.pos.sourcePos
509537
(pos.line, pos.column)
510538
}
511-
UnusedResult(warnings, Nil)
539+
UnusedResult(warnings.toSet)
512540
end getUnused
513541
//============================ HELPERS ====================================
514542

@@ -707,7 +735,11 @@ object CheckUnused:
707735
case _:tpd.Block => Local
708736
case _ => Other
709737

738+
case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes)
710739
/** A container for the results of the used elements analysis */
711-
case class UnusedResult(warnings: List[(dotty.tools.dotc.util.SrcPos, WarnTypes)], usedImports: List[(tpd.Import, untpd.ImportSelector)])
740+
case class UnusedResult(warnings: Set[UnusedSymbol])
741+
object UnusedResult:
742+
val Empty = UnusedResult(Set.empty)
743+
712744
end CheckUnused
713745

tests/neg-custom-args/fatal-warnings/i15503a.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ object FooTypeName:
6363

6464
object InlineChecks:
6565
object InlineFoo:
66-
import collection.mutable.Set // OK
66+
import collection.mutable.Set // ok
6767
import collection.mutable.Map // error
6868
inline def getSet = Set(1)
6969

7070
object InlinedBar:
71-
import collection.mutable.Set // error
71+
import collection.mutable.Set // ok
7272
import collection.mutable.Map // error
7373
val a = InlineFoo.getSet
7474

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted.*
2+
3+
def findMethodSymbol(using q: Quotes)(s: quotes.reflect.Symbol): quotes.reflect.Symbol =
4+
if s.isDefDef then
5+
s
6+
else
7+
findMethodSymbol(using q)(s.owner)
8+
end findMethodSymbol
9+
10+
11+
inline def adder: Int = ${
12+
adderImpl
13+
}
14+
15+
def adderImpl(using q: Quotes): Expr[Int] =
16+
import quotes.reflect.*
17+
18+
val inputs = findMethodSymbol(using q)(q.reflect.Symbol.spliceOwner).tree match
19+
case DefDef(_, params, _, _) =>
20+
params.last match
21+
case TermParamClause(valDefs) =>
22+
valDefs.map(vd => Ref(vd.symbol).asExprOf[Int])
23+
inputs.reduce((exp1, exp2) => '{ $exp1 + $exp2 })
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// scalac: -Wunused:all
2+
3+
object Foo {
4+
private def myMethod(a: Int, b: Int, c: Int) = adder // ok
5+
myMethod(1, 2, 3)
6+
7+
private def myMethodFailing(a: Int, b: Int, c: Int) = a + 0 // error // error
8+
myMethodFailing(1, 2, 3)
9+
}
10+
11+

0 commit comments

Comments
 (0)