gzoller/scala-reflection

Compiler Plugin failing with ClassNotFoundException

pjfanning opened this issue · 11 comments

In the build.sbt of https://github.com/pjfanning/jackson-scala3-reflection-extensions, I have commented out this:

//addCompilerPlugin("co.blocke" %% "scala-reflection" % scalaReflectionVersion)

When I uncomment it, I get this failure:

[error] java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.json.JsonMapper
[error] 	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
[error] 	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
[error] 	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
[error] 	at java.base/java.lang.Class.forName0(Native Method)
[error] 	at java.base/java.lang.Class.forName(Class.java:315)
[error] 	at co.blocke.scala_reflection.info.JavaClassInfo.infoClass(ClassInfo.scala:234)
[error] 	at co.blocke.scala_reflection.info.JavaClassInfo.proxy$$anonfun$1(ClassInfo.scala:235)
[error] 	at scala.Option.getOrElse(Option.scala:201)
[error] 	at co.blocke.scala_reflection.info.JavaClassInfo.proxy(ClassInfo.scala:235)
[error] 	at co.blocke.scala_reflection.info.JavaClassInfo.fields(ClassInfo.scala:236)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.$anonfun$12(TastyReflection.scala:403)
[error] 	at scala.Option.flatMap(Option.scala:283)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.reflectOnField(TastyReflection.scala:403)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.$anonfun$11(TastyReflection.scala:368)
[error] 	at scala.collection.immutable.List.map(List.scala:246)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.reflectOnClass(TastyReflection.scala:368)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.reflectOnType$$anonfun$3(TastyReflection.scala:97)
[error] 	at scala.Option.getOrElse(Option.scala:201)
[error] 	at co.blocke.scala_reflection.impl.TastyReflection$.reflectOnType(TastyReflection.scala:98)
[error] 	at co.blocke.scala_reflection.RType$.unwindType$$anonfun$1(RType.scala:208)
[error] 	at scala.collection.mutable.HashMap.getOrElse(HashMap.scala:436)
[error] 	at co.blocke.scala_reflection.RType$.unwindType(RType.scala:211)
[error] 	at co.blocke.scala_reflection.ReflectionWorkerPhase.transformTypeDef(ReflectionWorker.scala:44)
[error] 	at dotty.tools.dotc.transform.MegaPhase.goTypeDef(MegaPhase.scala:989)
[error] 	at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:257)

I think this is happening with the trait or companion object for ScalaReflectionExtensions.

I tried to produce a simplified test case without much luck.

Hmm. That’s gonna take some thought, unles you have a more simplified example. I don’t have any Jackson dependencies. What I CAN try is the pattern you have where a 3rd party Java class (not compiled with this library/plugin) being used by the plugin. What may be happening is the plugin is looking for the annotation with the serialized RType in it, and of course the Jackson libs wouldn’t have this annotation. Maybe…? I’ll play around with it a bit and see if I can replicate.

thanks - I also tested the https://github.com/pjfanning/jackson-scala3-reflection-extensions build with scala 3.1.1-RC2 and had the same issue

Check out the cachefix branch. I've added a test directory to exercise the complier plugin, and that seems to work, although right now you need to manually set the version. You do a publishLocal first, notice the version then cut/paste that into the build.sbt file. Then do a tests/test to run the test. Very clumsy but I'll make that better later.

I see from your code you were doing some fancy stuff like this:

object Outer:
  final class Mixin2 private[Outer] (nada: String)

I thought that might be the problem, but no... that actually works fine. The issue seems to be JsonMapper. When I ran the sample with JsonMapper I got:

==> X co.blocke.scala_reflection.ScalaTasty.fancy annotation test  0.067s java.lang.ClassCastException: class sun.reflect.generics.reflectiveObjects.WildcardTypeImpl cannot be cast to class java.lang.Class (sun.reflect.generics.reflectiveObjects.WildcardTypeImpl and java.lang.Class are in module java.base of loader 'bootstrap')
    at co.blocke.scala_reflection.impl.JavaClassInspector$.$anonfun$3(JavaClassInspector.scala:84)
    at scala.collection.immutable.List.map(List.scala:246)
    at co.blocke.scala_reflection.impl.JavaClassInspector$.co$blocke$scala_reflection$impl$JavaClassInspector$$$inspectType(JavaClassInspector.scala:84)
    at co.blocke.scala_reflection.impl.JavaClassInspector$$anon$1.applyOrElse(JavaClassInspector.scala:51)
    at co.blocke.scala_reflection.impl.JavaClassInspector$$anon$1.applyOrElse(JavaClassInspector.scala:43)
    at scala.collection.immutable.List.collect(List.scala:267)
    at co.blocke.scala_reflection.impl.JavaClassInspector$.parseFields(JavaClassInspector.scala:53)
    at co.blocke.scala_reflection.impl.JavaClassInspector$.inspectClass(JavaClassInspector.scala:28)
    at co.blocke.scala_reflection.info.JavaClassInfo.proxy$$anonfun$1(ClassInfo.scala:235)
    at scala.Option.getOrElse(Option.scala:201)
    at co.blocke.scala_reflection.info.JavaClassInfo.proxy(ClassInfo.scala:235)
    at co.blocke.scala_reflection.info.JavaClassInfo.fields(ClassInfo.scala:236)
    at co.blocke.scala_reflection.info.JavaClassInfo.show(ClassInfo.scala:265)
    at co.blocke.scala_reflection.info.FieldInfo.show(FieldInfo.scala:28)
    at co.blocke.scala_reflection.info.FieldInfo.show$(FieldInfo.scala:9)
    at co.blocke.scala_reflection.info.ScalaFieldInfo.show(FieldInfo.scala:46)
    at co.blocke.scala_reflection.info.ScalaClassInfo.show$$anonfun$1(ClassInfo.scala:192)
    at scala.collection.ArrayOps$.map$extension(ArrayOps.scala:924)
    at co.blocke.scala_reflection.info.ScalaClassInfo.show(ClassInfo.scala:192)
    at co.blocke.scala_reflection.RType.toString(RType.scala:31)
    at co.blocke.scala_reflection.RType.toString$(RType.scala:14)
    at co.blocke.scala_reflection.info.ScalaClassInfo.toString(ClassInfo.scala:158)
    at java.lang.String.valueOf(String.java:3352)
    at java.io.PrintStream.println(PrintStream.java:977)
    at scala.Console$.println(Console.scala:268)
    at scala.Predef$.println(Predef.scala:426)
    at co.blocke.scala_reflection.ScalaTasty.$init$$$anonfun$3(ScalaTasty.scala:43)

That's a different error but it may point to the problem. Somewhere deep in Jackson they're using a sun.reflect.generics.reflectiveObjects.WildcardTypeImpl type, which can't be case to a Class. I currently don't support that in my library--yet. I'll need to dig into what this strange thing actually is, and whether some kind of bridge can be built between that thing and an class/RType.

More detail: Fails while trying to inspect class: com.fasterxml.jackson.databind.introspect.VisibilityChecker<?>
I'm really beginning to wonder... by default scala-reflection tries its best to dive deep in non-case classes, including Java classes. The Java ecosystem is much older and potentially weirder--I'm unlikely to be able to account for it all.

What would you say to a change in policy, where instead of trying to reflect on all Java classes that Java classes will be ignored from reflection UNLESS they are annotated to tell scala-reflection you do want them? The idea here is, perhaps DON'T reflect on all these Java library classes--only those we own/compile ourselves. (3rd party Scala classes don't seem to suffer from this problem...)

If you need to reflect on com.fasterxml.jackson.databind.json.JsonMapper then we have a different challenge... We'll need to figure out what this Wildcard thing is...

How's this:

ScalaClassInfo(co.blocke.scala_reflection.Mixin):
   fields:
      (0) mapper: JavaClassInfo(com.fasterxml.jackson.databind.json.JsonMapper):
         fields:
            (0) visibilityChecker: TraitInfo(com.fasterxml.jackson.databind.introspect.VisibilityChecker) with fields:
               annotations: Map(java.lang.Deprecated -> Map(since -> , forRemoval -> false))
   non-constructor fields:

Try building the feature/cachefix branch locally and see if it works for you in your code.

Thanks @gzoller for looking in this. The Mixin class is not something I really need to introspect. It's just that the ClassNotFoundException was worrying - that it looked possible that there was a more fundamental problem. I'm happy to annotate this Mixin class with the Skip_Reflection annotation.

All good. I am curious if the cachefix code fixed it tho.

I built the changes on my laptop and published to my local maven repo. When I tried the changes in the jackson-scala3-reflection-extensions build, they did not help - I still got the java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.json.JsonMapper issue.

This issue is easily worked around in my use case - so don't worry about spending too much time on it.

Ok.... now I can't let this go. It's very strange. I've distilled it down to a minimal set:
This works:

package com.foo

class Foo2()

class Mixin() extends Foo2

I create a Java class Foo:

package com.foo;

public class Foo {
	int x = 5;
}

And this works:

package com.foo

class Mixin() extends Foo

But.... This does not work:

package com.foo

class Mixin(i:Int) extends Foo

What?!?!?! Adding a trivial constructor parameter causes this to fail? And only with a Java class? (Using Foo2 works.). This doesn't make much sense. It's like somewhere it's somehow picking up a different ClassLoader that doesn't know where Foo is. Hmm....

Found the problem! It was obscure. For Java classes (and only Java classes) it's not possible to load the Class at compile-time. All the paths aren't set up, etc., as they are at runtime. So I use a proxy Info class and everything there is lazy. I had one thing in TastyInspection that attempts to resolve the proxy while still in compile mode--that caused the ClassNotFound. I put a check in that code to prevent the lazy things from resolving too early (during compile) and it seems to work. Compiled your original code fine.

Try next release (1.1.4 if all goes well) when it comes out shortly (building now).

@gzoller I tested the 1.1.4 release and it fixes this issue - thanks.