Skip to content

Commit fc38252

Browse files
committed
Fix regressions: use typer skolems as inline proxy's type
Fixes #26153 Fixes #26031 **problem** When an inline expansion has a type that depends on one of the inline method arguments, and that argument is not stable, there's a type mismatch error on inline function call. For example, ```scala trait Mon[F[_]] { type Arg } class Wrap[F[_], A]: inline def apply[T](inline f: A => T): F[T] = ... inline def async[F[_]](using am: Mon[F]) = new Wrap[F, am.Arg] object Test: val r = async[[A] =>> (String, A)](_ => 42) // given TupleMon[E] extends Mon[[A] ==> (E, A)] ``` The result of `async` depends on the argument `am`: `Wrap[F, am.Arg]`. At the call site, the given argument for `am` is not a stable path. When `TypeAssigner` computes the dependent result type, it skolemizes that argument. So the inline call is typed as something like: `Wrap[[A] =>> (String, A), ?1.Arg]` where: `?1` is some value of type `Mon[[A] =>> (String, A)]`. On the other hand, `Inlining` creates a runtime proxy for the same arugment: `am$proxy` = <actual given>. When the proxy is typed as `am$proxy: Mon[[A] ==> (String, A)]`, then types in the expanded tree is like `am$proxy.Arg`. While the call tree was typed as `?1.Arg`. As a result, compile fails: ```scala val r = async[[A] =>> (String, A)](_ => 42) ^^^^^^^ Found: ?1.Arg => Int Required: am$proxy1.Arg => Int where: ?1 is an unknown value of type Mon[[A] =>> (String, A)] ``` The same root issue also causes regression #26031 ```scala trait Ctx[F[_]] trait Mon[F[_]]: type Context <: Ctx[F] object Mon: type Aux[F[_], C <: Ctx[F]] = Mon[F] { type Context = C } class Wrap[F[_], C <: Ctx[F]](using val am: Mon.Aux[F, C]) transparent inline def doIt[F[_]](using am: Mon[F]) = new Wrap(using am) ``` The call tree fixes `C = ?1.Context`, so the expansion needs an argument of type `Mon.Aux[F, ?1.Context]`. A proxy typed only as `Mon[F]` does not prove that its `Context` member is `?1.Context`. **Fix** The idea is Use the skolem created by typer as the type of the inline proxy. `val am$proxy: ?1 = <actual given>.asInstanceOf[?1]`. The runtime value is still the proxy, but its type-level prefix is now the same skolem generated by typer. This fixes the mismatch in #26153, and also fixes #26031 because the proxy term itself now has the skolem type. The idea is from: > I was wondering it would be great if we can use something like `Wrap[F, m.Arg] forSome { val m: Mon[F] }` instead of `?`, which is too coarse. And ... I realized that typer already uses skolem type `?1.Arg` when the result type is dependent. > #26033 (comment) --- This commit also dealias `NamedTypes` when homogenizing for `-Ytest-pickler`, otherwise, skolem before pickle vs unpickled has different type printing `Predef.String` vs `String` (tests/run/i14020) **note** Original PR #26033 was to special-case `selectionType` for an `Inlined` receiver. if the saved type of the `Inlined` tree was avoided to `?`, use the expansion's type instead. That can fix the immediate receiver mismatch, but it is workaround-ish. It recovers `am$proxy.Arg` after avoidance, which isn't right. while typer already had the better stable prefix `?1.Arg`. Another fix I tried was to rewrite only type positions: `am$proxy.Arg` => `?1.Arg` This helps #26153, where the mismatch is in the expanded type. But it does not fix #26031. A term with type `Mon[F]` still does not conform, it's required refinements.
1 parent b5c77de commit fc38252

10 files changed

Lines changed: 246 additions & 9 deletions

File tree

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,36 @@ class Inliner(val call: tpd.Tree)(using Context):
229229
private val inlineCallPrefix =
230230
qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass))
231231

