Skip to content

Commit

Permalink
Improve api, documentation and add conParamsPrivateWithin
Browse files Browse the repository at this point in the history
  • Loading branch information
jchyb committed Jan 31, 2025
1 parent d683839 commit c11f7f0
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 32 deletions.
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 @@ -2653,10 +2653,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 @@ -2669,7 +2668,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 @@ -2684,7 +2684,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 @@ -2723,7 +2724,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 @@ -2734,13 +2735,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 @@ -3152,10 +3153,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 @@ -3847,8 +3847,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 @@ -3862,31 +3861,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 @@ -3896,6 +3957,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 @@ -3908,7 +3971,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])
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
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 comments on commit c11f7f0

Please sign in to comment.