Skip to content

Commit

Permalink
Fix method signatures and add test for abstract classes and traits
Browse files Browse the repository at this point in the history
  • Loading branch information
jchyb committed Jan 8, 2025
1 parent 81cea85 commit 30abd4f
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 55 deletions.
47 changes: 23 additions & 24 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2645,13 +2645,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
parents: Symbol => List[TypeRepr],
decls: Symbol => List[Symbol],
selfType: Option[TypeRepr],
paramNames: List[String],
paramTypes: List[TypeRepr],
clsFlags: Flags,
clsPrivateWithin: Symbol
clsPrivateWithin: Symbol,
conParamNames: List[String],
conParamTypes: List[TypeRepr],
): Symbol =
checkValidFlags(clsFlags.toTermFlags, Flags.validClassFlags)
assert(paramNames.length == paramTypes.length, "paramNames and paramTypes must have the same length")
checkValidFlags(clsFlags, Flags.validClassFlags)
assert(conParamNames.length == conParamTypes.length, "paramNames and paramTypes must have the same length")
assert(!clsPrivateWithin.exists || clsPrivateWithin.isType, "clsPrivateWithin must be a type symbol or `Symbol.noSymbol`")
val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents(
owner,
Expand All @@ -2660,8 +2660,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
parents,
selfType.getOrElse(Types.NoType),
clsPrivateWithin)
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, paramNames.map(_.toTermName), paramTypes))
for (name, tpe) <- paramNames.zip(paramTypes) do
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, conParamNames.map(_.toTermName), conParamTypes))
for (name, tpe) <- conParamNames.zip(conParamTypes) do
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor, tpe, Symbol.noSymbol))
for sym <- decls(cls) do cls.enter(sym)
cls
Expand All @@ -2672,39 +2672,39 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
parents: Symbol => List[TypeRepr],
decls: Symbol => List[Symbol],
selfType: Option[TypeRepr],
constructorMethodType: TypeRepr => MethodOrPoly,
clsFlags: Flags,
clsPrivateWithin: Symbol,
consFlags: Flags,
consPrivateWithin: Symbol,
consParamFlags: List[List[Flags]]
conMethodType: TypeRepr => MethodOrPoly,
conFlags: Flags,
conPrivateWithin: Symbol,
conParamFlags: List[List[Flags]]
) =
assert(!clsPrivateWithin.exists || clsPrivateWithin.isType, "clsPrivateWithin must be a type symbol or `Symbol.noSymbol`")
assert(!consPrivateWithin.exists || consPrivateWithin.isType, "consPrivateWithin must be a type symbol or `Symbol.noSymbol`")
checkValidFlags(clsFlags.toTermFlags, Flags.validClassFlags)
assert(!conPrivateWithin.exists || conPrivateWithin.isType, "consPrivateWithin must be a type symbol or `Symbol.noSymbol`")
checkValidFlags(clsFlags.toTypeFlags, Flags.validClassFlags)
val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents(
owner,
name.toTypeName,
clsFlags,
parents,
selfType.getOrElse(Types.NoType),
clsPrivateWithin)
val methodType: MethodOrPoly = constructorMethodType(cls.typeRef)
def throwShapeException() = throw new Exception("Shapes of constructorMethodType and consParamFlags differ.")
val methodType: MethodOrPoly = conMethodType(cls.typeRef)
def throwShapeException() = throw new Exception("Shapes of conMethodType and conParamFlags differ.")
def checkMethodOrPolyShape(checkedMethodType: TypeRepr, clauseIdx: Int): Unit =
checkedMethodType match
case PolyType(params, _, res) if clauseIdx == 0 =>
if (consParamFlags.length < clauseIdx) throwShapeException()
if (consParamFlags(clauseIdx).length != params.length) throwShapeException()
if (conParamFlags.length < clauseIdx) throwShapeException()
if (conParamFlags(clauseIdx).length != params.length) throwShapeException()
checkMethodOrPolyShape(res, clauseIdx + 1)
case PolyType(_, _, _) => throw new Exception("Clause interleaving not supported for constructors")
case MethodType(params, _, res) =>
if (consParamFlags.length < clauseIdx) throwShapeException()
if (consParamFlags(clauseIdx).length != params.length) throwShapeException()
if (conParamFlags.length <= clauseIdx) throwShapeException()
if (conParamFlags(clauseIdx).length != params.length) throwShapeException()
checkMethodOrPolyShape(res, clauseIdx + 1)
case _ =>
checkMethodOrPolyShape(methodType, clauseIdx = 0)
cls.enter(dotc.core.Symbols.newSymbol(cls, nme.CONSTRUCTOR, Flags.Synthetic | Flags.Method | consFlags, methodType, consPrivateWithin, dotty.tools.dotc.util.Spans.NoCoord)) // constructor flags
cls.enter(dotc.core.Symbols.newSymbol(cls, nme.CONSTRUCTOR, Flags.Synthetic | Flags.Method | conFlags, methodType, conPrivateWithin, dotty.tools.dotc.util.Spans.NoCoord))
def getParamAccessors(methodType: TypeRepr, clauseIdx: Int): List[((String, TypeRepr, Boolean, Int), Int)] =
methodType match
case MethodType(paramInfosExp, resultTypeExp, res) =>
Expand All @@ -2723,17 +2723,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
}
for ((name, tpe, isType, clauseIdx), elementIdx) <- getParamAccessors(methodType, 0) do
if isType then
val symbol = dotc.core.Symbols.newSymbol(cls, name.toTypeName, Flags.Param | Flags.Deferred | consParamFlags(clauseIdx)(elementIdx), tpe, Symbol.noSymbol)
val symbol = dotc.core.Symbols.newSymbol(cls, name.toTypeName, Flags.Param | Flags.Deferred | Flags.Private | Flags.PrivateLocal | Flags.Local | conParamFlags(clauseIdx)(elementIdx), tpe, Symbol.noSymbol)
paramRefMap.addOne(elementIdx, symbol)
cls.enter(symbol)
else
val fixedType = paramRefRemapper(tpe)
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | consParamFlags(clauseIdx)(elementIdx), fixedType, Symbol.noSymbol)) // add other flags (local, private, privatelocal) and set privateWithin
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | conParamFlags(clauseIdx)(elementIdx), fixedType, Symbol.noSymbol)) // set privateWithin
for sym <- decls(cls) do cls.enter(sym)
cls

