Skip to content

Commit 2254fa4

Browse files
committed
Add reflect ClassDef.apply and Symbol.newClass
1 parent fae7c09 commit 2254fa4

File tree

19 files changed

+369
-10
lines changed

19 files changed

+369
-10
lines changed

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ object Symbols {
577577
def complete(denot: SymDenotation)(using Context): Unit = {
578578
val cls = denot.asClass.classSymbol
579579
val decls = newScope
580-
denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls)
580+
denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls, selfInfo)
581581
}
582582
}
583583
newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile)

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
227227
end ClassDefTypeTest
228228

229229
object ClassDef extends ClassDefModule:
230+
def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef =
231+
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree)
232+
val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor)
233+
tpd.ClassDefWithParents(cls.asClass, ctr, parents, body)
234+
230235
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = {
231236
val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original
232237
tpd.cpy.TypeDef(original)(name.toTypeName, tpd.cpy.Template(originalImpl)(constr, parents, derived = Nil, selfOpt.getOrElse(tpd.EmptyValDef), body))
@@ -259,6 +264,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
259264

260265
object DefDef extends DefDefModule:
261266
def apply(symbol: Symbol, rhsFn: List[List[Tree]] => Option[Term]): DefDef =
267+
assert(symbol.isTerm, s"expected a term symbol but received $symbol")
262268
withDefaultPos(tpd.DefDef(symbol.asTerm, prefss =>
263269
xCheckMacroedOwners(xCheckMacroValidExpr(rhsFn(prefss)), symbol).getOrElse(tpd.EmptyTree)
264270
))
@@ -1803,7 +1809,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
18031809
(x.prefix, x.name.toString)
18041810
end TermRef
18051811

1806-
type TypeRef = dotc.core.Types.NamedType
1812+
type TypeRef = dotc.core.Types.TypeRef
18071813

18081814
object TypeRefTypeTest extends TypeTest[TypeRepr, TypeRef]:
18091815
def unapply(x: TypeRepr): Option[TypeRef & x.type] = x match
@@ -2453,6 +2459,20 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
24532459
def requiredModule(path: String): Symbol = dotc.core.Symbols.requiredModule(path)
24542460
def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path)
24552461
def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName)
2462+
2463+
def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfInfo: Option[TypeRepr]): Symbol =
2464+
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
2465+
val cls = dotc.core.Symbols.newNormalizedClassSymbol(
2466+
owner,
2467+
name.toTypeName,
2468+
dotc.core.Flags.EmptyFlags,
2469+
parents,
2470+
selfInfo.getOrElse(Types.NoType),
2471+
dotc.core.Symbols.NoSymbol)
2472+
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil))
2473+
for sym <- decls(cls) do cls.enter(sym)
2474+
cls
2475+
24562476
def newMethod(owner: Symbol, name: String, tpe: TypeRepr): Symbol =
24572477
newMethod(owner, name, tpe, Flags.EmptyFlags, noSymbol)
24582478
def newMethod(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol =
@@ -2621,6 +2641,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26212641
def companionClass: Symbol = self.denot.companionClass
26222642
def companionModule: Symbol = self.denot.companionModule
26232643
def children: List[Symbol] = self.denot.children
2644+
def typeRef: TypeRef = self.denot.typeRef
2645+
def termRef: TermRef = self.denot.termRef
26242646

26252647
def show(using printer: Printer[Symbol]): String = printer.show(self)
26262648

library/src/scala/quoted/Quotes.scala

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
464464

465465
/** Methods of the module object `val ClassDef` */
466466
trait ClassDefModule { this: ClassDef.type =>
467+
@experimental def apply(cls: Symbol, parents: List[Tree /* Term | TypeTree */], body: List[Statement]): ClassDef
467468
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree /* Term | TypeTree */], selfOpt: Option[ValDef], body: List[Statement]): ClassDef
468469
def unapply(cdef: ClassDef): (String, DefDef, List[Tree /* Term | TypeTree */], Option[ValDef], List[Statement])
469470
}
@@ -3533,19 +3534,73 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
35333534
/** The class Symbol of a global class definition */
35343535
def classSymbol(fullName: String): Symbol
35353536

