Skip to content

Commit 28cb854

Browse files
committed
Add class term parameters, flags, and privateWithin to newClass in reflect API
1 parent 01288d2 commit 28cb854

File tree

13 files changed

+216
-26
lines changed

13 files changed

+216
-26
lines changed

Diff for: compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

+25-21
Original file line numberDiff line numberDiff line change
@@ -854,30 +854,34 @@ object TreeChecker {
854854
val phases = ctx.base.allPhases.toList
855855
val treeChecker = new LocalChecker(previousPhases(phases))
856856

857+
def reportMalformedMacroTree(msg: String | Null, err: Throwable) =
858+
val stack =
859+
if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`"
860+
else if err.getStackTrace == null then " no stacktrace"
861+
else err.getStackTrace.nn.mkString(" ", " \n", "")
862+
report.error(
863+
em"""Malformed tree was found while expanding macro with -Xcheck-macros.
864+
|The tree does not conform to the compiler's tree invariants.
865+
|
866+
|Macro was:
867+
|${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)}
868+
|
869+
|The macro returned:
870+
|${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)}
871+
|
872+
|Error:
873+
|$msg
874+
|$stack
875+
|""",
876+
original
877+
)
878+
857879
try treeChecker.typed(expansion)(using checkingCtx)
858880
catch
859881
case err: java.lang.AssertionError =>
860-
val stack =
861-
if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`"
862-
else if err.getStackTrace == null then " no stacktrace"
863-
else err.getStackTrace.nn.mkString(" ", " \n", "")
864-
865-
report.error(
866-
em"""Malformed tree was found while expanding macro with -Xcheck-macros.
867-
|The tree does not conform to the compiler's tree invariants.
868-
|
869-
|Macro was:
870-
|${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)}
871-
|
872-
|The macro returned:
873-
|${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)}
874-
|
875-
|Error:
876-
|${err.getMessage}
877-
|$stack
878-
|""",
879-
original
880-
)
882+
reportMalformedMacroTree(err.getMessage(), err)
883+
case err: UnhandledError =>
884+
reportMalformedMacroTree(err.diagnostic.message, err)
881885

882886
private[TreeChecker] def previousPhases(phases: List[Phase])(using Context): List[Phase] = phases match {
883887
case (phase: MegaPhase) :: phases1 =>

Diff for: compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import collection.mutable
1212
import reporting.*
1313
import Checking.{checkNoPrivateLeaks, checkNoWildcard}
1414
import cc.CaptureSet
15+
import transform.Splicer
1516

1617
trait TypeAssigner {
1718
import tpd.*
@@ -301,7 +302,10 @@ trait TypeAssigner {
301302
if fntpe.isResultDependent then safeSubstMethodParams(fntpe, args.tpes)
302303
else fntpe.resultType // fast path optimization
303304
else
304-
errorType(em"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos)
305+
val erroringPhase =
306+
if Splicer.inMacroExpansion then i"${ctx.phase} (while expanding macro)"
307+
else ctx.phase.prev.toString
308+
errorType(em"wrong number of arguments at $erroringPhase for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos)
305309
case err: ErrorType =>
306310
err
307311
case t =>

Diff for: compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+37-4
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,23 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
241241

242242
object ClassDef extends ClassDefModule:
243243
def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef =
244-
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree)
244+
val paramsDefs: List[untpd.ParamClause] =
245+
cls.primaryConstructor.paramSymss.map { paramSym =>
246+
paramSym.map( symm =>
247+
ValDef(symm, None)
248+
)
249+
}
250+
val paramsAccessDefs: List[untpd.ParamClause] =
251+
cls.primaryConstructor.paramSymss.map { paramSym =>
252+
paramSym.map( symm =>
253+
ValDef(cls.fieldMember(symm.name.toString()), None) // TODO I don't like the toString here
254+
)
255+
}
256+
257+
val termSymbol: dotc.core.Symbols.TermSymbol = cls.primaryConstructor.asTerm
258+
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, paramsDefs, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree)
245259
val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor)
246-
tpd.ClassDefWithParents(cls.asClass, ctr, parents, body)
260+
tpd.ClassDefWithParents(cls.asClass, ctr, parents, paramsAccessDefs.flatten ++ body)
247261

248262
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = {
249263
val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original: @unchecked
@@ -2605,10 +2619,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26052619
def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path)
26062620
def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName)
26072621

2608-
def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol =
2622+
def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol =
26092623
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
26102624
val cls = dotc.core.Symbols.newNormalizedClassSymbol(
2611-
owner,
2625+
parent,
26122626
name.toTypeName,
26132627
dotc.core.Flags.EmptyFlags,
26142628
parents,
@@ -2618,6 +2632,22 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26182632
for sym <- decls(cls) do cls.enter(sym)
26192633
cls
26202634

2635+
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 =
2636+
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
2637+
checkValidFlags(flags.toTermFlags, Flags.validClassFlags)
2638+
val cls = dotc.core.Symbols.newNormalizedClassSymbol(
2639+
parent,
2640+
name.toTypeName,
2641+
flags,
2642+
parents,
2643+
selfType.getOrElse(Types.NoType),
2644+
privateWithin)
2645+
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, paramNames.map(_.toTermName), paramTypes))
2646+
for (name, tpe) <- paramNames.zip(paramTypes) do
2647+
cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor, tpe, Symbol.noSymbol)) // add other flags (local, private, privatelocal) and set privateWithin
2648+
for sym <- decls(cls) do cls.enter(sym)
2649+
cls
2650+
26212651
def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol =
26222652
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
26232653
assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`")
@@ -3006,6 +3036,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
30063036
// Keep: aligned with Quotes's `newTypeAlias` doc
30073037
private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local
30083038

3039+
// Keep: aligned with Quotes's `newClass`
3040+
private[QuotesImpl] def validClassFlags: Flags = Private | Protected | Final // Abstract, AbsOverride Local OPen ? PrivateLocal Protected ?
3041+
30093042
end Flags
30103043

30113044
given FlagsMethods: FlagsMethods with

Diff for: library/src/scala/quoted/Quotes.scala

+10
Original file line numberDiff line numberDiff line change
@@ -3840,6 +3840,16 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
38403840
// TODO: add flags and privateWithin
38413841
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol
38423842

3843+
/*
3844+
* @param paramNames constructor parameter names.
3845+
* @param paramTypes constructor parameter types.
3846+
* @param flags extra flags with which the class symbol should be constructed.
3847+
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
3848+
*
3849+
* Parameters can be obtained via classSymbol.memberField
3850+
*/
3851+
@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
3852+
38433853
/** Generates a new module symbol with an associated module class symbol,
38443854
* this is equivalent to an `object` declaration in source code.
38453855
* This method returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol.

Diff for: tests/neg-macros/newClassParamsMissingArgument.check

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:2 ----------------------------------------------
3+
4 | makeClass("foo") // error // error
4+
| ^^^^^^^^^^^^^^^^
5+
|wrong number of arguments at inlining (while expanding macro) for (idx: Int): foo: (foo#<init> : (idx: Int): foo), expected: 1, found: 0
6+
-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:11 ---------------------------------------------
7+
4 | makeClass("foo") // error // error
8+
| ^^^^^^^^^^^^^^^^
9+
|Malformed tree was found while expanding macro with -Xcheck-macros.
10+
|The tree does not conform to the compiler's tree invariants.
11+
|
12+
|Macro was:
13+
|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)))
14+
|
15+
|The macro returned:
16+
|{
17+
| class foo(val idx: scala.Int) extends java.lang.Object
18+
|
19+
| (new foo(): java.lang.Object)
20+
|}
21+
|
22+
|Error:
23+
|missing argument for parameter idx of constructor foo in class foo: (idx: Int): foo
24+
|
25+
|stacktrace available when compiling with `-Ydebug`
26+
|---------------------------------------------------------------------------------------------------------------------
27+
|Inline stack trace
28+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29+
|This location contains code that was inlined from Macro_1.scala:5
30+
5 |inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) }
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
32+
---------------------------------------------------------------------------------------------------------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//> using options -experimental
2+
3+
import scala.quoted._
4+
5+
inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) }
6+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Object] = {
7+
import quotes.reflect.*
8+
9+
val name = nameExpr.valueOrAbort
10+
val parents = List(TypeTree.of[Object])
11+
def decls(cls: Symbol): List[Symbol] = Nil
12+
13+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)
14+
15+
val clsDef = ClassDef(cls, parents, body = Nil)
16+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
17+
18+
Block(List(clsDef), newCls).asExprOf[Object]
19+
20+
// '{
21+
// class `name`(idx: Int)
22+
// new `name`
23+
// }
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//> using options -experimental
2+
3+
@main def Test: Unit = {
4+
makeClass("foo") // error // error
5+
}

Diff for: tests/run-macros/newClassParams.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Foo method call with (10, test)

Diff for: tests/run-macros/newClassParams/Macro_1.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//> using options -experimental
2+
3+
import scala.quoted._
4+
5+
inline def makeClassAndCall(inline name: String, idx: Int, str: String): Unit = ${ makeClassAndCallExpr('name, 'idx, 'str) }
6+
private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], strExpr: Expr[String])(using Quotes): Expr[Unit] = {
7+
import quotes.reflect.*
8+
9+
val name = nameExpr.valueOrAbort
10+
11+
def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
12+
val parents = List(TypeTree.of[Object])
13+
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)
14+
15+
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))
16+
val clsDef = ClassDef(cls, parents, body = List(fooDef))
17+
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm))
18+
19+
Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit]
20+
21+
// '{
22+
// class `name`(idx: Int, str: String) {
23+
// def foo() = println("Foo method call with ($idx, $str)")
24+
// }
25+
// new `name`(`idx`, `str`)
26+
// }
27+
}
28+

Diff for: tests/run-macros/newClassParams/Test_2.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//> using options -experimental
2+
3+
@main def Test: Unit = {
4+
makeClassAndCall("bar", 10, "test")
5+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Calling Foo.foo with i = 22
2+
class Test_2$package$foo$1
3+
Calling Foo.foo with i = 22
4+
class Test_2$package$bar$1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -experimental
2+
3+
import scala.quoted._
4+
5+
inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
6+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
7+
import quotes.reflect.*
8+
9+
val name = nameExpr.valueOrAbort
10+
val parents = List('{ new Foo(1) }.asTerm)
11+
def decls(cls: Symbol): List[Symbol] = Nil
12+
13+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None, List("idx"), List(TypeRepr.of[Int]), Flags.EmptyFlags, Symbol.noSymbol)
14+
15+
val parentsWithSym = List(Apply(Select(New(TypeTree.of[Foo]), TypeRepr.of[Foo].typeSymbol.primaryConstructor), List(Ref(cls.fieldMember("idx")))))
16+
val clsDef = ClassDef(cls, parentsWithSym, body = Nil)
17+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(Literal(IntConstant(22)))), TypeTree.of[Foo])
18+
19+
Block(List(clsDef), newCls).asExprOf[Foo]
20+
21+
// '{
22+
// class `name`(idx: Int) extends Foo(idx)
23+
// new `name`(22)
24+
// }
25+
}
26+
27+
class Foo(i: Int) {
28+
def foo(): Unit = println(s"Calling Foo.foo with i = $i")
29+
}
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//> using options -experimental
2+
3+
@main def Test: Unit = {
4+
val foo: Foo = makeClass("foo")
5+
foo.foo()
6+
println(foo.getClass)
7+
val bar: Foo = makeClass("bar")
8+
bar.foo()
9+
println(bar.getClass)
10+
}

0 commit comments

Comments
 (0)