Skip to content

Allow macros to generate method symbols, add missing method type constructors #8090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions library/src/scala/tasty/reflect/CompilerInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions library/src/scala/tasty/reflect/SymbolOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions library/src/scala/tasty/reflect/TypeOrBoundsOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions tests/run-macros/tasty-create-method-symbol.check
Original file line number Diff line number Diff line change
@@ -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
199 changes: 199 additions & 0 deletions tests/run-macros/tasty-create-method-symbol/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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]]
}
}

6 changes: 6 additions & 0 deletions tests/run-macros/tasty-create-method-symbol/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

object Test {
def main(argv: Array[String]): Unit =
Macros.theTestBlock
}