Skip to content

Commit

Permalink
Add class term parameters, flags, and privateWithin to newClass in re…
Browse files Browse the repository at this point in the history
…flect API
  • Loading branch information
jchyb committed Nov 3, 2024
1 parent 01288d2 commit 28cb854
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 26 deletions.
46 changes: 25 additions & 21 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import collection.mutable
import reporting.*
import Checking.{checkNoPrivateLeaks, checkNoWildcard}
import cc.CaptureSet
import transform.Splicer

trait TypeAssigner {
import tpd.*
Expand Down Expand Up @@ -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 =>
Expand Down
41 changes: 37 additions & 4 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2605,10 +2619,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,
Expand All @@ -2618,6 +2632,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`")
Expand Down Expand Up @@ -3006,6 +3036,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
Expand Down
10 changes: 10 additions & 0 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3840,6 +3840,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.
Expand Down
32 changes: 32 additions & 0 deletions tests/neg-macros/newClassParamsMissingArgument.check
Original file line number Diff line number Diff line change
@@ -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#<init> : (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) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
---------------------------------------------------------------------------------------------------------------------
24 changes: 24 additions & 0 deletions tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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`
// }
}
5 changes: 5 additions & 0 deletions tests/neg-macros/newClassParamsMissingArgument/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//> using options -experimental

@main def Test: Unit = {
makeClass("foo") // error // error
}
1 change: 1 addition & 0 deletions tests/run-macros/newClassParams.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Foo method call with (10, test)
28 changes: 28 additions & 0 deletions tests/run-macros/newClassParams/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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`)
// }
}

5 changes: 5 additions & 0 deletions tests/run-macros/newClassParams/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//> using options -experimental

@main def Test: Unit = {
makeClassAndCall("bar", 10, "test")
}
4 changes: 4 additions & 0 deletions tests/run-macros/newClassParamsExtendsClassParams.check
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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")
}

10 changes: 10 additions & 0 deletions tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 28cb854

Please sign in to comment.