def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol =
// assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`")
val mod = dotc.core.Symbols.newNormalizedModuleSymbolUsingClassSymbolInParents(
owner,
Expand Down Expand Up @@ -3122,7 +3121,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local

// Keep: aligned with Quotes's `newClass`
private[QuotesImpl] def validClassFlags: Flags = Private | Protected | Final // Abstract, AbsOverride Local OPen ? PrivateLocal Protected ?
private[QuotesImpl] def validClassFlags: Flags = Private | Protected | PrivateLocal | Local | Final | Trait | Abstract // AbsOverride, Open

end Flags

Expand Down
30 changes: 15 additions & 15 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3846,10 +3846,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* @param name The name of the class
* @param parents Function returning the parent classes of the class. The first parent must not be a trait.
* Takes the constructed class symbol as an argument. Calling `cls.typeRef.asType` as part of this function will lead to cyclic reference errors.
* @param paramNames constructor parameter names.
* @param paramTypes constructor parameter types.
* @param clsFlags extra flags with which the class symbol should be constructed.
* @param clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol.
* @param conParamNames constructor parameter names.
* @param conParamTypes constructor parameter types.
*
* Parameters can be obtained via classSymbol.memberField
*/
Expand All @@ -3858,28 +3858,28 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
name: String,
parents: Symbol => List[TypeRepr],
decls: Symbol => List[Symbol], selfType: Option[TypeRepr],
paramNames: List[String],
paramTypes: List[TypeRepr],
clsFlags: Flags,
clsPrivateWithin: Symbol
clsPrivateWithin: Symbol,
conParamNames: List[String],
conParamTypes: List[TypeRepr]
): Symbol

