Skip to content

Commit d1041f6

Browse files
Backport "Update staging.Compiler.make documentation" to LTS (#20839)
Backports #19428 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 5982b8a + fb8f8df commit d1041f6

File tree

8 files changed

+85
-12
lines changed

8 files changed

+85
-12
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2406,9 +2406,14 @@ class UnqualifiedCallToAnyRefMethod(stat: untpd.Tree, method: Symbol)(using Cont
24062406
def kind = MessageKind.PotentialIssue
24072407
def msg(using Context) = i"Suspicious top-level unqualified call to ${hl(method.name.toString)}"
24082408
def explain(using Context) =
2409+
val getClassExtraHint =
2410+
if method.name == nme.getClass_ && ctx.settings.classpath.value.contains("scala3-staging") then
2411+
i"""\n\n
2412+
|This class should not be used to get the classloader for `scala.quoted.staging.Compile.make`."""
2413+
else ""
24092414
i"""Top-level unqualified calls to ${hl("AnyRef")} or ${hl("Any")} methods such as ${hl(method.name.toString)} are
24102415
|resolved to calls on ${hl("Predef")} or on imported methods. This might not be what
2411-
|you intended."""
2416+
|you intended.$getClassExtraHint"""
24122417
}
24132418

24142419
class SynchronizedCallOnBoxedClass(stat: tpd.Tree)(using Context)

docs/_spec/TODOreference/metaprogramming/staging.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ to get a source-like representation of the expression.
108108
import scala.quoted.*
109109

110110
// make available the necessary compiler for runtime code generation
111-
given staging.Compiler = staging.Compiler.make(getClass.getClassLoader)
111+
given staging.Compiler =
112+
// We need an instance of a class that is defined in the current application (not the standard library)
113+
// `this` can be used instead of an instance of `Dummy` if the Compiler is instantiated within one of the application classes.
114+
object Dummy
115+
staging.Compiler.make(Dummy.getClass.getClassLoader)
112116

113117
val f: Array[Int] => Int = staging.run {
114118
val stagedSum: Expr[Array[Int] => Int] =

staging/src/scala/quoted/staging/Compiler.scala

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ object Compiler:
1313

1414
/** Create a new instance of the compiler using the the classloader of the application.
1515
*
16-
* Usage:
17-
* ```
18-
* import scala.quoted.staging._
19-
* given Compiler = Compiler.make(getClass.getClassLoader)
20-
* ```
16+
* Usage:
17+
* ```
18+
* import scala.quoted.staging._
19+
* given Compiler =
20+
* object Dummy
21+
* Compiler.make(Dummy.getClass.getClassLoader)
22+
* ```
2123
*
22-
* @param appClassloader classloader of the application that generated the quotes
23-
* @param settings compiler settings
24-
* @return A new instance of the compiler
24+
* Note that we use an instance of `Dummy` to get the classloader that loaded the application.
25+
* Any other instance of a class defined in the application would also work.
26+
* Using a class defined in the standard library should be avoided as it might be loaded by a different classloader.
27+
*
28+
* If the given compiler is defined in one of your classes (e.i. not as a top-level definition), then
29+
* the compiler can be instantiated with:
30+
* ```
31+
* given Compiler = Compiler.make(this.getClass.getClassLoader)
32+
* ```
33+
*
34+
* @param appClassloader classloader of the application that generated the quotes
35+
* @param settings compiler settings
36+
* @return A new instance of the compiler
2537
*/
2638
def make(appClassloader: ClassLoader)(implicit settings: Settings): Compiler =
2739
new Compiler:

staging/src/scala/quoted/staging/QuoteDriver.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,19 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver:
5353
val method = clazz.getMethod("apply")
5454
val inst = clazz.getConstructor().newInstance()
5555

56-
method.invoke(inst).asInstanceOf[T]
56+
try method.invoke(inst).asInstanceOf[T]
57+
catch case ex: java.lang.reflect.InvocationTargetException =>
58+
ex.getCause match
59+
case ex: java.lang.NoClassDefFoundError =>
60+
throw new Exception(
61+
s"""`scala.quoted.staging.run` failed to load a class.
62+
|The classloader used for the `staging.Compiler` instance might not be the correct one.
63+
|Make sure that this classloader is the one that loaded the missing class.
64+
|Note that the classloader that loads the standard library might not be the same as
65+
|the one that loaded the application classes.""".stripMargin,
66+
ex)
67+
68+
case _ => throw ex
5769
end match
5870

5971
end run

tests/run-staging/i11162.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import scala.quoted.*
22

33
object Test {
44

5-
given staging.Compiler = staging.Compiler.make(getClass.getClassLoader)
5+
given staging.Compiler = staging.Compiler.make(this.getClass.getClassLoader)
66

77
def main(args: Array[String]): Unit =
88
staging.run {

tests/run-staging/i19170.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted.*
2+
3+
given staging.Compiler =
4+
object Dummy
5+
staging.Compiler.make(Dummy.getClass.getClassLoader)
6+
7+
class A(i: Int)
8+
9+
def f(i: Expr[Int])(using Quotes): Expr[A] = { '{ new A($i) } }
10+
11+
@main def Test = {
12+
val g: Int => A = staging.run { '{ (i: Int) => ${ f('{i}) } } }
13+
println(g(3))
14+
}

tests/run-staging/i19170b.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.quoted.*
2+
3+
given staging.Compiler =
4+
staging.Compiler.make(getClass.getClassLoader) // warn: Suspicious top-level unqualified call to getClass
5+
6+
class A(i: Int)
7+
8+
def f(i: Expr[Int])(using Quotes): Expr[A] = { '{ new A($i) } }
9+
10+
@main def Test = {
11+
try
12+
val g: Int => A = staging.run { '{ (i: Int) => ${ f('{i}) } } }
13+
println(g(3))
14+
catch case ex: Exception =>
15+
assert(ex.getMessage().startsWith("`scala.quoted.staging.run` failed to load a class."))
16+
}

tests/run-staging/i19176.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted.*
2+
3+
given staging.Compiler =
4+
object Dummy
5+
staging.Compiler.make(Dummy.getClass.getClassLoader)
6+
7+
class A
8+
val f: (A, Int) => Int = staging.run { '{ (q: A, x: Int) => x } }
9+
10+
@main def Test = f(new A, 3)

0 commit comments

Comments
 (0)