232+
/** Skolems created from typing this call tree.
233+
*
234+
* ```
235+
* trait Ctx[F[_]]
236+
* trait Mon[F[_]]:
237+
* type Context <: Ctx[F]
238+
* inline def async[F[_]](using am: Mon[F]): Wrap[F, am.Context] = ???
239+
* ```
240+
*
241+
* When TypeAssigner types `async(...)`, it skolemizes unstable `am` in the
242+
* dependent result type, and the call tree is typed as `Wrap[F, ?1.Context]`.
243+
* Then the inliner expands the method body and replace `am` by a proxy `am$proxy`.
244+
* Then `new Wrap[F, am.Context]` becomes `new Wrap[F, am$proxy.Context]`.
245+
*
246+
* In `paramBindingDef`, we type `am$proxy` as $1, so that both expanded tree and
247+
* call tree is typed `Wrap[F, ?1.Context]`. Otherwise, call tree and expanded tree
248+
* has different types `?1.Context` vs `am$proxy.Context` and compile fails.
249+
* See #26153 and #26031.
250+
*/
251+
private val callValueSkolemss: List[List[Option[SkolemType]]] =
252+
def loop(tree: Tree, skolemss: List[List[Option[SkolemType]]]): List[List[Option[SkolemType]]] = tree match
253+
case app @ Apply(fn, args) =>
254+
val mapping = app.getAttachment(TypeAssigner.SkolemizedArgs).getOrElse(Map.empty)
255+
loop(fn, args.map(mapping.get) :: skolemss)
256+
case TypeApply(fn, _) =>
257+
loop(fn, skolemss)
258+
case _ =>
259+
skolemss
260+
loop(call, Nil)
261+
232262
// Make sure all type arguments to the call are fully determined,
233263
// but continue if that's not achievable (or else i7459.scala would crash).
234264
for arg <- callTypeArgs do
@@ -279,9 +309,11 @@ class Inliner(val call: tpd.Tree)(using Context):
279309
* @param formal the type of the parameter
280310
* @param arg0 the argument corresponding to the parameter
281311
* @param buf the buffer to which the definition should be appended
312+
* @param skolem optional skolem from the call's dependent result type.
313+
* If present, use it as the proxy type.
282314
*/
283315
private[inlines] def paramBindingDef(name: Name, formal: Type, arg0: Tree,
284-
buf: DefBuffer)(using Context): ValOrDefDef = {
316+
buf: DefBuffer, skolem: Option[SkolemType] = None)(using Context): ValOrDefDef = {
285317
val isByName = formal.dealias.isInstanceOf[ExprType]
286318
val arg =
287319
def dropNameArg(arg: Tree): Tree = arg match
@@ -297,10 +329,17 @@ class Inliner(val call: tpd.Tree)(using Context):
297329
dropNameArg(arg0)
298330
val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false)
299331
val argIsBottom = argtpe.isBottomTypeAfterErasure
300-
val bindingType =
332+
val baseBindingType =
301333
if argIsBottom then formal
302334
else if isByName then ExprType(argtpe.widen)
303335
else argtpe.widen
336+
// If the call result type used a skolem for this argument, use the same skolem
337+
// as the proxy type. `?1` has `argtpe.widen` as its underlying type.
338+
val proxySkolem = if argIsBottom then None else skolem
339+
val bindingType = proxySkolem match
340+
case Some(sk) => if isByName then ExprType(sk) else sk
341+
case None => baseBindingType
342+
304343
var bindingFlags: FlagSet = InlineProxy
305344
if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then
306345
bindingFlags |= Inline
@@ -313,6 +352,10 @@ class Inliner(val call: tpd.Tree)(using Context):
313352
var newArg = arg.changeOwner(ctx.owner, boundSym)
314353
if bindingFlags.is(Inline) && argIsBottom then
315354
newArg = Typed(newArg, TypeTree(formal.widenExpr)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala
355+
else proxySkolem match
356+
case Some(sk) =>
357+
newArg = newArg.cast(sk) // adapt the rhs to the skolem-typed proxy
358+
case None => ()
316359
if isByName then DefDef(boundSym, newArg)
317360
else ValDef(boundSym, newArg, inferred = true)
318361
}.withSpan(boundSym.span)
@@ -328,31 +371,35 @@ class Inliner(val call: tpd.Tree)(using Context):
328371
private def computeParamBindings(
329372
tp: Type, targs: List[Tree],
330373
argss: List[List[Tree]], formalss: List[List[Type]],
374+
skolemss: List[List[Option[SkolemType]]],
331375
buf: DefBuffer): Boolean =
332376
tp match
333377
case tp: PolyType =>
334378
tp.paramNames.lazyZip(targs).foreach { (name, arg) =>
335379
paramSpan(name) = arg.span
336380
paramBinding(name) = arg.tpe.stripTypeVar
337381
}
338-
computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, buf)
382+
computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, skolemss, buf)
339383
case tp: MethodType =>
340384
if argss.isEmpty then
341385
report.error(em"missing arguments for inline method $inlinedMethod", call.srcPos)
342386
false
343387
else
344-
tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) =>
388+
val skolems = skolemss.headOption.getOrElse(List.fill(argss.head.length)(None))
389+
tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).lazyZip(skolems).foreach {
390+
(name, formal, arg, skolem) =>
345391
paramSpan(name) = arg.span
346392
paramBinding(name) = arg.tpe.dealias match
347393
case _: SingletonType if isIdempotentPath(arg) =>
348394
arg.tpe
349395
case _ =>
350-
paramBindingDef(name, formal, arg, buf).symbol.termRef
396+
paramBindingDef(name, formal, arg, buf, skolem).symbol.termRef
351397
}
352-
computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, buf)
398+
computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, skolemss.tail, buf)
353399
case _ =>
354400
assert(targs.isEmpty)
355401
assert(argss.isEmpty)
402+
assert(skolemss.isEmpty)
356403
true
357404

