Skip to content

Commit 065f774

Browse files
mbovelclaude
andauthored
Use wildcard type instead of whitebox cast in IsoFields (#1574)
* 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) <noreply@anthropic.com> * 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 <noreply@anthropic.com> * Reformat with scalafmt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1441106 commit 065f774

3 files changed

Lines changed: 19 additions & 11 deletions

File tree

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
package monocle.internal
22

3-
import monocle.Iso
3+
import monocle.{Iso, PIso}
44
import scala.quoted.{quotes, Expr, Quotes, Type}
55
import scala.deriving.Mirror
66

77
object IsoFields {
8-
transparent inline def apply[S <: Product](using mirror: Mirror.ProductOf[S]): Iso[S, Tuple] =
8+
transparent inline def apply[S <: Product](using mirror: Mirror.ProductOf[S]): PIso[S, S, ? <: Tuple, ? <: Tuple] =
99
${ IsoFieldsImpl.apply[S]('mirror) }
1010
}
1111

1212
private[monocle] object IsoFieldsImpl {
1313

14-
def apply[S <: Product](mirror: Expr[Mirror.ProductOf[S]])(using Quotes, Type[S]): Expr[Iso[S, Tuple]] = {
15-
16-
def whitebox[A <: Tuple](e: Expr[Iso[S, A]]): Expr[Iso[S, Tuple]] =
17-
e.asInstanceOf[Expr[Iso[S, Tuple]]]
18-
14+
def apply[S <: Product](
15+
mirror: Expr[Mirror.ProductOf[S]]
16+
)(using Quotes, Type[S]): Expr[PIso[S, S, ? <: Tuple, ? <: Tuple]] =
1917
mirror match {
2018
case '{ type a <: Tuple; $m: Mirror.ProductOf[S] { type MirroredElemTypes = `a` } } =>
21-
whitebox('{
19+
'{
2220
val f: S => a = Tuple.fromProductTyped(_)(using $m)
2321
val g: a => S = $m.fromProduct(_)
2422
Iso[S, a](f)(g)
25-
})
23+
}
24+
case other =>
25+
quotes.reflect.report.errorAndAbort(s"Unexpected mirror type: ${other.show}")
2626
}
27-
}
2827
}

core/shared/src/main/scala-3/monocle/syntax/MacroSyntax.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ trait MacroSyntax {
1313
* Case classes with 0 fields will correspond with `EmptyTuple`, 1 with `Tuple1[field type]`, 2 or more with a
1414
* tuple of all field types in the same order as the fields themselves.
1515
*/
16-
transparent inline def fields[S <: Product: Mirror.ProductOf]: Iso[S, Tuple] =
16+
transparent inline def fields[S <: Product: Mirror.ProductOf]: PIso[S, S, ? <: Tuple, ? <: Tuple] =
1717
IsoFields[S]
1818
}
1919

core/shared/src/test/scala-3/monocle/internal/IsoFieldsTest.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@ final class IsoFieldsTest extends munit.FunSuite {
3131
assertEquals(iso.reverseGet(("hi", 5)), Foo("hi", 5))
3232
assertEquals(iso.reverseGet(iso.get(Foo("hi", 5))), Foo("hi", 5))
3333
}
34+
35+
test("fields extension method works") {
36+
case class Foo(s: String, i: Int)
37+
val foo = Foo("hi", 5)
38+
val iso: Iso[Foo, (String, Int)] = Iso.fields[Foo]
39+
40+
assertEquals(iso.get(foo), ("hi", 5))
41+
assertEquals(iso.reverseGet(("hi", 5)), foo)
42+
}
3443
}

0 commit comments

Comments
 (0)