8000 Add class parameters, flags, and privateWithin and annotations to newClass in reflect API by jchyb · Pull Request #21880 · scala/scala3 · GitHub
[go: up one dir, main page]

Skip to content

Add class parameters, flags, and privateWithin and annotations to newClass in reflect API #21880

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

Merged
Merged
Prev Previous commit
Next Next commit
Improve api, documentation and add conParamsPrivateWithin
  • Loading branch information
jchyb committed Mar 10, 2025
commit 2ca2fc3ba15c086f8a2b2d4c76b813ce99462ac6
20 changes: 10 additions & 10 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2690,10 +2690,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
selfType: Option[TypeRepr],
clsFlags: Flags,
clsPrivateWithin: Symbol,
conParamNames: List[String],
conParamTypes: List[TypeRepr],
conParams: List[(String, TypeRepr)]
): Symbol =
assert(conParamNames.length == conParamTypes.length, "Lengths of conParamNames and conParamTypes must be equal")
val (conParamNames, conParamTypes) = conParams.unzip()
newClass(
owner,
name,
Expand All @@ -2706,7 +2705,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
conMethodType = res => MethodType(conParamNames)(_ => conParamTypes, _ => res),
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(for i <- conParamNames yield Flags.EmptyFlags)
conParamFlags = List(for i <- conParamNames yield Flags.EmptyFlags),
conParamPrivateWithins = List(for i <- conParamNames yield Symbol.noSymbol)
)

def newClass(
Expand All @@ -2721,7 +2721,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
conMethodType: TypeRepr => MethodOrPoly,
conFlags: Flags,
conPrivateWithin: Symbol,
conParamFlags: List[List[Flags]]
conParamFlags: List[List[Flags]],
conParamPrivateWithins: List[List[Symbol]]
) =
assert(!clsPrivateWithin.exists || clsPrivateWithin.isType, "clsPrivateWithin must be a type symbol or `Symbol.noSymbol`")
assert(!conPrivateWithin.exists || conPrivateWithin.isType, "consPrivateWithin must be a type symbol or `Symbol.noSymbol`")
Expand Down Expand Up @@ -2760,7 +2761,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
paramNames.zip(paramBounds).map(_ :* true :* clauseIdx).zipWithIndex ++ getParamAccessors(res, clauseIdx + 1)
case result =>
List()
// Maps PolyType indexes to type symbols
// Maps PolyType indexes to type parameter symbols
val paramRefMap = collection.mutable.HashMap[Int, Symbol]()
val paramRefRemapper = new Types.TypeMap {
def apply(tp: Types.Type) = tp match {
Expand All @@ -2771,13 +2772,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
for ((name, tpe, isType, clauseIdx), elementIdx) <- getParamAccessors(methodType, 0) do
if isType then
checkValidFlags(conParamFlags(clauseIdx)(elementIdx), Flags.validClassTypeParamFlags)
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)
val symbol = dotc.core.Symbols.newSymbol(cls, name.toTypeName, Flags.Param | Flags.Deferred | Flags.Private | Flags.PrivateLocal | Flags.Local | conParamFlags(clauseIdx)(elementIdx), tpe, conParamPrivateWithins(clauseIdx)(elementIdx))
paramRefMap.addOne(elementIdx, symbol)
cls.enter(symbol)
else
checkValidFlags(conParamFlags(clauseIdx)(elementIdx), Flags.validClassTermParamFlags)
val fixedType = paramRefRemapper(tpe)
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | conParamFlags(clauseIdx)(elementIdx), fixedType, Symbol.noSymbol)) // TODO set privateWithin?
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | conParamFlags(clauseIdx)(elementIdx), fixedType, conParamPrivateWithins(clauseIdx)(elementIdx)))
for sym <- decls(cls) do cls.enter(sym)
cls

Expand Down Expand Up @@ -3189,10 +3190,9 @@ 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 | PrivateLocal | Local | Final | Trait | Abstract // AbsOverride, Open
private[QuotesImpl] def validClassFlags: Flags = Private | Protected | PrivateLocal | Local | Final | Trait | Abstract | Open

// Keep: aligned with Quote's 'newClass'
// Private constructor would be currently useless, but if we decide to add a way to register companions in the future it might be useful
private[QuotesImpl] def validClassConstructorFlags: Flags = Synthetic | Method | Private | Protected | PrivateLocal | Local