358405
/** The number of enclosing classes of this class, plus one */
@@ -666,6 +713,7 @@ class Inliner(val call: tpd.Tree)(using Context):
666713
if !computeParamBindings(
667714
inlinedMethod.info, callTypeArgs,
668715
mappedCallValueArgss, paramTypess(call, Nil),
716+
callValueSkolemss,
669717
paramBindingsBuf)
670718
then
671719
return (Nil, EmptyTree)

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ object Inlines:
598598

599599
// Take care that only argument bindings go into `bindings`, since positions are
600600
// different for bindings from arguments and bindings from body.
601-
val inlined = tpd.Inlined(call, bindings, expansion)
601+
val inlined0 = tpd.Inlined(call, bindings, expansion)
602602

603603
val hasOpaquesInResultFromCallWithTransparentContext =
604604
val owners = call.symbol.ownersIterator.toSet
@@ -626,6 +626,36 @@ object Inlines:
626626
case _ => mapOver(t)
627627
).typeMap(tpe)
628628

629+
/** Argument proxies may be typed with the skolems (see Inliner#callValueSkolemss).
630+
* For example:
631+
* {{{
632+
* transparent inline def apply[T](using m: Mirror) =
633+
* new Generic[T] { type Repr = Box[m.type] }
634+
* Generic.apply[Foo](using mkMirror())
635+
* // expands to:
636+
* val m$proxy: (?1 : Mirror) = mkMirror().$asInstanceOf[?1]
637+
* new Generic[Foo] { type Repr = Box[m$proxy.type] }
638+
* }}}
639+
* Normally, `TypeOps.avoid` avoids `m$proxy` to `?1` (since `m$proxy.type =:= ?1`).
640+
* However, TASTy pickles skolem type as its underlying type, so it's pickled as
641+
* `{ type Repr = Box[Mirror] }`. Later, unpickler reads from TASTy as
642+
* `{ type Repr = Box[Mirror] }` and `TypeOps.avoid` to `{ type Repr <: Box[? <: Mirror] }`
643+
* (because `m$proxy.type =:= Mirror` doesn't hold).
644+
* This breaks the roundtrip property of pickle/unpickle (`-Ytest-pickler`).
645+
*
646+
* To let pickle/unpickle know the skolem should be typed as its underlying type,
647+
* here we insert a no-op cast on the expansion tree.
648+
* `(... { type Repr = Box[m$proxy.type] }).asInstanceOf[... { type Repr = Box[Mirror]} ]`.
649+
*/
650+
val inlined =
651+
val proxySkolems = bindings.map(_.symbol.info.widenExpr).collect { case sk: SkolemType => sk }
652+
if proxySkolems.nonEmpty && inlined0.tpe.existsPart(proxySkolems.contains) then
653+
val sealedExpansion =
654+
inContext(ctx.withSource(expansion.source)):
655+
expansion.cast(inlined0.tpe).withSpan(expansion.span)
656+
tpd.Inlined(call, bindings, sealedExpansion)
657+
else inlined0
658+
629659
if !hasOpaqueProxies && !hasOpaquesInResultFromCallWithTransparentContext then inlined
630660
else
631661
val (target, forceCast) =

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,4 +885,3 @@ class PlainPrinter(_ctx: Context) extends Printer {
885885
protected def coloredText(text: Text, color: String): Text =
886886
if (ctx.useColors) color ~ text ~ SyntaxHighlighting.NoColor else text
887887
}
888-

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,10 @@ object TypeAssigner extends TypeAssigner:
592592

