Skip to content

Commit d82e068

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 d82e068

12 files changed

Lines changed: 282 additions & 9 deletions

File tree

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

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,38 @@ 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+
*
244+
* Later, the inliner expands the method body and replace `am` by a proxy `am$proxy`.
245+
* Then `new Wrap[F, am.Context]` becomes `new Wrap[F, am$proxy.Context]`.
246+
* If `am$proxy` only has type `Mon[F]`, this prefix is not connected to the
247+
* `?1` in `Wrap[F, ?1.Context`, then it leads to type mismatch error.
248+
* See #26153 and #26031.
249+
*
250+
* To avoid this problem, type `am$proxy` as the skolem `$1` created by typer.
251+
* Then the expanded tree and the call types match.
252+
*/
253+
private val callValueSkolemss: List[List[Option[SkolemType]]] =
254+
def loop(tree: Tree, skolemss: List[List[Option[SkolemType]]]): List[List[Option[SkolemType]]] = tree match
255+
case app @ Apply(fn, args) =>
256+
val mapping = app.getAttachment(TypeAssigner.SkolemizedArgs).getOrElse(Map.empty)
257+
loop(fn, args.map(mapping.get) :: skolemss)
258+
case TypeApply(fn, _) =>
259+
loop(fn, skolemss)
260+
case _ =>
261+
skolemss
262+
loop(call, Nil)
263+
232264
// Make sure all type arguments to the call are fully determined,
233265
// but continue if that's not achievable (or else i7459.scala would crash).
234266
for arg <- callTypeArgs do
@@ -279,9 +311,11 @@ class Inliner(val call: tpd.Tree)(using Context):
279311
* @param formal the type of the parameter
280312
* @param arg0 the argument corresponding to the parameter
281313
* @param buf the buffer to which the definition should be appended
314+
* @param skolem optional skolem from the call's dependent result type.
315+
* If present, use it as the proxy type.
282316
*/
283317
private[inlines] def paramBindingDef(name: Name, formal: Type, arg0: Tree,
284-
buf: DefBuffer)(using Context): ValOrDefDef = {
318+
buf: DefBuffer, skolem: Option[SkolemType] = None)(using Context): ValOrDefDef = {
285319
val isByName = formal.dealias.isInstanceOf[ExprType]
286320
val arg =
287321
def dropNameArg(arg: Tree): Tree = arg match
@@ -297,10 +331,17 @@ class Inliner(val call: tpd.Tree)(using Context):
297331
dropNameArg(arg0)
298332
val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false)
299333
val argIsBottom = argtpe.isBottomTypeAfterErasure
300-
val bindingType =
334+
val baseBindingType =
301335
if argIsBottom then formal
302336
else if isByName then ExprType(argtpe.widen)
303337
else argtpe.widen
338+
// If the call result type used a skolem for this argument, use the same skolem
339+
// as the proxy type. `?1` has `argtpe.widen` as its underlying type.
340+
val proxySkolem = if argIsBottom then None else skolem
341+
val bindingType = proxySkolem match
342+
case Some(sk) => if isByName then ExprType(sk) else sk
343+
case None => baseBindingType
344+
304345
var bindingFlags: FlagSet = InlineProxy
305346
if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then
306347
bindingFlags |= Inline
@@ -313,6 +354,10 @@ class Inliner(val call: tpd.Tree)(using Context):
313354
var newArg = arg.changeOwner(ctx.owner, boundSym)
314355
if bindingFlags.is(Inline) && argIsBottom then
315356
newArg = Typed(newArg, TypeTree(formal.widenExpr)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala
357+
else proxySkolem match
358+
case Some(sk) =>
359+
newArg = newArg.cast(sk) // adapt the rhs to the skolem-typed proxy
360+
case None => ()
316361
if isByName then DefDef(boundSym, newArg)
317362
else ValDef(boundSym, newArg, inferred = true)
318363
}.withSpan(boundSym.span)
@@ -328,31 +373,35 @@ class Inliner(val call: tpd.Tree)(using Context):
328373
private def computeParamBindings(
329374
tp: Type, targs: List[Tree],
330375
argss: List[List[Tree]], formalss: List[List[Type]],
376+
skolemss: List[List[Option[SkolemType]]],
331377
buf: DefBuffer): Boolean =
332378
tp match
333379
case tp: PolyType =>
334380
tp.paramNames.lazyZip(targs).foreach { (name, arg) =>
335381
paramSpan(name) = arg.span
336382
paramBinding(name) = arg.tpe.stripTypeVar
337383
}
338-
computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, buf)
384+
computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, skolemss, buf)
339385
case tp: MethodType =>
340386
if argss.isEmpty then
341387
report.error(em"missing arguments for inline method $inlinedMethod", call.srcPos)
342388
false
343389
else
344-
tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) =>
390+
val skolems = skolemss.headOption.getOrElse(List.fill(argss.head.length)(None))
391+
tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).lazyZip(skolems).foreach {
392+
(name, formal, arg, skolem) =>
345393
paramSpan(name) = arg.span
346394
paramBinding(name) = arg.tpe.dealias match
347395
case _: SingletonType if isIdempotentPath(arg) =>
348396
arg.tpe
349397
case _ =>
350-
paramBindingDef(name, formal, arg, buf).symbol.termRef
398+
paramBindingDef(name, formal, arg, buf, skolem).symbol.termRef
351399
}
352-
computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, buf)
400+
computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, skolemss.tail, buf)
353401
case _ =>
354402
assert(targs.isEmpty)
355403
assert(argss.isEmpty)
404+
assert(skolemss.isEmpty)
356405
true
357406

