From bcc08aa72a2d9e07c40789664070f97f38f322f3 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 7 Nov 2024 13:56:37 +0100 Subject: [PATCH] Add ability to use the class symbol in classdef parents --- .../src/dotty/tools/dotc/core/Symbols.scala | 52 +++++++++++++++++++ .../quoted/runtime/impl/QuotesImpl.scala | 13 ++--- library/src/scala/quoted/Quotes.scala | 14 ++--- .../Macro_1.scala | 2 +- .../Macro_1.scala | 40 ++++++++++++++ .../Test_2.scala | 6 +++ tests/run-macros/newClassParams/Macro_1.scala | 2 +- .../Macro_1.scala | 2 +- 8 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala create mode 100644 tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 7de75e371752..210b960708a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -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) @@ -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. */ diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 8a69b8539d3e..e80e808270d3 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -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 @@ -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, @@ -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, diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 5c9e23811210..3e74e1f44064 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3838,9 +3838,11 @@ 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. @@ -3848,7 +3850,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * 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. @@ -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 * @@ -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. * @@ -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. * diff --git a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala index abb671b1c8bf..24d790fb031f 100644 --- a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala +++ b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala @@ -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]) diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala new file mode 100644 index 000000000000..087eefebdd25 --- /dev/null +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala @@ -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 +} diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala new file mode 100644 index 000000000000..bb48bfadd96b --- /dev/null +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala @@ -0,0 +1,6 @@ +//> using options -experimental + +@main def Test: Unit = { + val foo = makeClass("Bar") + foo.getSelf +} diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala index e60d4bd97909..83b7383686c2 100644 --- a/tests/run-macros/newClassParams/Macro_1.scala +++ b/tests/run-macros/newClassParams/Macro_1.scala @@ -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)) diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala index 061a0a231e75..e3148ec784c2 100644 --- a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala +++ b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala @@ -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)