Skip to content

Commit a3b0e2b

Browse files
committed
Fix #8290: Make Expr.betaReduce give up when it sees a non-function typed closure
Adapted from 30b35f1
1 parent 055132d commit a3b0e2b

File tree

5 files changed

+69
-10
lines changed

5 files changed

+69
-10
lines changed

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,23 +2051,33 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
20512051
}}
20522052
val argVals = argVals0.reverse
20532053
val argRefs = argRefs0.reverse
2054-
val reducedBody = lambdaExtractor(fn) match {
2054+
val reducedBody = lambdaExtractor(fn, argRefs.map(_.tpe)) match {
20552055
case Some(body) => body(argRefs)
20562056
case None => fn.select(nme.apply).appliedToArgs(argRefs)
20572057
}
20582058
seq(argVals, reducedBody).withSpan(fn.span)
20592059
}
20602060

2061-
def lambdaExtractor(fn: Term)(using ctx: Context): Option[List[Term] => Term] = {
2061+
def lambdaExtractor(fn: Term, paramTypes: List[Type])(using ctx: Context): Option[List[Term] => Term] = {
20622062
def rec(fn: Term, transformBody: Term => Term): Option[List[Term] => Term] = {
20632063
fn match {
20642064
case Inlined(call, bindings, expansion) =>
20652065
// this case must go before closureDef to avoid dropping the inline node
20662066
rec(expansion, cpy.Inlined(fn)(call, bindings, _))
20672067
case Typed(expr, tpt) =>
2068-
val resTpe = tpt.tpe.dropDependentRefinement.argInfos.last
2069-
rec(expr, Typed(_, TypeTree(resTpe).withSpan(tpt.span)))
2070-
case closureDef(ddef) =>
2068+
val tpe = tpt.tpe.dropDependentRefinement
2069+
// we checked that this is a plain Function closure, so there will be an apply method with a MethodType
2070+
// and the expected signature based on param types
2071+
val expectedSig = Signature.NotAMethod.prependTermParams(paramTypes, false)
2072+
val method = tpt.tpe.member(nme.apply).atSignature(expectedSig)
2073+
if method.symbol.is(Deferred) then
2074+
val methodType = method.info.asInstanceOf[MethodType]
2075+
// result might contain paramrefs, so we substitute them with arg termrefs
2076+
val resultTypeWithSubst = methodType.resultType.substParams(methodType, paramTypes)
2077+
rec(expr, Typed(_, TypeTree(resultTypeWithSubst).withSpan(tpt.span)))
2078+
else
2079+
None
2080+
case cl @ closureDef(ddef) =>
20712081
def replace(body: Term, argRefs: List[Term]): Term = {
20722082
val paramSyms = ddef.vparamss.head.map(param => param.symbol)
20732083
val paramToVals = paramSyms.zip(argRefs).toMap

library/src/scala/quoted/matching/Lambda.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ object Lambda {
1616
* body('{3}) // returns '{ println(3) }
1717
* ```
1818
*/
19-
def unapply[F, Args <: Tuple, Res, G](expr: Expr[F])(using qctx: QuoteContext, tf: TupledFunction[F, Args => Res], tg: TupledFunction[G, Tuple.Map[Args, Expr] => Expr[Res]]): Option[/*QuoteContext ?=>*/ G] = {
19+
def unapply[F, Args <: Tuple, Res, G](expr: Expr[F])(using qctx: QuoteContext, tf: TupledFunction[F, Args => Res], tg: TupledFunction[G, Tuple.Map[Args, Expr] => Expr[Res]], functionType: Type[F]): Option[/*QuoteContext ?=>*/ G] = {
2020
import qctx.tasty.{_, given _ }
21-
qctx.tasty.internal.lambdaExtractor(expr.unseal).map { fn =>
21+
val argTypes = functionType.unseal.tpe match
22+
case AppliedType(_, functionArguments) => functionArguments.init.asInstanceOf[List[Type]]
23+
qctx.tasty.internal.lambdaExtractor(expr.unseal, argTypes).map { fn =>
2224
def f(args: Tuple.Map[Args, Expr]): Expr[Res] =
2325
fn(args.toArray.map(_.asInstanceOf[Expr[Any]].unseal).toList).seal.asInstanceOf[Expr[Res]]
2426
tg.untupled(f)

library/src/scala/tasty/reflect/CompilerInterface.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,6 @@ trait CompilerInterface {
15431543
*/
15441544
def betaReduce(f: Term, args: List[Term])(using ctx: Context): Term
15451545

1546-
def lambdaExtractor(term: Term)(using ctx: Context): Option[List[Term] => Term]
1546+
def lambdaExtractor(term: Term, paramTypes: List[Type])(using ctx: Context): Option[List[Term] => Term]
15471547

15481548
}

tests/run-macros/beta-reduce-inline-result.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ run-time: 4
33
compile-time: 1
44
run-time: 1
55
run-time: 5
6+
run-time: 7
7+
run-time: -1
8+
run-time: 9

tests/run-macros/beta-reduce-inline-result/Test_2.scala

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.compiletime._
22

33
object Test {
4-
4+
55
inline def dummy1: Int => Int =
66
(i: Int) => i + 1
77

@@ -14,6 +14,36 @@ object Test {
1414
inline def dummy4: Int => Int =
1515
???
1616

17+
object I extends (Int => Int) {
18+
def apply(i: Int): i.type = i
19+
}
20+
21+
abstract class II extends (Int => Int) {
22+
val apply = 123
23+
}
24+
25+
inline def dummy5: II =
26+
(i: Int) => i + 1
27+
28+
abstract class III extends (Int => Int) {
29+
def impl(i: Int): Int
30+
def apply(i: Int): Int = -1
31+
}
32+
33+
inline def dummy6: III =
34+
(i: Int) => i + 1
35+
36+
abstract class IV extends (Int => Int) {
37+
def apply(s: String): String
38+
}
39+
40+
abstract class V extends IV {
41+
def apply(s: String): String = "gotcha"
42+
}
43+
44+
inline def dummy7: IV =
45+
{ (i: Int) => i + 1 } : V
46+
1747
def main(argv : Array[String]) : Unit = {
1848
println(code"compile-time: ${Macros.betaReduce(dummy1)(3)}")
1949
println(s"run-time: ${Macros.betaReduce(dummy1)(3)}")
@@ -27,7 +57,21 @@ object Test {
2757
def throwsNotImplemented2 = Macros.betaReduce(dummy4)(6)
2858

2959
// make sure paramref types work when inlining is not possible
30-
println(s"run-time: ${Macros.betaReduce(Predef.identity)(5)}")
60+
println(s"run-time: ${Macros.betaReduce(I)(5)}")
61+
62+
// -- cases below are non-function types, which are currently not inlined for simplicity but may be in the future
63+
// (also, this tests that we return something valid when we see a closure that we can't inline)
64+
65+
// A non-function type with an apply value that can be confused with the apply method.
66+
println(s"run-time: ${Macros.betaReduce(dummy5)(6)}")
67+
68+
// should print -1 (without inlining), because the apparent apply method actually
69+
// has nothing to do with the function literal
70+
println(s"run-time: ${Macros.betaReduce(dummy6)(7)}")
71+
72+
// the literal does contain the implementation of the apply method, but there are two abstract apply methods
73+
// in the outermost abstract type
74+
println(s"run-time: ${Macros.betaReduce(dummy7)(8)}")
3175
}
3276
}
3377

0 commit comments

Comments
 (0)