Skip to content

Commit 0c34994

Browse files
committed
Fix #8250: Allow Expr.betaReduce to rewrite type ascriptions
When a blackbox inline def is inlined, a type ascription is added to preserve the apparent return type of the inline def. Prior to this commit, the quoted.Expr.betaReduce function was unable to properly inline a known lambda expression coming from an inline def as it would see the type ascription and give up. This change makes betaReduce rewrite type ascriptions, allowing it to inline type-ascribed functions such as those coming from inlined defs. - When type ascriptions are not present, behaviour does not change. - When type ascriptions are encountered, the outermost ascription is kept. - If the inline is successful, then the result type of the ascribed function type is ascribed to the inlined body. - If the inline is not successful, the outermost ascribed type is re-ascribed to the non-inlineable expression before inserting a call to that expression. There are several test cases, covering: - verifying that betaReduce can correctly reduce the application of the result of an inline def to a constant value, with the reduction result being printed using the `compiletime.code` interpolator. - verifying that the result type ascription handles dependent function types - inserting ??? as either the inlined body or a function expression that cannot be inlined compiles without error (by re-ascribing the apparent type) - a neg-test that a function whose type is `Int=>Int` and whose body inlines to `4` does not typecheck against the singleton type `4`.
1 parent abaf47e commit 0c34994

File tree

7 files changed

+109
-6
lines changed

7 files changed

