Skip to content

Can't get constructor of java defined class in reflect api #18694

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

Open
goshacodes opened this issue Oct 13, 2023 · 10 comments
Open

Can't get constructor of java defined class in reflect api #18694

goshacodes opened this issue Oct 13, 2023 · 10 comments
Assignees
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug itype:question

Comments

@goshacodes
Copy link

goshacodes commented Oct 13, 2023

Compiler version

3.3.0

Description

I'm trying to extend a java class using reflect api.
To create a symbol for a class I need parent, either TypeTree or Term. TypeTree is not working here, so I'am trying to get a constructor and apply nulls as arguments

Minimized code

public class JavaClassWithBridgeMethod {
    public String overloadedMethod(String a) { return "bar1"; }
}
def macro[T: Type](using quotes: Quotes): Expr[Any] =
  import quotes.reflect.*
  val tpe = TypeRepr.of[T]
  ...

macro[JavaClassWithBridgeMethod]

When I'm trying to use tpe.typeSymbol.primaryConstructor compiler crushes with this:

[error] java.lang.AssertionError: assertion failed: private constructor JavaClassWithBridgeMethod in class JavaClassWithBridgeMethod in /Users/g.kovalev/IdeaProjects/ScalaMock/jvm/src/test/java/com/paulbutcher/test/JavaClassWithBridgeMethod.java accessed from constructor $anon in /Users/g.kovalev/IdeaProjects/ScalaMock/jvm/src/test/scala/com.paulbutcher.test/mock/JavaMocksTest.scala
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.dotc.transform.ExpandPrivate.ensurePrivateAccessible(ExpandPrivate.scala:88)
[error] dotty.tools.dotc.transform.ExpandPrivate.transformSelect(ExpandPrivate.scala:98)
[error] dotty.tools.dotc.transform.ExpandPrivate.transformSelect(ExpandPrivate.scala:97)
[error] dotty.tools.dotc.transform.MegaPhase.goSelect(MegaPhase.scala:605)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:228)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:425)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:278)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.loop$2(MegaPhase.scala:444)
[error] dotty.tools.dotc.transform.MegaPhase.transformBlock(MegaPhase.scala:449)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:298)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.mapDefDef$1(MegaPhase.scala:248)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:251)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:425)
[error] dotty.tools.dotc.transform.MegaPhase.transformSpecificTree(MegaPhase.scala:434)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:356)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:255)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:425)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:438)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:438)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:359)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:255)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:425)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:438)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:438)
[error] dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:379)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:382)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:466)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:324)
[error] scala.collection.immutable.List.map(List.scala:250)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:328)
[error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:247)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321)
[error] dotty.tools.dotc.Run.runPhases$1(Run.scala:263)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:271)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:280)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:67)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:280)
[error] dotty.tools.dotc.Run.compileSources(Run.scala:195)
[error] dotty.tools.dotc.Run.compile(Run.scala:179)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:35)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:193)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:248)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:183)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:211)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:179)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:177)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:463)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:263)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:418)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:505)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:405)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:171)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:488)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:425)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2363)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2313)
[error] sbt.internal.server.BspCompileTask$.$anonfun$compute$1(BspCompileTask.scala:30)
[error] sbt.internal.io.Retry$.apply(Retry.scala:46)
[error] sbt.internal.io.Retry$.apply(Retry.scala:28)
[error] sbt.internal.io.Retry$.apply(Retry.scala:23)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:30)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2311)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:68)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:282)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
[error] sbt.Execute.work(Execute.scala:291)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:282)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
[error] java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:577)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
[error] java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[error] java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)

So instead of trying to use primary constructor, I am trying to use another public one and I got this one.

wrong number of arguments at dropBreaks for (x$1: Unit): com.paulbutcher.test.JavaClassWithBridgeMethod: (com.paulbutcher.test.JavaClassWithBridgeMethod#<init> :
[error]    |  (x$1: Unit): com.paulbutcher.test.JavaClassWithBridgeMethod), expected: 1, found: 0

After it I am trying to pass there a () as argument and it gives me another error:

wrong number of arguments at pickler for (): com.paulbutcher.test.JavaClassWithBridgeMethod: (com.paulbutcher.test.JavaClassWithBridgeMethod#<init> :
[error]    |  (): com.paulbutcher.test.JavaClassWithBridgeMethod), expected: 0, found: 1
scala.MatchError: val <none> (of class dotty.tools.dotc.core.Symbols$NoSymbol$)
@goshacodes goshacodes added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 13, 2023
@nicolasstucki nicolasstucki added itype:question area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug and removed stat:needs triage Every issue needs to have an "area" and "itype" label itype:bug labels Oct 16, 2023
@nicolasstucki
Copy link
Contributor

