Skip to content

Commit

Permalink
Update staging.Compiler.make documentation (scala#19428)
Browse files Browse the repository at this point in the history
Fixes scala#19211
This addresses part of scala#19170, and scala#19176
  • Loading branch information
nicolasstucki authored Jan 18, 2024
2 parents e768883 + a34cd7d commit 34628b3
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 12 deletions.
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2432,9 +2432,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)
Expand Down
6 changes: 5 additions & 1 deletion docs/_spec/TODOreference/metaprogramming/staging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand Down
28 changes: 20 additions & 8 deletions staging/src/scala/quoted/staging/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 13 additions & 1 deletion staging/src/scala/quoted/staging/QuoteDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/run-staging/i11162.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions tests/run-staging/i19170.scala
Original file line number Diff line number Diff line change
@@ -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))
}
16 changes: 16 additions & 0 deletions tests/run-staging/i19170b.scala
Original file line number Diff line number Diff line change
@@ -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."))
}
10 changes: 10 additions & 0 deletions tests/run-staging/i19176.scala
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 34628b3

Please sign in to comment.