diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7df98d57b5ff..9bd7c8b01c76 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -625,9 +625,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Apply extends ApplyModule: def apply(fun: Term, args: List[Term]): Apply = + xCheckMacroAssert(fun.tpe.widen.isInstanceOf[dotc.core.Types.MethodType], "Expected `fun.tpe` to widen into a `MethodType`") xCheckMacroValidExprs(args) withDefaultPos(tpd.Apply(fun, args)) def copy(original: Tree)(fun: Term, args: List[Term]): Apply = + xCheckMacroAssert(fun.tpe.widen.isInstanceOf[dotc.core.Types.MethodType], "Expected `fun.tpe` to widen into a `MethodType`") xCheckMacroValidExprs(args) tpd.cpy.Apply(original)(fun, args) def unapply(x: Apply): (Term, List[Term]) = @@ -666,8 +668,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object TypeApply extends TypeApplyModule: def apply(fun: Term, args: List[TypeTree]): TypeApply = + xCheckMacroAssert(fun.tpe.widen.isInstanceOf[dotc.core.Types.PolyType], "Expected `fun.tpe` to widen into a `PolyType`") withDefaultPos(tpd.TypeApply(fun, args)) def copy(original: Tree)(fun: Term, args: List[TypeTree]): TypeApply = + xCheckMacroAssert(fun.tpe.widen.isInstanceOf[dotc.core.Types.PolyType], "Expected `fun.tpe` to widen into a `PolyType`") tpd.cpy.TypeApply(original)(fun, args) def unapply(x: TypeApply): (Term, List[TypeTree]) = (x.fun, x.args) @@ -791,7 +795,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Block extends BlockModule: def apply(stats: List[Statement], expr: Term): Block = - xCheckMacroBlockOwners(withDefaultPos(tpd.Block(stats, expr))) + xCheckMacroBlockOwners(withDefaultPos(tpd.Block(stats, xCheckMacroValidExpr(expr)))) def copy(original: Tree)(stats: List[Statement], expr: Term): Block = xCheckMacroBlockOwners(tpd.cpy.Block(original)(stats, expr)) def unapply(x: Block): (List[Statement], Term) = @@ -3095,13 +3099,22 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler if xCheckMacro then termOpt.foreach(xCheckMacroValidExpr) termOpt private def xCheckMacroValidExpr(term: Term): term.type = - if xCheckMacro then - assert(!term.tpe.widenDealias.isInstanceOf[dotc.core.Types.MethodicType], + xCheckMacroAssert(!term.tpe.widenDealias.isInstanceOf[dotc.core.Types.MethodicType], "Reference to a method must be eta-expanded before it is used as an expression: " + term.show) term private inline def xCheckMacroAssert(inline cond: Boolean, inline msg: String): Unit = - assert(!xCheckMacro || cond, msg) + if xCheckMacro && !cond then + xCheckMacroAssertFail(msg) + + private def xCheckMacroAssertFail(msg: String): Unit = + val error = new AssertionError(msg) + if !yDebugMacro then + // start stack trace at the place where the user called the reflection method + error.setStackTrace( + error.getStackTrace + .dropWhile(_.getClassName().startsWith("scala.quoted.runtime.impl"))) + throw error object Printer extends PrinterModule: diff --git a/tests/neg-macros/i18113.check b/tests/neg-macros/i18113.check new file mode 100644 index 000000000000..7a7a7069bd92 --- /dev/null +++ b/tests/neg-macros/i18113.check @@ -0,0 +1,15 @@ + +-- Error: tests/neg-macros/i18113/Test_2.scala:7:8 --------------------------------------------------------------------- + 7 | X.test(ref) // error + | ^^^^^^^^^^^ + |Exception occurred while executing macro expansion. + |java.lang.AssertionError: Reference to a method must be eta-expanded before it is used as an expression: x.Main.ref.plus + | at x.X$.testImpl(Macro_1.scala:16) + | + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:11 +11 | inline def test(ref:IntRef):Int = ${ testImpl('ref) } + | ^^^^^^^^^^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i18113/Macro_1.scala b/tests/neg-macros/i18113/Macro_1.scala new file mode 100644 index 000000000000..0dd95c690b6a --- /dev/null +++ b/tests/neg-macros/i18113/Macro_1.scala @@ -0,0 +1,21 @@ +package x + +import scala.quoted.* + +class IntRef(var x: Int) { + def plus(y:Int): Int = ??? +} + +object X { + + inline def test(ref:IntRef):Int = ${ testImpl('ref) } + + def testImpl(ref:Expr[IntRef])(using Quotes):Expr[Int] = { + import quotes.reflect.* + val fun0 = Select.unique(ref.asTerm,"plus") + val fun1 = Block(List(Assign(Select.unique(ref.asTerm,"x"),Literal(IntConstant(1)))),fun0) + val r = Apply(fun1,List(Literal(IntConstant(2)))) + r.asExprOf[Int] + } + +} diff --git a/tests/neg-macros/i18113/Test_2.scala b/tests/neg-macros/i18113/Test_2.scala new file mode 100644 index 000000000000..d09536008046 --- /dev/null +++ b/tests/neg-macros/i18113/Test_2.scala @@ -0,0 +1,9 @@ +package x + +object Main { + + val ref = IntRef(0) + + X.test(ref) // error + +} diff --git a/tests/neg-macros/i18113b.check b/tests/neg-macros/i18113b.check new file mode 100644 index 000000000000..dd2091eba09f --- /dev/null +++ b/tests/neg-macros/i18113b.check @@ -0,0 +1,15 @@ + +-- Error: tests/neg-macros/i18113b/Test_2.scala:7:8 -------------------------------------------------------------------- + 7 | X.test(ref) // error + | ^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.AssertionError: Expected `fun.tpe` to widen into a `MethodType` + | at x.X$.testImpl(Macro_1.scala:27) + | + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:12 +12 | inline def test(ref:IntRef):Int = ${ testImpl('ref) } + | ^^^^^^^^^^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i18113b/Macro_1.scala b/tests/neg-macros/i18113b/Macro_1.scala new file mode 100644 index 000000000000..2f404f0368cb --- /dev/null +++ b/tests/neg-macros/i18113b/Macro_1.scala @@ -0,0 +1,31 @@ +package x + +import scala.quoted.* + +class IntRef(var x: Int) { + def plus(y:Int): Int = ??? +} + + +object X { + + inline def test(ref:IntRef):Int = ${ testImpl('ref) } + + def testImpl(ref:Expr[IntRef])(using Quotes):Expr[Int] = { + import quotes.reflect.* + val fun0 = Select.unique(ref.asTerm,"plus") + val mt = MethodType(List("p"))( + _ => List(TypeRepr.of[Int]), + _ => TypeRepr.of[Int] + ) + val etaExpanded = Lambda(Symbol.spliceOwner, mt, (owner, params) => { + Block( + List(Assign(Select.unique(ref.asTerm,"x"),Literal(IntConstant(1)))), + Apply(fun0,params.map(_.asInstanceOf[Term])) + ) + }) + val r = Apply(etaExpanded,List(Literal(IntConstant(2)))) + r.asExprOf[Int] + } + +} diff --git a/tests/neg-macros/i18113b/Test_2.scala b/tests/neg-macros/i18113b/Test_2.scala new file mode 100644 index 000000000000..d09536008046 --- /dev/null +++ b/tests/neg-macros/i18113b/Test_2.scala @@ -0,0 +1,9 @@ +package x + +object Main { + + val ref = IntRef(0) + + X.test(ref) // error + +}