Skip to content

Commit

Permalink
Add ability to use the class symbol in classdef parents
Browse files Browse the repository at this point in the history
  • Loading branch information
jchyb committed Nov 8, 2024
1 parent 33fbd5f commit bcc08aa
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 15 deletions.
52 changes: 52 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,30 @@ object Symbols extends SymUtils {
newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo)
}

/** Same as `newNormalizedClassSymbol` except that `parents` can be a function returning a list of arbitrary
* types which get normalized into type refs and parameter bindings.
*/
def newNormalizedClassSymbolUsingClassSymbolinParents(
owner: Symbol,
name: TypeName,
flags: FlagSet,
parentTypes: Symbol => List[Type],
selfInfo: Type = NoType,
privateWithin: Symbol = NoSymbol,
coord: Coord = NoCoord,
compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = {
def completer = new LazyType {
def complete(denot: SymDenotation)(using Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val parents = parentTypes(cls).map(_.dealias)
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
denot.info = ClassInfo(owner.thisType, cls, parents, decls, selfInfo)
}
}
newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo)
}

def newRefinedClassSymbol(coord: Coord = NoCoord)(using Context): ClassSymbol =
newCompleteClassSymbol(ctx.owner, tpnme.REFINE_CLASS, NonMember, parents = Nil, newScope, coord = coord)

Expand Down Expand Up @@ -706,6 +730,34 @@ object Symbols extends SymUtils {
privateWithin, coord, compUnitInfo)
}

/** Same as `newNormalizedModuleSymbol` except that `parents` can be a function returning a list of arbitrary
* types which get normalized into type refs and parameter bindings.
*/
def newNormalizedModuleSymbolUsingClassSymbolInParents(
owner: Symbol,
name: TermName,
modFlags: FlagSet,
clsFlags: FlagSet,
parentTypes: ClassSymbol => List[Type],
decls: Scope,
privateWithin: Symbol = NoSymbol,
coord: Coord = NoCoord,
compUnitInfo: CompilationUnitInfo | Null = null)(using Context): TermSymbol = {
def completer(module: Symbol) = new LazyType {
def complete(denot: SymDenotation)(using Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val parents = parentTypes(cls)
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
denot.info = ClassInfo(owner.thisType, cls, parents.map(_.dealias), decls, TermRef(owner.thisType, module))
}
}
newModuleSymbol(
owner, name, modFlags, clsFlags,
(module, modcls) => completer(module),
privateWithin, coord, compUnitInfo)
}

/** Create a package symbol with associated package class
* from its non-info fields and a lazy type for loading the package's members.
*/
Expand Down
13 changes: 7 additions & 6 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
case x: (tpd.NamedArg & x.type) => Some(x)
case x: (tpd.Typed & x.type) =>
TypedTypeTest.unapply(x) // Matches `Typed` but not `TypedOrTest`
case x: (tpd.TypeDef & x.type) => Some(x)
case _ => if x.isTerm then Some(x) else None
end TermTypeTest

Expand Down Expand Up @@ -2632,10 +2633,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
for sym <- decls(cls) do cls.enter(sym)
cls

def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr], paramNames: List[String], paramTypes: List[TypeRepr], flags: Flags, privateWithin: Symbol): Symbol =
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
def newClass(parent: Symbol, name: String, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr], paramNames: List[String], paramTypes: List[TypeRepr], flags: Flags, privateWithin: Symbol): Symbol =
checkValidFlags(flags.toTermFlags, Flags.validClassFlags)
val cls = dotc.core.Symbols.newNormalizedClassSymbol(
assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`")
val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents(
parent,
name.toTypeName,
flags,
Expand All @@ -2648,10 +2649,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
for sym <- decls(cls) do cls.enter(sym)
cls

def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: 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")
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.newNormalizedModuleSymbol(
val mod = dotc.core.Symbols.newNormalizedModuleSymbolUsingClassSymbolInParents(
owner,
name.toTermName,
modFlags | dotc.core.Flags.ModuleValCreationFlags,
Expand Down
14 changes: 8 additions & 6 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3838,17 +3838,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* direct or indirect children of the reflection context's owner.
*/
// TODO: add flags and privateWithin
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol
@experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol

/*
/**
* @param parent declerations of this class provided the symbol of this class.
* 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 flags extra flags with which the class symbol should be constructed.
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
*
* Parameters can be obtained via classSymbol.memberField
*/
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr], paramNames: List[String], paramTypes: List[TypeRepr], flags: Flags, privateWithin: Symbol): Symbol
@experimental def newClass(owner: Symbol, name: String, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr], paramNames: List[String], paramTypes: List[TypeRepr], flags: Flags, privateWithin: Symbol): Symbol

/** Generates a new module symbol with an associated module class symbol,
* this is equivalent to an `object` declaration in source code.
Expand All @@ -3865,7 +3867,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* def decls(cls: Symbol): List[Symbol] =
* List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol))
*
* val mod = Symbol.newModule(Symbol.spliceOwner, moduleName, Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol)
* val mod = Symbol.newModule(Symbol.spliceOwner, moduleName, Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol)
* val cls = mod.moduleClass
* val runSym = cls.declaredMethod("run").head
*
Expand Down Expand Up @@ -3893,7 +3895,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* @param name The name of the class
* @param modFlags extra flags with which the module symbol should be constructed
* @param clsFlags extra flags with which the module class symbol should be constructed
* @param parents The parent classes of the class. The first parent must not be a trait.
* @param parents A function that takes the symbol of the module class as input and returns the parent classes of the class. The first parent must not be a trait.
* @param decls A function that takes the symbol of the module class as input and return the symbols of its declared members
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
*
Expand All @@ -3906,7 +3908,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
*
* @syntax markdown
*/
@experimental def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol
@experimental def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol

/** Generates a new method symbol with the given parent, name and type.
*
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, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)

val clsDef = ClassDef(cls, parents, body = Nil)
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
Expand Down
40 changes: 40 additions & 0 deletions tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//> using options -experimental

import scala.quoted.*

transparent inline def makeClass(inline name: String): Foo[_] = ${ makeClassExpr('name) }
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo[_]] = {
import quotes.reflect.*

val name = nameExpr.valueOrAbort

// using asType on the passed Symbol leads to cyclic reference errors
def parents(cls: Symbol) =
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 parentsWithSym =
cls.typeRef.asType match
case '[t] =>
List(Apply(TypeApply(Select(New(TypeTree.of[Foo[t]]), TypeRepr.of[Foo[t]].typeSymbol.primaryConstructor), List(TypeTree.of[t])), List()))
val clsDef = ClassDef(cls, parentsWithSym, body = Nil)

val newCls = cls.typeRef.asType match
case '[t] =>
Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo[t]])

cls.typeRef.asType match
case '[t] =>
Block(List(clsDef), newCls).asExprOf[Foo[t]]

// '{
// class Name() extends Foo[Name.type]()
// new Name()
// }
}

class Foo[X]() { self: X =>
def getSelf: X = self
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//> using options -experimental

@main def Test: Unit = {
val foo = makeClass("Bar")
foo.getSelf
}
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, 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, List("idx", "str"), List(TypeRepr.of[Int], TypeRepr.of[String]), Flags.EmptyFlags, Symbol.noSymbol)

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, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)
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 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

0 comments on commit bcc08aa

Please sign in to comment.