Skip to content

Commit af99289

Browse files
authored
Merge pull request #622 from scala/backport-lts-3.3-24043
Backport "Always traverse Inlined.call in linter" to 3.3 LTS
2 parents 3849032 + b454c6b commit af99289

File tree

5 files changed

+160
-25
lines changed

5 files changed

+160
-25
lines changed

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

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc.transform
1+
package dotty.tools.dotc
2+
package transform
23

34
import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar}
45
import dotty.tools.dotc.ast.tpd.*
@@ -58,17 +59,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
5859
if tree.symbol.exists then
5960
// if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site
6061
val resolving =
61-
refInfos.inlined.isEmpty
62-
|| tree.srcPos.isZeroExtentSynthetic
63-
|| refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos))
64-
if resolving && !ignoreTree(tree) then
62+
tree.srcPos.isUserCode
63+
|| tree.srcPos.isZeroExtentSynthetic // take as summonInline
64+
if !ignoreTree(tree) then
6565
def loopOverPrefixes(prefix: Type, depth: Int): Unit =
6666
if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then
67-
resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix)
67+
resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix, imports = resolving)
6868
loopOverPrefixes(prefix.normalizedPrefix, depth + 1)
6969
if tree.srcPos.isZeroExtentSynthetic then
7070
loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0)
71-
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject)
71+
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject, imports = resolving)
7272
else if tree.hasType then
7373
resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject)
7474
refInfos.isAssignment = false
@@ -160,14 +160,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
160160
case _ =>
161161
tree
162162

163-
override def prepareForInlined(tree: Inlined)(using Context): Context =
164-
refInfos.inlined.push(tree.call.srcPos)
165-
ctx
166163
override def transformInlined(tree: Inlined)(using Context): tree.type =
167-
//transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs
168-
val _ = refInfos.inlined.pop()
169-
if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then
170-
transformAllDeep(tree.call)
164+
transformAllDeep(tree.call)
171165
tree
172166

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

@@ -398,7 +395,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
398395
// record usage and possibly an import
399396
if !enclosed then
400397
refInfos.addRef(sym)
401-
if candidate != NoContext && candidate.isImportContext && importer != null then
398+
if imports && candidate != NoContext && candidate.isImportContext && importer != null then
402399
refInfos.sels.put(importer, ())
403400
end resolveUsage
404401

@@ -470,7 +467,7 @@ object CheckUnused:
470467
val nowarn = mutable.Set.empty[Symbol] // marked @nowarn
471468
val imps = new IdentityHashMap[Import, Unit] // imports
472469
val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors
473-
def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then
470+
def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then
474471
tree match
475472
case imp: Import =>
476473
if inliners == 0
@@ -499,7 +496,6 @@ object CheckUnused:
499496
if tree.symbol ne NoSymbol then
500497
defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path
501498

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

505501
// instead of refs.addOne, use addRef to distinguish a read from a write to var
@@ -1010,6 +1006,10 @@ object CheckUnused:
10101006
extension (pos: SrcPos)
10111007
def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent
10121008
def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists
1009+
def isUserCode(using Context): Boolean =
1010+
val inlineds = enclosingInlineds // per current context
1011+
inlineds.isEmpty
1012+
|| inlineds.last.srcPos.sourcePos.contains(pos.sourcePos)
10131013

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

tests/warn/i15503a.scala

Lines changed: 5 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,6 @@ object IgnoreExclusion:
9195
val a = Set(1)
9296
val b = Map(1 -> 2)
9397
def c = Seq(42)
94-
/**
95-
* Some given values for the test
96-
*/
97-
object SomeGivenImports:
98-
given Int = 0
99-
given String = "foo"
10098

10199
/* BEGIN : Check on packages*/
102100
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)