@goshacodes could you provide the implementation of the macro?

@goshacodes
Copy link
Author

goshacodes commented Oct 16, 2023

@goshacodes
Copy link
Author

goshacodes commented Jan 7, 2025

@nicolasstucki minimized example. Also I can try contribute this with a spree, want this fixed

build.sbt

lazy val root = (project in file("."))
  .settings(
    scalaVersion := "3.6.2",
    name := "java_class",
    scalacOptions ++= Seq(
      "-explain",
      "-Xcheck-macros"
    )
  )

JavaClass.java

public class JavaClass {
}

main.scala

import scala.annotation.experimental

@main
@experimental
def main(): Unit =
  mock[JavaClass]

Macro.scala

import scala.annotation.experimental
import scala.quoted.{Expr, Quotes, Type}

@experimental
inline def mock[T]: T = ${mockMacro[T]}

@experimental
def mockMacro[T: Type](using quotes: Quotes): Expr[T] =
  import quotes.reflect.*
  val tree = TypeTree.of[T]
  val parents =
    List(
      Select(
        New(TypeIdent(tree.tpe.typeSymbol)),
        tree.tpe.typeSymbol.primaryConstructor
      ).appliedToArgs(List('{()}.asTerm))
    )

  val sym = Symbol.newClass(
    Symbol.spliceOwner,
    "anon",
    parents.map {
      case term: Term => term.tpe
      case tpt: TypeTree => tpt.tpe
    },
    decls = _ => Nil,
    selfType = None
  )

  val cls = ClassDef(sym, parents, Nil)

  Block(
    List(cls),
    Typed(Apply(Select(New(TypeIdent(sym)), sym.primaryConstructor), Nil), TypeTree.of[T])
  ).asExprOf[T]

Output

[error] java.lang.AssertionError: assertion failed: private constructor JavaClass in class JavaClass in /Users/gosha/IdeaProjects/java_class/src/main/java/JavaClass.java accessed from constructor anon in class anon in /Users/gosha/IdeaProjects/java_class/src/main/scala/main.scala
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.dotc.transform.ExpandPrivate.ensurePrivateAccessible(ExpandPrivate.scala:88)
[error] dotty.tools.dotc.transform.ExpandPrivate.transformSelect(ExpandPrivate.scala:98)
[error] dotty.tools.dotc.transform.ExpandPrivate.transformSelect(ExpandPrivate.scala:97)
[error] dotty.tools.dotc.transform.MegaPhase.goSelect(MegaPhase.scala:636)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:245)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:295)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.loop$2(MegaPhase.scala:471)

@goshacodes
Copy link
Author

@jchyb Also tagging you. Nicolas seems not active on github currently

@jchyb jchyb self-assigned this Jan 7, 2025
@jchyb
Copy link
Contributor

jchyb commented Jan 7, 2025

Yes, I recently took over some of Nicolas' responsibilities. Thank you for the minimization! I believe this might be getting fixed soon with #22104. I'll make sure it is in 3.6.4 (should be back portable to LTS).

@joan38
Copy link
Contributor

joan38 commented Apr 24, 2025

Is this fixed in 3.6.4?
@goshacodes I'm using Scalamock with 3.6.4 but sounds like there is custom message before this blows?

@goshacodes
Copy link
Author

goshacodes commented Apr 25, 2025

Not sure if removing custom message would help even if it is fixed since scalamock is bound to 3.3.0 and Scala version is not updatable until experimental quotes api like Symbol.newClass and .info becomes stable.

Is this fixed in 3.6.4?
@goshacodes I'm using Scalamock with 3.6.4 but sounds like there is custom message before this blows?

@joan38
Copy link
Contributor

joan38 commented Apr 26, 2025

Why can we not upgrade and still keep experimental?

@goshacodes
Copy link
Author

goshacodes commented May 7, 2025

This won't allow users on LTS use scalamock at all since there is no -experimental compiler flag and this is a real problem, so I guess we are waiting next LTS and would be great if there quotes api will be stabilized

Why can we not upgrade and still keep experimental?

@joan38
Copy link
Contributor

joan38 commented May 8, 2025

The @experimental annotation is here either way. I'm trying to find a way to make it work for Scala 3.6.4 at least.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug itype:question
Projects
None yet
Development

No branches or pull requests

4 participants