Skip to content

Commit f38f384

Browse files
Backport "Always traverse Inlined.call in linter" to 3.7.4 (#24227)
Backports #24043 to the 3.7.4. PR submitted by the release tooling. [skip ci]
2 parents 3557626 + b650ed6 commit f38f384

File tree

5 files changed

+182
-48
lines changed

5 files changed

+182
-48
lines changed

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

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
1-
package dotty.tools.dotc.transform
2-
3-
import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar}
4-
import dotty.tools.dotc.ast.tpd.*
5-
import dotty.tools.dotc.ast.untpd, untpd.ImportSelector
6-
import dotty.tools.dotc.config.ScalaSettings
7-
import dotty.tools.dotc.core.Contexts.*
8-
import dotty.tools.dotc.core.Flags.*
9-
import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName}
10-
import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName}
11-
import dotty.tools.dotc.core.NameKinds.{
12-
BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
13-
import dotty.tools.dotc.core.StdNames.nme
14-
import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule}
15-
import dotty.tools.dotc.core.Types.*
16-
import dotty.tools.dotc.report
17-
import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol}
18-
import dotty.tools.dotc.rewrites.Rewrites
19-
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
20-
import dotty.tools.dotc.typer.{ImportInfo, Typer}
21-
import dotty.tools.dotc.typer.Deriving.OriginalTypeClass
22-
import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span
23-
import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace}
24-
import dotty.tools.dotc.util.chaining.*
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import ast.*, desugar.{ForArtifact, PatternVar}, tpd.*, untpd.ImportSelector
5+
import config.ScalaSettings
6+
import core.*, Contexts.*, Flags.*
7+
import Names.{Name, SimpleName, DerivedName, TermName, termName}
8+
import NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
9+
import NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName}
10+
import Scopes.newScope
11+
import StdNames.nme
12+
import Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule}
13+
import Types.*
14+
import reporting.{CodeAction, UnusedSymbol}
15+
import rewrites.Rewrites
16+
17+
import MegaPhase.MiniPhase
18+
import typer.{ImportInfo, Typer}
19+
import typer.Deriving.OriginalTypeClass
20+
import typer.Implicits.{ContextualImplicits, RenamedImplicitRef}
21+
import util.{Property, Spans, SrcPos}, Spans.Span
22+
import util.Chars.{isLineBreakChar, isWhitespace}
23+
import util.chaining.*
2524

2625
import java.util.IdentityHashMap
2726

@@ -56,17 +55,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
5655
if tree.symbol.exists then
5756
// if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site
5857
val resolving =
59-
refInfos.inlined.isEmpty
60-
|| tree.srcPos.isZeroExtentSynthetic
61-
|| refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos))
62-
if resolving && !ignoreTree(tree) then
58+
tree.srcPos.isUserCode
59+
|| tree.srcPos.isZeroExtentSynthetic // take as summonInline
60+
if !ignoreTree(tree) then
6361
def loopOverPrefixes(prefix: Type, depth: Int): Unit =
6462
if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then
65-
resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix)
63+
resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix, imports = resolving)
6664
loopOverPrefixes(prefix.normalizedPrefix, depth + 1)
6765
if tree.srcPos.isZeroExtentSynthetic then
6866
loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0)
69-
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject)
67+
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject, imports = resolving)
7068
else if tree.hasType then
7169
resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject)
7270
refInfos.isAssignment = false
@@ -142,14 +140,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
142140
case _ =>
143141
tree
144142

145-
override def prepareForInlined(tree: Inlined)(using Context): Context =
146-
refInfos.inlined.push(tree.call.srcPos)
147-
ctx
148143
override def transformInlined(tree: Inlined)(using Context): tree.type =
149-
//transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs
150-
val _ = refInfos.inlined.pop()
151-
if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then
152-
transformAllDeep(tree.call)
144+
transformAllDeep(tree.call)
153145
tree
154146

155147
override def prepareForBind(tree: Bind)(using Context): Context =
@@ -297,8 +289,11 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
297289
* e.g., in `scala.Int`, `scala` is in scope for typer, but here we reverse-engineer the attribution.
298290
* For Select, lint does not look up `<empty>.scala` (so top-level syms look like magic) but records `scala.Int`.
299291
* For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence.
292+
*
293+
* The `imports` flag is whether an identifier can mark an import as used: the flag is false
294+
* for inlined code, except for `summonInline` (and related constructs) which are resolved at inlining.
300295
*/
301-
def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit =
296+
def resolveUsage(sym0: Symbol, name: Name, prefix: Type, imports: Boolean = true)(using Context): Unit =
302297
import PrecedenceLevels.*
303298
val sym = sym0.userSymbol
304299

