From 0f46d4f7cbeea6fcb65e122e94ad5318e4f1c45b Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 21 Oct 2024 14:20:36 +0200 Subject: [PATCH 01/11] Add class term parameters, flags, and privateWithin to newClass in reflect API --- .../tools/dotc/transform/TreeChecker.scala | 46 ++++++++++--------- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 ++- .../quoted/runtime/impl/QuotesImpl.scala | 41 +++++++++++++++-- library/src/scala/quoted/Quotes.scala | 10 ++++ tests/neg-macros/i19842-a.check | 4 +- tests/neg-macros/i19842-b.check | 4 +- .../newClassParamsMissingArgument.check | 32 +++++++++++++ .../Macro_1.scala | 24 ++++++++++ .../Test_2.scala | 5 ++ tests/run-macros/newClassParams.check | 1 + tests/run-macros/newClassParams/Macro_1.scala | 28 +++++++++++ tests/run-macros/newClassParams/Test_2.scala | 5 ++ .../newClassParamsExtendsClassParams.check | 4 ++ .../Macro_1.scala | 30 ++++++++++++ .../Test_2.scala | 10 ++++ 15 files changed, 220 insertions(+), 30 deletions(-) create mode 100644 tests/neg-macros/newClassParamsMissingArgument.check create mode 100644 tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala create mode 100644 tests/neg-macros/newClassParamsMissingArgument/Test_2.scala create mode 100644 tests/run-macros/newClassParams.check create mode 100644 tests/run-macros/newClassParams/Macro_1.scala create mode 100644 tests/run-macros/newClassParams/Test_2.scala create mode 100644 tests/run-macros/newClassParamsExtendsClassParams.check create mode 100644 tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala create mode 100644 tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index c35dc80c04a5..90262bc5da85 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -854,30 +854,34 @@ object TreeChecker { val phases = ctx.base.allPhases.toList val treeChecker = new LocalChecker(previousPhases(phases)) + def reportMalformedMacroTree(msg: String | Null, err: Throwable) = + val stack = + if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`" + else if err.getStackTrace == null then " no stacktrace" + else err.getStackTrace.nn.mkString(" ", " \n", "") + report.error( + em"""Malformed tree was found while expanding macro with -Xcheck-macros. + |The tree does not conform to the compiler's tree invariants. + | + |Macro was: + |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)} + | + |The macro returned: + |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)} + | + |Error: + |$msg + |$stack + |""", + original + ) + try treeChecker.typed(expansion)(using checkingCtx) catch case err: java.lang.AssertionError => - val stack = - if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`" - else if err.getStackTrace == null then " no stacktrace" - else err.getStackTrace.nn.mkString(" ", " \n", "") - - report.error( - em"""Malformed tree was found while expanding macro with -Xcheck-macros. - |The tree does not conform to the compiler's tree invariants. - | - |Macro was: - |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)} - | - |The macro returned: - |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)} - | - |Error: - |${err.getMessage} - |$stack - |""", - original - ) + reportMalformedMacroTree(err.getMessage(), err) + case err: UnhandledError => + reportMalformedMacroTree(err.diagnostic.message, err) private[TreeChecker] def previousPhases(phases: List[Phase])(using Context): List[Phase] = phases match { case (phase: MegaPhase) :: phases1 => diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 28af86344621..4d16a342f484 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,6 +12,7 @@ import collection.mutable import reporting.* import Checking.{checkNoPrivateLeaks, checkNoWildcard} import cc.CaptureSet +import transform.Splicer trait TypeAssigner { import tpd.* @@ -301,7 +302,10 @@ trait TypeAssigner { if fntpe.isResultDependent then safeSubstMethodParams(fntpe, args.tpes) else fntpe.resultType // fast path optimization else - errorType(em"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos) + val erroringPhase = + if Splicer.inMacroExpansion then i"${ctx.phase} (while expanding macro)" + else ctx.phase.prev.toString + errorType(em"wrong number of arguments at $erroringPhase for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos) case err: ErrorType => err case t => diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 0965a1174500..5c0fc2ddfcc7 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -241,9 +241,23 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object ClassDef extends ClassDefModule: def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef = - val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree) + val paramsDefs: List[untpd.ParamClause] = + cls.primaryConstructor.paramSymss.map { paramSym => + paramSym.map( symm => + ValDef(symm, None) + ) + } + val paramsAccessDefs: List[untpd.ParamClause] = + cls.primaryConstructor.paramSymss.map { paramSym => + paramSym.map( symm => + ValDef(cls.fieldMember(symm.name.toString()), None) // TODO I don't like the toString here + ) + } + + val termSymbol: dotc.core.Symbols.TermSymbol = cls.primaryConstructor.asTerm + val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, paramsDefs, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree) val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor) - tpd.ClassDefWithParents(cls.asClass, ctr, parents, body) + tpd.ClassDefWithParents(cls.asClass, ctr, parents, paramsAccessDefs.flatten ++ body) def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = { val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original: @unchecked @@ -2642,10 +2656,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path) def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName) - def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol = + def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol = assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") val cls = dotc.core.Symbols.newNormalizedClassSymbol( - owner, + parent, name.toTypeName, dotc.core.Flags.EmptyFlags, parents, @@ -2655,6 +2669,22 @@ 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") + checkValidFlags(flags.toTermFlags, Flags.validClassFlags) + val cls = dotc.core.Symbols.newNormalizedClassSymbol( + parent, + name.toTypeName, + flags, + parents, + selfType.getOrElse(Types.NoType), + privateWithin) + 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.newSymbol(cls, name.toTermName, Flags.ParamAccessor, tpe, Symbol.noSymbol)) // add other flags (local, private, privatelocal) and set privateWithin + 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") assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") @@ -3063,6 +3093,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler // Keep: aligned with Quotes's `newTypeAlias` doc 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 ? + end Flags given FlagsMethods: FlagsMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 5ada585c23a1..1464cbc823c3 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3855,6 +3855,16 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => // TODO: add flags and privateWithin @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol + /* + * @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 + /** Generates a new module symbol with an associated module class symbol, * this is equivalent to an `object` declaration in source code. * This method returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol. diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index 30b295cd05a5..6e3aed860e61 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -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:257) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:256) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:271) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:270) | at Macros$.makeSerializer(Macro.scala:25) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index d84d916acb66..98425513e19d 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -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:257) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:256) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:271) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:270) | at Macros$.makeSerializer(Macro.scala:27) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassParamsMissingArgument.check b/tests/neg-macros/newClassParamsMissingArgument.check new file mode 100644 index 000000000000..2a6b53bd2d79 --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument.check @@ -0,0 +1,32 @@ + +-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:2 ---------------------------------------------- +4 | makeClass("foo") // error // error + | ^^^^^^^^^^^^^^^^ + |wrong number of arguments at inlining (while expanding macro) for (idx: Int): foo: (foo# : (idx: Int): foo), expected: 1, found: 0 +-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:11 --------------------------------------------- +4 | makeClass("foo") // error // error + | ^^^^^^^^^^^^^^^^ + |Malformed tree was found while expanding macro with -Xcheck-macros. + |The tree does not conform to the compiler's tree invariants. + | + |Macro was: + |scala.quoted.runtime.Expr.splice[java.lang.Object](((contextual$1: scala.quoted.Quotes) ?=> Macro_1$package.inline$makeClassExpr(scala.quoted.runtime.Expr.quote[scala.Predef.String]("foo").apply(using contextual$1))(contextual$1))) + | + |The macro returned: + |{ + | class foo(val idx: scala.Int) extends java.lang.Object + | + | (new foo(): java.lang.Object) + |} + | + |Error: + |missing argument for parameter idx of constructor foo in class foo: (idx: Int): foo + | + |stacktrace available when compiling with `-Ydebug` + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:5 +5 |inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala new file mode 100644 index 000000000000..abb671b1c8bf --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala @@ -0,0 +1,24 @@ +//> using options -experimental + +import scala.quoted._ + +inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Object] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + 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 clsDef = ClassDef(cls, parents, body = Nil) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object]) + + Block(List(clsDef), newCls).asExprOf[Object] + + // '{ + // class `name`(idx: Int) + // new `name` + // } +} diff --git a/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala b/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala new file mode 100644 index 000000000000..8d7142f7bd72 --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + makeClass("foo") // error // error +} diff --git a/tests/run-macros/newClassParams.check b/tests/run-macros/newClassParams.check new file mode 100644 index 000000000000..02e16275a822 --- /dev/null +++ b/tests/run-macros/newClassParams.check @@ -0,0 +1 @@ +Foo method call with (10, test) \ No newline at end of file diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala new file mode 100644 index 000000000000..e60d4bd97909 --- /dev/null +++ b/tests/run-macros/newClassParams/Macro_1.scala @@ -0,0 +1,28 @@ +//> using options -experimental + +import scala.quoted._ + +inline def makeClassAndCall(inline name: String, idx: Int, str: String): Unit = ${ makeClassAndCallExpr('name, 'idx, 'str) } +private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], strExpr: Expr[String])(using Quotes): Expr[Unit] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + + 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 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)) + val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) + + Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] + + // '{ + // class `name`(idx: Int, str: String) { + // def foo() = println("Foo method call with ($idx, $str)") + // } + // new `name`(`idx`, `str`) + // } +} + diff --git a/tests/run-macros/newClassParams/Test_2.scala b/tests/run-macros/newClassParams/Test_2.scala new file mode 100644 index 000000000000..64762f17b92f --- /dev/null +++ b/tests/run-macros/newClassParams/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + makeClassAndCall("bar", 10, "test") +} diff --git a/tests/run-macros/newClassParamsExtendsClassParams.check b/tests/run-macros/newClassParamsExtendsClassParams.check new file mode 100644 index 000000000000..86d844c7d00f --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams.check @@ -0,0 +1,4 @@ +Calling Foo.foo with i = 22 +class Test_2$package$foo$1 +Calling Foo.foo with i = 22 +class Test_2$package$bar$1 diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala new file mode 100644 index 000000000000..061a0a231e75 --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala @@ -0,0 +1,30 @@ +//> using options -experimental + +import scala.quoted._ + +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 + 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 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) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(Literal(IntConstant(22)))), TypeTree.of[Foo]) + + Block(List(clsDef), newCls).asExprOf[Foo] + + // '{ + // class `name`(idx: Int) extends Foo(idx) + // new `name`(22) + // } +} + +class Foo(i: Int) { + def foo(): Unit = println(s"Calling Foo.foo with i = $i") +} + diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala b/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala new file mode 100644 index 000000000000..6e902825fdc6 --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +@main def Test: Unit = { + val foo: Foo = makeClass("foo") + foo.foo() + println(foo.getClass) + val bar: Foo = makeClass("bar") + bar.foo() + println(bar.getClass) +} From d6cc6c90e6402d466bc6daaa6e902d816fdbb237 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 7 Nov 2024 13:56:37 +0100 Subject: [PATCH 02/11] 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 5c0fc2ddfcc7..7a59b0c8920a 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 @@ -2669,10 +2670,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, @@ -2685,10 +2686,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 1464cbc823c3..61c8a0118b83 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3853,9 +3853,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. @@ -3863,7 +3865,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. @@ -3880,7 +3882,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 * @@ -3908,7 +3910,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. * @@ -3921,7 +3923,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) From 5e1eb60708b9be54c547b4eed5cb927f4ec41678 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 31 Dec 2024 14:55:39 +0100 Subject: [PATCH 03/11] Allow to add type parameters to newClass --- .../quoted/runtime/impl/QuotesImpl.scala | 115 +++++++++++++++--- .../runtime/impl/printers/SourceCode.scala | 4 +- library/src/scala/quoted/Quotes.scala | 60 +++++++-- tests/neg-macros/i19842-a.check | 4 +- tests/neg-macros/i19842-a/Macro.scala | 2 +- tests/neg-macros/i19842-b.check | 4 +- tests/neg-macros/i19842-b/Macro.scala | 2 +- .../annot-add-global-object/Macro_1.scala | 2 +- .../annot-add-local-object/Macro_1.scala | 2 +- .../annot-add-nested-object/Macro_1.scala | 2 +- tests/run-macros/newClassTypeParams.check | 6 + .../newClassTypeParams/Macro_1.scala | 45 +++++++ .../newClassTypeParams/Test_2.scala | 7 ++ 13 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 tests/run-macros/newClassTypeParams.check create mode 100644 tests/run-macros/newClassTypeParams/Macro_1.scala create mode 100644 tests/run-macros/newClassTypeParams/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7a59b0c8920a..aa6951a00a8a 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -25,6 +25,7 @@ import scala.quoted.runtime.impl.printers.* import scala.reflect.TypeTest import dotty.tools.dotc.core.NameKinds.ExceptionBinderName import dotty.tools.dotc.transform.TreeChecker +import dotty.tools.dotc.core.Names object QuotesImpl { @@ -243,15 +244,21 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef = val paramsDefs: List[untpd.ParamClause] = cls.primaryConstructor.paramSymss.map { paramSym => - paramSym.map( symm => - ValDef(symm, None) - ) + if paramSym.headOption.map(_.isType).getOrElse(false) then + paramSym.map(sym => TypeDef(sym)) + else + paramSym.map(ValDef(_, None)) } val paramsAccessDefs: List[untpd.ParamClause] = cls.primaryConstructor.paramSymss.map { paramSym => - paramSym.map( symm => - ValDef(cls.fieldMember(symm.name.toString()), None) // TODO I don't like the toString here - ) + if paramSym.headOption.map(_.isType).getOrElse(false) then + paramSym.map { symm => + TypeDef(cls.typeMember(symm.name.toString())) + } + else + paramSym.map { symm => + ValDef(cls.fieldMember(symm.name.toString()), None)// TODO I don't like the toString here + } } val termSymbol: dotc.core.Symbols.TermSymbol = cls.primaryConstructor.asTerm @@ -406,7 +413,6 @@ 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 @@ -2657,10 +2663,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path) def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName) - def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol = + def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol = assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") val cls = dotc.core.Symbols.newNormalizedClassSymbol( - parent, + owner, name.toTypeName, dotc.core.Flags.EmptyFlags, parents, @@ -2670,19 +2676,96 @@ 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: 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) - assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") + def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + paramNames: List[String], + paramTypes: List[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol + ): Symbol = + checkValidFlags(clsFlags.toTermFlags, Flags.validClassFlags) + assert(paramNames.length == paramTypes.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( - parent, + owner, name.toTypeName, - flags, + clsFlags, parents, selfType.getOrElse(Types.NoType), - privateWithin) + 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.newSymbol(cls, name.toTermName, Flags.ParamAccessor, tpe, Symbol.noSymbol)) // add other flags (local, private, privatelocal) and set privateWithin + cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor, tpe, Symbol.noSymbol)) + for sym <- decls(cls) do cls.enter(sym) + cls + + def newClass( + owner: Symbol, + name: String, + 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]] + ) = + 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) + 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.") + 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() + 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() + 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 + def getParamAccessors(methodType: TypeRepr, clauseIdx: Int): List[((String, TypeRepr, Boolean, Int), Int)] = + methodType match + case MethodType(paramInfosExp, resultTypeExp, res) => + paramInfosExp.zip(resultTypeExp).map(_ :* false :* clauseIdx).zipWithIndex ++ getParamAccessors(res, clauseIdx + 1) + case pt @ PolyType(paramNames, paramBounds, res) => + paramNames.zip(paramBounds).map(_ :* true :* clauseIdx).zipWithIndex ++ getParamAccessors(res, clauseIdx + 1) + case result => + List() + // Maps PolyType indexes to type symbols + val paramRefMap = collection.mutable.HashMap[Int, Symbol]() + val paramRefRemapper = new Types.TypeMap { + def apply(tp: Types.Type) = tp match { + case pRef: ParamRef if pRef.binder == methodType => paramRefMap(pRef.paramNum).typeRef + case _ => mapOver(tp) + } + } + 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) + 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 for sym <- decls(cls) do cls.enter(sym) cls diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 64a0ff9db9ec..7c7a688eccb2 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1379,13 +1379,13 @@ object SourceCode { printTypeTree(bounds.low) else bounds.low match { - case Inferred() => + case Inferred() if bounds.low.tpe.typeSymbol == TypeRepr.of[Nothing].typeSymbol => case low => this += " >: " printTypeTree(low) } bounds.hi match { - case Inferred() => this + case Inferred() if bounds.hi.tpe.typeSymbol == TypeRepr.of[Any].typeSymbol => this case hi => this += " <: " printTypeTree(hi) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 61c8a0118b83..16201cce33d7 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3811,7 +3811,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The class Symbol of a global class definition */ def classSymbol(fullName: String): Symbol - /** Generates a new class symbol for a class with a parameterless constructor. + /** Generates a new class symbol for a class with a public parameterless constructor. * * Example usage: * ``` @@ -3839,7 +3839,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * } * ``` * - * @param parent The owner of the class + * @param owner The owner of the class * @param name The name of the class * @param parents The parent classes of the class. The first parent must not be a trait. * @param decls The member declarations of the class provided the symbol of this class @@ -3855,17 +3855,61 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => // TODO: add flags and privateWithin @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. + /** Generates a new class symbol for a class with a public constructor. + * + * @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 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. + * @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. * * Parameters can be obtained via classSymbol.memberField */ - @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 + @experimental def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], selfType: Option[TypeRepr], + paramNames: List[String], + paramTypes: List[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol + ): 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. + * 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 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 + * + */ + @experimental def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + constructorMethodType: TypeRepr => MethodOrPoly, + clsFlags: Flags, + clsPrivateWithin: Symbol, + consFlags: Flags, + consPrivateWithin: Symbol, + conParamFlags: List[List[Flags]] + ): Symbol /** Generates a new module symbol with an associated module class symbol, * this is equivalent to an `object` declaration in source code. diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index 6e3aed860e61..cdf0921f1087 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -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:271) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:270) + | 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 Macros$.makeSerializer(Macro.scala:25) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-a/Macro.scala b/tests/neg-macros/i19842-a/Macro.scala index 18a1bc16045f..14b7c3f24a1a 100644 --- a/tests/neg-macros/i19842-a/Macro.scala +++ b/tests/neg-macros/i19842-a/Macro.scala @@ -16,7 +16,7 @@ object Macros { name, Flags.Implicit, Flags.EmptyFlags, - List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), + _ => List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), _ => Nil, Symbol.noSymbol ) diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index 98425513e19d..8039f278d2af 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -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:271) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:270) + | 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 Macros$.makeSerializer(Macro.scala:27) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b/Macro.scala b/tests/neg-macros/i19842-b/Macro.scala index f1399d328f49..8b43faab76dd 100644 --- a/tests/neg-macros/i19842-b/Macro.scala +++ b/tests/neg-macros/i19842-b/Macro.scala @@ -18,7 +18,7 @@ object Macros { name, Flags.Implicit, Flags.EmptyFlags, - List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), + _ => List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), _ => Nil, Symbol.noSymbol ) diff --git a/tests/run-macros/annot-add-global-object/Macro_1.scala b/tests/run-macros/annot-add-global-object/Macro_1.scala index 031d6e33fefe..b928cf23c2e8 100644 --- a/tests/run-macros/annot-add-global-object/Macro_1.scala +++ b/tests/run-macros/annot-add-global-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: 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, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/annot-add-local-object/Macro_1.scala b/tests/run-macros/annot-add-local-object/Macro_1.scala index 3d47fafd599a..bbdfbfd287d4 100644 --- a/tests/run-macros/annot-add-local-object/Macro_1.scala +++ b/tests/run-macros/annot-add-local-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: 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, "Baz", Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, "Baz", Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/annot-add-nested-object/Macro_1.scala b/tests/run-macros/annot-add-nested-object/Macro_1.scala index ce6cbaa67a57..cfc4691e95c9 100644 --- a/tests/run-macros/annot-add-nested-object/Macro_1.scala +++ b/tests/run-macros/annot-add-nested-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: 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, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/newClassTypeParams.check b/tests/run-macros/newClassTypeParams.check new file mode 100644 index 000000000000..8c400039681c --- /dev/null +++ b/tests/run-macros/newClassTypeParams.check @@ -0,0 +1,6 @@ +class Test_2$package$foo$1 +{ + class foo[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object + + (new foo[java.lang.String, scala.Int]("test", 1): scala.Any) +} diff --git a/tests/run-macros/newClassTypeParams/Macro_1.scala b/tests/run-macros/newClassTypeParams/Macro_1.scala new file mode 100644 index 000000000000..bf84a338f806 --- /dev/null +++ b/tests/run-macros/newClassTypeParams/Macro_1.scala @@ -0,0 +1,45 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + def decls(cls: Symbol): List[Symbol] = Nil + val constrType = + (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 cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + constrType, + Flags.EmptyFlags, + Symbol.noSymbol, + Flags.EmptyFlags, + Symbol.noSymbol, + List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)) + ) + + val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) + val newCls = + cls.typeRef.asType match + case '[t] => + Typed(Apply(TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String], TypeTree.of[Int])), List(Expr("test").asTerm, Expr(1).asTerm)), TypeTree.of[Any]) + + val res = Block(List(clsDef), newCls).asExpr + + Expr.ofTuple(res, Expr(res.show)) + + // '{ + // class `name`[A, B <: Int](param1: A, param2: B) + // new `name`[String, Int]("a", 1) + // } +} diff --git a/tests/run-macros/newClassTypeParams/Test_2.scala b/tests/run-macros/newClassTypeParams/Test_2.scala new file mode 100644 index 000000000000..742c0f480a77 --- /dev/null +++ b/tests/run-macros/newClassTypeParams/Test_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = { + val (cls, show) = makeClass("foo") + println(cls.getClass) + println(show) +} From ef8853d57266081a11e8330612c7b6c34cabd774 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 9 Jan 2025 15:41:39 +0100 Subject: [PATCH 04/11] Change parameter names, add tests --- .../quoted/runtime/impl/QuotesImpl.scala | 57 ++++++------ library/src/scala/quoted/Quotes.scala | 30 +++--- .../Macro_1.scala | 2 +- .../Macro_1.scala | 2 +- .../run-macros/newClassExtendsJavaClass.check | 2 + .../newClassExtendsJavaClass/JavaClass.java | 9 ++ .../newClassExtendsJavaClass/Macro_1.scala | 24 +++++ .../newClassExtendsJavaClass/Main_2.scala | 7 ++ tests/run-macros/newClassParams.check | 2 +- tests/run-macros/newClassParams/Macro_1.scala | 3 +- .../Macro_1.scala | 3 +- .../run-macros/newClassTraitAndAbstract.check | 14 +++ .../newClassTraitAndAbstract/Macro_1.scala | 93 +++++++++++++++++++ .../newClassTraitAndAbstract/Test_2.scala | 11 +++ .../newClassTypeParams/Macro_1.scala | 24 +++-- 15 files changed, 225 insertions(+), 58 deletions(-) create mode 100644 tests/run-macros/newClassExtendsJavaClass.check create mode 100644 tests/run-macros/newClassExtendsJavaClass/JavaClass.java create mode 100644 tests/run-macros/newClassExtendsJavaClass/Macro_1.scala create mode 100644 tests/run-macros/newClassExtendsJavaClass/Main_2.scala create mode 100644 tests/run-macros/newClassTraitAndAbstract.check create mode 100644 tests/run-macros/newClassTraitAndAbstract/Macro_1.scala create mode 100644 tests/run-macros/newClassTraitAndAbstract/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index aa6951a00a8a..1bd6384b2d81 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -249,15 +249,21 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler else paramSym.map(ValDef(_, None)) } + def throwError() = + throw new RuntimeException( + "Symbols necessary for creation of the ClassDef tree could not be found." + ) val paramsAccessDefs: List[untpd.ParamClause] = cls.primaryConstructor.paramSymss.map { paramSym => if paramSym.headOption.map(_.isType).getOrElse(false) then paramSym.map { symm => - TypeDef(cls.typeMember(symm.name.toString())) + def isParamAccessor(memberSym: Symbol) = memberSym.flags.is(Flags.Param) && memberSym.name == symm.name + TypeDef(cls.typeMembers.find(isParamAccessor).getOrElse(throwError())) } else paramSym.map { symm => - ValDef(cls.fieldMember(symm.name.toString()), None)// TODO I don't like the toString here + def isParam(memberSym: Symbol) = memberSym.flags.is(Flags.ParamAccessor) && memberSym.name == symm.name + ValDef(cls.fieldMembers.find(isParam).getOrElse(throwError()), None) } } @@ -2682,13 +2688,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, @@ -2697,8 +2703,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 @@ -2709,16 +2715,16 @@ 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, @@ -2726,22 +2732,22 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler 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) => @@ -2760,17 +2766,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, @@ -3178,7 +3183,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 diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 16201cce33d7..daa3282cab33 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3861,10 +3861,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 */ @@ -3873,10 +3873,10 @@ 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 /** @@ -3884,17 +3884,17 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @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( @@ -3903,11 +3903,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 diff --git a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala index 24d790fb031f..41dd9bcdda34 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, 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]) diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala index 087eefebdd25..c1483ac8f182 100644 --- a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala @@ -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 diff --git a/tests/run-macros/newClassExtendsJavaClass.check b/tests/run-macros/newClassExtendsJavaClass.check new file mode 100644 index 000000000000..bd07b5536ebc --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass.check @@ -0,0 +1,2 @@ +class Main_2$package$foo$1 +22 diff --git a/tests/run-macros/newClassExtendsJavaClass/JavaClass.java b/tests/run-macros/newClassExtendsJavaClass/JavaClass.java new file mode 100644 index 000000000000..8a02cf294a01 --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/JavaClass.java @@ -0,0 +1,9 @@ +class JavaClass { + T value; + public JavaClass(T value) { + this.value = value; + } + public T getT() { + return value; + } +} \ No newline at end of file diff --git a/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala new file mode 100644 index 000000000000..b19210d092ab --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala @@ -0,0 +1,24 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(inline name: String): JavaClass[Int] = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[JavaClass[Int]] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + 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 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) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(Literal(IntConstant(22)))), TypeTree.of[JavaClass[Int]]) + + Block(List(clsDef), newCls).asExprOf[JavaClass[Int]] + // '{ + // class `name`(idx: Int) extends JavaClass[Int](idx) + // new `name`(22) + // } +} \ No newline at end of file diff --git a/tests/run-macros/newClassExtendsJavaClass/Main_2.scala b/tests/run-macros/newClassExtendsJavaClass/Main_2.scala new file mode 100644 index 000000000000..b41c22427c8e --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/Main_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = { + val cls = makeClass("foo") + println(cls.getClass) + println(cls.getT()) +} diff --git a/tests/run-macros/newClassParams.check b/tests/run-macros/newClassParams.check index 02e16275a822..314cd012f3e5 100644 --- a/tests/run-macros/newClassParams.check +++ b/tests/run-macros/newClassParams.check @@ -1 +1 @@ -Foo method call with (10, test) \ No newline at end of file +Foo method call with (10, test) diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala index 83b7383686c2..3e3b9d1e0894 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, 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)) @@ -25,4 +25,3 @@ private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], str // new `name`(`idx`, `str`) // } } - diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala index e3148ec784c2..1cf0110c7936 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, 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) @@ -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") } - diff --git a/tests/run-macros/newClassTraitAndAbstract.check b/tests/run-macros/newClassTraitAndAbstract.check new file mode 100644 index 000000000000..65e8c6435722 --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract.check @@ -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) +} diff --git a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala new file mode 100644 index 000000000000..e92b40a4045b --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala @@ -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)) +} diff --git a/tests/run-macros/newClassTraitAndAbstract/Test_2.scala b/tests/run-macros/newClassTraitAndAbstract/Test_2.scala new file mode 100644 index 000000000000..5b6512a49010 --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract/Test_2.scala @@ -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) +} diff --git a/tests/run-macros/newClassTypeParams/Macro_1.scala b/tests/run-macros/newClassTypeParams/Macro_1.scala index bf84a338f806..c843511d7eda 100644 --- a/tests/run-macros/newClassTypeParams/Macro_1.scala +++ b/tests/run-macros/newClassTypeParams/Macro_1.scala @@ -8,7 +8,7 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { val name = nameExpr.valueOrAbort def decls(cls: Symbol): List[Symbol] = Nil - val constrType = + 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) @@ -20,19 +20,23 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { parents = _ => List(TypeRepr.of[Object]), decls, selfType = None, - constrType, - Flags.EmptyFlags, - Symbol.noSymbol, - Flags.EmptyFlags, - Symbol.noSymbol, - List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)) + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)) ) val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) val newCls = - cls.typeRef.asType match - case '[t] => - Typed(Apply(TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String], TypeTree.of[Int])), List(Expr("test").asTerm, Expr(1).asTerm)), TypeTree.of[Any]) + Typed( + Apply( + TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String], TypeTree.of[Int])), + List(Expr("test").asTerm, Expr(1).asTerm) + ), + TypeTree.of[Any] + ) val res = Block(List(clsDef), newCls).asExpr From 9afd40cbec137ba1fb396ab9fd195d37d83a540f Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 9 Jan 2025 16:02:53 +0100 Subject: [PATCH 05/11] Refactor: reuse newClass --- .../quoted/runtime/impl/QuotesImpl.scala | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 1bd6384b2d81..1e24c8b6806c 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2693,21 +2693,20 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler conParamNames: List[String], conParamTypes: List[TypeRepr], ): Symbol = - 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( + assert(conParamNames.length == conParamTypes.length, "Lengths of conParamNames and conParamTypes must be equal") + newClass( owner, - name.toTypeName, - clsFlags, + name, parents, - selfType.getOrElse(Types.NoType), - clsPrivateWithin) - 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 + decls, + selfType, + clsFlags, + clsPrivateWithin, + conMethodType = res => MethodType(conParamNames)(_ => conParamTypes, _ => res), + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(for i <- conParamNames yield Flags.EmptyFlags) + ) def newClass( owner: Symbol, From 7e991d2161279b0dc784ad68505578a82d0b9c8f Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 10 Jan 2025 11:18:03 +0100 Subject: [PATCH 06/11] Add more tests, checks for flags and improve docs --- .../quoted/runtime/impl/QuotesImpl.scala | 15 ++++- library/src/scala/quoted/Quotes.scala | 55 ++++++++++++------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 1e24c8b6806c..7477d2ee8835 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2724,6 +2724,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler 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`") checkValidFlags(clsFlags.toTypeFlags, Flags.validClassFlags) + checkValidFlags(conFlags, Flags.validClassConstructorFlags) val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents( owner, name.toTypeName, @@ -2765,12 +2766,14 @@ 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) 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)) // set privateWithin + cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | conParamFlags(clauseIdx)(elementIdx), fixedType, Symbol.noSymbol)) // TODO set privateWithin? for sym <- decls(cls) do cls.enter(sym) cls @@ -3184,6 +3187,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler // Keep: aligned with Quotes's `newClass` private[QuotesImpl] def validClassFlags: Flags = Private | Protected | PrivateLocal | Local | Final | Trait | Abstract // AbsOverride, 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` + private[QuotesImpl] def validClassTypeParamFlags: Flags = Param | Deferred | Private | PrivateLocal | Local + + // Keep: aligned with Quotes's `newClass` + private[QuotesImpl] def validClassTermParamFlags: Flags = ParamAccessor | Private | Protected | PrivateLocal | Local + end Flags given FlagsMethods: FlagsMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index daa3282cab33..36cfd514c731 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3852,10 +3852,9 @@ 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. */ - // TODO: add flags and privateWithin @experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol - /** Generates a new class symbol for a class with a public constructor. + /** Generates a new class symbol for a class with a public single term clause constructor. * * @param owner The owner of the class * @param name The name of the class @@ -3866,7 +3865,13 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param conParamNames constructor parameter names. * @param conParamTypes constructor parameter types. * - * Parameters can be obtained via classSymbol.memberField + * Parameters assigned by the constructor can be obtained via `classSymbol.memberField`. + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ClassDef constructor. + * + * @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. */ @experimental def newClass( owner: Symbol, @@ -3879,24 +3884,32 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => 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 - * 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 clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol - * @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`. - * - */ + /** Generates a new class symbol with a constructor of the shape signified by a passed PolyOrMethod parameter. + * TODO example with PolyType + * + * @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 clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol + * @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 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`. + * + * Term and type parameters assigned by the constructor can be obtained via `classSymbol.memberField`/`classSymbol.memberType`. + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ClassDef constructor. + * + * @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. + */ @experimental def newClass( owner: Symbol, name: String, From 0f020f843b878c9efd6e5a2fb1e399f39222fdd9 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 23 Jan 2025 12:14:28 +0100 Subject: [PATCH 07/11] Allow to annotate classes --- .../src/dotty/tools/dotc/core/Symbols.scala | 2 + .../quoted/runtime/impl/QuotesImpl.scala | 6 +- library/src/scala/quoted/Quotes.scala | 2 + tests/run-macros/newClassAnnotation.check | 7 +++ .../newClassAnnotation/JavaAnnot.java | 5 ++ .../newClassAnnotation/Macro_1.scala | 55 +++++++++++++++++++ .../newClassAnnotation/Test_2.scala | 7 +++ 7 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/run-macros/newClassAnnotation.check create mode 100644 tests/run-macros/newClassAnnotation/JavaAnnot.java create mode 100644 tests/run-macros/newClassAnnotation/Macro_1.scala create mode 100644 tests/run-macros/newClassAnnotation/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 210b960708a8..b589f1f66ccb 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -639,6 +639,7 @@ object Symbols extends SymUtils { parentTypes: Symbol => List[Type], selfInfo: Type = NoType, privateWithin: Symbol = NoSymbol, + annotations: List[Tree] = Nil, coord: Coord = NoCoord, compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = { def completer = new LazyType { @@ -648,6 +649,7 @@ object Symbols extends SymUtils { 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) + denot.annotations = annotations.map(Annotations.Annotation(_)) } } newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7477d2ee8835..9d87259b2e51 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2702,6 +2702,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler selfType, clsFlags, clsPrivateWithin, + Nil, conMethodType = res => MethodType(conParamNames)(_ => conParamTypes, _ => res), conFlags = Flags.EmptyFlags, conPrivateWithin = Symbol.noSymbol, @@ -2716,6 +2717,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler selfType: Option[TypeRepr], clsFlags: Flags, clsPrivateWithin: Symbol, + clsAnnotations: List[Term], conMethodType: TypeRepr => MethodOrPoly, conFlags: Flags, conPrivateWithin: Symbol, @@ -2731,7 +2733,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler clsFlags, parents, selfType.getOrElse(Types.NoType), - clsPrivateWithin) + clsPrivateWithin, + clsAnnotations + ) val methodType: MethodOrPoly = conMethodType(cls.typeRef) def throwShapeException() = throw new Exception("Shapes of conMethodType and conParamFlags differ.") def checkMethodOrPolyShape(checkedMethodType: TypeRepr, clauseIdx: Int): Unit = diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 36cfd514c731..c95746d9c658 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3895,6 +3895,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @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 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. @@ -3918,6 +3919,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => selfType: Option[TypeRepr], clsFlags: Flags, clsPrivateWithin: Symbol, + clsAnnotations: List[Term], conMethodType: TypeRepr => MethodOrPoly, conFlags: Flags, conPrivateWithin: Symbol, diff --git a/tests/run-macros/newClassAnnotation.check b/tests/run-macros/newClassAnnotation.check new file mode 100644 index 000000000000..433c40f72180 --- /dev/null +++ b/tests/run-macros/newClassAnnotation.check @@ -0,0 +1,7 @@ +{ + @JavaAnnot("string in a java annotation") @ScalaAnnotation("string in a scala annotation") class name() extends java.lang.Object + + (new name(): scala.Any) +} +class Test_2$package$name$1 +string in a java annotation diff --git a/tests/run-macros/newClassAnnotation/JavaAnnot.java b/tests/run-macros/newClassAnnotation/JavaAnnot.java new file mode 100644 index 000000000000..2c815478c198 --- /dev/null +++ b/tests/run-macros/newClassAnnotation/JavaAnnot.java @@ -0,0 +1,5 @@ +import java.lang.annotation.*; +public @Retention(RetentionPolicy.RUNTIME) +@interface JavaAnnot { + public String value(); +} \ No newline at end of file diff --git a/tests/run-macros/newClassAnnotation/Macro_1.scala b/tests/run-macros/newClassAnnotation/Macro_1.scala new file mode 100644 index 000000000000..4192a5c61dd5 --- /dev/null +++ b/tests/run-macros/newClassAnnotation/Macro_1.scala @@ -0,0 +1,55 @@ +//> using options -experimental + +import scala.quoted.* +import scala.annotation.StaticAnnotation + +class ScalaAnnotation(value: String) extends StaticAnnotation + +inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + def decls(cls: Symbol): List[Symbol] = Nil + val conMethodType = (classType: TypeRepr) => MethodType(Nil)((_: MethodType) => Nil, (_: MethodType) => classType) + + val javaAnnotSym = TypeRepr.of[JavaAnnot].typeSymbol + val scalaAnnotSym = TypeRepr.of[ScalaAnnotation].typeSymbol + + val javaAnnotationDef = Apply(Select(New(TypeIdent(javaAnnotSym)), javaAnnotSym.primaryConstructor), List(Literal(StringConstant("string in a java annotation")))) + val scalaAnnotationDef = Apply(Select(New(TypeIdent(scalaAnnotSym)), scalaAnnotSym.primaryConstructor), List(Literal(StringConstant("string in a scala annotation")))) + + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = List(javaAnnotationDef, scalaAnnotationDef), + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List()) + ) + + val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) + val newCls = + Typed( + Apply( + Select(New(TypeIdent(cls)), cls.primaryConstructor), + Nil + ), + TypeTree.of[Any] + ) + + val res = Block(List(clsDef), newCls).asExpr + + Expr.ofTuple(res, Expr(res.show)) + + // { + // @JavaAnnot("string in a java annotation") @ScalaAnnotation("string in a scala annotation") class name() extends java.lang.Object + // (new name(): scala.Any) + // } +} diff --git a/tests/run-macros/newClassAnnotation/Test_2.scala b/tests/run-macros/newClassAnnotation/Test_2.scala new file mode 100644 index 000000000000..a824259eba09 --- /dev/null +++ b/tests/run-macros/newClassAnnotation/Test_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test = + val (cls, str) = makeClass("name") + println(str) + println(cls.getClass) + println(cls.getClass.getAnnotation(classOf[JavaAnnot]).value) From 2ca2fc3ba15c086f8a2b2d4c76b813ce99462ac6 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 30 Jan 2025 18:43:20 +0100 Subject: [PATCH 08/11] Improve api, documentation and add conParamsPrivateWithin --- .../quoted/runtime/impl/QuotesImpl.scala | 20 ++--- library/src/scala/quoted/Quotes.scala | 82 +++++++++++++++++-- tests/neg-macros/i19842-a.check | 4 +- tests/neg-macros/i19842-b.check | 4 +- .../Macro_1.scala | 2 +- .../Macro_1.scala | 2 +- .../newClassAnnotation/Macro_1.scala | 3 +- .../newClassExtendsJavaClass/Macro_1.scala | 2 +- tests/run-macros/newClassParams/Macro_1.scala | 2 +- .../Macro_1.scala | 2 +- .../newClassTraitAndAbstract/Macro_1.scala | 8 +- tests/run-macros/newClassTypeParamDoc.check | 2 + .../newClassTypeParamDoc/Macro_1.scala | 67 +++++++++++++++ .../newClassTypeParamDoc/Test_2.scala | 5 ++ .../newClassTypeParams/Macro_1.scala | 4 +- 15 files changed, 177 insertions(+), 32 deletions(-) create mode 100644 tests/run-macros/newClassTypeParamDoc.check create mode 100644 tests/run-macros/newClassTypeParamDoc/Macro_1.scala create mode 100644 tests/run-macros/newClassTypeParamDoc/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 9d87259b2e51..a36bbac0f51b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -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, @@ -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( @@ -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`") @@ -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 { @@ -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 @@ -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` diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index c95746d9c658..dfccc769796d 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -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. @@ -3877,15 +3876,74 @@ 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 @@ -3893,15 +3951,18 @@ 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 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. @@ -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, @@ -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, diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index cdf0921f1087..49dd95b6e09f 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -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) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index 8039f278d2af..b93df5c05035 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -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) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala index 41dd9bcdda34..a0ee5a899e62 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, 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]) diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala index c1483ac8f182..569d8e0c25be 100644 --- a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala @@ -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 diff --git a/tests/run-macros/newClassAnnotation/Macro_1.scala b/tests/run-macros/newClassAnnotation/Macro_1.scala index 4192a5c61dd5..c7631317675b 100644 --- a/tests/run-macros/newClassAnnotation/Macro_1.scala +++ b/tests/run-macros/newClassAnnotation/Macro_1.scala @@ -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) diff --git a/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala index b19210d092ab..c342c78da796 100644 --- a/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala +++ b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala @@ -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) diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala index 3e3b9d1e0894..5090fd7a0116 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, 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)) diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala index 1cf0110c7936..de35a4dfa206 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, 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) diff --git a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala index e92b40a4045b..3a285c7e22dc 100644 --- a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala +++ b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala @@ -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) @@ -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 diff --git a/tests/run-macros/newClassTypeParamDoc.check b/tests/run-macros/newClassTypeParamDoc.check new file mode 100644 index 000000000000..5d3b08bf509c --- /dev/null +++ b/tests/run-macros/newClassTypeParamDoc.check @@ -0,0 +1,2 @@ +Calling getParam +test diff --git a/tests/run-macros/newClassTypeParamDoc/Macro_1.scala b/tests/run-macros/newClassTypeParamDoc/Macro_1.scala new file mode 100644 index 000000000000..9fb81e6f7348 --- /dev/null +++ b/tests/run-macros/newClassTypeParamDoc/Macro_1.scala @@ -0,0 +1,67 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(): Any = ${ makeClassExpr } +private def makeClassExpr(using Quotes): Expr[Any] = { + import quotes.reflect.* + + 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) => + AppliedType(classType, List(polyType.param(0))) + ) + ) + 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 appliedTypeTree = Applied(TypeIdent(cls), List(TypeTree.of[String])) + val newCls = + Typed( + Apply( + Select( + Apply( + TypeApply(Select(New(appliedTypeTree), cls.primaryConstructor), List(TypeTree.of[String])), + List(Expr("test").asTerm) + ), + cls.methodMember("getParam").head + ), + Nil + ), + TypeTree.of[String] + ) + + Block(List(clsDef), newCls).asExpr + + // '{ + // class myClass[T](val param: T) { + // def getParam(): T = + // println("Calling getParam") + // param + // } + // new myClass[String]("test").getParam() + // } +} diff --git a/tests/run-macros/newClassTypeParamDoc/Test_2.scala b/tests/run-macros/newClassTypeParamDoc/Test_2.scala new file mode 100644 index 000000000000..318db32242fc --- /dev/null +++ b/tests/run-macros/newClassTypeParamDoc/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + println(makeClass()) +} diff --git a/tests/run-macros/newClassTypeParams/Macro_1.scala b/tests/run-macros/newClassTypeParams/Macro_1.scala index c843511d7eda..3d0a9b61bf7e 100644 --- a/tests/run-macros/newClassTypeParams/Macro_1.scala +++ b/tests/run-macros/newClassTypeParams/Macro_1.scala @@ -22,10 +22,12 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { selfType = None, clsFlags = Flags.EmptyFlags, 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 clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) From e404de49d1ad58e7306c582a977c683355bfd75a Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 3 Feb 2025 17:53:29 +0100 Subject: [PATCH 09/11] Add additional checks for type parameters and update tests --- .../quoted/runtime/impl/QuotesImpl.scala | 45 ++++++++++++++---- library/src/scala/quoted/Quotes.scala | 47 +++++++++++++++---- tests/run-macros/newClassParams/Macro_1.scala | 21 +++++++-- .../newClassTraitAndAbstract/Macro_1.scala | 4 +- .../newClassTypeParams/Macro_1.scala | 4 +- ...mDoc.check => newClassTypeParamsDoc.check} | 0 .../Macro_1.scala | 0 .../Test_2.scala | 0 8 files changed, 97 insertions(+), 24 deletions(-) rename tests/run-macros/{newClassTypeParamDoc.check => newClassTypeParamsDoc.check} (100%) rename tests/run-macros/{newClassTypeParamDoc => newClassTypeParamsDoc}/Macro_1.scala (100%) rename tests/run-macros/{newClassTypeParamDoc => newClassTypeParamsDoc}/Test_2.scala (100%) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a36bbac0f51b..fbf1749d5cfc 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2727,7 +2727,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler 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`") checkValidFlags(clsFlags.toTypeFlags, Flags.validClassFlags) - checkValidFlags(conFlags, Flags.validClassConstructorFlags) + checkValidFlags(conFlags.toTermFlags, Flags.validClassConstructorFlags) val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents( owner, name.toTypeName, @@ -2750,18 +2750,43 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler if (conParamFlags.length <= clauseIdx) throwShapeException() if (conParamFlags(clauseIdx).length != params.length) throwShapeException() checkMethodOrPolyShape(res, clauseIdx + 1) - case _ => + case other => + xCheckMacroAssert( + other.typeSymbol == cls, + "Incorrect type returned from the innermost PolyOrMethod." + ) + (other, methodType) match + case (AppliedType(tycon, args), pt: PolyType) => + xCheckMacroAssert( + args.length == pt.typeParams.length && + args.zip(pt.typeParams).forall { + case (arg, param) => arg == param.paramRef + }, + "Constructor result type does not correspond to the declared type parameters" + ) + case _ => + xCheckMacroAssert( + !(other.isInstanceOf[AppliedType] || methodType.isInstanceOf[PolyType]), + "AppliedType has to be the innermost resultTypeExp result if and only if conMethodType returns a PolyType" + ) checkMethodOrPolyShape(methodType, clauseIdx = 0) + 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)] = + + case class ParamSymbolData(name: String, tpe: TypeRepr, isTypeParam: Boolean, clauseIdx: Int, elementIdx: Int) + def getParamSymbolsData(methodType: TypeRepr, clauseIdx: Int): List[ParamSymbolData] = methodType match case MethodType(paramInfosExp, resultTypeExp, res) => - paramInfosExp.zip(resultTypeExp).map(_ :* false :* clauseIdx).zipWithIndex ++ getParamAccessors(res, clauseIdx + 1) + paramInfosExp.zip(resultTypeExp).zipWithIndex.map { case ((name, tpe), elementIdx) => + ParamSymbolData(name, tpe, isTypeParam = false, clauseIdx, elementIdx) + } ++ getParamSymbolsData(res, clauseIdx + 1) case pt @ PolyType(paramNames, paramBounds, res) => - paramNames.zip(paramBounds).map(_ :* true :* clauseIdx).zipWithIndex ++ getParamAccessors(res, clauseIdx + 1) + paramNames.zip(paramBounds).zipWithIndex.map {case ((name, tpe), elementIdx) => + ParamSymbolData(name, tpe, isTypeParam = true, clauseIdx, elementIdx) + } ++ getParamSymbolsData(res, clauseIdx + 1) case result => List() - // Maps PolyType indexes to type parameter symbols + // Maps PolyType indexes to type parameter symbol typerefs val paramRefMap = collection.mutable.HashMap[Int, Symbol]() val paramRefRemapper = new Types.TypeMap { def apply(tp: Types.Type) = tp match { @@ -2769,14 +2794,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case _ => mapOver(tp) } } - for ((name, tpe, isType, clauseIdx), elementIdx) <- getParamAccessors(methodType, 0) do - if isType then - checkValidFlags(conParamFlags(clauseIdx)(elementIdx), Flags.validClassTypeParamFlags) + for case ParamSymbolData(name, tpe, isTypeParam, clauseIdx, elementIdx) <- getParamSymbolsData(methodType, 0) do + if isTypeParam then + checkValidFlags(conParamFlags(clauseIdx)(elementIdx).toTypeFlags, 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, conParamPrivateWithins(clauseIdx)(elementIdx)) paramRefMap.addOne(elementIdx, symbol) cls.enter(symbol) else - checkValidFlags(conParamFlags(clauseIdx)(elementIdx), Flags.validClassTermParamFlags) + checkValidFlags(conParamFlags(clauseIdx)(elementIdx).toTermFlags, Flags.validClassTermParamFlags) val fixedType = paramRefRemapper(tpe) 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) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index dfccc769796d..a8c459942e61 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3812,6 +3812,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def classSymbol(fullName: String): Symbol /** Generates a new class symbol for a class with a public parameterless constructor. + * For more settings, look to the other newClass methods. * * Example usage: * ``` @@ -3856,13 +3857,41 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Generates a new class symbol for a class with a public single term clause constructor. * - * @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 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 conParams constructor parameter pairs of names and types. + * Example usage: + * ``` + * val name = nameExpr.valueOrAbort + * 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, + * clsFlags = Flags.EmptyFlags, + * Symbol.noSymbol, + * List(("idx", TypeRepr.of[Int]), ("str", TypeRepr.of[String])) + * ) + * + * val fooSym = cls.declaredMethod("foo").head + * val idxSym = cls.fieldMember("idx") + * val strSym = cls.fieldMember("str") + * val fooDef = DefDef(fooSym, argss => + * Some('{println(s"Foo method call with (${${Ref(idxSym).asExpr}}, ${${Ref(strSym).asExpr}})")}.asTerm) + * ) + * val clsDef = ClassDef(cls, parents, body = List(fooDef)) + * val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) + * + * Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] + * ``` + * @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 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 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. @@ -3893,7 +3922,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * val conMethodType = * (classType: TypeRepr) => PolyType(List("T"))(_ => List(TypeBounds.empty), polyType => * MethodType(List("param"))((_: MethodType) => List(polyType.param(0)), (_: MethodType) => - * classType + * AppliedType(classType, List(polyType.param(0))) * ) * ) * val cls = Symbol.newClass( @@ -3955,7 +3984,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @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. + * Takes the result type as parameter which must be returned from the innermost MethodOrPoly and have type parameters applied if those are used. * PolyType may only represent the first clause of the constructor. * @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. diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala index 5090fd7a0116..6ec48e24ebe3 100644 --- a/tests/run-macros/newClassParams/Macro_1.scala +++ b/tests/run-macros/newClassParams/Macro_1.scala @@ -8,11 +8,26 @@ private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], str val name = nameExpr.valueOrAbort - def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) + 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", TypeRepr.of[Int]), ("str", TypeRepr.of[String]))) + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => parents.map(_.tpe), + decls, + selfType = None, + clsFlags = 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 fooSym = cls.declaredMethod("foo").head + val idxSym = cls.fieldMember("idx") + val strSym = cls.fieldMember("str") + val fooDef = DefDef(fooSym, argss => + Some('{println(s"Foo method call with (${${Ref(idxSym).asExpr}}, ${${Ref(strSym).asExpr}})")}.asTerm) + ) val clsDef = ClassDef(cls, parents, body = List(fooDef)) val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) diff --git a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala index 3a285c7e22dc..56cd320085db 100644 --- a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala +++ b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala @@ -33,7 +33,9 @@ private def makeClassExpr(using Quotes)( 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) + polyType => MethodType(List("param1", "param2"))((_: MethodType) => List(polyType.param(0), polyType.param(1)), (_: MethodType) => + AppliedType(classType, List(polyType.param(0), polyType.param(1))) + ) ) val traitSymbol = Symbol.newClass( diff --git a/tests/run-macros/newClassTypeParams/Macro_1.scala b/tests/run-macros/newClassTypeParams/Macro_1.scala index 3d0a9b61bf7e..2efb31610c73 100644 --- a/tests/run-macros/newClassTypeParams/Macro_1.scala +++ b/tests/run-macros/newClassTypeParams/Macro_1.scala @@ -11,7 +11,9 @@ private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { 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) + polyType => MethodType(List("param1", "param2"))((_: MethodType) => List(polyType.param(0), polyType.param(1)), (_: MethodType) => + AppliedType(classType, List(polyType.param(0), polyType.param(1))) + ) ) val cls = Symbol.newClass( diff --git a/tests/run-macros/newClassTypeParamDoc.check b/tests/run-macros/newClassTypeParamsDoc.check similarity index 100% rename from tests/run-macros/newClassTypeParamDoc.check rename to tests/run-macros/newClassTypeParamsDoc.check diff --git a/tests/run-macros/newClassTypeParamDoc/Macro_1.scala b/tests/run-macros/newClassTypeParamsDoc/Macro_1.scala similarity index 100% rename from tests/run-macros/newClassTypeParamDoc/Macro_1.scala rename to tests/run-macros/newClassTypeParamsDoc/Macro_1.scala diff --git a/tests/run-macros/newClassTypeParamDoc/Test_2.scala b/tests/run-macros/newClassTypeParamsDoc/Test_2.scala similarity index 100% rename from tests/run-macros/newClassTypeParamDoc/Test_2.scala rename to tests/run-macros/newClassTypeParamsDoc/Test_2.scala From f4a06bde8f22a861d0085bda8c3233bd3d033981 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 10 Mar 2025 14:24:33 +0100 Subject: [PATCH 10/11] address review comments --- .../src/dotty/tools/dotc/core/Symbols.scala | 28 +++++++++---------- .../quoted/runtime/impl/QuotesImpl.scala | 16 +++++++---- library/src/scala/quoted/Quotes.scala | 16 +++++++++-- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index b589f1f66ccb..379e2e5fbe0b 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -629,19 +629,19 @@ 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. + /** Same as the other `newNormalizedClassSymbol` except that `parents` can be a function returning a list of arbitrary + * types which get normalized into type refs and parameter bindings and annotations can be assigned in the completer. */ - def newNormalizedClassSymbolUsingClassSymbolinParents( + def newNormalizedClassSymbol( owner: Symbol, name: TypeName, flags: FlagSet, parentTypes: Symbol => List[Type], - selfInfo: Type = NoType, - privateWithin: Symbol = NoSymbol, - annotations: List[Tree] = Nil, - coord: Coord = NoCoord, - compUnitInfo: CompilationUnitInfo | Null = null)(using Context): ClassSymbol = { + selfInfo: Type, + privateWithin: Symbol, + annotations: List[Tree], + coord: Coord, + compUnitInfo: CompilationUnitInfo | Null)(using Context): ClassSymbol = { def completer = new LazyType { def complete(denot: SymDenotation)(using Context): Unit = { val cls = denot.asClass.classSymbol @@ -735,23 +735,23 @@ object Symbols extends SymUtils { /** 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( + def newNormalizedModuleSymbol( 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 = { + privateWithin: Symbol, + coord: Coord, + compUnitInfo: CompilationUnitInfo | 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) + 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.map(_.dealias), decls, TermRef(owner.thisType, module)) + denot.info = ClassInfo(owner.thisType, cls, parents, decls, TermRef(owner.thisType, module)) } } newModuleSymbol( diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index fbf1749d5cfc..06d3ccd8f792 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -26,6 +26,7 @@ import scala.reflect.TypeTest import dotty.tools.dotc.core.NameKinds.ExceptionBinderName import dotty.tools.dotc.transform.TreeChecker import dotty.tools.dotc.core.Names +import dotty.tools.dotc.util.Spans.NoCoord object QuotesImpl { @@ -2692,7 +2693,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler clsPrivateWithin: Symbol, conParams: List[(String, TypeRepr)] ): Symbol = - val (conParamNames, conParamTypes) = conParams.unzip() + val (conParamNames, conParamTypes) = conParams.unzip newClass( owner, name, @@ -2728,14 +2729,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler assert(!conPrivateWithin.exists || conPrivateWithin.isType, "consPrivateWithin must be a type symbol or `Symbol.noSymbol`") checkValidFlags(clsFlags.toTypeFlags, Flags.validClassFlags) checkValidFlags(conFlags.toTermFlags, Flags.validClassConstructorFlags) - val cls = dotc.core.Symbols.newNormalizedClassSymbolUsingClassSymbolinParents( + val cls = dotc.core.Symbols.newNormalizedClassSymbol( owner, name.toTypeName, clsFlags, parents, selfType.getOrElse(Types.NoType), clsPrivateWithin, - clsAnnotations + clsAnnotations, + NoCoord, + compUnitInfo = null ) val methodType: MethodOrPoly = conMethodType(cls.typeRef) def throwShapeException() = throw new Exception("Shapes of conMethodType and conParamFlags differ.") @@ -2809,14 +2812,17 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol = assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") - val mod = dotc.core.Symbols.newNormalizedModuleSymbolUsingClassSymbolInParents( + val mod = dotc.core.Symbols.newNormalizedModuleSymbol( owner, name.toTermName, modFlags | dotc.core.Flags.ModuleValCreationFlags, clsFlags | dotc.core.Flags.ModuleClassCreationFlags, parents, dotc.core.Scopes.newScope, - privateWithin) + privateWithin, + NoCoord, + compUnitInfo = null + ) val cls = mod.moduleClass.asClass cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil)) for sym <- decls(cls) do cls.enter(sym) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a8c459942e61..e0c06d006a76 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3859,7 +3859,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * Example usage: * ``` - * val name = nameExpr.valueOrAbort + * val name = "myClass" * def decls(cls: Symbol): List[Symbol] = * List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) * val parents = List(TypeTree.of[Object]) @@ -3881,10 +3881,20 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * Some('{println(s"Foo method call with (${${Ref(idxSym).asExpr}}, ${${Ref(strSym).asExpr}})")}.asTerm) * ) * val clsDef = ClassDef(cls, parents, body = List(fooDef)) - * val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) + * val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List('{0}.asTerm, '{string}.asTerm)) * * Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] * ``` + * construct the equivalent to + * ``` + * '{ + * class myClass(idx: Int, str: String) extends Object { + * def foo() = + * println(s"Foo method call with $idx, $str") + * } + * new myClass(0, "string").foo() + * } + * ``` * @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. @@ -3965,7 +3975,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * constructs the equivalent to * ``` * '{ - * class myClass[T](val param: T) { + * class myClass[T](val param: T) extends Object { * def getParam: T = * println("Calling getParam") * param From aaef41272b231befe32b4bf50528f567d4d3c161 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 11 Mar 2025 15:47:37 +0100 Subject: [PATCH 11/11] Update positions in a checkfile --- tests/neg-macros/i19842-a.check | 2 +- tests/neg-macros/i19842-b.check | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index 49dd95b6e09f..af628c566c15 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -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:285) | 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) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index b93df5c05035..b402006c2d4b 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -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:285) | 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) | |---------------------------------------------------------------------------------------------------------------------