From c5b986ec1bb3c88d4a21710ffe3389bbe7bfcedc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 4 Nov 2018 19:51:08 +0000 Subject: [PATCH 1/2] Port gestalt TypeToolbox --- .../dotc/tastyreflect/SymbolOpsImpl.scala | 55 ++++++ .../TypeOrBoundsTreesOpsImpl.scala | 4 + .../src/scala/tasty/reflect/SymbolOps.scala | 33 ++++ .../tasty/reflect/TypeOrBoundsTreeOps.scala | 1 + .../Macro_1.scala | 119 ++++++++++++ .../gestalt-type-toolbox-reflect/Test_2.scala | 174 ++++++++++++++++++ 6 files changed, 386 insertions(+) create mode 100644 tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala create mode 100644 tests/run-separate-compilation/gestalt-type-toolbox-reflect/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala index 61c6c92eef6c..055fd789da89 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala @@ -3,6 +3,7 @@ package tastyreflect import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Decorators._ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { @@ -93,6 +94,10 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def tree(implicit ctx: Context): TypeDef = FromSymbol.typeDefFromSym(symbol) } + object ClassSymbol extends ClassSymbolModule { + def of(fullName: String)(implicit ctx: Context): ClassSymbol = ctx.requiredClass(fullName) + } + object IsClassSymbol extends IsClassSymbolExtractor { def unapply(symbol: Symbol)(implicit ctx: Context): Option[ClassSymbol] = if (symbol.isClass) Some(symbol.asClass) else None @@ -100,6 +105,56 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def ClassSymbolDeco(symbol: ClassSymbol): ClassSymbolAPI = new ClassSymbolAPI { def tree(implicit ctx: Context): ClassDef = FromSymbol.classDef(symbol) + + def fields(implicit ctx: Context): List[Symbol] = { + symbol.unforcedDecls.filter(isField) + } + + def field(name: String)(implicit ctx: Context): Option[Symbol] = { + val sym = symbol.unforcedDecls.find(sym => sym.name == name.toTermName) + if (sym.exists && isField(sym)) Some(sym) else None + } + + def classMethod(name: String)(implicit ctx: Context): List[DefSymbol] = { + symbol.typeRef.decls.iterator.collect { + case sym if isMethod(sym) && sym.name.toString == name => sym.asTerm + }.toList + } + + def classMethods(implicit ctx: Context): List[DefSymbol] = { + symbol.typeRef.decls.iterator.collect { + case sym if isMethod(sym) => sym.asTerm + }.toList + } + + def method(name: String)(implicit ctx: Context): List[DefSymbol] = { + symbol.typeRef.allMembers.iterator.map(_.symbol).collect { + case sym if isMethod(sym) && sym.name.toString == name => sym.asTerm + }.toList + } + + def methods(implicit ctx: Context): List[DefSymbol] = { + symbol.typeRef.allMembers.iterator.map(_.symbol).collect { + case sym if isMethod(sym) => sym.asTerm + }.toList + } + + private def isMethod(sym: Symbol)(implicit ctx: Context): Boolean = + sym.isTerm && sym.is(Flags.Method) && !sym.isConstructor + + def caseFields(implicit ctx: Context): List[ValSymbol] = { + if (!symbol.isClass) Nil + else symbol.asClass.paramAccessors.collect { + case sym if sym.is(Flags.CaseAccessor) => sym.asTerm + } + } + + def companionClass(implicit ctx: Context): Option[ClassSymbol] = { + val sym = symbol.companionModule.companionClass + if (sym.exists) Some(sym.asClass) else None + } + + private def isField(sym: Symbol)(implicit ctx: Context): Boolean = sym.isTerm && !sym.is(Flags.Method) } object IsDefSymbol extends IsDefSymbolExtractor { diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala index 0302ddc3e566..f61740892c60 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala @@ -19,6 +19,10 @@ trait TypeOrBoundsTreesOpsImpl extends scala.tasty.reflect.TypeOrBoundsTreeOps w def TypeTreeDeco(tpt: TypeTree): TypeTreeAPI = new TypeTreeAPI { def pos(implicit ctx: Context): Position = tpt.pos def tpe(implicit ctx: Context): Type = tpt.tpe.stripTypeVar + def symbol(implicit ctx: Context): Symbol = { + val sym = tpt.symbol + if (sym.isType) sym else sym.companionClass + } } object IsTypeTree extends IsTypeTreeExtractor { diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index 6cca7a17a208..38fc6b2d1a9b 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -15,7 +15,10 @@ trait SymbolOps extends Core { def privateWithin(implicit ctx: Context): Option[Type] def protectedWithin(implicit ctx: Context): Option[Type] + /** Name of the definition */ def name(implicit ctx: Context): String + + /** Full name of the definition from the _root_ package */ def fullName(implicit ctx: Context): String def localContext(implicit ctx: Context): Context @@ -51,8 +54,38 @@ trait SymbolOps extends Core { def unapply(symbol: Symbol)(implicit ctx: Context): Option[ClassSymbol] } + val ClassSymbol: ClassSymbolModule + abstract class ClassSymbolModule { + /** The ClassSymbol of a global class definition */ + def of(fullName: String)(implicit ctx: Context): ClassSymbol + } + trait ClassSymbolAPI { + /** Tree of this class definition */ def tree(implicit ctx: Context): ClassDef + + /** Fields directly declared in the class */ + def fields(implicit ctx: Context): List[Symbol] + + /** Field with the given name directly declared in the class */ + def field(name: String)(implicit ctx: Context): Option[Symbol] + + /** Get non-private named methods defined directly inside the class */ + def classMethod(name: String)(implicit ctx: Context): List[DefSymbol] + + /** Get all non-private methods defined directly inside the class, exluding constructors */ + def classMethods(implicit ctx: Context): List[DefSymbol] + + /** Get named non-private methods declared or inherited */ + def method(name: String)(implicit ctx: Context): List[DefSymbol] + + /** Get all non-private methods declared or inherited */ + def methods(implicit ctx: Context): List[DefSymbol] + + /** Fields of a case class type -- only the ones declared in primary constructor */ + def caseFields(implicit ctx: Context): List[ValSymbol] + + def companionClass(implicit ctx: Context): Option[ClassSymbol] } implicit def ClassSymbolDeco(symbol: ClassSymbol): ClassSymbolAPI diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala index 6851f455d38e..42d092c30b6f 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala @@ -13,6 +13,7 @@ trait TypeOrBoundsTreeOps extends Core { trait TypeTreeAPI { def pos(implicit ctx: Context): Position def tpe(implicit ctx: Context): Type + def symbol(implicit ctx: Context): Symbol } implicit def TypeTreeDeco(tpt: TypeTree): TypeTreeAPI diff --git a/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala new file mode 100644 index 000000000000..807b110e48c6 --- /dev/null +++ b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala @@ -0,0 +1,119 @@ +// Port of https://github.com/liufengyun/gestalt/blob/master/macros/src/main/scala/gestalt/macros/TypeToolbox.scala +// using staging reflection + +import scala.quoted._ +import scala.tasty._ + +object TypeToolbox { + /** are the two types equal? */ + inline def =:=[A, B]: Boolean = ~tpEqImpl('[A], '[B]) + private def tpEqImpl[A, B](a: Type[A], b: Type[B])(implicit reflect: Reflection): Expr[Boolean] = { + import reflect._ + val res = a.reflect.tpe =:= b.reflect.tpe + res.toExpr + } + + /** is `tp1` a subtype of `tp2` */ + inline def <:<[A, B]: Boolean = ~tpLEqImpl('[A], '[B]) + private def tpLEqImpl[A, B](a: Type[A], b: Type[B])(implicit reflect: Reflection): Expr[Boolean] = { + import reflect._ + val res = a.reflect.tpe <:< b.reflect.tpe + res.toExpr + } + + /** type associated with the tree */ + inline def typeOf[T, Expected](a: T): Boolean = ~typeOfImpl('(a), '[Expected]) + private def typeOfImpl(a: Expr[_], expected: Type[_])(implicit reflect: Reflection): Expr[Boolean] = { + import reflect._ + val res = a.reflect.tpe =:= expected.reflect.tpe + res.toExpr + } + + /** does the type refer to a case class? */ + inline def isCaseClass[A]: Boolean = ~isCaseClassImpl('[A]) + private def isCaseClassImpl(tp: Type[_])(implicit reflect: Reflection): Expr[Boolean] = { + import reflect._ + val res = tp.reflect.symbol match { + case IsClassSymbol(sym) => sym.flags.isCase + case _ => false + } + res.toExpr + } + + /** val fields of a case class Type -- only the ones declared in primary constructor */ + inline def caseFields[T]: List[String] = ~caseFieldsImpl('[T]) + private def caseFieldsImpl(tp: Type[_])(implicit reflect: Reflection): Expr[List[String]] = { + import reflect._ + val fields = tp.reflect.symbol.asClass.caseFields.map(_.name) + fields.toExpr + } + + inline def fieldIn[T](inline mem: String): String = ~fieldInImpl('[T], mem) + private def fieldInImpl(t: Type[_], mem: String)(implicit reflect: Reflection): Expr[String] = { + import reflect._ + val field = t.reflect.symbol.asClass.field(mem) + field.map(_.name).getOrElse("").toExpr + } + + inline def fieldsIn[T]: Seq[String] = ~fieldsInImpl('[T]) + private def fieldsInImpl(t: Type[_])(implicit reflect: Reflection): Expr[Seq[String]] = { + import reflect._ + val fields = t.reflect.symbol.asClass.fields + fields.map(_.name).toList.toExpr + } + + inline def methodIn[T](inline mem: String): Seq[String] = ~methodInImpl('[T], mem) + private def methodInImpl(t: Type[_], mem: String)(implicit reflect: Reflection): Expr[Seq[String]] = { + import reflect._ + t.reflect.symbol.asClass.classMethod(mem).map(_.name).toExpr + } + + inline def methodsIn[T]: Seq[String] = ~methodsInImpl('[T]) + private def methodsInImpl(t: Type[_])(implicit reflect: Reflection): Expr[Seq[String]] = { + import reflect._ + t.reflect.symbol.asClass.classMethods.map(_.name).toExpr + } + + inline def method[T](inline mem: String): Seq[String] = ~methodImpl('[T], mem) + private def methodImpl(t: Type[_], mem: String)(implicit reflect: Reflection): Expr[Seq[String]] = { + import reflect._ + t.reflect.symbol.asClass.method(mem).map(_.name).toExpr + } + + inline def methods[T]: Seq[String] = ~methodsImpl('[T]) + private def methodsImpl(t: Type[_])(implicit reflect: Reflection): Expr[Seq[String]] = { + import reflect._ + t.reflect.symbol.asClass.methods.map(_.name).toExpr + } + + inline def typeTag[T](x: T): String = ~typeTagImpl('[T]) + private def typeTagImpl(tp: Type[_])(implicit reflect: Reflection): Expr[String] = { + import reflect._ + val res = tp.reflect.tpe.showCode + res.toExpr + } + + inline def companion[T1, T2]: Boolean = ~companionImpl('[T1], '[T2]) + private def companionImpl(t1: Type[_], t2: Type[_])(implicit reflect: Reflection): Expr[Boolean] = { + import reflect._ + val res = t1.reflect.symbol.asClass.companionClass.contains(t2.reflect.symbol) + res.toExpr + } + + inline def companionName[T1]: String = ~companionNameImpl('[T1]) + private def companionNameImpl(tp: Type[_])(implicit reflect: Reflection): Expr[String] = { + import reflect._ + tp.reflect.symbol match { + case IsClassSymbol(sym) => sym.companionClass.map(_.fullName).getOrElse("").toExpr + case _ => '("") + } + } + + // TODO add to the std lib + private implicit def listIsLiftable[T: Type: Liftable]: Liftable[List[T]] = new Liftable { + def toExpr(list: List[T]): Expr[List[T]] = list match { + case x :: xs => '(~x.toExpr :: ~toExpr(xs)) + case Nil => '(Nil) + } + } +} diff --git a/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Test_2.scala b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Test_2.scala new file mode 100644 index 000000000000..aee787026958 --- /dev/null +++ b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Test_2.scala @@ -0,0 +1,174 @@ + +object Test { + import TypeToolbox._ + + type Age = Int + + def main(args: Array[String]): Unit = { + + def test(name: String)(tests: => Unit): Unit = { + println("Testing " + name) + tests + } + + test("=:=") { + assert(=:=[Nil.type, Nil.type]) + assert(=:=[Int, Int]) + assert(! =:=[Int, String]) + assert(=:=[Int, Age]) + } + + test("<:<") { + assert(<:<[Int, Int]) + assert(<:<[Age, Int]) + assert(<:<[None.type, Option[Int]]) + assert(<:<[Nil.type, List[Int]]) + assert(! <:<[Int, String]) + + val a = 5 + assert(<:<[3, Int]) + assert(<:<[a.type, Int]) + } + + // TODO + // test("typeRef") { + // assert(typeRef[String]("java.lang.String")) + // assert(typeRef[String]("scala.String")) + // assert(typeRef[Int]("scala.Int")) + // assert(typeRef[Boolean]("scala.Boolean")) + // } + + // TODO + // test("termRef") { + // assert(termRef[None.type]("scala.None")) + // assert(termRef[Nil.type]("scala.Nil")) + // } + + test("isCaseClass") { + case class Student(name: String, age: Int) + class Teacher(name: String, age: Int) + trait Staff + assert(isCaseClass[Student]) + assert(!isCaseClass[Teacher]) + assert(!isCaseClass[Staff]) + + } + + test("caseFields") { + case class Student(name: String, age: Int) + case class Teacher(name: String, age: Int) { + val school: String = "EPFL" + var salary: Int = 20000 + } + + assert(caseFields[Student] == List("name", "age")) + assert(caseFields[Teacher] == List("name", "age")) + } + + // TODO + // test("asSeenFrom") { + // case class M[T](x: T) + // val a = new M(4) + + // assert(fieldType[a.type, Int]("x")) + + + // trait Base { + // val x: InBase + // trait InBase + // } + + // class Child extends Base { + // val x: Inner = new Inner + // class Inner extends InBase + // } + + // val m = new Child + // assert(fieldType[m.type, m.Inner]("x")) + + // trait Box { + // type T + // val x: T + // } + // class InBox extends Box { + // type T = Int + // val x = 3 + // } + // val box = new InBox + // assert(fieldType[box.type, Int]("x")) + // } + + test("fields") { + trait Base { + val x = 3 + + def f = 4 + } + + class Derived extends Base { + val y = 3 + + def g = 3 + } + + assert(fieldIn[Base]("x") == "x") + assert(fieldIn[Derived]("x") == "") + assert(fieldIn[Derived]("y") == "y") + assert(fieldsIn[Base] == List("x")) + assert(fieldsIn[Derived] == List("y")) + } + + test("methods") { + trait Base { + val x = 3 + + def f = 4 + } + + class Derived extends Base { + val y = 3 + + def g = 3 + } + + assert(method[Base]("f") == List("f")) + assert(method[Base]("x") == Nil) + assert(methodIn[Base]("f") == List("f")) + assert(methodIn[Base]("x") == Nil) + assert(methodsIn[Base] == List("f")) + + assert(method[Derived]("f") == List("f")) + assert(method[Derived]("g") == List("g")) + assert(method[Derived]("y") == Nil) + assert(methodIn[Derived]("f") == Nil) + assert(methodIn[Derived]("g") == List("g")) + assert(methodIn[Derived]("x") == Nil) + assert(methodsIn[Derived] == List("g")) + + class Overloading { + def f(a: Int): Int = ??? + def f(a: String): Int = ??? + } + + assert(methodsIn[Overloading] == List("f", "f")) + assert(methodIn[Overloading]("f") == List("f", "f")) + } + + test("typeTag") { + assert(typeTag(3) == "scala.Int") + assert(typeTag(Some(4)) == "scala.Some[scala.Int]", typeTag(Some(4))) + } + + test("companion") { + class A + object A + class B + object C + assert(companion[A, A.type]) + assert(companionName[A] == "Test$._$A") + assert(companionName[A.type] == "Test$._$A") + assert(companionName[B] == "", companionName[B]) + assert(companionName[C.type] == "", companionName[C.type]) + } + } +} From 906ad2a1bb5fb03eb47d1cea8ce214849b38bb25 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 12 Nov 2018 13:05:17 +0100 Subject: [PATCH 2/2] Fix module class logic (now works as in compiler) --- .../dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala | 10 ++++++++++ .../dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala | 5 +---- library/src/scala/tasty/reflect/SymbolOps.scala | 9 +++++++++ .../gestalt-type-toolbox-reflect/Macro_1.scala | 10 ++++++---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala index 055fd789da89..b0be74120ce9 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala @@ -154,6 +154,11 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { if (sym.exists) Some(sym.asClass) else None } + def companionModule(implicit ctx: Context): Option[ValSymbol] = { + val sym = symbol.companionModule + if (sym.exists) Some(sym.asTerm) else None + } + private def isField(sym: Symbol)(implicit ctx: Context): Boolean = sym.isTerm && !sym.is(Flags.Method) } @@ -173,6 +178,11 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def ValSymbolDeco(symbol: ValSymbol): ValSymbolAPI = new ValSymbolAPI { def tree(implicit ctx: Context): ValDef = FromSymbol.valDefFromSym(symbol) + + def companionClass(implicit ctx: Context): Option[ClassSymbol] = { + val sym = symbol.companionClass + if (sym.exists) Some(sym.asClass) else None + } } object IsBindSymbol extends IsBindSymbolExtractor { diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala index f61740892c60..2bebdd84e06b 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala @@ -19,10 +19,7 @@ trait TypeOrBoundsTreesOpsImpl extends scala.tasty.reflect.TypeOrBoundsTreeOps w def TypeTreeDeco(tpt: TypeTree): TypeTreeAPI = new TypeTreeAPI { def pos(implicit ctx: Context): Position = tpt.pos def tpe(implicit ctx: Context): Type = tpt.tpe.stripTypeVar - def symbol(implicit ctx: Context): Symbol = { - val sym = tpt.symbol - if (sym.isType) sym else sym.companionClass - } + def symbol(implicit ctx: Context): Symbol = tpt.symbol } object IsTypeTree extends IsTypeTreeExtractor { diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index 38fc6b2d1a9b..163a62199c10 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -85,7 +85,12 @@ trait SymbolOps extends Core { /** Fields of a case class type -- only the ones declared in primary constructor */ def caseFields(implicit ctx: Context): List[ValSymbol] + /** The class symbol of the companion module class */ def companionClass(implicit ctx: Context): Option[ClassSymbol] + + /** The symbol of the companion module */ + def companionModule(implicit ctx: Context): Option[ValSymbol] + } implicit def ClassSymbolDeco(symbol: ClassSymbol): ClassSymbolAPI @@ -122,6 +127,10 @@ trait SymbolOps extends Core { trait ValSymbolAPI { def tree(implicit ctx: Context): ValDef + + /** The class symbol of the companion module class */ + def companionClass(implicit ctx: Context): Option[ClassSymbol] + } implicit def ValSymbolDeco(symbol: ValSymbol): ValSymbolAPI diff --git a/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala index 807b110e48c6..f186dcef5d12 100644 --- a/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala +++ b/tests/run-separate-compilation/gestalt-type-toolbox-reflect/Macro_1.scala @@ -96,17 +96,19 @@ object TypeToolbox { inline def companion[T1, T2]: Boolean = ~companionImpl('[T1], '[T2]) private def companionImpl(t1: Type[_], t2: Type[_])(implicit reflect: Reflection): Expr[Boolean] = { import reflect._ - val res = t1.reflect.symbol.asClass.companionClass.contains(t2.reflect.symbol) + val res = t1.reflect.symbol.asClass.companionModule.contains(t2.reflect.symbol) res.toExpr } inline def companionName[T1]: String = ~companionNameImpl('[T1]) private def companionNameImpl(tp: Type[_])(implicit reflect: Reflection): Expr[String] = { import reflect._ - tp.reflect.symbol match { - case IsClassSymbol(sym) => sym.companionClass.map(_.fullName).getOrElse("").toExpr - case _ => '("") + val companionClassOpt = tp.reflect.symbol match { + case IsClassSymbol(sym) => sym.companionClass + case IsValSymbol(sym) => sym.companionClass + case _ => None } + companionClassOpt.map(_.fullName).getOrElse("").toExpr } // TODO add to the std lib