diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index 339ea20dbae6..006ff0450662 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -1372,6 +1372,8 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend case _ => None } + def ByNameType_apply(underlying: Type)(given Context): Type = Types.ExprType(underlying) + def ByNameType_underlying(self: ByNameType)(given Context): Type = self.resType.stripTypeVar type ParamRef = Types.ParamRef @@ -1437,6 +1439,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def MethodType_isErased(self: MethodType): Boolean = self.isErasedMethod def MethodType_isImplicit(self: MethodType): Boolean = self.isImplicitMethod + def MethodType_param(self: MethodType, idx: Int)(given Context): Type = self.newParamRef(idx) def MethodType_paramNames(self: MethodType)(given Context): List[String] = self.paramNames.map(_.toString) def MethodType_paramTypes(self: MethodType)(given Context): List[Type] = self.paramInfos def MethodType_resType(self: MethodType)(given Context): Type = self.resType @@ -1450,6 +1453,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend case _ => None } + def PolyType_apply(paramNames: List[String])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type)(given Context): PolyType = + Types.PolyType(paramNames.map(_.toTypeName))(paramBoundsExp, resultTypeExp) + + def PolyType_param(self: PolyType, idx: Int)(given Context): Type = self.newParamRef(idx) def PolyType_paramNames(self: PolyType)(given Context): List[String] = self.paramNames.map(_.toString) def PolyType_paramBounds(self: PolyType)(given Context): List[TypeBounds] = self.paramInfos def PolyType_resType(self: PolyType)(given Context): Type = self.resType @@ -1717,6 +1724,11 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Symbol_of(fullName: String)(given ctx: Context): Symbol = ctx.requiredClass(fullName) + def Symbol_newMethod(parent: Symbol, name: String, flags: Flags, tpe: Type, privateWithin: Symbol)(given ctx: Context): Symbol = { + val computedFlags = flags | Flags.Method + ctx.newSymbol(parent, name.toTermName, computedFlags, tpe, privateWithin) + } + def Symbol_isTypeParam(self: Symbol)(given Context): Boolean = self.isTypeParam diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index 4ec673e9754f..7cd5e1fbdc6a 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -986,6 +986,8 @@ trait CompilerInterface { def isInstanceOfByNameType(given ctx: Context): IsInstanceOf[ByNameType] + def ByNameType_apply(underlying: Type)(given ctx: Context): Type + def ByNameType_underlying(self: ByNameType)(given ctx: Context): Type /** Type of a parameter reference */ @@ -1031,6 +1033,7 @@ trait CompilerInterface { def MethodType_isErased(self: MethodType): Boolean def MethodType_isImplicit(self: MethodType): Boolean + def MethodType_param(self: MethodType, ids: Int)(given ctx: Context): Type def MethodType_paramNames(self: MethodType)(given ctx: Context): List[String] def MethodType_paramTypes(self: MethodType)(given ctx: Context): List[Type] def MethodType_resType(self: MethodType)(given ctx: Context): Type @@ -1040,6 +1043,9 @@ trait CompilerInterface { def isInstanceOfPolyType(given ctx: Context): IsInstanceOf[PolyType] + def PolyType_apply(paramNames: List[String])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type)(given ctx: Context): PolyType + + def PolyType_param(self: PolyType, idx: Int)(given ctx: Context): Type def PolyType_paramNames(self: PolyType)(given ctx: Context): List[String] def PolyType_paramBounds(self: PolyType)(given ctx: Context): List[TypeBounds] def PolyType_resType(self: PolyType)(given ctx: Context): Type @@ -1264,6 +1270,8 @@ trait CompilerInterface { def Symbol_of(fullName: String)(given ctx: Context): Symbol + def Symbol_newMethod(parent: Symbol, name: String, flags: Flags, tpe: Type, privateWithin: Symbol)(given ctx: Context): Symbol + def Symbol_isTypeParam(self: Symbol)(given ctx: Context): Boolean def Symbol_isPackageDef(symbol: Symbol)(given ctx: Context): Boolean diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index 7b22c57e9dbd..98eddfc57216 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -9,6 +9,25 @@ trait SymbolOps extends Core { selfSymbolOps: FlagsOps => def classSymbol(fullName: String)(given ctx: Context): Symbol = internal.Symbol_of(fullName) + /** Generates a new method symbol with the given parent, name and type. + * + * 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 DefDef 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. */ + def newMethod(parent: Symbol, name: String, tpe: Type)(given ctx: Context): Symbol = + newMethod(parent, name, tpe, Flags.EmptyFlags, noSymbol) + + /** Works as the other newMethod, but with additional parameters. + * + * @param flags extra flags to with which the symbol should be constructed + * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. + * */ + def newMethod(parent: Symbol, name: String, tpe: Type, flags: Flags, privateWithin: Symbol)(given ctx: Context): Symbol = + internal.Symbol_newMethod(parent, name, flags, tpe, privateWithin) + /** Definition not available */ def noSymbol(given ctx: Context): Symbol = internal.Symbol_noSymbol diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala index dbf9cd39ee57..b8205fa0ed2a 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala @@ -286,6 +286,7 @@ trait TypeOrBoundsOps extends Core { def unapply(x: ByNameType)(given ctx: Context): Option[ByNameType] = Some(x) object ByNameType { + def apply(underlying: Type)(given ctx: Context): Type = internal.ByNameType_apply(underlying) def unapply(x: ByNameType)(given ctx: Context): Option[Type] = Some(x.underlying) } @@ -368,6 +369,7 @@ trait TypeOrBoundsOps extends Core { given MethodTypeOps: extension (self: MethodType) { def isImplicit: Boolean = internal.MethodType_isImplicit(self) def isErased: Boolean = internal.MethodType_isErased(self) + def param(idx: Int)(given ctx: Context): Type = internal.MethodType_param(self, idx) def paramNames(given ctx: Context): List[String] = internal.MethodType_paramNames(self) def paramTypes(given ctx: Context): List[Type] = internal.MethodType_paramTypes(self) def resType(given ctx: Context): Type = internal.MethodType_resType(self) @@ -380,11 +382,14 @@ trait TypeOrBoundsOps extends Core { def unapply(x: PolyType)(given ctx: Context): Option[PolyType] = Some(x) object PolyType { + def apply(paramNames: List[String])(paramBoundsExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type)(given ctx: Context): PolyType = + internal.PolyType_apply(paramNames)(paramBoundsExp, resultTypeExp) def unapply(x: PolyType)(given ctx: Context): Option[(List[String], List[TypeBounds], Type)] = Some((x.paramNames, x.paramBounds, x.resType)) } given PolyTypeOps: extension (self: PolyType) { + def param(idx: Int)(given ctx: Context): Type = internal.PolyType_param(self, idx) def paramNames(given ctx: Context): List[String] = internal.PolyType_paramNames(self) def paramBounds(given ctx: Context): List[TypeBounds] = internal.PolyType_paramBounds(self) def resType(given ctx: Context): Type = internal.PolyType_resType(self) diff --git a/tests/run-macros/tasty-create-method-symbol.check b/tests/run-macros/tasty-create-method-symbol.check new file mode 100644 index 000000000000..a7b45ea3f6ca --- /dev/null +++ b/tests/run-macros/tasty-create-method-symbol.check @@ -0,0 +1,8 @@ +sym6_2: 6 +sym6_1: 5 +sym6_2: 4 +sym6_1: 3 +sym6_2: 2 +sym6_1: 1 +sym6_2: 0 +Ok diff --git a/tests/run-macros/tasty-create-method-symbol/Macro_1.scala b/tests/run-macros/tasty-create-method-symbol/Macro_1.scala new file mode 100644 index 000000000000..8296d95f962b --- /dev/null +++ b/tests/run-macros/tasty-create-method-symbol/Macro_1.scala @@ -0,0 +1,199 @@ +import quoted._ + +object Macros { + + inline def theTestBlock : Unit = ${ theTestBlockImpl } + + def theTestBlockImpl(given qctx: QuoteContext) : Expr[Unit] = { + import qctx.tasty.{_,given} + + // simple smoke test + val sym1 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym1", + MethodType(List("a","b"))( + _ => List(typeOf[Int], typeOf[Int]), + _ => typeOf[Int])) + assert(sym1.isDefDef) + assert(sym1.name == "sym1") + val sym1Statements : List[Statement] = List( + DefDef(sym1, { + case List() => { + case List(List(a, b)) => + Some('{ ${ a.seal.asInstanceOf[Expr[Int]] } - ${ b.seal.asInstanceOf[Expr[Int]] } }.unseal) + } + }), + '{ assert(${ Apply(Ref(sym1), List(Literal(Constant(2)), Literal(Constant(3)))).seal.asInstanceOf[Expr[Int]] } == -1) }.unseal) + + // test for no argument list (no Apply node) + val sym2 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym2", + ByNameType(typeOf[Int])) + assert(sym2.isDefDef) + assert(sym2.name == "sym2") + val sym2Statements : List[Statement] = List( + DefDef(sym2, { + case List() => { + case List() => + Some(Literal(Constant(2))) + } + }), + '{ assert(${ Ref(sym2).seal.asInstanceOf[Expr[Int]] } == 2) }.unseal) + + // test for multiple argument lists + val sym3 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym3", + MethodType(List("a"))( + _ => List(typeOf[Int]), + mt => MethodType(List("b"))( + _ => List(mt.param(0)), + _ => mt.param(0)))) + assert(sym3.isDefDef) + assert(sym3.name == "sym3") + val sym3Statements : List[Statement] = List( + DefDef(sym3, { + case List() => { + case List(List(a), List(b)) => + Some(a) + } + }), + '{ assert(${ Apply(Apply(Ref(sym3), List(Literal(Constant(3)))), List(Literal(Constant(3)))).seal.asInstanceOf[Expr[Int]] } == 3) }.unseal) + + // test for recursive references + val sym4 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym4", + MethodType(List("x"))( + _ => List(typeOf[Int]), + _ => typeOf[Int])) + assert(sym4.isDefDef) + assert(sym4.name == "sym4") + val sym4Statements : List[Statement] = List( + DefDef(sym4, { + case List() => { + case List(List(x)) => + Some('{ + if ${ x.seal.asInstanceOf[Expr[Int]] } == 0 + then 0 + else ${ Apply(Ref(sym4), List('{ ${ x.seal.asInstanceOf[Expr[Int]] } - 1 }.unseal)).seal.asInstanceOf[Expr[Int]] } + }.unseal) + } + }), + '{ assert(${ Apply(Ref(sym4), List(Literal(Constant(4)))).seal.asInstanceOf[Expr[Int]] } == 0) }.unseal) + + // test for nested functions (one symbol is the other's parent, and we use a Closure) + val sym5 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym5", + MethodType(List("x"))( + _ => List(typeOf[Int]), + _ => typeOf[Int=>Int])) + assert(sym5.isDefDef) + assert(sym5.name == "sym5") + val sym5Statements : List[Statement] = List( + DefDef(sym5, { + case List() => { + case List(List(x)) => + Some { + val sym51 : Symbol = Symbol.newMethod( + sym5, + "sym51", + MethodType(List("x"))( + _ => List(typeOf[Int]), + _ => typeOf[Int])) + Block( + List( + DefDef(sym51, { + case List() => { + case List(List(xx)) => + Some('{ ${ x.seal.asInstanceOf[Expr[Int]] } - ${ xx.seal.asInstanceOf[Expr[Int]] } }.unseal) + } + })), + Closure(Ref(sym51), None)) + } + } + }), + '{ assert(${ Apply(Ref(sym5), List(Literal(Constant(5)))).seal.asInstanceOf[Expr[Int=>Int]] }(4) == 1) }.unseal) + + // test mutually recursive definitions + val sym6_1 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym6_1", + MethodType(List("x"))( + _ => List(typeOf[Int]), + _ => typeOf[Int])) + val sym6_2 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym6_2", + MethodType(List("x"))( + _ => List(typeOf[Int]), + _ => typeOf[Int])) + assert(sym6_1.isDefDef) + assert(sym6_2.isDefDef) + assert(sym6_1.name == "sym6_1") + assert(sym6_2.name == "sym6_2") + val sym6Statements : List[Statement] = List( + DefDef(sym6_1, { + case List() => { + case List(List(x)) => + Some { + '{ + println(s"sym6_1: ${ ${ x.seal.asInstanceOf[Expr[Int]] } }") + if ${ x.seal.asInstanceOf[Expr[Int]] } == 0 + then 0 + else ${ Apply(Ref(sym6_2), List('{ ${ x.seal.asInstanceOf[Expr[Int]] } - 1 }.unseal)).seal.asInstanceOf[Expr[Int]] } + }.unseal + } + } + }), + DefDef(sym6_2, { + case List() => { + case List(List(x)) => + Some { + '{ + println(s"sym6_2: ${ ${ x.seal.asInstanceOf[Expr[Int]] } }") + if ${ x.seal.asInstanceOf[Expr[Int]] } == 0 + then 0 + else ${ Apply(Ref(sym6_1), List('{ ${ x.seal.asInstanceOf[Expr[Int]] } - 1 }.unseal)).seal.asInstanceOf[Expr[Int]] } + }.unseal + } + } + + }), + '{ assert(${ Apply(Ref(sym6_2), List(Literal(Constant(6)))).seal.asInstanceOf[Expr[Int]] } == 0) }.unseal) + + // test polymorphic methods by synthesizing an identity method + val sym7 : Symbol = Symbol.newMethod( + rootContext.owner, + "sym7", + PolyType(List("T"))( + tp => List(TypeBounds(typeOf[Nothing], typeOf[Any])), + tp => MethodType(List("t"))( + _ => List(tp.param(0)), + _ => tp.param(0)))) + assert(sym7.isDefDef) + assert(sym7.name == "sym7") + val sym7Statements : List[Statement] = List( + DefDef(sym7, { + case List(t) => { + case List(List(x)) => + Some(Typed(x, Inferred(t))) + } + }), + '{ assert(${ Apply(TypeApply(Ref(sym7), List(Inferred(typeOf[Int]))), List(Literal(Constant(7)))).seal.asInstanceOf[Expr[Int]] } == 7) }.unseal) + + Block( + sym1Statements ++ + sym2Statements ++ + sym3Statements ++ + sym4Statements ++ + sym5Statements ++ + sym6Statements ++ + sym7Statements ++ + List('{ println("Ok") }.unseal), + Literal(Constant(()))).seal.asInstanceOf[Expr[Unit]] + } +} + diff --git a/tests/run-macros/tasty-create-method-symbol/Test_2.scala b/tests/run-macros/tasty-create-method-symbol/Test_2.scala new file mode 100644 index 000000000000..63e15a06e189 --- /dev/null +++ b/tests/run-macros/tasty-create-method-symbol/Test_2.scala @@ -0,0 +1,6 @@ + +object Test { + def main(argv: Array[String]): Unit = + Macros.theTestBlock +} +