Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update staging.Compiler.make documentation #19428

Merged
merged 3 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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)
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)
Loading