@@ -392,7 +387,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
392387
// record usage and possibly an import
393388
if !enclosed then
394389
refInfos.addRef(sym)
395-
if candidate != NoContext && candidate.isImportContext && importer != null then
390+
if imports && candidate != NoContext && candidate.isImportContext && importer != null then
396391
refInfos.sels.put(importer, ())
397392
end resolveUsage
398393
end CheckUnused
@@ -432,7 +427,7 @@ object CheckUnused:
432427
val nowarn = mutable.Set.empty[Symbol] // marked @nowarn
433428
val imps = new IdentityHashMap[Import, Unit] // imports
434429
val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors
435-
def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then
430+
def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then
436431
tree match
437432
case imp: Import =>
438433
if inliners == 0
@@ -461,7 +456,6 @@ object CheckUnused:
461456
if tree.symbol ne NoSymbol then
462457
defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path
463458

464-
val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions)
465459
var inliners = 0 // depth of inline def (not inlined yet)
466460

467461
// instead of refs.addOne, use addRef to distinguish a read from a write to var
@@ -999,6 +993,10 @@ object CheckUnused:
999993
extension (pos: SrcPos)
1000994
def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent
1001995
def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists
996+
def isUserCode(using Context): Boolean =
997+
val inlineds = enclosingInlineds // per current context
998+
inlineds.isEmpty
999+
|| inlineds.last.srcPos.sourcePos.contains(pos.sourcePos)
10021000

10031001
extension [A <: AnyRef](arr: Array[A])
10041002
// returns `until` if not satisfied

tests/warn/i15503a.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ object FooGiven:
3939

4040
val foo = summon[Int]
4141

42+
object SomeGivenImports:
43+
given Int = 0
44+
given String = "foo"
45+
4246
/**
4347
* Import used as type name are considered
4448
* as used.
@@ -69,7 +73,7 @@ object InlineChecks:
6973
object InlinedBar:
7074
import collection.mutable.Set // warn (don't be fooled by inline expansion)
7175
import collection.mutable.Map // warn
72-
val a = InlineFoo.getSet
76+
val a = InlineFoo.getSet // expansion is attributed mutable.Set.apply(1)
7377

7478
object MacroChecks:
7579
object StringInterpol:
@@ -91,12 +95,7 @@ object IgnoreExclusion:
9195
def check =
9296
val a = Set(1)
9397
val b = Map(1 -> 2)
94-
/**
95-
* Some given values for the test
96-
*/
97-
object SomeGivenImports:
98-
given Int = 0
99-
given String = "foo"
98+
def c = Seq(42)
10099

101100
/* BEGIN : Check on packages*/
102101
package nestedpackageimport:

tests/warn/i24034/circe.scala

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
2+
// circe.scala
3+
4+
package io.circe
5+
6+
import scala.compiletime.*
7+
import scala.deriving.Mirror
8+
import scala.quoted.*
9+
10+
trait Encoder[A]:
11+
def encode(value: A): String
12+
13+
object Encoder:
14+
trait AsObject[A] extends Encoder[A]
15+
given Encoder[String] = ???
16+
17+
trait Configuration
18+
object Configuration:
19+
val default: Configuration = ???
20+
21+
object Default:
22+
given [A]: Default[A] = ???
23+
trait Default[T]
24+
25+
trait Codec[A] extends Encoder[A]
26+
object Codec:
27+
trait AsObject[A] extends Encoder.AsObject[A]
28+
object AsObject:
29+
inline final def derived[A](using inline A: Mirror.Of[A]): Codec.AsObject[A] =
30+
ConfiguredCodec.derived[A](using Configuration.default)
31+
inline final def derivedConfigured[A](using
32+
inline A: Mirror.Of[A],
33+
inline conf: Configuration
34+
): Codec.AsObject[A] = ConfiguredCodec.derived[A]
35+
36+
trait ConfiguredEncoder[A](using conf: Configuration) extends Encoder.AsObject[A]
37+
trait ConfiguredCodec[A] extends Codec.AsObject[A], ConfiguredEncoder[A]
38+
object ConfiguredCodec:
39+
inline final def derive[A: Mirror.Of](): ConfiguredCodec[A] =
40+
derived[A](using Configuration.default)
41+
inline final def derived[A](using
42+
conf: Configuration,
43+
inline mirror: Mirror.Of[A]
44+
): ConfiguredCodec[A] = ${ derivedImpl[A]('conf, 'mirror) }
45+
def ofProduct[A](
46+
encoders: => List[Encoder[?]]
47+
)(using Configuration, Default[A]): ConfiguredCodec[A] = ???
48+
def derivedImpl[A: Type](conf: Expr[Configuration], mirror: Expr[Mirror.Of[A]])(using
49+
q: Quotes
50+
): Expr[ConfiguredCodec[A]] = {
51+
mirror match {
52+
case '{
53+
${ _ }: Mirror.ProductOf[A] {
54+
type MirroredLabel = l
55+
type MirroredElemLabels = el
56+
type MirroredElemTypes = et
57+
}
58+
} =>
59+
'{
60+
ConfiguredCodec.ofProduct[A](
61+
derivation.summonEncoders[et & Tuple](false)(using $conf)
62+
)(using $conf)
63+
}
64+
}
65+
}
66+
67+
object derivation:
68+
sealed trait Inliner[A, Arg]:
69+
inline def apply[T](inline arg: Arg): A
70+
71+
class EncoderNotDeriveSum(using config: Configuration) extends Inliner[Encoder[?], Unit]:
72+
inline def apply[T](inline arg: Unit): Encoder[?] = summonEncoder[T](false)
73+
74+
inline final def loopUnrolled[A, Arg, T <: Tuple](f: Inliner[A, Arg], inline arg: Arg): List[A] =
75+
inline erasedValue[T] match
76+
case _: EmptyTuple => Nil
77+
case _: (h *: ts) => f[h](arg) :: loopUnrolled[A, Arg, ts](f, arg)
78+
79+
inline def loopUnrolledNoArg[A, T <: Tuple](f: Inliner[A, Unit]): List[A] =
80+
loopUnrolled[A, Unit, T](f, ())
81+
82+
inline final def summonEncoders[T <: Tuple](inline derivingForSum: Boolean)(using
83+
Configuration
84+
): List[Encoder[?]] =
85+
loopUnrolledNoArg[Encoder[?], T](
86+
inline if (derivingForSum) compiletime.error("unreachable")
87+
else new EncoderNotDeriveSum
88+
)
89+
90+
private[circe] inline final def summonEncoder[A](
91+
inline derivingForSum: Boolean
92+
)(using Configuration): Encoder[A] = summonFrom {
93+
case encodeA: Encoder[A] => encodeA
94+
case _: Mirror.Of[A] =>
95+
inline if (derivingForSum) compiletime.error("unreachable")
96+
else error("Failed to find an instance of Encoder[]")
97+
}

tests/warn/i24034/iron.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
// iron.scala
3+
4+
package iron
5+
6+
import io.circe.*
7+
8+
opaque type IronType[A, C] <: A = A
9+
type :|[A, C] = IronType[A, C]
10+
trait Constraint[A, C]
11+
12+
package constraint:
13+
object string:
14+
final class StartWith[V <: String]
15+
object StartWith:
16+
inline given [V <: String]: Constraint[String, StartWith[V]] = ???
17+
18+
object circe:
19+
inline given XXX[A, B](using inline encoder: Encoder[A]): Encoder[A :| B] = ???
20+
inline given YYY[A, B](using inline encoder: Encoder[A], dummy: scala.util.NotGiven[DummyImplicit]): Encoder[A :| B] = ???
21+
// inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Encoder[mirror.IronType]): Encoder[T] = ???
22+
23+
// trait RefinedTypeOps[A, C, T]:
24+
// inline given RefinedTypeOps.Mirror[T] = ???
25+
// object RefinedTypeOps:
26+
// trait Mirror[T]:
27+
// type BaseType
28+
// type ConstraintType
29+
// type IronType = BaseType :| ConstraintType
30+

tests/warn/i24034/test.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//> using options -Wunused:all
2+
3+
import io.circe.Codec
4+
5+
import iron.:|
6+
import iron.circe.given
7+
import iron.constraint.string.StartWith
8+
9+
case class Alien(name: String :| StartWith["alen"]) derives Codec.AsObject
10+

0 commit comments

Comments
 (0)