3537+
/** Generates a new class symbol for a class with a parameterless constructor.
3538+
*
3539+
* Example usage:
3540+
* ```scala
3541+
* val name: String = ...
3542+
* val parents = List(TypeTree.of[Object], TypeTree.of[Foo])
3543+
* def decls(cls: Symbol): List[Symbol] =
3544+
* List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
3545+
*
3546+
* val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None)
3547+
* val fooSym = cls.declaredMethod("foo").head
3548+
*
3549+
* val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling foo")}.asTerm))
3550+
* val clsDef = ClassDef(cls, parents, body = List(fooDef))
3551+
* val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo])
3552+
*
3553+
* Block(List(clsDef), newCls).asExprOf[Foo]
3554+
* ```
3555+
* constructs the equivalent to
3556+
* ```scala
3557+
* '{
3558+
* class `name`() extends Object with Foo {
3559+
* def foo(): Unit = println("Calling foo")
3560+
* }
3561+
* new `name`(): Foo
3562+
* }
3563+
* ```
3564+
*
3565+
* @param parent The owner of the class
3566+
* @param name The name of the class
3567+
* @param parents The parent classes of the class
3568+
* @param decls The member declarations of the class provided the symbol of this class
3569+
* @param self The self type of the class if it has one
3570+
*
3571+
* This symbol starts without an accompanying definition.
3572+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3573+
* this symbol to the ClassDef constructor.
3574+
*
3575+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3576+
* direct or indirect children of the reflection context's owner.
3577+
*/
3578+
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfInfo: Option[TypeRepr]): Symbol
3579+
35363580
/** Generates a new method symbol with the given parent, name and type.
3537-
*
3538-
* This symbol starts without an accompanying definition.
3539-
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3540-
* this symbol to the DefDef constructor.
3541-
*
3542-
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3543-
* direct or indirect children of the reflection context's owner.
3544-
*/
3581+
*
3582+
* To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`.
3583+
*
3584+
* @param parent The owner of the method
3585+
* @param name The name of the method
3586+
* @param tpe The type of the method (MethodType, PolyType, ByNameType)
3587+
*
3588+
* This symbol starts without an accompanying definition.
3589+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3590+
* this symbol to the DefDef constructor.
3591+
*
3592+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3593+
* direct or indirect children of the reflection context's owner.
3594+
*/
35453595
def newMethod(parent: Symbol, name: String, tpe: TypeRepr): Symbol
35463596

35473597
/** Works as the other newMethod, but with additional parameters.
35483598
*
3599+
* To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`.
3600+
*
3601+
* @param parent The owner of the method
3602+
* @param name The name of the method
3603+
* @param tpe The type of the method (MethodType, PolyType, ByNameType)
35493604
* @param flags extra flags to with which the symbol should be constructed
35503605
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
35513606
*/
@@ -3559,6 +3614,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
35593614
*
35603615
* Note: Also see reflect.let
35613616
*
3617+
* @param parent The owner of the /var/lazy val
3618+
* @param name The name of the val/var/lazy val
3619+
* @param tpe The type of the val/var/lazy val
35623620
* @param flags extra flags to with which the symbol should be constructed
35633621
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
35643622
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
@@ -3572,7 +3630,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
35723630
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
35733631
* this symbol to the BindDef constructor.
35743632
*
3633+
* @param parent The owner of the binding
3634+
* @param name The name of the binding
35753635
* @param flags extra flags to with which the symbol should be constructed
3636+
* @param tpe The type of the binding
35763637
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
35773638
* direct or indirect children of the reflection context's owner.
35783639
*/
@@ -3807,6 +3868,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
38073868

38083869
/** Case class or case object children of a sealed trait or cases of an `enum`. */
38093870
def children: List[Symbol]
3871+
3872+
/** Type reference to the symbol */
3873+
@experimental
3874+
def typeRef: TypeRef
3875+
3876+
/** Term reference to the symbol */
3877+
@experimental
3878+
def termRef: TermRef
38103879
end extension
38113880
}
38123881

