From c004c13d03f63870356326d46b28d2d6f43c64a1 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 9 Oct 2025 15:00:31 +0200 Subject: [PATCH 1/3] Add ensureApplied to the quotes reflect API --- .../quoted/runtime/impl/QuotesImpl.scala | 2 ++ library/src/scala/quoted/Quotes.scala | 3 +++ project/MiMaFilters.scala | 1 + tests/pos-macros/i23969/Macro.scala | 26 +++++++++++++++++++ tests/pos-macros/i23969/Main.scala | 1 + 5 files changed, 33 insertions(+) create mode 100644 tests/pos-macros/i23969/Macro.scala create mode 100644 tests/pos-macros/i23969/Main.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 62080c41719a..10843c824283 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -484,6 +484,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler argss.foldLeft(self: Term)(Apply(_, _)) def appliedToNone: Apply = self.appliedToArgs(Nil) + def ensureApplied: Term = + if (self.tpe.widen.isParameterless) self else self.appliedToNone def appliedToType(targ: TypeRepr): Term = self.appliedToTypes(targ :: Nil) def appliedToTypes(targs: List[TypeRepr]): Term = diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a00c620788c9..a31d26cb3df4 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -890,6 +890,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The current tree applied to (): `tree()` */ def appliedToNone: Apply + /** The current tree applied to `()` unless the tree's widened type is parameterless */ + def ensureApplied: Term + /** The current tree applied to given type argument: `tree[targ]` */ def appliedToType(targ: TypeRepr): Term diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 3c6af327bb68..9b6d779692df 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -140,6 +140,7 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newModule"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermMethods.ensureApplied"), // Changes to lazy vals (added static constructors) ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Tuple."), diff --git a/tests/pos-macros/i23969/Macro.scala b/tests/pos-macros/i23969/Macro.scala new file mode 100644 index 000000000000..e659810b278f --- /dev/null +++ b/tests/pos-macros/i23969/Macro.scala @@ -0,0 +1,26 @@ + +import scala.quoted._ + +object TestMethods: + def a1 = () + def a2() = () + +transparent inline def runMacro() = ${runMacroImpl} +def runMacroImpl(using Quotes): Expr[Any] = + import quotes.reflect._ + + // ensureApplied test + Select.unique('{TestMethods}.asTerm, "a1").ensureApplied match + case Select(_, _) => + case _ => assert(false) + Select.unique('{TestMethods}.asTerm, "a2").ensureApplied match + case Apply(_, _) => + case _ => assert(false) + + // regression test + val Inlined(_, _, generated) = '{BigDecimal(0).toString()}.asTerm : @unchecked + val Inlined(_, _, bigDecimal) = '{BigDecimal(0)}.asTerm : @unchecked + val custom = Select.unique(bigDecimal, "toString").ensureApplied + // ensure both have the same shape + assert(custom.toString == generated.toString) + custom.asExpr diff --git a/tests/pos-macros/i23969/Main.scala b/tests/pos-macros/i23969/Main.scala new file mode 100644 index 000000000000..455b473d0157 --- /dev/null +++ b/tests/pos-macros/i23969/Main.scala @@ -0,0 +1 @@ +@main def Test() = runMacro() From 6bbd7dc73bfc4435338e7b12df1069deb8372992 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 10 Oct 2025 14:38:04 +0200 Subject: [PATCH 2/3] Strip unnecessary stack trace from a test --- tests/neg-macros/i23008.check | 7 ------- tests/neg-macros/i23008/Macro_1.scala | 7 ++++++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/neg-macros/i23008.check b/tests/neg-macros/i23008.check index d661e59fb74b..3d342ef797d7 100644 --- a/tests/neg-macros/i23008.check +++ b/tests/neg-macros/i23008.check @@ -3,13 +3,6 @@ | ^^^^^^^^^^^^^^^^^^ | Exception occurred while executing macro expansion. | java.lang.IllegalArgumentException: requirement failed: value of StringConstant cannot be `null` - | at scala.Predef$.require(Predef.scala:394) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2542) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2541) - | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:82) - | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80) - | at scala.quoted.Expr$.apply(Expr.scala:72) - | at Macros$.buildStringCode(Macro_1.scala:9) | |--------------------------------------------------------------------------------------------------------------------- |Inline stack trace diff --git a/tests/neg-macros/i23008/Macro_1.scala b/tests/neg-macros/i23008/Macro_1.scala index 67bd091cd9f3..ef3e00cc08ba 100644 --- a/tests/neg-macros/i23008/Macro_1.scala +++ b/tests/neg-macros/i23008/Macro_1.scala @@ -6,6 +6,11 @@ object Macros { def buildStringCode(using Quotes): Expr[String] = { import quotes.reflect.* val str: String = null - Expr(str) + try + Expr(str) + catch + case error: java.lang.IllegalArgumentException => + error.setStackTrace(Array[StackTraceElement]()) + throw error } } From b145f7d28e7ba5d263441a535689248ecdaa8900 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 28 Oct 2025 02:37:23 +0100 Subject: [PATCH 3/3] Do not wrap into an Apply in ensureApplied if a type parameter is expected --- .../scala/quoted/runtime/impl/QuotesImpl.scala | 3 ++- library/src/scala/quoted/Quotes.scala | 2 +- tests/pos-macros/i23969/Macro.scala | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 10843c824283..eba009fef4f2 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -485,7 +485,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def appliedToNone: Apply = self.appliedToArgs(Nil) def ensureApplied: Term = - if (self.tpe.widen.isParameterless) self else self.appliedToNone + def isParameterless(tpe: TypeRepr): Boolean = !tpe.isInstanceOf[MethodType] + if (isParameterless(self.tpe.widen)) self else self.appliedToNone def appliedToType(targ: TypeRepr): Term = self.appliedToTypes(targ :: Nil) def appliedToTypes(targs: List[TypeRepr]): Term = diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a31d26cb3df4..f0619459df76 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -890,7 +890,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The current tree applied to (): `tree()` */ def appliedToNone: Apply - /** The current tree applied to `()` unless the tree's widened type is parameterless */ + /** The current tree applied to `()` unless the tree's widened type is parameterless or expects type parameters */ def ensureApplied: Term /** The current tree applied to given type argument: `tree[targ]` */ diff --git a/tests/pos-macros/i23969/Macro.scala b/tests/pos-macros/i23969/Macro.scala index e659810b278f..8c4845ce87ae 100644 --- a/tests/pos-macros/i23969/Macro.scala +++ b/tests/pos-macros/i23969/Macro.scala @@ -4,6 +4,8 @@ import scala.quoted._ object TestMethods: def a1 = () def a2() = () + def a3[T] = () + def a4[T]() = () transparent inline def runMacro() = ${runMacroImpl} def runMacroImpl(using Quotes): Expr[Any] = @@ -16,6 +18,19 @@ def runMacroImpl(using Quotes): Expr[Any] = Select.unique('{TestMethods}.asTerm, "a2").ensureApplied match case Apply(_, _) => case _ => assert(false) + Select.unique('{TestMethods}.asTerm, "a3").ensureApplied match + case Select(_, _) => + case other => assert(false) + Select.unique('{TestMethods}.asTerm, "a4").ensureApplied match + case Select(_, _) => + case other => assert(false) + + TypeApply(Select.unique('{TestMethods}.asTerm, "a3"), List(TypeTree.of[Nothing])).ensureApplied match + case TypeApply(_, _) => + case other => assert(false) + TypeApply(Select.unique('{TestMethods}.asTerm, "a4"), List(TypeTree.of[Nothing])).ensureApplied match + case Apply(_, _) => + case other => assert(false) // regression test val Inlined(_, _, generated) = '{BigDecimal(0).toString()}.asTerm : @unchecked