From 7a081bddeb23e1fc5df453b6e13bb7a64d77cdd2 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 13 Nov 2025 16:58:42 +0000 Subject: [PATCH 1/5] Allow references to erased values in types --- .../tools/dotc/inlines/InlineReducer.scala | 1 + tests/run/erased-inline-product.check | 1 + tests/run/erased-inline-product.scala | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/run/erased-inline-product.check create mode 100644 tests/run/erased-inline-product.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 7043169e2ae3..7c0f795012cf 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -418,6 +418,7 @@ class InlineReducer(inliner: Inliner)(using Context): // drop unusable vals and check that no referenes to unusable symbols remain val cleanupUnusable = new TreeMap: override def transform(tree: Tree)(using Context): Tree = + if tree.isType then return tree tree match case tree: ValDef if unusable.contains(tree.symbol) => EmptyTree case id: Ident if unusable.contains(id.symbol) => diff --git a/tests/run/erased-inline-product.check b/tests/run/erased-inline-product.check new file mode 100644 index 000000000000..48082f72f087 --- /dev/null +++ b/tests/run/erased-inline-product.check @@ -0,0 +1 @@ +12 diff --git a/tests/run/erased-inline-product.scala b/tests/run/erased-inline-product.scala new file mode 100644 index 000000000000..559f1f9cd07e --- /dev/null +++ b/tests/run/erased-inline-product.scala @@ -0,0 +1,21 @@ +import scala.compiletime.{erasedValue, summonInline, error} + +inline def sizeTuple[T<: Tuple](): Long = + inline erasedValue[T] match + case _: EmptyTuple => 0 + case _: (h *: t) => size[h] + sizeTuple[t]() + +inline def sizeProduct[T <: Product](m: scala.deriving.Mirror.ProductOf[T]): Long = + sizeTuple[m.MirroredElemTypes]() + +inline def size[T]: Long = + inline erasedValue[T] match + case _: Char => 2 + case _: Int => 4 + case _: Long => 8 + case _: Double => 8 + case p: Product => sizeProduct(summonInline[scala.deriving.Mirror.ProductOf[p.type]]) + case _ => error(s"unsupported type") + +@main def Test = + println(size[(Int, Long)]) From a7b9fbeb075a82df68338e41f1cbe6f942d21ca0 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 13 Nov 2025 16:59:24 +0000 Subject: [PATCH 2/5] Add erased-inline-product alternative implementation --- tests/run/erased-inline-product-2.check | 1 + tests/run/erased-inline-product-2.scala | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/run/erased-inline-product-2.check create mode 100644 tests/run/erased-inline-product-2.scala diff --git a/tests/run/erased-inline-product-2.check b/tests/run/erased-inline-product-2.check new file mode 100644 index 000000000000..48082f72f087 --- /dev/null +++ b/tests/run/erased-inline-product-2.check @@ -0,0 +1 @@ +12 diff --git a/tests/run/erased-inline-product-2.scala b/tests/run/erased-inline-product-2.scala new file mode 100644 index 000000000000..b852206eacb0 --- /dev/null +++ b/tests/run/erased-inline-product-2.scala @@ -0,0 +1,21 @@ +import scala.compiletime.{erasedValue, summonInline, error} + +inline def sizeTuple[T<: Tuple](): Long = + inline erasedValue[T] match + case _: EmptyTuple => 0 + case _: (h *: t) => size[h] + sizeTuple[t]() + +inline def sizeProduct[T <: Product](m: scala.deriving.Mirror.ProductOf[T]): Long = + sizeTuple[m.MirroredElemTypes]() + +inline def size[T]: Long = + inline erasedValue[T] match + case _: Char => 2 + case _: Int => 4 + case _: Long => 8 + case _: Double => 8 + case _: (p & Product) => sizeProduct[(p & Product)](summonInline[scala.deriving.Mirror.ProductOf[p & Product]]) + case _ => error(s"unsupported type") + +@main def Test = + println(size[(Int, Long)]) From 689ac774ec3b1dc6e5b63b31468a8c06815ac391 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 18 Nov 2025 15:44:57 +0000 Subject: [PATCH 3/5] Check realizability of references to erased terms --- .../tools/dotc/inlines/InlineReducer.scala | 32 ++++++++++++++----- .../neg/erased-inline-unrealizable-path.scala | 22 +++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 tests/neg/erased-inline-unrealizable-path.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 7c0f795012cf..60f0e75ec4c8 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -10,6 +10,7 @@ import Names.TermName import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName} import config.Printers.inlining import util.SimpleIdentityMap +import CheckRealizable.{Realizable, realizability} import collection.mutable @@ -417,19 +418,34 @@ class InlineReducer(inliner: Inliner)(using Context): for (bindings, expr) <- recur(cases) yield // drop unusable vals and check that no referenes to unusable symbols remain val cleanupUnusable = new TreeMap: + + /** Whether we are currently in a type position */ + var inType: Boolean = false + override def transform(tree: Tree)(using Context): Tree = - if tree.isType then return tree tree match case tree: ValDef if unusable.contains(tree.symbol) => EmptyTree case id: Ident if unusable.contains(id.symbol) => - report.error( - em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression - |in the selector of an inline match that reduces to - | - |${Block(bindings, expr)}""", - tree.srcPos) + // This conditions allows references to erased values in type + // positions provided the types of these references are + // realizable. See erased-inline-product.scala and + // tests/neg/erased-inline-unrealizable-path.scala. + if !inType || !(realizability(id.tpe.widen) eq Realizable) then + report.error( + em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression + |in the selector of an inline match that reduces to + | + |${Block(bindings, expr)}""", + tree.srcPos) tree - case _ => super.transform(tree) + case _ if tree.isType => + val saved = inType + inType = true + val tree1 = super.transform(tree) + inType = saved + tree1 + case _ => + super.transform(tree) val bindings1 = bindings.mapConserve(cleanupUnusable.transform).collect: case mdef: MemberDef => mdef diff --git a/tests/neg/erased-inline-unrealizable-path.scala b/tests/neg/erased-inline-unrealizable-path.scala new file mode 100644 index 000000000000..6b44e891f585 --- /dev/null +++ b/tests/neg/erased-inline-unrealizable-path.scala @@ -0,0 +1,22 @@ +import scala.compiletime.erasedValue + +class Box[T] + +trait T: + type L + +class A extends T: + type L <: Int + +class B extends T: + type L >: String + +inline def f() = + inline erasedValue[A & B] match + case x: (A & B) => + val y: String = "String" + val z: x.L = y + z: Int + +@main def Test = + println(f().abs) // error From 27691966f49f1cd5bcc1024938398202848367f7 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 18 Nov 2025 19:42:49 +0000 Subject: [PATCH 4/5] Cleanup tests --- ...ct.scala => erased-inline-product-1.scala} | 2 +- tests/run/erased-inline-product-2.check | 1 - tests/run/erased-inline-product-2.scala | 2 +- tests/run/erased-inline-product-3.scala | 21 +++++++++++++++++++ tests/run/erased-inline-product.check | 1 - 5 files changed, 23 insertions(+), 4 deletions(-) rename tests/run/{erased-inline-product.scala => erased-inline-product-1.scala} (94%) delete mode 100644 tests/run/erased-inline-product-2.check create mode 100644 tests/run/erased-inline-product-3.scala delete mode 100644 tests/run/erased-inline-product.check diff --git a/tests/run/erased-inline-product.scala b/tests/run/erased-inline-product-1.scala similarity index 94% rename from tests/run/erased-inline-product.scala rename to tests/run/erased-inline-product-1.scala index 559f1f9cd07e..91e8b8d1dd9b 100644 --- a/tests/run/erased-inline-product.scala +++ b/tests/run/erased-inline-product-1.scala @@ -18,4 +18,4 @@ inline def size[T]: Long = case _ => error(s"unsupported type") @main def Test = - println(size[(Int, Long)]) + assert(size[(Int, Long)] == 12) diff --git a/tests/run/erased-inline-product-2.check b/tests/run/erased-inline-product-2.check deleted file mode 100644 index 48082f72f087..000000000000 --- a/tests/run/erased-inline-product-2.check +++ /dev/null @@ -1 +0,0 @@ -12 diff --git a/tests/run/erased-inline-product-2.scala b/tests/run/erased-inline-product-2.scala index b852206eacb0..d7210088b89d 100644 --- a/tests/run/erased-inline-product-2.scala +++ b/tests/run/erased-inline-product-2.scala @@ -18,4 +18,4 @@ inline def size[T]: Long = case _ => error(s"unsupported type") @main def Test = - println(size[(Int, Long)]) + assert(size[(Int, Long)] == 12) diff --git a/tests/run/erased-inline-product-3.scala b/tests/run/erased-inline-product-3.scala new file mode 100644 index 000000000000..7101847e548e --- /dev/null +++ b/tests/run/erased-inline-product-3.scala @@ -0,0 +1,21 @@ +import scala.compiletime.{erasedValue, summonInline, error} + +inline def sizeTuple[T <: Tuple](): Long = + inline erasedValue[T] match + case _: EmptyTuple => 0 + case _: (h *: t) => size[h] + sizeTuple[t]() + +inline def sizeProduct[T](m: scala.deriving.Mirror.ProductOf[T]): Long = + sizeTuple[m.MirroredElemTypes]() + +inline def size[T]: Long = + inline erasedValue[T] match + case _: Char => 2 + case _: Int => 4 + case _: Long => 8 + case _: Double => 8 + case _: Product => sizeProduct[T](summonInline[scala.deriving.Mirror.ProductOf[T]]) + case _ => error(s"unsupported type") + +@main def Test = + assert(size[(Int, Long)] == 12) diff --git a/tests/run/erased-inline-product.check b/tests/run/erased-inline-product.check deleted file mode 100644 index 48082f72f087..000000000000 --- a/tests/run/erased-inline-product.check +++ /dev/null @@ -1 +0,0 @@ -12 From df82a5882c3ddd7e54a9a37f582141b206bedfd4 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 26 Nov 2025 16:03:39 +0100 Subject: [PATCH 5/5] Update compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala Co-authored-by: odersky --- compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 60f0e75ec4c8..cda74e710047 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -430,7 +430,7 @@ class InlineReducer(inliner: Inliner)(using Context): // positions provided the types of these references are // realizable. See erased-inline-product.scala and // tests/neg/erased-inline-unrealizable-path.scala. - if !inType || !(realizability(id.tpe.widen) eq Realizable) then + if !inType || (realizability(id.tpe.widen) ne Realizable) then report.error( em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression |in the selector of an inline match that reduces to