593593
/** An attachment on an application indicating a map from arguments to the skolem types
594594
* that were created in safeSubstParams.
595+
* We use StickyKey so the Inlining phase can reuse the
596+
* same skolems for path-dependent types in the expansion (see #26153).
595597
*/
596-
private[typer] val SkolemizedArgs = new Property.Key[Map[tpd.Tree, SkolemType]]
598+
private[dotc] val SkolemizedArgs = new Property.StickyKey[Map[tpd.Tree, SkolemType]]
597599

598600
def seqLitType(tree: untpd.SeqLiteral, elemType: Type)(using Context) = tree match
599601
case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemType)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package i26031multi
2+
3+
trait Ctx[F[_]]
4+
trait Key[F[_]]
5+
6+
trait Mon[F[_]]:
7+
type Context <: Ctx[F]
8+
type Token <: Key[F]
9+
10+
object Mon:
11+
type Aux[F[_], C <: Ctx[F], K <: Key[F]] = Mon[F] { type Context = C; type Token = K }
12+
13+
class Wrap[F[_], C <: Ctx[F], K <: Key[F]](using val am: Mon.Aux[F, C, K]):
14+
transparent inline def apply[T](inline expr: (C, K) ?=> T): F[T] =
15+
inner[F, T, C, K](am, expr)
16+
17+
transparent inline def inner[F[_], T, C <: Ctx[F], K <: Key[F]](
18+
inline am: Mon.Aux[F, C, K],
19+
inline expr: (C, K) ?=> T
20+
): F[T] = ???
21+
22+
transparent inline def doIt[F[_]](using am: Mon[F]) =
23+
new Wrap(using am)
24+
25+
class Pair[A, B]
26+
class PairCtx[E] extends Ctx[[A] =>> Pair[E, A]]
27+
class PairKey[E] extends Key[[A] =>> Pair[E, A]]
28+
class PairMon[E] extends Mon[[A] =>> Pair[E, A]]:
29+
type Context = PairCtx[E]
30+
type Token = PairKey[E]
31+
32+
given pm[E]: Mon[[A] =>> Pair[E, A]] = new PairMon[E]
33+
34+
object Test:
35+
val r = doIt[[A] =>> Pair[String, A]] { 42 }

tests/pos/i26031-two-args.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package i26031twoargs
2+
3+
trait Ctx[F[_]]
4+
5+
trait Mon[F[_]]:
6+
type Context <: Ctx[F]
7+
8+
object Mon:
9+
type Aux[F[_], C <: Ctx[F]] = Mon[F] { type Context = C }
10+
11+
class Both[F[_], C1 <: Ctx[F], C2 <: Ctx[F]](
12+
using val am1: Mon.Aux[F, C1],
13+
val am2: Mon.Aux[F, C2]
14+
)
15+
16+
transparent inline def both[F[_]](using am1: Mon[F], am2: Mon[F]) =
17+
new Both(using am1, am2)
18+
19+
class Pair[A, B]
20+
class PairCtx1[E] extends Ctx[[A] =>> Pair[E, A]]
21+
class PairCtx2[E] extends Ctx[[A] =>> Pair[E, A]]
22+
23+
class PairMon1[E] extends Mon[[A] =>> Pair[E, A]]:
24+
type Context = PairCtx1[E]
25+
26+
class PairMon2[E] extends Mon[[A] =>> Pair[E, A]]:
27+
type Context = PairCtx2[E]
28+
29+
object Test:
30+
val am1: Mon[[A] =>> Pair[String, A]] = new PairMon1[String]
31+
val am2: Mon[[A] =>> Pair[String, A]] = new PairMon2[String]
32+
val r = both[[A] =>> Pair[String, A]](using am1, am2)

