Skip to content

Commit 34628b3

Browse files
Update staging.Compiler.make documentation (#19428)
Fixes #19211 This addresses part of #19170, and #19176
2 parents e768883 + a34cd7d commit 34628b3

File tree

8 files changed

+85
-12
lines changed

8 files changed

+85
-12
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -2432,9 +2432,14 @@ class UnqualifiedCallToAnyRefMethod(stat: untpd.Tree, method: Symbol)(using Cont
24322432
def kind = MessageKind.PotentialIssue
24332433
def msg(using Context) = i"Suspicious top-level unqualified call to ${hl(method.name.toString)}"
24342434
def explain(using Context) =
2435+
val getClassExtraHint =
2436+
if method.name == nme.getClass_ && ctx.settings.classpath.value.contains("scala3-staging") then
2437+
i"""\n\n
2438+
|This class should not be used to get the classloader for `scala.quoted.staging.Compile.make`."""
2439+
else ""
24352440
i"""Top-level unqualified calls to ${hl("AnyRef")} or ${hl("Any")} methods such as ${hl(method.name.toString)} are
24362441
|resolved to calls on ${hl("Predef")} or on imported methods. This might not be what
2437-
|you intended."""
2442+
|you intended.$getClassExtraHint"""
24382443
}
24392444

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

docs/_spec/TODOreference/metaprogramming/staging.md

+5-1
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

+20-8
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

+13-1
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

+1-1
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

+14
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

+16
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

+10
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)