diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 85d1f084e6a0..1772155e54c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -429,7 +429,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) - withNoErrors(mirrorRef.cast(mirrorType)) + withNoErrors(mirrorRef.cast(mirrorType).withSpan(span)) end makeProductMirror MirrorSource.reduce(mirroredType) match @@ -442,12 +442,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name) } val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, singletonPath :: Nil) - withNoErrors(mirrorRef.cast(mirrorType)) + withNoErrors(mirrorRef.cast(mirrorType).withSpan(span)) else val mirrorType = formal.constrained_& { mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, singleton.name) } - withNoErrors(singletonPath.cast(mirrorType)) + withNoErrors(singletonPath.cast(mirrorType).withSpan(span)) case MirrorSource.GenericTuple(tps) => val maxArity = Definitions.MaxTupleArity val arity = tps.size diff --git a/tests/pos-macros/i18353/Macro_1.scala b/tests/pos-macros/i18353/Macro_1.scala new file mode 100644 index 000000000000..d0f5dd84ea66 --- /dev/null +++ b/tests/pos-macros/i18353/Macro_1.scala @@ -0,0 +1,64 @@ +import scala.compiletime.* +import scala.deriving.* +import scala.quoted.* + +trait Getter[S, A]: + def view: S => A + +trait Lens[S, A] extends Getter[S, A]: + def set: S => A => S + +object Lens { + inline def apply[S, A](_view: S => A)(_set: S => A => S): Lens[S, A] = + new Lens[S, A]: + def view: S => A = _view + def set: S => A => S = _set + + inline given derived[T <: Product, A]: Lens[T, A] = ${ + ProductMacros.genLens[T, A] + } +} + +object ProductMacros { + private def indexOf[T: Type, A: Type](using Quotes): Int = + indexOf0[T, A](0) + + private def indexOf0[T: Type, A: Type](acc: Int)(using Quotes): Int = + Type.of[T] match + case '[EmptyTuple] => -1 + case '[A *: tpes] => acc + case '[tpe *: tpes] => indexOf0[tpes, A](acc + 1) + + def genLens[T <: Product: Type, A: Type](using + q: Quotes + ): Expr[Lens[T, A]] = { + import quotes.reflect.* + + Expr + .summon[Mirror.ProductOf[T]] + .map { + case '{ + $m: Mirror.ProductOf[T] { type MirroredElemTypes = elementTypes } + } => + val i = indexOf[elementTypes, A] + if i < 0 then + report.errorAndAbort(s"has no the field of ${Type.show[A]}") + else + val ii: Expr[Int] = Expr(i) + val view: Expr[T => A] = '{ t => + t.productElement($ii).asInstanceOf[A] + } + val set: Expr[T => A => T] = '{ t => a => + val arr = Tuple.fromProduct(t).toArray + arr($ii) = a.asInstanceOf[Object] + // Check-macros fails here probably + $m.fromTuple(Tuple.fromArray(arr).asInstanceOf[elementTypes]) + } + '{ Lens[T, A]($view)($set) } + } + .getOrElse( + report.errorAndAbort(s"${Type.show[T]} is not a product type") + ) + + } +} diff --git a/tests/pos-macros/i18353/Test_2.scala b/tests/pos-macros/i18353/Test_2.scala new file mode 100644 index 000000000000..08a79953097e --- /dev/null +++ b/tests/pos-macros/i18353/Test_2.scala @@ -0,0 +1,7 @@ +def Test = { + + type TupleConfig = (Int, String) + + val tConfig = (1, "string") + val fails = summon[Lens[TupleConfig, Int]].view(tConfig) +}