Skip to content

Commit 5e342ad

Browse files
committed
Handle generic tuples when inlining
Fixes #14182
1 parent 584c05b commit 5e342ad

File tree

7 files changed

+49
-4
lines changed

7 files changed

+49
-4
lines changed

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
486486
}
487487
val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false)
488488
val argIsBottom = argtpe.isBottomTypeAfterErasure
489-
val bindingType =
489+
val argwtpe =
490490
if argIsBottom then formal
491-
else if isByName then ExprType(argtpe.widen)
491+
else if defn.isTupleNType(formal.widenExpr) && !defn.isTupleNType(argtpe.widen) then
492+
// A generic tuple with know size N is considered as a subtype of TupleN but it does not
493+
// contain the members of TupleN. All these tuples will be erased to TupleN and will have
494+
// those members at runtime. Therefore when inlining it is important to keep the fact that
495+
// this is TupleN and that the members can be accessed when the tree is re-typechecked.
496+
// Also see `newArg` bellow
497+
// See i14182
498+
argtpe.widen & formal.widenExpr
492499
else argtpe.widen
500+
val bindingType = if isByName then ExprType(argwtpe) else argwtpe
493501
var bindingFlags: FlagSet = InlineProxy
494502
if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then
495503
bindingFlags |= Inline
@@ -500,8 +508,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
500508
val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm
501509
val binding = {
502510
var newArg = arg.changeOwner(ctx.owner, boundSym)
503-
if bindingFlags.is(Inline) && argIsBottom then
511+
if argIsBottom && bindingFlags.is(Inline) then
504512
newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala
513+
else if defn.isTupleNType(formal.widenExpr) && !defn.isTupleNType(argtpe.widen) then
514+
// Also see `argtpe1` above
515+
newArg = Typed(newArg, TypeTree(formal.widenExpr))
516+
505517
if isByName then DefDef(boundSym, newArg)
506518
else ValDef(boundSym, newArg)
507519
}.withSpan(boundSym.span)
@@ -972,7 +984,21 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
972984
paramProxy.get(tree.tpe) match {
973985
case Some(t) if tree.isTerm && t.isSingleton =>
974986
val inlinedSingleton = singleton(t).withSpan(argSpan)
975-
inlinedFromOutside(inlinedSingleton)(tree.span)
987+
val tree1 = inlinedFromOutside(inlinedSingleton)(tree.span)
988+
if defn.isTupleNType(tree.tpe.widenTermRefExpr) && !defn.isTupleNType(t.widenTermRefExpr) then
989+
// See tests/pos/i14182b.scala
990+
// FIXME: We have a `tree1` that has a term ref `tup.type` with underlying `1 *: 2 *: EmptyTuple` type but the original reference is to a `Tuple2[Int, Int]`
991+
// If we take the `tree1` then it is impossible to select the `_1` field. If we take the parameter type we can lose singletons references that we should keep.
992+
// Ideally we should ascribe it to a `tup.type & Tuple2[Int, Int]` but that is not working because we have the some special sub-typing rule that makes
993+
// `1 *: 2 *: EmptyTuple <:< Tuple2[Int, Int]`. Therefore the glb of the two types ends up being `tup.type` which drops the extra information we need to add.
994+
// None of the following attempts work.
995+
996+
997+
// tree1.ensureConforms(tree.tpe.widenTermRefExpr)
998+
// Typed(tree1, TypeTree(tree.tpe.widenTermRefExpr & t))
999+
Typed(tree1, TypeTree(AndType(tree.tpe.widenTermRefExpr, t)))
1000+
// tree1.asInstance(AndType(tree.tpe.widenTermRefExpr, t))
1001+
else tree1
9761002
case Some(t) if tree.isType =>
9771003
inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span)
9781004
case _ => tree

tests/pos-macros/i14182/Macro_1.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import scala.quoted._
2+
def fooImpl(xs: Expr[(Int, Int)])(using Quotes): Expr[Unit] =
3+
'{ val a: Int = $xs._1; }

tests/pos-macros/i14182/Test_2.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
inline def foo(inline xs: (Int, Int)): Unit = ${ fooImpl('xs) }
2+
def fail = foo(1 *: 2 *: EmptyTuple)
3+
def ok = foo((1, 2))

tests/pos/i14182.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
inline def foo(inline xs: (Int, Int)): Unit = { val a: Int = xs._1; }
2+
def fail = foo(1 *: 2 *: EmptyTuple)
3+
def ok = foo((1, 2))

tests/pos/i14182a.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
inline def foo(xs: (Int, Int)): Unit = { val a: Int = xs._1; }
2+
def fail = foo(1 *: 2 *: EmptyTuple)
3+
def ok = foo((1, 2))

tests/pos/i14182b.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
inline def foo(inline xs: (Int, Int)): xs.type = { val a: Int = xs._1; xs }
2+
def bar =
3+
val tup: 1 *: 2 *: EmptyTuple = ???
4+
val tup2: tup.type = foo(tup)

tests/pos/i14182c.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
inline def foo(xs: => (Int, Int)): Unit = { val a: Int = xs._1; }
2+
def bar =
3+
foo(1 *: 2 *: EmptyTuple)

0 commit comments

Comments
 (0)