diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 4c7cf003cbc9..61f9c13a5867 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2415,9 +2415,14 @@ class UnqualifiedCallToAnyRefMethod(stat: untpd.Tree, method: Symbol)(using Cont def kind = MessageKind.PotentialIssue def msg(using Context) = i"Suspicious top-level unqualified call to ${hl(method.name.toString)}" def explain(using Context) = + val getClassExtraHint = + if method.name == nme.getClass_ && ctx.settings.classpath.value.contains("scala3-staging") then + i"""\n\n + |This class should not be used to get the classloader for `scala.quoted.staging.Compile.make`.""" + else "" i"""Top-level unqualified calls to ${hl("AnyRef")} or ${hl("Any")} methods such as ${hl(method.name.toString)} are |resolved to calls on ${hl("Predef")} or on imported methods. This might not be what - |you intended.""" + |you intended.$getClassExtraHint""" } class SynchronizedCallOnBoxedClass(stat: tpd.Tree)(using Context) diff --git a/docs/_spec/TODOreference/metaprogramming/staging.md b/docs/_spec/TODOreference/metaprogramming/staging.md index 6d9166e8249e..073012761513 100644 --- a/docs/_spec/TODOreference/metaprogramming/staging.md +++ b/docs/_spec/TODOreference/metaprogramming/staging.md @@ -108,7 +108,11 @@ to get a source-like representation of the expression. import scala.quoted.* // make available the necessary compiler for runtime code generation -given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) +given staging.Compiler = + // We need an instance of a class that is defined in the current application (not the standard library) + // `this` can be used instead of an instance of `Dummy` if the Compiler is instantiated within one of the application classes. + object Dummy + staging.Compiler.make(Dummy.getClass.getClassLoader) val f: Array[Int] => Int = staging.run { val stagedSum: Expr[Array[Int] => Int] = diff --git a/staging/src/scala/quoted/staging/Compiler.scala b/staging/src/scala/quoted/staging/Compiler.scala index 7f380dabd4e2..b37e8d4f70f2 100644 --- a/staging/src/scala/quoted/staging/Compiler.scala +++ b/staging/src/scala/quoted/staging/Compiler.scala @@ -13,15 +13,27 @@ object Compiler: /** Create a new instance of the compiler using the the classloader of the application. * - * Usage: - * ``` - * import scala.quoted.staging._ - * given Compiler = Compiler.make(getClass.getClassLoader) - * ``` + * Usage: + * ``` + * import scala.quoted.staging._ + * given Compiler = + * object Dummy + * Compiler.make(Dummy.getClass.getClassLoader) + * ``` * - * @param appClassloader classloader of the application that generated the quotes - * @param settings compiler settings - * @return A new instance of the compiler + * Note that we use an instance of `Dummy` to get the classloader that loaded the application. + * Any other instance of a class defined in the application would also work. + * Using a class defined in the standard library should be avoided as it might be loaded by a different classloader. + * + * If the given compiler is defined in one of your classes (e.i. not as a top-level definition), then + * the compiler can be instantiated with: + * ``` + * given Compiler = Compiler.make(this.getClass.getClassLoader) + * ``` + * + * @param appClassloader classloader of the application that generated the quotes + * @param settings compiler settings + * @return A new instance of the compiler */ def make(appClassloader: ClassLoader)(implicit settings: Settings): Compiler = new Compiler: diff --git a/staging/src/scala/quoted/staging/QuoteDriver.scala b/staging/src/scala/quoted/staging/QuoteDriver.scala index 93e19f195e00..821324ec2779 100644 --- a/staging/src/scala/quoted/staging/QuoteDriver.scala +++ b/staging/src/scala/quoted/staging/QuoteDriver.scala @@ -53,7 +53,19 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver: val method = clazz.getMethod("apply") val inst = clazz.getConstructor().newInstance() - method.invoke(inst).asInstanceOf[T] + try method.invoke(inst).asInstanceOf[T] + catch case ex: java.lang.reflect.InvocationTargetException => + ex.getCause match + case ex: java.lang.NoClassDefFoundError => + throw new Exception( + s"""`scala.quoted.staging.run` failed to load a class. + |The classloader used for the `staging.Compiler` instance might not be the correct one. + |Make sure that this classloader is the one that loaded the missing class. + |Note that the classloader that loads the standard library might not be the same as + |the one that loaded the application classes.""".stripMargin, + ex) + + case _ => throw ex end match end run diff --git a/tests/run-staging/i11162.scala b/tests/run-staging/i11162.scala index 81db85f24274..d15e976611af 100644 --- a/tests/run-staging/i11162.scala +++ b/tests/run-staging/i11162.scala @@ -2,7 +2,7 @@ import scala.quoted.* object Test { - given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) + given staging.Compiler = staging.Compiler.make(this.getClass.getClassLoader) def main(args: Array[String]): Unit = staging.run { diff --git a/tests/run-staging/i19170.scala b/tests/run-staging/i19170.scala new file mode 100644 index 000000000000..5462929f9302 --- /dev/null +++ b/tests/run-staging/i19170.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +given staging.Compiler = + object Dummy + staging.Compiler.make(Dummy.getClass.getClassLoader) + +class A(i: Int) + +def f(i: Expr[Int])(using Quotes): Expr[A] = { '{ new A($i) } } + +@main def Test = { + val g: Int => A = staging.run { '{ (i: Int) => ${ f('{i}) } } } + println(g(3)) +} diff --git a/tests/run-staging/i19170b.scala b/tests/run-staging/i19170b.scala new file mode 100644 index 000000000000..7fee2d2bc822 --- /dev/null +++ b/tests/run-staging/i19170b.scala @@ -0,0 +1,16 @@ +import scala.quoted.* + +given staging.Compiler = + staging.Compiler.make(getClass.getClassLoader) // warn: Suspicious top-level unqualified call to getClass + +class A(i: Int) + +def f(i: Expr[Int])(using Quotes): Expr[A] = { '{ new A($i) } } + +@main def Test = { + try + val g: Int => A = staging.run { '{ (i: Int) => ${ f('{i}) } } } + println(g(3)) + catch case ex: Exception => + assert(ex.getMessage().startsWith("`scala.quoted.staging.run` failed to load a class.")) +} diff --git a/tests/run-staging/i19176.scala b/tests/run-staging/i19176.scala new file mode 100644 index 000000000000..a8367074d48e --- /dev/null +++ b/tests/run-staging/i19176.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +given staging.Compiler = + object Dummy + staging.Compiler.make(Dummy.getClass.getClassLoader) + +class A +val f: (A, Int) => Int = staging.run { '{ (q: A, x: Int) => x } } + +@main def Test = f(new A, 3)