project/MiMaFilters.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ object MiMaFilters {
1919
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#CompilationInfoModule.XmacroSettings"),
2020
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromProductTyped"),
2121
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromTuple"),
22+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"),
23+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"),
24+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"),
25+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"),
26+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"),
27+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"),
28+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"),
29+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"),
2230

2331
// Private to the compiler - needed for forward binary compatibility
2432
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
-- Error: tests/neg-macros/newClassExtendsNoParents/Test_2.scala:1:25 --------------------------------------------------
3+
1 |def test: Any = makeClass("foo") // error
4+
| ^^^^^^^^^^^^^^^^
5+
| Exception occurred while executing macro expansion.
6+
| java.lang.AssertionError: assertion failed: First parent must be a class
7+
| at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
8+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2464)
9+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2463)
10+
| at Macro_1$package$.makeClassExpr(Macro_1.scala:11)
11+
| at Macro_1$package$.inline$makeClassExpr(Macro_1.scala:4)
12+
|
13+
|---------------------------------------------------------------------------------------------------------------------
14+
|Inline stack trace
15+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16+
|This location contains code that was inlined from Macro_1.scala:3
17+
3 |inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
19+
---------------------------------------------------------------------------------------------------------------------
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List.empty[Tree] // BUG: first parent is not a class
9+
def decls(cls: Symbol): List[Symbol] = Nil
10+
11+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = Nil, decls, selfInfo = None)
12+
val clsDef = ClassDef(cls, parents, body = List())
13+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
14+
15+
Block(List(clsDef), newCls).asExpr
16+
17+
// '{
18+
// class `name`() {
19+
// def foo(): Unit = println("Calling `name`.foo")
20+
// }
21+
// new `name`()
22+
// }
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test: Any = makeClass("foo") // error
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
-- Error: tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala:1:25 --------------------------------------------------
3+
1 |def test: Foo = makeClass("foo") // error
4+
| ^^^^^^^^^^^^^^^^
5+
| Exception occurred while executing macro expansion.
6+
| java.lang.AssertionError: assertion failed: First parent must be a class
7+
| at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
8+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2464)
9+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2463)
10+
| at Macro_1$package$.makeClassExpr(Macro_1.scala:12)
11+
| at Macro_1$package$.inline$makeClassExpr(Macro_1.scala:4)
12+
|
13+
|---------------------------------------------------------------------------------------------------------------------
14+
|Inline stack trace
15+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16+
|This location contains code that was inlined from Macro_1.scala:3
17+
3 |inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
19+
---------------------------------------------------------------------------------------------------------------------
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List(TypeTree.of[Foo]) // BUG: first parent is not a class
9+
def decls(cls: Symbol): List[Symbol] =
10+
List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
11+
12+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None)
13+
val fooSym = cls.declaredMethod("foo").head
14+
15+
val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling ${$nameExpr}.foo")}.asTerm))
16+
val clsDef = ClassDef(cls, parents, body = List(fooDef))
17+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo])
18+
19+
Block(List(clsDef), newCls).asExprOf[Foo]
20+
21+
// '{
22+
// class `name`() extends Foo {
23+
// def foo(): Unit = println("Calling `name`.foo")
24+
// }
25+
// new `name`()
26+
// }
27+
}
28+
29+
trait Foo {
30+
def foo(): Unit
31+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test: Foo = makeClass("foo") // error

tests/run-macros/newClass.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Constructing foo
2+
class Test_2$package$foo$1
3+
Constructing bar
4+
class Test_2$package$bar$1
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List(TypeTree.of[Object])
9+
def decls(cls: Symbol): List[Symbol] = Nil
10+
11+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None)
12+
13+
val clsDef = ClassDef(cls, parents, body = List('{println(s"Constructing ${$nameExpr}")}.asTerm))
14+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
15+
16+
Block(List(clsDef), newCls).asExpr
17+
// '{
18+
// class `name`() { println("Constructing `name`") }
19+
// new `name`()
20+
// }
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@main def Test: Unit = {
2+
val foo = makeClass("foo")
3+
println(foo.getClass)
4+
val bar = makeClass("bar")
5+
println(bar.getClass)
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Calling foo.foo
2+
class Test_2$package$foo$1
3+
Calling bar.foo
4+
class Test_2$package$bar$1
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List(TypeTree.of[Object], TypeTree.of[Foo])
9+
def decls(cls: Symbol): List[Symbol] =
10+
List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
11+
12+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None)
13+
val fooSym = cls.declaredMethod("foo").head
14+
15+
val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling ${$nameExpr}.foo")}.asTerm))
16+
val clsDef = ClassDef(cls, parents, body = List(fooDef))
17+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo])
18+
19+
Block(List(clsDef), newCls).asExprOf[Foo]
20+
21+
// '{
22+
// class `name`() extends Object, Foo {
23+
// def foo(): Unit = println("Calling `name`.foo")
24+
// }
25+
// new `name`()
26+
// }
27+
}
28+
29+
trait Foo {
30+
def foo(): Unit
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@main def Test: Unit = {
2+
val foo: Foo = makeClass("foo")
3+
foo.foo()
4+
println(foo.getClass)
5+
val bar: Foo = makeClass("bar")
6+
bar.foo()
7+
println(bar.getClass)
8+
}

tests/run-macros/newClassSelf.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Calling Bar.bar
2+
Calling Foo.foo
3+
Calling Bar.bar
4+
class Test_2$package$A$1
5+
Calling Bar.bar
6+
Calling Foo.foo
7+
Calling Bar.bar
8+
class Test_2$package$B$1

0 commit comments

Comments
 (0)