From 744624d4c9441b4e2b7a0f5bb9fd3f56b6956ef5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Apr 2026 18:58:48 +0000 Subject: [PATCH 1/2] Fix #15798: expand transparent inline givens inside inline method bodies An implicit argument resolved from a transparent inline given was left as a bare, non-stable TermRef when typing the body of an inline def. When that reference was substituted into a dependent result type, `TypeAssigner` skolemized it (because it is not `isStable`), so type members such as `tc.Out` became opaque "unknown value" projections and subsequent type checks like `get[T <: Int]` failed. `Inlines.needsInlining` now permits expansion of transparent inline **givens** during the typer phase even when we are inside an enclosing inline method, with two important exclusions: - Transparent inline *methods* (e.g. `summonInline`) remain delayed: they are typically expanded later at outer call sites with concrete type info. - *Macro-based* transparent inline givens (bodies containing a splice) are also excluded, since running their macro at the def site with abstract types causes cyclic-macro-dependency errors (see tests/neg-macros/i18695). https://claude.ai/code/session_01LBtsQb6hXekva2zhouidnb --- .../dotty/tools/dotc/inlines/Inlines.scala | 16 +++++++++++- tests/pos/i15798.scala | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i15798.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 859648a47c6a..48d8530751eb 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -76,9 +76,23 @@ object Inlines: case _ => false tree.symbol.name.isUnapplyName && rec(tree) - isInlineable(tree.symbol) + val sym = tree.symbol + sym.is(Inline) && sym.hasAnnotation(defn.BodyAnnot) && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] && StagingLevel.level == 0 + && ( + // Outside of inline method bodies, any inline call may be inlined. + !inInlineMethod + // Inside inline method bodies, a transparent inline given must still be + // expanded at typer so that dependent type members of its result + // (e.g. `tc.Out` from `fromVal[Int, Int]`) are known when typing the + // enclosing body; otherwise the implicit-argument reference is + // non-stable and gets skolemized, making `tc.Out` opaque. See #15798. + // Macro-based givens are excluded: eager expansion of their bodies + // would run a macro at the def site with abstract types, typically + // causing cyclic-macro-dependency errors (see tests/neg-macros/i18695). + || (sym.is(Given) && !sym.is(Macro) && needsTransparentInlining(tree)) + ) && ( ctx.phase == Phases.inliningPhase || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) diff --git a/tests/pos/i15798.scala b/tests/pos/i15798.scala new file mode 100644 index 000000000000..1020832b55e6 --- /dev/null +++ b/tests/pos/i15798.scala @@ -0,0 +1,25 @@ +import compiletime.ops.* + +class Inlined[T](val value : T) +object Inlined: + trait TC[UB, R]: + type Out <: UB + def apply(arg: R): Inlined[Out] + object TC: + transparent inline given fromVal[UB, R <: UB]: TC[UB, R] = new TC[UB, R]: + type Out = R + def apply(arg: R): Inlined[R] = forced[R](arg) + transparent inline given fromInline[UB, R <: UB, I <: Inlined[R]]: TC[UB, I] = + new TC[UB, I]: + type Out = R + def apply(arg: I): Inlined[R] = arg + + protected inline def forced[T](_value: Any): Inlined[T] = Inlined[T](_value.asInstanceOf[T]) + + def add[T <: Int, R](lhs: Inlined[T], rhs: R)(using tc: TC[Int, R]) = + forced[int.+[T, tc.Out]](lhs.value + tc(rhs).value) + def get[T <: Int](inlined: Inlined[T]) : T = inlined.value + +object Test: + inline def check[UB <: Int](ub: Inlined[UB]): Unit = + val y = Inlined.get(Inlined.add(ub, 1)) From 60f8817ac7ae92979253daf3eb7d90bb03dc49c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 23:05:38 +0000 Subject: [PATCH 2/2] Restrict #15798 fix to givens with no using-clause PR #25906 makes transparent inline givens expand at typer phase even inside inline method bodies, so dependent type members of the result (e.g. tc.Out from `fromVal[Int, Int]`) survive substitution into a non-stable implicit-argument reference. The change as written also expands transparent inline givens whose body branches on the path of a using parameter (e.g. perspective's `HKDGeneric.derived` does `inline m match` over its `Mirror.Of[A]` using-arg). Eager expansion at typer time loses the path information and produces a result that no longer matches the declared given type, breaking community-build B's `dottyPerspectiveExamples/compile`. Limit the new expansion to givens whose signature has only type parameters (no value-parameter clauses), which matches the i15798 reproducer (`fromVal[UB, R]`, `fromInline[UB, R, I]`) and excludes the problematic perspective-style derivations. Adds tests/pos/i15798b.scala as a regression test that mirrors the HKDGeneric.derived / Encoder.derived structure of the failing community-build project. https://claude.ai/code/session_01BuwkgbJ17f9vEgHmKeC6j8 --- .../dotty/tools/dotc/inlines/Inlines.scala | 22 ++++---- tests/pos/i15798b.scala | 54 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 tests/pos/i15798b.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 48d8530751eb..475f617c8106 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -83,15 +83,19 @@ object Inlines: && ( // Outside of inline method bodies, any inline call may be inlined. !inInlineMethod - // Inside inline method bodies, a transparent inline given must still be - // expanded at typer so that dependent type members of its result - // (e.g. `tc.Out` from `fromVal[Int, Int]`) are known when typing the - // enclosing body; otherwise the implicit-argument reference is - // non-stable and gets skolemized, making `tc.Out` opaque. See #15798. - // Macro-based givens are excluded: eager expansion of their bodies - // would run a macro at the def site with abstract types, typically - // causing cyclic-macro-dependency errors (see tests/neg-macros/i18695). - || (sym.is(Given) && !sym.is(Macro) && needsTransparentInlining(tree)) + // Inside inline method bodies, expand a transparent inline given at + // typer so that dependent type members of its result (e.g. `tc.Out` + // from `fromVal[Int, Int]`) are known when typing the enclosing + // body; otherwise the implicit-argument reference is non-stable and + // gets skolemized, making `tc.Out` opaque (#15798). Only givens with + // no value parameter clauses are eligible: a body that branches on a + // using arg's path (e.g. `inline m match` in HKDGeneric.derived) can + // produce a result that no longer matches the declared type when + // expanded at typer. Macro givens are also excluded; running their + // macro at the def site with abstract types causes cyclic-macro- + // dependency errors (tests/neg-macros/i18695). + || (sym.is(Given) && !sym.is(Macro) && needsTransparentInlining(tree) + && !sym.info.stripPoly.isInstanceOf[MethodType]) ) && ( ctx.phase == Phases.inliningPhase diff --git a/tests/pos/i15798b.scala b/tests/pos/i15798b.scala new file mode 100644 index 000000000000..bc9695cf7910 --- /dev/null +++ b/tests/pos/i15798b.scala @@ -0,0 +1,54 @@ +// Companion to i15798.scala. The fix for #15798 expands transparent +// inline givens at typer phase even inside inline method bodies; do not +// expand givens whose signature has a using-clause, since their body may +// branch on the path of the using arg (e.g. the perspective community +// build's `HKDGeneric.derived`, which uses `inline m match`). Eager +// expansion would lose path information from the implicit argument and +// produce a result that no longer matches the declared type. + +import scala.deriving.* +import scala.compiletime.* + +sealed trait HKDGeneric[A]: + type Gen + def label: String + +object HKDGeneric: + type Aux[A, G] = HKDGeneric[A] { type Gen = G } + + transparent inline given derived[A](using m: Mirror.Of[A]): HKDGeneric[A] = inline m match + case m: Mirror.ProductOf[A] { type MirroredElemTypes = m.MirroredElemTypes } => + HKDProductGeneric.derived[A](using m) + case _: Mirror.SumOf[A] => + ??? + +trait HKDProductGeneric[A] extends HKDGeneric[A] +object HKDProductGeneric: + type Aux[A, G] = HKDProductGeneric[A] { type Gen = G } + + transparent inline given derived[A](using m: Mirror.ProductOf[A]): HKDProductGeneric[A] = + derivedImpl[A, m.MirroredElemTypes, m.MirroredLabel](constValue[m.MirroredLabel]) + + def derivedImpl[A, ElemTypes <: Tuple, Label <: String](label0: Label)( + using m: Mirror.ProductOf[A] { type MirroredElemTypes = ElemTypes; type MirroredLabel = Label } + ): HKDProductGeneric[A] { type Gen = ElemTypes } = + new HKDProductGeneric[A]: + type Gen = ElemTypes + def label: String = label0 + + +trait Encoder[A]: + def name: String +object Encoder: + inline given derived[A](using gen: HKDGeneric[A]): Encoder[A] = inline gen match + case gen: HKDProductGeneric.Aux[A, gen.Gen] => + derivedProductEncoder[A](using gen) + case _ => ??? + + def derivedProductEncoder[A](using gen: HKDProductGeneric[A]): Encoder[A] = new Encoder[A] { + def name = gen.label + } + +@main def loopTest = + case class A(value: String) derives Encoder + println("compiled")