358407
/** The number of enclosing classes of this class, plus one */
@@ -666,6 +715,7 @@ class Inliner(val call: tpd.Tree)(using Context):
666715
if !computeParamBindings(
667716
inlinedMethod.info, callTypeArgs,
668717
mappedCallValueArgss, paramTypess(call, Nil),
718+
callValueSkolemss,
669719
paramBindingsBuf)
670720
then
671721
return (Nil, EmptyTree)

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

Lines changed: 33 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,38 @@ object Inlines:
626626
case _ => mapOver(t)
627627
).typeMap(tpe)
628628

629+
// Argument proxies may be typed with the skolems (see Inliner#paramBindingDef).
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()) // expands to:
635+
//
636+
// Inlined:
637+
// val m$proxy: (?1 : Mirror) = mkMirror().$asInstanceOf[?1]
638+
// new Generic[Foo] { type Repr = Box[m$proxy.type] }
639+
// }}}
640+
// Both call tree and expanded tree type has `Generic[Foo] { type Repr = ?1.type }`.
641+
// However, pickling breaks the correspondence: skolems are pickled as
642+
// their underlying types (`?1` becomes a `Mirror`), and the type
643+
// of an Inlined is recomputed with avoiding the proxy type.
644+
// - before pickling, `m$proxy.type` widens to the singleton `?1`,
645+
// and the precise type is `Generic[Foo] { type Repr = Box[?1] }`.
646+
// - after unpickling, `m$proxy` is no longer a singleton type,
647+
// (it's converted to underlying type `Mirror`), so avoidance
648+
// approximate to `Generic[Foo] { type Repr <: Box[? <: Mirror] }`.
649+
// However, skolem types should be pickled/unpickled as its underlying
650+
// type, it should be unpickled as `{ type Repr = Mirror }`.
651+
//
652+
// For the unpickler to recover the above precise type instead of
653+
// recomputing the wrong type, we insert a no-op cast on the expansion,
654+
// so unpickler can pick that type.
655+
val inlined =
656+
val proxySkolems = bindings.map(_.symbol.info.widenExpr).collect { case sk: SkolemType => sk }
657+
if proxySkolems.nonEmpty && inlined0.tpe.existsPart(proxySkolems.contains) then
658+
tpd.Inlined(call, bindings, expansion.cast(inlined0.tpe).withSpan(expansion.span))
659+
else inlined0
660+
629661
if !hasOpaqueProxies && !hasOpaquesInResultFromCallWithTransparentContext then inlined
630662
else
631663
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 }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package i26031pickle
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 Wrap[F[_], C <: Ctx[F]](using val am: Mon.Aux[F, C]):
12+
transparent inline def apply[T](inline expr: C ?=> T): F[T] =
13+
inner[F, T, C](am, expr)
14+
15+
transparent inline def inner[F[_], T, C <: Ctx[F]](
16+
inline am: Mon.Aux[F, C],
17+
inline expr: C ?=> T
18+
): F[T] = ???
19+
20+
transparent inline def doIt[F[_]](using am: Mon[F]) =
21+
new Wrap(using am)
22+
23+
class Pair[A, B]
24+
class PairCtx[E] extends Ctx[[A] =>> Pair[E, A]]
25+
class PairMon[E] extends Mon[[A] =>> Pair[E, A]]:
26+
type Context = PairCtx[E]
27+
28+
given pm[E]: Mon[[A] =>> Pair[E, A]] = new PairMon[E]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package i26031pickle
2+
3+
object Test:
4+
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 }

0 commit comments

Comments
 (0)