/**
*
*
* @param owner The owner of the class
* @param name The name of the class
* @param parents Function returning the parent classes of the class. The first parent must not be a trait.
* @param parents Function returning the parent classes of the class. The first parent must not be a trait
* Takes the constructed class symbol as an argument. Calling `cls.typeRef.asType` as part of this function will lead to cyclic reference errors.
* @param decls The member declarations of the class provided the symbol of this class
* @param selfType The self type of the class if it has one
* @param constructorMethodType The MethodOrPoly type representing the type of the constructor.
* PolyType may only represent only the first clause of the constructor.
* @param clsFlags extra flags with which the class symbol should be constructed.
* @param clsFlags extra flags with which the class symbol should be constructed
* @param clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol
* @param consFlags extra flags with which the constructor symbol should be constructed.
* @param consPrivateWithin the symbol within which the constructor for this new class symbol should be private. May be noSymbol
* @param conParamFlags extra flags with which the constructor parameter symbols should be constructed. Must match the shape of @param constructorMethodType
* @param conMethodType The MethodOrPoly type representing the type of the constructor.
* PolyType may only represent the first clause of the constructor.
* @param conFlags extra flags with which the constructor symbol should be constructed
* @param conPrivateWithin the symbol within which the constructor for this new class symbol should be private. May be noSymbol.
* @param conParamFlags extra flags with which the constructor parameter symbols should be constructed. Must match the shape of `conMethodType`.
*
*/
@experimental def newClass(
Expand All @@ -3888,11 +3888,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
parents: Symbol => List[TypeRepr],
decls: Symbol => List[Symbol],
selfType: Option[TypeRepr],
constructorMethodType: TypeRepr => MethodOrPoly,
clsFlags: Flags,
clsPrivateWithin: Symbol,
consFlags: Flags,
consPrivateWithin: Symbol,
conMethodType: TypeRepr => MethodOrPoly,
conFlags: Flags,
conPrivateWithin: Symbol,
conParamFlags: List[List[Flags]]
): Symbol

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Object] =
val parents = List(TypeTree.of[Object])
def decls(cls: Symbol): List[Symbol] = Nil

val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List("idx"), List(TypeRepr.of[Int]))

val clsDef = ClassDef(cls, parents, body = Nil)
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo[_]] =
List(AppliedType(TypeRepr.typeConstructorOf(Class.forName("Foo")), List(TypeIdent(cls).tpe)))
def decls(cls: Symbol): List[Symbol] = Nil

val cls = Symbol.newClass(Symbol.spliceOwner, name, parents, decls, selfType = None, paramNames = Nil, paramTypes = Nil, Flags.EmptyFlags, Symbol.noSymbol)
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents, decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, conParamNames = Nil, conParamTypes = Nil)

val parentsWithSym =
cls.typeRef.asType match
Expand Down
3 changes: 1 addition & 2 deletions tests/run-macros/newClassParams/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], str

def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
val parents = List(TypeTree.of[Object])
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, List("idx", "str"), List(TypeRepr.of[Int], TypeRepr.of[String]), Flags.EmptyFlags, Symbol.noSymbol)
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List("idx", "str"), List(TypeRepr.of[Int], TypeRepr.of[String]))

