diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 865b4272690a..acb129130cda 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,29 +1,26 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar} -import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.ast.untpd, untpd.ImportSelector -import dotty.tools.dotc.config.ScalaSettings -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName} -import dotty.tools.dotc.core.NameKinds.{ - BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} -import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} -import dotty.tools.dotc.core.Scopes.newScope -import dotty.tools.dotc.core.StdNames.nme -import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} -import dotty.tools.dotc.core.Types.* -import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol} -import dotty.tools.dotc.rewrites.Rewrites -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.typer.{ImportInfo, Typer} -import dotty.tools.dotc.typer.Deriving.OriginalTypeClass -import dotty.tools.dotc.typer.Implicits.{ContextualImplicits, RenamedImplicitRef} -import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span -import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace} -import dotty.tools.dotc.util.chaining.* +package dotty.tools.dotc +package transform + +import ast.*, desugar.{ForArtifact, PatternVar}, tpd.*, untpd.ImportSelector +import config.ScalaSettings +import core.*, Contexts.*, Flags.* +import Names.{Name, SimpleName, DerivedName, TermName, termName} +import NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} +import NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} +import Scopes.newScope +import StdNames.nme +import Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import Types.* +import reporting.{CodeAction, UnusedSymbol} +import rewrites.Rewrites + +import MegaPhase.MiniPhase +import typer.{ImportInfo, Typer} +import typer.Deriving.OriginalTypeClass +import typer.Implicits.{ContextualImplicits, RenamedImplicitRef} +import util.{Property, Spans, SrcPos}, Spans.Span +import util.Chars.{isLineBreakChar, isWhitespace} +import util.chaining.* import java.util.IdentityHashMap @@ -58,17 +55,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if tree.symbol.exists then // if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site val resolving = - refInfos.inlined.isEmpty - || tree.srcPos.isZeroExtentSynthetic - || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) - if resolving && !ignoreTree(tree) then + tree.srcPos.isUserCode + || tree.srcPos.isZeroExtentSynthetic // take as summonInline + if !ignoreTree(tree) then def loopOverPrefixes(prefix: Type, depth: Int): Unit = if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then - resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix) + resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix, imports = resolving) loopOverPrefixes(prefix.normalizedPrefix, depth + 1) if tree.srcPos.isZeroExtentSynthetic then loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject, imports = resolving) else if tree.hasType then resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) refInfos.isAssignment = false @@ -160,14 +156,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha case _ => tree - override def prepareForInlined(tree: Inlined)(using Context): Context = - refInfos.inlined.push(tree.call.srcPos) - ctx override def transformInlined(tree: Inlined)(using Context): tree.type = - //transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs - val _ = refInfos.inlined.pop() - if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then - transformAllDeep(tree.call) + transformAllDeep(tree.call) tree override def prepareForBind(tree: Bind)(using Context): Context = @@ -325,8 +315,11 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha * e.g., in `scala.Int`, `scala` is in scope for typer, but here we reverse-engineer the attribution. * For Select, lint does not look up `.scala` (so top-level syms look like magic) but records `scala.Int`. * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence. + * + * The `imports` flag is whether an identifier can mark an import as used: the flag is false + * for inlined code, except for `summonInline` (and related constructs) which are resolved at inlining. */ - def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit = + def resolveUsage(sym0: Symbol, name: Name, prefix: Type, imports: Boolean = true)(using Context): Unit = import PrecedenceLevels.* val sym = sym0.userSymbol @@ -430,7 +423,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha // record usage and possibly an import if !enclosed then refInfos.addRef(sym) - if candidate != NoContext && candidate.isImportContext && importer != null then + if imports && candidate != NoContext && candidate.isImportContext && importer != null then refInfos.sels.put(importer, ()) end resolveUsage @@ -502,7 +495,7 @@ object CheckUnused: val nowarn = mutable.Set.empty[Symbol] // marked @nowarn val imps = new IdentityHashMap[Import, Unit] // imports val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors - def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then + def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then tree match case imp: Import => if inliners == 0 @@ -531,7 +524,6 @@ object CheckUnused: if tree.symbol ne NoSymbol then defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path - val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions) var inliners = 0 // depth of inline def (not inlined yet) // instead of refs.addOne, use addRef to distinguish a read from a write to var @@ -1042,6 +1034,10 @@ object CheckUnused: extension (pos: SrcPos) def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists + def isUserCode(using Context): Boolean = + val inlineds = enclosingInlineds // per current context + inlineds.isEmpty + || inlineds.last.srcPos.sourcePos.contains(pos.sourcePos) extension [A <: AnyRef](arr: Array[A]) // returns `until` if not satisfied diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index 439799ee8e3d..795873b53e73 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -39,6 +39,10 @@ object FooGiven: val foo = summon[Int] +object SomeGivenImports: + given Int = 0 + given String = "foo" + /** * Import used as type name are considered * as used. @@ -69,7 +73,7 @@ object InlineChecks: object InlinedBar: import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn - val a = InlineFoo.getSet + val a = InlineFoo.getSet // expansion is attributed mutable.Set.apply(1) object MacroChecks: object StringInterpol: @@ -91,12 +95,6 @@ object IgnoreExclusion: val a = Set(1) val b = Map(1 -> 2) def c = Seq(42) -/** - * Some given values for the test - */ -object SomeGivenImports: - given Int = 0 - given String = "foo" /* BEGIN : Check on packages*/ package nestedpackageimport: diff --git a/tests/warn/i24034/circe.scala b/tests/warn/i24034/circe.scala new file mode 100644 index 000000000000..5d2e646f67b2 --- /dev/null +++ b/tests/warn/i24034/circe.scala @@ -0,0 +1,97 @@ + +// circe.scala + +package io.circe + +import scala.compiletime.* +import scala.deriving.Mirror +import scala.quoted.* + +trait Encoder[A]: + def encode(value: A): String + +object Encoder: + trait AsObject[A] extends Encoder[A] + given Encoder[String] = ??? + +trait Configuration +object Configuration: + val default: Configuration = ??? + +object Default: + given [A]: Default[A] = ??? +trait Default[T] + +trait Codec[A] extends Encoder[A] +object Codec: + trait AsObject[A] extends Encoder.AsObject[A] + object AsObject: + inline final def derived[A](using inline A: Mirror.Of[A]): Codec.AsObject[A] = + ConfiguredCodec.derived[A](using Configuration.default) + inline final def derivedConfigured[A](using + inline A: Mirror.Of[A], + inline conf: Configuration + ): Codec.AsObject[A] = ConfiguredCodec.derived[A] + +trait ConfiguredEncoder[A](using conf: Configuration) extends Encoder.AsObject[A] +trait ConfiguredCodec[A] extends Codec.AsObject[A], ConfiguredEncoder[A] +object ConfiguredCodec: + inline final def derive[A: Mirror.Of](): ConfiguredCodec[A] = + derived[A](using Configuration.default) + inline final def derived[A](using + conf: Configuration, + inline mirror: Mirror.Of[A] + ): ConfiguredCodec[A] = ${ derivedImpl[A]('conf, 'mirror) } + def ofProduct[A]( + encoders: => List[Encoder[?]] + )(using Configuration, Default[A]): ConfiguredCodec[A] = ??? + def derivedImpl[A: Type](conf: Expr[Configuration], mirror: Expr[Mirror.Of[A]])(using + q: Quotes + ): Expr[ConfiguredCodec[A]] = { + mirror match { + case '{ + ${ _ }: Mirror.ProductOf[A] { + type MirroredLabel = l + type MirroredElemLabels = el + type MirroredElemTypes = et + } + } => + '{ + ConfiguredCodec.ofProduct[A]( + derivation.summonEncoders[et & Tuple](false)(using $conf) + )(using $conf) + } + } + } + +object derivation: + sealed trait Inliner[A, Arg]: + inline def apply[T](inline arg: Arg): A + + class EncoderNotDeriveSum(using config: Configuration) extends Inliner[Encoder[?], Unit]: + inline def apply[T](inline arg: Unit): Encoder[?] = summonEncoder[T](false) + + inline final def loopUnrolled[A, Arg, T <: Tuple](f: Inliner[A, Arg], inline arg: Arg): List[A] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (h *: ts) => f[h](arg) :: loopUnrolled[A, Arg, ts](f, arg) + + inline def loopUnrolledNoArg[A, T <: Tuple](f: Inliner[A, Unit]): List[A] = + loopUnrolled[A, Unit, T](f, ()) + + inline final def summonEncoders[T <: Tuple](inline derivingForSum: Boolean)(using + Configuration + ): List[Encoder[?]] = + loopUnrolledNoArg[Encoder[?], T]( + inline if (derivingForSum) compiletime.error("unreachable") + else new EncoderNotDeriveSum + ) + + private[circe] inline final def summonEncoder[A]( + inline derivingForSum: Boolean + )(using Configuration): Encoder[A] = summonFrom { + case encodeA: Encoder[A] => encodeA + case _: Mirror.Of[A] => + inline if (derivingForSum) compiletime.error("unreachable") + else error("Failed to find an instance of Encoder[]") + } diff --git a/tests/warn/i24034/iron.scala b/tests/warn/i24034/iron.scala new file mode 100644 index 000000000000..17cb26bf6408 --- /dev/null +++ b/tests/warn/i24034/iron.scala @@ -0,0 +1,30 @@ + +// iron.scala + +package iron + +import io.circe.* + +opaque type IronType[A, C] <: A = A +type :|[A, C] = IronType[A, C] +trait Constraint[A, C] + +package constraint: + object string: + final class StartWith[V <: String] + object StartWith: + inline given [V <: String]: Constraint[String, StartWith[V]] = ??? + +object circe: + inline given XXX[A, B](using inline encoder: Encoder[A]): Encoder[A :| B] = ??? + inline given YYY[A, B](using inline encoder: Encoder[A], dummy: scala.util.NotGiven[DummyImplicit]): Encoder[A :| B] = ??? + // inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Encoder[mirror.IronType]): Encoder[T] = ??? + +// trait RefinedTypeOps[A, C, T]: +// inline given RefinedTypeOps.Mirror[T] = ??? +// object RefinedTypeOps: +// trait Mirror[T]: +// type BaseType +// type ConstraintType +// type IronType = BaseType :| ConstraintType + diff --git a/tests/warn/i24034/test.scala b/tests/warn/i24034/test.scala new file mode 100644 index 000000000000..f87be8da2c36 --- /dev/null +++ b/tests/warn/i24034/test.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +import io.circe.Codec + +import iron.:| +import iron.circe.given +import iron.constraint.string.StartWith + +case class Alien(name: String :| StartWith["alen"]) derives Codec.AsObject +