tests/pos/i26031-val.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
trait Mirror:
2+
type Elems
3+
4+
trait Generic[T]:
5+
type Repr
6+
7+
class Box[M]
8+
9+
object Generic:
10+
transparent inline def apply[T](using m: Mirror) =
11+
new Generic[T] { type Repr = Box[m.type] }
12+
13+
class Foo
14+
def mkMirror(): Mirror = ???
15+
16+
object Test:
17+
val g = Generic[Foo](using mkMirror())

tests/pos/i26031.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
trait Ctx[F[_]]
2+
3+
trait Mon[F[_]]:
4+
type Context <: Ctx[F]
5+
6+
object Mon:
7+
type Aux[F[_], C <: Ctx[F]] = Mon[F] { type Context = C }
8+
9+
class Wrap[F[_], C <: Ctx[F]](using val am: Mon.Aux[F, C]):
10+
transparent inline def apply[T](inline expr: C ?=> T): F[T] =
11+
inner[F, T, C](am, expr)
12+
13+
transparent inline def inner[F[_], T, C <: Ctx[F]](
14+
inline am: Mon.Aux[F, C],
15+
inline expr: C ?=> T
16+
): F[T] = ???
17+
18+
transparent inline def doIt[F[_]](using am: Mon[F]) =
19+
new Wrap(using am)
20+
21+
class Pair[A, B]
22+
class PairCtx[E] extends Ctx[[A] =>> Pair[E, A]]
23+
class PairMon[E] extends Mon[[A] =>> Pair[E, A]]:
24+
type Context = PairCtx[E]
25+
26+
given pm[E]: Mon[[A] =>> Pair[E, A]] = new PairMon[E]
27+
28+
object Test:
29+
val r = doIt[[A] =>> Pair[String, A]] { 42 }

tests/pos/i26153-plain.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Plain-inline variant of #26153.
2+
trait Ctx[F[_]]
3+
4+
trait Mon[F[_]]:
5+
type Context <: Ctx[F]
6+
7+
class Wrap[F[_], C <: Ctx[F]]:
8+
inline def apply[T](inline expr: C ?=> T): F[T] =
9+
stage2[F, T, C](expr)
10+
11+
inline def stage2[F[_], T, C <: Ctx[F]](inline expr: C ?=> T): F[T] = ???
12+
13+
inline def async[F[_]](using am: Mon[F]) =
14+
new Wrap[F, am.Context]
15+
16+
class TupleMon[E] extends Mon[[A] =>> (E, A)]:
17+
type Context = Ctx[[A] =>> (E, A)]
18+
19+
given pm[E]: Mon[[A] =>> (E, A)] = new TupleMon[E]
20+
21+
object Test:
22+
val r = async[[A] =>> (String, A)] { 42 }

tests/pos/i26153.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
trait Ctx[F[_]]
2+
3+
trait Mon[F[_]]:
4+
type Context <: Ctx[F]
5+
6+
class Wrap[F[_], C <: Ctx[F]]:
7+
transparent inline def apply[T](inline expr: C ?=> T): F[T] =
8+
stage2[F, T, C](expr)
9+
10+
transparent inline def stage2[F[_], T, C <: Ctx[F]](
11+
inline expr: C ?=> T
12+
): F[T] = ???
13+
14+
transparent inline def async[F[_]](using am: Mon[F]) =
15+
new Wrap[F, am.Context]
16+
17+
class TupleMon[E] extends Mon[[A] =>> (E, A)]:
18+
type Context = Ctx[[A] =>> (E, A)]
19+
20+
given pm[E]: Mon[[A] =>> (E, A)] = new TupleMon[E]
21+
22+
object Test:
23+
val r = async[[A] =>> (String, A)] { 42 }

0 commit comments

Comments
 (0)