val fooDef = DefDef(cls.methodMember("foo")(0), argss => Some('{println(s"Foo method call with (${${Ref(cls.fieldMember("idx")).asExpr}}, ${${Ref(cls.fieldMember("str")).asExpr}})")}.asTerm))
val clsDef = ClassDef(cls, parents, body = List(fooDef))
Expand All @@ -25,4 +25,3 @@ private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], str
// new `name`(`idx`, `str`)
// }
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
val parents = List('{ new Foo(1) }.asTerm)
def decls(cls: Symbol): List[Symbol] = Nil

val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List("idx"), List(TypeRepr.of[Int]))

val parentsWithSym = List(Apply(Select(New(TypeTree.of[Foo]), TypeRepr.of[Foo].typeSymbol.primaryConstructor), List(Ref(cls.fieldMember("idx")))))
val clsDef = ClassDef(cls, parentsWithSym, body = Nil)
Expand All @@ -27,4 +27,3 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
class Foo(i: Int) {
def foo(): Unit = println(s"Calling Foo.foo with i = $i")
}

14 changes: 14 additions & 0 deletions tests/run-macros/newClassTraitAndAbstract.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Test_2$package$anon$1
{
trait foo[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object
class anon() extends java.lang.Object with foo[java.lang.String, scala.Int]("a", 1)

(new anon(): scala.Any)
}
class Test_2$package$anon$2
{
abstract class bar[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object
class anon() extends bar[java.lang.String, scala.Int]("a", 1)

(new anon(): scala.Any)
}
93 changes: 93 additions & 0 deletions tests/run-macros/newClassTraitAndAbstract/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//> using options -experimental

import scala.quoted.*

transparent inline def makeTrait(inline name: String): Any = ${ makeTraitExpr('name) }
transparent inline def makeAbstractClass(inline name: String): Any = ${ makeAbstractClassExpr('name) }

private def makeTraitExpr(name: Expr[String])(using Quotes): Expr[Any] = {
makeClassExpr(name, quotes.reflect.Flags.Trait, List(quotes.reflect.TypeTree.of[Object]))
// '{
// trait `name`[A, B <: Int](param1: A, param2: B)
// class anon() extends `name`[String, Int]("a", 1)
// new $anon()
// }
}

private def makeAbstractClassExpr(name: Expr[String])(using Quotes): Expr[Any] = {
makeClassExpr(name, quotes.reflect.Flags.Abstract, Nil)
// '{
// abstract class `name`[A, B <: Int](param1: A, param2: B)
// class anon() extends `name`[String, Int]("a", 1)
// new $anon()
// }
}

private def makeClassExpr(using Quotes)(
nameExpr: Expr[String], clsFlags: quotes.reflect.Flags, childExtraParents: List[quotes.reflect.TypeTree]
): Expr[Any] = {
import quotes.reflect.*

val name = nameExpr.valueOrAbort
def decls(cls: Symbol): List[Symbol] = Nil
val conMethodType =
(classType: TypeRepr) => PolyType(List("A", "B"))(
_ => List(TypeBounds.empty, TypeBounds.upper(TypeRepr.of[Int])),
polyType => MethodType(List("param1", "param2"))((_: MethodType) => List(polyType.param(0), polyType.param(1)), (_: MethodType) => classType)
)

val traitSymbol = Symbol.newClass(
Symbol.spliceOwner,
name,
parents = _ => List(TypeRepr.of[Object]),
decls,
selfType = None,
clsFlags,
clsPrivateWithin = Symbol.noSymbol,
conMethodType,
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags))
)
val traitDef = ClassDef(traitSymbol, List(TypeTree.of[Object]), body = Nil)

val traitTypeTree = Applied(TypeIdent(traitSymbol), List(TypeTree.of[String], TypeTree.of[Int]))
val clsSymbol = Symbol.newClass(
Symbol.spliceOwner,
"anon",
parents = _ => childExtraParents.map(_.tpe) ++ List(traitTypeTree.tpe),
decls = _ => Nil,
selfType = None,
clsFlags = Flags.EmptyFlags,
clsPrivateWithin = Symbol.noSymbol,
conMethodType = (classType: TypeRepr) => MethodType(Nil)(_ => Nil, _ => classType),
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(List())
)
val obj = '{new java.lang.Object()}.asTerm match
case Inlined(_, _, term) => term

val parentsWithSym = childExtraParents ++ List(
Apply(
TypeApply(
Select(New(traitTypeTree), traitSymbol.primaryConstructor),
List(TypeTree.of[String], TypeTree.of[Int])
),
List(Expr("a").asTerm, Expr(1).asTerm)
)
)
val clsDef = ClassDef(clsSymbol, parentsWithSym, body = Nil)

val newCls =
Typed(
Apply(
Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor),
Nil
),
TypeTree.of[Any]
)
val res = Block(List(traitDef), Block(List(clsDef), newCls)).asExpr

Expr.ofTuple(res, Expr(res.show))
}
11 changes: 11 additions & 0 deletions tests/run-macros/newClassTraitAndAbstract/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//> using options -experimental

@main def Test: Unit = {
val (cls1, show1) = makeTrait("foo")
println(cls1.getClass)
println(show1)

val (cls2, show2) = makeAbstractClass("bar")
println(cls2.getClass)
println(show2)
}
Loading

0 comments on commit 30abd4f

Please sign in to comment.