gzoller/scala-reflection

infoClass fails with ClassNotFoundException for inner abstract classes

luis-orgvue opened this issue · 11 comments

import co.blocke.scala_reflection._

trait Trait:
	abstract class Field protected(name: String)

object MyObject extends Trait:
	val field = new Field("John Doe") {}

object Main extends App:
	println(RType.of[MyObject.field.type].infoClass)

Scastie: https://scastie.scala-lang.org/SPG2AaSKTzum09cxTJ9NeQ

In the code above Field's actual class name is MyObject$$anon$1.

Thanks for your great work. I will submit a PR if I find how to solve this case :)

@luis-orgvue It is an anonymous class. I don't see anything wrong with the class name.

Some users will prefer to get MyObject$$anon$1 as this could be used in Java reflection calls (for instance).

If you want to construct some sort of fake class name that says something like 'anonymous class declared in MyObject and that subclasses Field' then that should be a different method.

You could also just take RType.of[MyObject.field.type].infoClass and use the Class API methods to work out what that class inherits from - eg use https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#isAssignableFrom-java.lang.Class-

@pjfanning sorry, I meant to say that calling RType.of[MyObject.field.type].infoClass fails with ClassNotFoundException. Not that I think the actual class name seems wrong. My apologies for not being clear.

Your scastie link shows no ClassNotFoundException. Can you provide code that shows the ClassNotFoundException?

Edit: I see the exception now. The first println works but then the 2nd one fails.

java.lang.ExceptionInInitializerError
	at Main.main(main.scala)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at sbt.Run.invokeMain(Run.scala:144)
	at sbt.Run.execute$1(Run.scala:94)
	at sbt.Run.$anonfun$runWithLoader$5(Run.scala:121)
	at sbt.Run$.executeSuccess(Run.scala:187)
	at sbt.Run.runWithLoader(Run.scala:121)
	at sbt.Run.run(Run.scala:128)
	at com.olegych.scastie.sbtscastie.SbtScastiePlugin$$anon$1.$anonfun$run$1(SbtScastiePlugin.scala:38)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:32)
	at sbt.ScastieTrapExit$App.run(ScastieTrapExit.scala:258)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ClassNotFoundException: Trait.Field
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
	at sbt.internal.ManagedClassLoader.findClass(ManagedClassLoader.java:103)
	at sbt.internal.BottomClassLoader.lambda$findClass$0(BottomClassLoader.java:57)
	at sbt.internal.ClassLoadingLock.withLock(ClassLoadingLock.java:30)
	at sbt.internal.BottomClassLoader.findClass(BottomClassLoader.java:52)
	at sbt.internal.ReverseLookupClassLoader.lambda$findClass$0(ReverseLookupClassLoader.java:131)
	at sbt.internal.ClassLoadingLock.withLock(ClassLoadingLock.java:30)
	at sbt.internal.ReverseLookupClassLoader.findClass(ReverseLookupClassLoader.java:120)
	at sbt.internal.ReverseLookupClassLoader.loadClass(ReverseLookupClassLoader.java:111)
	at sbt.internal.ReverseLookupClassLoader.loadClass(ReverseLookupClassLoader.java:102)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:375)
	at co.blocke.scala_reflection.info.ScalaClassInfoBase.liftedTree1$1(ClassInfo.scala:47)
	at co.blocke.scala_reflection.info.ScalaClassInfoBase.infoClass(ClassInfo.scala:56)
	at co.blocke.scala_reflection.info.ScalaClassInfoBase.infoClass$(ClassInfo.scala:26)
	at co.blocke.scala_reflection.info.ScalaClassInfo.infoClass$lzyINIT2(ClassInfo.scala:197)
	at co.blocke.scala_reflection.info.ScalaClassInfo.infoClass(ClassInfo.scala:197)
	at Main$.<clinit>(main.scala:11)
	... 16 more

I just found out that in the example above this works Class.forName("Trait$Field") which could be an alternative way to find the class for these anonymous cases.

@luis-orgvue I created #69 - let's see what @gzoller thinks

@luis-orgvue I maintain an independent fork of this lib. I have published a 1.3.1 release of that, if you would like to try that lib instead.

https://github.com/pjfanning/scala3-reflection

Sorry I'm late to the party, guys! Thank you, @pjfanning for fixing this. I've just merged and will soon cut v1.2.3 of this repo.

I've been busy working on the next-gen of this library, which will hopefully be better, but break some things, for example all the "Info" classes will now be RType classes, and hopefully be even more type-correct. Will publish an "M" release before making this 1.x series go to sleep on a branch.

Relative to this change I'll see how I can adapt this to the 2.x code so we don't lose this functionality.

v1.2.3 released with pjfannings changes, so Inner Classes should enjoy improved support.

Just ran this example with latest fixes in 2.0.1. Got this result if I output the given RType (raw and pretty view):

ScalaClassRType(co.blocke.scala_reflection.model.Trait.Field,co.blocke.scala_reflection.model.Trait.Field,List(),List(),List(),List(ScalaFieldInfo(0,name,StringRType(),Map(),None,None,true)),Map(),List(java.lang.Object),false,false,false,true,Map(),List(),List(),false)

----- Pretty >>

co.blocke.scala_reflection.model.Trait.Field (abstract class):
   fields ->
      name (set-only): String

That seems right to me (?). I'm going to close this ticket, but if this seems wrong to you, please re-open and describe the output you would expect for this reflection.