// Keep: aligned with Quotes's `newClass`
Expand Down
82 changes: 73 additions & 9 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3862,8 +3862,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* Takes the constructed class symbol as an argument. Calling `cls.typeRef.asType` as part of this function will lead to cyclic reference errors.
* @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.
* @param conParams constructor parameter pairs of names and types.
*
* Parameters assigned by the constructor can be obtained via `classSymbol.memberField`.
* This symbol starts without an accompanying definition.
Expand All @@ -3877,31 +3876,93 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
owner: Symbol,
name: String,
parents: Symbol => List[TypeRepr],
decls: Symbol => List[Symbol], selfType: Option[TypeRepr],
decls: Symbol => List[Symbol],
selfType: Option[TypeRepr],
clsFlags: Flags,
clsPrivateWithin: Symbol,
conParamNames: List[String],
conParamTypes: List[TypeRepr]
conParams: List[(String, TypeRepr)]
): Symbol

/** Generates a new class symbol with a constructor of the shape signified by a passed PolyOrMethod parameter.
* TODO example with PolyType
*
* Example usage:
* ```
* val name = "myClass"
* def decls(cls: Symbol): List[Symbol] =
* List(Symbol.newMethod(cls, "getParam", MethodType(Nil)(_ => Nil, _ => cls.typeMember("T").typeRef)))
* val conMethodType =
* (classType: TypeRepr) => PolyType(List("T"))(_ => List(TypeBounds.empty), polyType =>
* MethodType(List("param"))((_: MethodType) => List(polyType.param(0)), (_: MethodType) =>
* classType
* )
* )
* val cls = Symbol.newClass(
* Symbol.spliceOwner,
* name,
* parents = _ => List(TypeRepr.of[Object]),
* decls,
* selfType = None,
* clsFlags = Flags.EmptyFlags,
* clsPrivateWithin = Symbol.noSymbol,
* clsAnnotations = Nil,
* conMethodType,
* conFlags = Flags.EmptyFlags,
* conPrivateWithin = Symbol.noSymbol,
* conParamFlags = List(List(Flags.EmptyFlags), List(Flags.EmptyFlags)),
* conParamPrivateWithins = List(List(Symbol.noSymbol), List(Symbol.noSymbol))
* )
*
* val getParamSym = cls.declaredMethod("getParam").head
* def getParamRhs(): Option[Term] =
* val paramValue = This(cls).select(cls.fieldMember("param")).asExpr
* Some('{ println("Calling getParam"); $paramValue }.asTerm)
* val getParamDef = DefDef(getParamSym, _ => getParamRhs())
*
* val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = List(getParamDef))
* val newCls =
* Apply(
* Select(
* Apply(
* TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String])),
* List(Expr("test").asTerm)
* ),
* cls.methodMember("getParam").head
* ),
* Nil
* )
*
* Block(List(clsDef), newCls).asExpr
* ```
* constructs the equivalent to
* ```
* '{
* class myClass[T](val param: T) {
* def getParam: T =
* println("Calling getParam")
* param
* }
* new myClass[String]("test").getParam()
* }
* ```
*
* @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
* 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 clsFlags extra flags with which the class symbol should be constructed
* @param clsFlags extra flags with which the class symbol should be constructed. Can be `Private` | `Protected` | `PrivateLocal` | `Local` | `Final` | `Trait` | `Abstract` | `Open`
* @param clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol
* @param clsAnnotations annotations of the class
* @param conMethodType Function returning MethodOrPoly type representing the type of the constructor.
* Takes the result type as parameter which must be returned from the innermost MethodOrPoly.
* PolyType may only represent the first clause of the constructor.
* @param conFlags extra flags with which the constructor symbol should be constructed
* @param conFlags extra flags with which the constructor symbol should be constructed. Can be `Synthetic` | `Method` | `Private` | `Protected` | `PrivateLocal` | `Local`
* @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`.
* For type parameters those can be `Param` | `Deferred` | `Private` | `PrivateLocal` | `Local`.
* For term parameters those can be `ParamAccessor` | `Private` | `Protected` | `PrivateLocal` | `Local`
* @param conParamPrivateWithins the symbols within which the constructor parameters should be private. Must match the shape of `conMethodType`. Can consist of noSymbol.
*
* Term and type parameters assigned by the constructor can be obtained via `classSymbol.memberField`/`classSymbol.memberType`.
* This symbol starts without an accompanying definition.
Expand All @@ -3911,6 +3972,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
* direct or indirect children of the reflection context's owner.
*/
// Keep doc aligned with QuotesImpl's validFlags: `clsFlags` with `validClassFlags`, `conFlags` with `validClassConstructorFlags`,
// conParamFlags with `validClassTypeParamFlags` and `validClassTermParamFlags`
@experimental def newClass(
owner: Symbol,
name: String,
Expand All @@ -3923,7 +3986,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
conMethodType: TypeRepr => MethodOrPoly,
conFlags: Flags,
conPrivateWithin: Symbol,
conParamFlags: List[List[Flags]]
conParamFlags: List[List[Flags]],
conParamPrivateWithins: List[List[Symbol]]
): Symbol

