scalameta / metals

Scala language server with rich IDE features 🚀
https://scalameta.org/metals/
Apache License 2.0
2.1k stars 336 forks source link

Type information errors when classloading in macros with `Selectable` #6494

Open ncreep opened 5 months ago

ncreep commented 5 months ago

Describe the bug

Hello,

I'm trying to connect to a database via JDBC in a macro, for, um, educational purposes. I then want to use Selectable to reflect the data from the database in a structural type (in the style of type providers).

Despite everything compiling fine, it seems that Metals is having issues when I actually try to touch the database.

Here's a minimized reproduction.

//> using scala "3.4.2"
//> using dep "com.h2database:h2:2.2.224"

import scala.quoted._

class LoadStuff extends Selectable:
  def selectDynamic(name: String): Any = name

object LoadStuff:
  transparent inline def make = ${ makeImpl }

  private def makeImpl(using Quotes): Expr[Any] =
    import quotes.reflect.*

    Class.forName("org.h2.Driver")

    val refinement = Refinement(TypeRepr.of[LoadStuff], "name", TypeRepr.of[String])

    refinement.asType match
      case '[refined] => '{ LoadStuff().asInstanceOf[refined] }

Notice that I'm accessing a class from the H2 driver while running the macro. The result of the macro is a refined type. I can then use it in a separate file as follows:

@main def test =
  val load = LoadStuff.make

  val x = load.name

  println(x)

This compiles and runs correctly. But Metals loses all type information. I can't get member completions on the load value. And I cannot automatically insert a type annotations, it fails with an error.

Following the logs, I see that I get the following error when trying to insert a type annotation:

Jun 11, 2024 12:56:11 AM scala.meta.internal.pc.CompilerAccess handleError
SEVERE: A severe compiler error occurred, full details of the error can be found in the error report /root/workspace/bug-reporting/class_loading_macro/.metals/.reports/metals-full/2024-06-11/r_compiler-error_(class_loading_macro_bd2c96d2de)_00-56-11-868.md
2024.06.11 00:56:11 WARN  Could not find semantic tokens for: file:///.../use_macro.scala

...

Jun 11, 2024 12:56:15 AM org.eclipse.lsp4j.jsonrpc.RemoteEndpoint fallbackResponseError
SEVERE: Internal error: scala.concurrent.Future$$anon$2: Future.filter predicate is not satisfied
java.util.concurrent.CompletionException: scala.concurrent.Future$$anon$2: Future.filter predicate is not satisfied
        at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:332)
        at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:347)
        at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:708)
        at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
        at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2162)
        at scala.meta.internal.metals.CancelTokens$.$anonfun$future$1(CancelTokens.scala:40)
        at scala.meta.internal.metals.CancelTokens$.$anonfun$future$1$adapted(CancelTokens.scala:38)
        at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:484)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: scala.concurrent.Future$$anon$2: Future.filter predicate is not satisfied

And opening one of the error files, I see this:

### java.util.NoSuchElementException: head of empty list

...

#### Error stacktrace:

scala.collection.immutable.Nil$.head(List.scala:662)
        scala.collection.immutable.Nil$.head(List.scala:661)
        dotty.tools.pc.PcCollector.<init>(PcCollector.scala:45)
        dotty.tools.pc.PcSemanticTokensProvider$Collector$.<init>(PcSemanticTokensProvider.scala:63)
        dotty.tools.pc.PcSemanticTokensProvider.Collector$lzyINIT1(PcSemanticTokensProvider.scala:63)
        dotty.tools.pc.PcSemanticTokensProvider.Collector(PcSemanticTokensProvider.scala:63)
        dotty.tools.pc.PcSemanticTokensProvider.provide(PcSemanticTokensProvider.scala:88)
        dotty.tools.pc.ScalaPresentationCompiler.semanticTokens$$anonfun$1(ScalaPresentationCompiler.scala:109)

If I remove the Class.forName call everything works as expected, with member completion and generated type annotations.

Please let me know if I can provide any further info.

Thanks

Expected behavior

Selectable types should work correctly, with type member selection hints regardless of the access to library classes.

Operating system

None

Editor/Extension

VS Code

Version of Metals

v1.35.5

Extra context or search terms

No response

kasiaMarek commented 5 months ago

Thanks for the report. It seems that loading java.sql.Driver causes an error in the interactive driver, when inside of a macro.

java.lang.ClassNotFoundException: java.sql.Driver
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:421)
        at java.base/java.lang.Class.forName(Class.java:412)
        at a.LoadStuff$.makeImpl(LoadStuff.scala:13)
        at a.LoadStuff$.inline$makeImpl(LoadStuff.scala:10)
ncreep commented 5 months ago

Thanks for the quick response.

How did you get that stack trace? I didn't see it in the logs that I referenced.

Is the classpath for the interactive driver configurable?

kasiaMarek commented 5 months ago

How did you get that stack trace?

I got it from diagnostics inside of presentation compiler. It isn't surfaced anywhere.

Is the classpath for the interactive driver configurable?

In Metals we take the classpath for the specific build target according to what the build server (so the build tool really) says. But this error only occurs if Class.forName("org.h2.Driver") (or Class.forName("java.sql.Driverr")) in done inside of a macro.

ncreep commented 5 months ago

Thanks for the info. If it's the same classpath, I wonder how it diverges from the build tool. Additionally, I can say that this also works correctly when building from sbt (but still broken in Metals).

Actually, thinking about it, this has nothing to do with Selectable. Apparently just being a transparent inline with class loading is enough to trigger this behavior:

object LoadStuff:
  transparent inline def make: Unit = ${ makeImpl }

  private def makeImpl(using Quotes): Expr[Unit] =
    Class.forName("org.h2.Driver")

    '{()}

This too triggers a loss of type-information at the call-site of make.

Removing transparent brings back type information.