From c16e53b1fab5d7e693d914e45d7a50b31ffa13ff Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Sat, 11 Apr 2026 11:10:16 +0000 Subject: [PATCH 1/3] Use wildcard type instead of whitebox cast in IsoFields Replace the `whitebox` cast (`asInstanceOf[Expr[Iso[S, Tuple]]]`) with a proper wildcard return type using `PIso[S, S, ? <: Tuple, ? <: Tuple]`. Since `IsoFields.apply` is a `transparent inline` method, the compiler still infers the precise tuple type at call sites. This avoids the unsafe cast and prepares for potential stricter macro type checking in future Scala versions. The motivation is to prepare for future stricter checks in the Scala 3 compiler. The current implementation exploits a missing check to generate an unsound cast. Note: we use `PIso` (a trait) directly instead of the `Iso` type alias (defined as `type Iso[S, A] = PIso[S, S, A, A]`) because Scala 3 cannot reduce higher-kinded type aliases applied to wildcard arguments. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../main/scala-3/monocle/internal/IsoFields.scala | 15 +++++---------- .../main/scala-3/monocle/syntax/MacroSyntax.scala | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala index 170e51622..baab33697 100644 --- a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala +++ b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala @@ -1,28 +1,23 @@ package monocle.internal -import monocle.Iso +import monocle.{Iso, PIso} import scala.quoted.{quotes, Expr, Quotes, Type} import scala.deriving.Mirror object IsoFields { - transparent inline def apply[S <: Product](using mirror: Mirror.ProductOf[S]): Iso[S, Tuple] = + transparent inline def apply[S <: Product](using mirror: Mirror.ProductOf[S]): PIso[S, S, ? <: Tuple, ? <: Tuple] = ${ IsoFieldsImpl.apply[S]('mirror) } } private[monocle] object IsoFieldsImpl { - def apply[S <: Product](mirror: Expr[Mirror.ProductOf[S]])(using Quotes, Type[S]): Expr[Iso[S, Tuple]] = { - - def whitebox[A <: Tuple](e: Expr[Iso[S, A]]): Expr[Iso[S, Tuple]] = - e.asInstanceOf[Expr[Iso[S, Tuple]]] - + def apply[S <: Product](mirror: Expr[Mirror.ProductOf[S]])(using Quotes, Type[S]): Expr[PIso[S, S, ? <: Tuple, ? <: Tuple]] = mirror match { case '{ type a <: Tuple; $m: Mirror.ProductOf[S] { type MirroredElemTypes = `a` } } => - whitebox('{ + '{ val f: S => a = Tuple.fromProductTyped(_)(using $m) val g: a => S = $m.fromProduct(_) Iso[S, a](f)(g) - }) + } } - } } diff --git a/core/shared/src/main/scala-3/monocle/syntax/MacroSyntax.scala b/core/shared/src/main/scala-3/monocle/syntax/MacroSyntax.scala index e4a8492d7..b44a819e9 100644 --- a/core/shared/src/main/scala-3/monocle/syntax/MacroSyntax.scala +++ b/core/shared/src/main/scala-3/monocle/syntax/MacroSyntax.scala @@ -13,7 +13,7 @@ trait MacroSyntax { * Case classes with 0 fields will correspond with `EmptyTuple`, 1 with `Tuple1[field type]`, 2 or more with a * tuple of all field types in the same order as the fields themselves. */ - transparent inline def fields[S <: Product: Mirror.ProductOf]: Iso[S, Tuple] = + transparent inline def fields[S <: Product: Mirror.ProductOf]: PIso[S, S, ? <: Tuple, ? <: Tuple] = IsoFields[S] } From d1d8414a036ce5de1e91806d4734cbb797894a1f Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 13 Jul 2026 14:39:11 +0200 Subject: [PATCH 2/3] Add fallback match arm and extension syntax test Address review comments: - Report a proper error message when the mirror expression doesn't match the expected shape in IsoFieldsImpl.apply - Add a test exercising the Iso.fields extension method on a value Co-Authored-By: Claude Fable 5 --- .../src/main/scala-3/monocle/internal/IsoFields.scala | 2 ++ .../test/scala-3/monocle/internal/IsoFieldsTest.scala | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala index baab33697..1f97b9f23 100644 --- a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala +++ b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala @@ -19,5 +19,7 @@ private[monocle] object IsoFieldsImpl { val g: a => S = $m.fromProduct(_) Iso[S, a](f)(g) } + case other => + quotes.reflect.report.errorAndAbort(s"Unexpected mirror type: ${other.show}") } } diff --git a/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala b/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala index ea4d5c59b..92967bf02 100644 --- a/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala +++ b/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala @@ -31,4 +31,13 @@ final class IsoFieldsTest extends munit.FunSuite { assertEquals(iso.reverseGet(("hi", 5)), Foo("hi", 5)) assertEquals(iso.reverseGet(iso.get(Foo("hi", 5))), Foo("hi", 5)) } + + test("fields extension method works") { + case class Foo(s: String, i: Int) + val foo = Foo("hi", 5) + val iso: Iso[Foo, (String, Int)] = Iso.fields[Foo] + + assertEquals(iso.get(foo), ("hi", 5)) + assertEquals(iso.reverseGet(("hi", 5)), foo) + } } From 9b87a443515928ce1e75727c4c7fb9d9596b7a75 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 13 Jul 2026 15:13:17 +0200 Subject: [PATCH 3/3] Reformat with scalafmt Co-Authored-By: Claude Fable 5 --- core/shared/src/main/scala-3/monocle/internal/IsoFields.scala | 4 +++- .../src/test/scala-3/monocle/internal/IsoFieldsTest.scala | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala index 1f97b9f23..bd3d2cb3f 100644 --- a/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala +++ b/core/shared/src/main/scala-3/monocle/internal/IsoFields.scala @@ -11,7 +11,9 @@ object IsoFields { private[monocle] object IsoFieldsImpl { - def apply[S <: Product](mirror: Expr[Mirror.ProductOf[S]])(using Quotes, Type[S]): Expr[PIso[S, S, ? <: Tuple, ? <: Tuple]] = + def apply[S <: Product]( + mirror: Expr[Mirror.ProductOf[S]] + )(using Quotes, Type[S]): Expr[PIso[S, S, ? <: Tuple, ? <: Tuple]] = mirror match { case '{ type a <: Tuple; $m: Mirror.ProductOf[S] { type MirroredElemTypes = `a` } } => '{ diff --git a/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala b/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala index 92967bf02..08fdad14f 100644 --- a/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala +++ b/core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala @@ -34,7 +34,7 @@ final class IsoFieldsTest extends munit.FunSuite { test("fields extension method works") { case class Foo(s: String, i: Int) - val foo = Foo("hi", 5) + val foo = Foo("hi", 5) val iso: Iso[Foo, (String, Int)] = Iso.fields[Foo] assertEquals(iso.get(foo), ("hi", 5))