+109
-6
lines changed

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,24 +2042,44 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
20422042
}}
20432043
val argVals = argVals0.reverse
20442044
val argRefs = argRefs0.reverse
2045-
def rec(fn: Tree): Tree = fn match {
2045+
def rec(fn: Tree, topAscription: Option[TypeTree]): Tree = fn match {
2046+
case Typed(expr, tpt) =>
2047+
// we need to retain any type ascriptions we see and:
2048+
// a) if we succeed, ascribe the result type of the ascription to the inlined body
2049+
// b) if we fail, re-ascribe the same type to whatever it was we couldn't inline
2050+
// note: if you see many nested ascriptions, keep the top one as that's what the enclosing expression expects
2051+
rec(expr, topAscription.orElse(Some(tpt)))
20462052
case Inlined(call, bindings, expansion) =>
20472053
// this case must go before closureDef to avoid dropping the inline node
2048-
cpy.Inlined(fn)(call, bindings, rec(expansion))
2054+
cpy.Inlined(fn)(call, bindings, rec(expansion, topAscription))
20492055
case closureDef(ddef) =>
20502056
val paramSyms = ddef.vparamss.head.map(param => param.symbol)
20512057
val paramToVals = paramSyms.zip(argRefs).toMap
2052-
new TreeTypeMap(
2058+
val result = new TreeTypeMap(
20532059
oldOwners = ddef.symbol :: Nil,
20542060
newOwners = ctx.owner :: Nil,
20552061
treeMap = tree => paramToVals.get(tree.symbol).map(_.withSpan(tree.span)).getOrElse(tree)
20562062
).transform(ddef.rhs)
2063+
topAscription match {
2064+
case Some(tpt) =>
2065+
// we assume the ascribed type has an apply that has a MethodType with a single param list (there should be no polys)
2066+
val methodType = tpt.tpe.member(nme.apply).info.asInstanceOf[MethodType]
2067+
// result might contain paramrefs, so we substitute them with arg termrefs
2068+
val resultTypeWithSubst = methodType.resultType.substParams(methodType, argRefs.map(_.tpe))
2069+
Typed(result, TypeTree(resultTypeWithSubst).withSpan(fn.span)).withSpan(fn.span)
2070+
case None =>
2071+
result
2072+
}
20572073
case tpd.Block(stats, expr) =>
2058-
seq(stats, rec(expr)).withSpan(fn.span)
2074+
seq(stats, rec(expr, topAscription)).withSpan(fn.span)
20592075
case _ =>
2060-
fn.select(nme.apply).appliedToArgs(argRefs).withSpan(fn.span)
2076+
val maybeAscribed = topAscription match {
2077+
case Some(tpt) => Typed(fn, tpt).withSpan(fn.span)
2078+
case None => fn
2079+
}
2080+
maybeAscribed.select(nme.apply).appliedToArgs(argRefs).withSpan(fn.span)
20612081
}
2062-
seq(argVals, rec(fn))
2082+
seq(argVals, rec(fn, None))
20632083
}
20642084

20652085
/////////////
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
-- [E007] Type Mismatch Error: tests/neg-macros/beta-reduce-inline-result/Test_2.scala:11:41 ---------------------------
3+
11 | val x2: 4 = Macros.betaReduce(dummy1)(3) // error
4+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5+
| Found: Int
6+
| Required: (4 : Int)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
inline def betaReduce[Arg,Result](inline fn: Arg=>Result)(inline arg: Arg): Result =
5+
${ betaReduceImpl('{ fn })('{ arg }) }
6+
7+
def betaReduceImpl[Arg,Result](fn: Expr[Arg=>Result])(arg: Expr[Arg])(using qctx: QuoteContext): Expr[Result] =
8+
Expr.betaReduce(fn)(arg)
9+
}
10+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
object Test {
3+
4+
inline def dummy1: Int => Int =
5+
(i: Int) => i + 1
6+
7+
inline def dummy2: Int => Int =
8+
(i: Int) => ???
9+
10+
val x1: Int = Macros.betaReduce(dummy1)(3)
11+
val x2: 4 = Macros.betaReduce(dummy1)(3) // error
12+
}
13+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
compile-time: 4
2+
run-time: 4
3+
compile-time: 1
4+
run-time: 1
5+
run-time: 5
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
inline def betaReduce[Arg,Result](inline fn : Arg=>Result)(inline arg: Arg): Result =
5+
${ betaReduceImpl('{ fn })('{ arg }) }
6+
7+
def betaReduceImpl[Arg,Result](fn: Expr[Arg=>Result])(arg: Expr[Arg])(using qctx : QuoteContext): Expr[Result] =
8+
Expr.betaReduce(fn)(arg)
9+
10+
inline def betaReduceAdd1[Arg](inline fn: Arg=>Int)(inline arg: Arg): Int =
11+
${ betaReduceAdd1Impl('{ fn })('{ arg }) }
12+
13+
def betaReduceAdd1Impl[Arg](fn: Expr[Arg=>Int])(arg: Expr[Arg])(using qctx: QuoteContext): Expr[Int] =
14+
'{ ${ Expr.betaReduce(fn)(arg) } + 1 }
15+
}
16+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import scala.compiletime._
2+
3+
object Test {
4+
5+
inline def dummy1: Int => Int =
6+
(i: Int) => i + 1
7+
8+
inline def dummy2: (i: Int) => i.type =
9+
(i: Int) => i
10+
11+
inline def dummy3: Int => Int =
12+
(i: Int) => ???
13+
14+
inline def dummy4: Int => Int =
15+
???
16+
17+
def main(argv : Array[String]) : Unit = {
18+
println(code"compile-time: ${Macros.betaReduce(dummy1)(3)}")
19+
println(s"run-time: ${Macros.betaReduce(dummy1)(3)}")
20+
println(code"compile-time: ${Macros.betaReduce(dummy2)(1)}")
21+
// paramrefs have to be properly substituted in this case
22+
println(s"run-time: ${Macros.betaReduce(dummy2)(1)}")
23+
24+
// ensure the inlined ??? is ascribed type Int so this compiles
25+
def throwsNotImplemented1 = Macros.betaReduceAdd1(dummy3)(4)
26+
// ensure we handle cases where the (non-inlineable) function itself needs ascribing
27+
def throwsNotImplemented2 = Macros.betaReduce(dummy4)(6)
28+
29+
// make sure paramref types work when inlining is not possible
30+
println(s"run-time: ${Macros.betaReduce(Predef.identity)(5)}")
31+
}
32+
}
33+

0 commit comments

Comments
 (0)