/** Generates a new module symbol with an associated module class symbol,
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-macros/i19842-a.check
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
|
| at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
| at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:278)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:277)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:283)
| at Macros$.makeSerializer(Macro.scala:25)
|
|---------------------------------------------------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-macros/i19842-b.check
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
|
| at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
| at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:278)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:277)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284)
| at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:283)
| at Macros$.makeSerializer(Macro.scala:27)
|
|---------------------------------------------------------------------------------------------------------------------
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])
Copy link
Member
@bishabosha bishabosha Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be a test case where the parent needs an argument supplied via the constructor parameters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried doing that in the not-very-well-named tests/run-macros/newClassParamsExtendsClassParams, which generates something like this:

'{
   class `name`(idx: Int) extends Foo(idx)
   new `name`(22)
}

Or do you mean something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that looks good

Copy link
@goshacodes goshacodes Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also such test cases could be added:

  1. Creating a trait with Symbol.newClass (is this possible?)
  2. Creating an abstract class with Symbol.newClass
  3. trait with parameter extending another trait with parameter (not sure about this)
  4. class with multiple parameters (this probably not needed)
  5. extending java class with parameter

def decls(cls: Symbol): List[Symbol] = Nil

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 cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", 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, Flags.EmptyFlags, Symbol.noSymbol, conParamNames = Nil, conParamTypes = Nil)
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents, decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, conParams = Nil)

val parentsWithSym =
cls.typeRef.asType match
Expand Down
3 changes: 2 additions & 1 deletion tests/run-macros/newClassAnnotation/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = {
conMethodType,
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(List())
conParamFlags = List(List()),
conParamPrivateWithins = List(List())
)

val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil)
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/newClassExtendsJavaClass/Macro_1.scala
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[JavaClass[
val parents = List(TypeTree.of[JavaClass[Int]])
def decls(cls: Symbol): List[Symbol] = Nil

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 cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", TypeRepr.of[Int])))

val parentsWithSym = List(Apply(TypeApply(Select(New(TypeTree.of[JavaClass[Int]]), TypeRepr.of[JavaClass].typeSymbol.primaryConstructor), List(TypeTree.of[Int])), List(Ref(cls.fieldMember("idx")))))
val clsDef = ClassDef(cls, parentsWithSym, body = Nil)
Expand Down
2 changes: 1 addition & 1 deletion 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, Flags.EmptyFlags, Symbol.noSymbol, List("idx", "str"), List(TypeRepr.of[Int], TypeRepr.of[String]))
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", TypeRepr.of[Int]), ("str", 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 Down
2 changes: 1 addition & 1 deletion tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala
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, Flags.EmptyFlags, Symbol.noSymbol, List("idx"), List(TypeRepr.of[Int]))
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", 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 Down
8 changes: 6 additions & 2 deletions tests/run-macros/newClassTraitAndAbstract/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ private def makeClassExpr(using Quotes)(
selfType = None,
clsFlags,
clsPrivateWithin = Symbol.noSymbol,
clsAnnotations = Nil,
conMethodType,
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags))
conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)),
conParamPrivateWithins = List(List(Symbol.noSymbol, Symbol.noSymbol), List(Symbol.noSymbol, Symbol.noSymbol))
)
val traitDef = ClassDef(traitSymbol, List(TypeTree.of[Object]), body = Nil)

Expand All @@ -60,10 +62,12 @@ private def makeClassExpr(using Quotes)(
selfType = None,
clsFlags = Flags.EmptyFlags,
clsPrivateWithin = Symbol.noSymbol,
clsAnnotations = Nil,
conMethodType = (classType: TypeRepr) => MethodType(Nil)(_ => Nil, _ => classType),
conFlags = Flags.EmptyFlags,
conPrivateWithin = Symbol.noSymbol,
conParamFlags = List(List())
conParamFlags = List(List()),
conParamPrivateWithins = List(List(Symbol.noSymbol, Symbol.noSymbol), List(Symbol.noSymbol, Symbol.noSymbol))
)
val obj = '{new java.lang.Object()}.asTerm match
case Inlined(_, _, term) => term
Expand Down
2 changes: 2 additions & 0 deletions tests/run-macros/newClassTypeParamDoc.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Calling getParam
test
Loading
0