-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add reflect Symbol.info and ClassInfo #11664
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1695,6 +1695,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |
dotc.core.Types.decorateTypeApplications(self).appliedTo(targ) | ||
def appliedTo(targs: List[TypeRepr]): TypeRepr = | ||
dotc.core.Types.decorateTypeApplications(self).appliedTo(targs) | ||
def asSeenFrom(pre: TypeRepr, clazz: Symbol): TypeRepr = | ||
self.asSeenFrom(pre, clazz) | ||
end extension | ||
end TypeReprMethods | ||
|
||
|
@@ -2130,6 +2132,32 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |
end extension | ||
end MatchCaseMethods | ||
|
||
type ClassInfo = dotc.core.Types.ClassInfo | ||
|
||
given ClassInfoTypeTest: TypeTest[TypeRepr, ClassInfo] with | ||
def unapply(x: TypeRepr): Option[ClassInfo & x.type] = x match | ||
case x: (Types.ClassInfo & x.type) => Some(x) | ||
case _ => None | ||
end ClassInfoTypeTest | ||
|
||
object ClassInfo extends ClassInfoModule: | ||
def unapply(x: ClassInfo): (TypeRepr, Symbol, List[TypeRepr], List[Symbol], Option[TypeRepr]) = | ||
(x.prefix, x.cls, x.declaredParents, ClassInfoMethods.decls(x), ClassInfoMethods.selfInfo(x)) | ||
end ClassInfo | ||
|
||
given ClassInfoMethods: ClassInfoMethods with | ||
extension (self: ClassInfo) | ||
def qualifier: TypeRepr = self.prefix | ||
def decls: List[Symbol] = self.decls.toList | ||
def declaredParents: List[TypeRepr] = self.declaredParents | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't expose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then we would have to always go to the tree which is the thing we are trying to avoid. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, we should expose Type#parents which doesn't involve trees. |
||
def selfInfo: Option[TypeRepr] = | ||
self.selfInfo match | ||
case dotc.core.Types.NoType => None | ||
case info: dotc.core.Types.Type => Some(info) | ||
case sym: dotc.core.Symbols.Symbol => Some(sym.typeRef) | ||
end extension | ||
end ClassInfoMethods | ||
|
||
type TypeBounds = dotc.core.Types.TypeBounds | ||
|
||
object TypeBoundsTypeTest extends TypeTest[TypeRepr, TypeBounds]: | ||
|
@@ -2408,6 +2436,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |
|
||
given SymbolMethods: SymbolMethods with | ||
extension (self: Symbol) | ||
def info: TypeRepr = self.denot.info | ||
def owner: Symbol = self.denot.owner | ||
def maybeOwner: Symbol = self.denot.maybeOwner | ||
def flags: Flags = self.denot.flags | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -214,7 +214,7 @@ object Extractors { | |||||
case ByNameType(underlying) => | ||||||
this += "ByNameType(" += underlying += ")" | ||||||
case ParamRef(binder, idx) => | ||||||
this += "ParamRef(" += binder += ", " += idx += ")" | ||||||
this += "ParamRef(binder, " += idx += ")" | ||||||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
case ThisType(tp) => | ||||||
this += "ThisType(" += tp += ")" | ||||||
case SuperType(thistpe, supertpe) => | ||||||
|
@@ -228,8 +228,9 @@ object Extractors { | |||||
case PolyType(argNames, argBounds, resType) => | ||||||
this += "PolyType(" ++= argNames += ", " ++= argBounds += ", " += resType += ")" | ||||||
case TypeLambda(argNames, argBounds, resType) => | ||||||
// resType is not printed to avoid cycles | ||||||
this += "TypeLambda(" ++= argNames += ", " ++= argBounds += ", _)" | ||||||
this += "TypeLambda(" ++= argNames += ", " ++= argBounds += ", " += resType += ")" | ||||||
case ClassInfo(prefix, cls, parents, decls, selfInfo) => | ||||||
this += "ClassInfo(" += prefix += ", cls/*" += cls.fullName += "*/, " ++= parents += ", decls, " += selfInfo += ")" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we want to generate something like |
||||||
case TypeBounds(lo, hi) => | ||||||
this += "TypeBounds(" += lo += ", " += hi += ")" | ||||||
case NoPrefix() => | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -183,6 +183,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
* | | +- PolyType | ||
* | +- TypeLambda | ||
* +- MatchCase | ||
* +- ClassInfo | ||
* +- TypeBounds | ||
* +- NoPrefix | ||
* | ||
|
@@ -2447,6 +2448,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
/** The current type applied to given type arguments: `this[targ0, ..., targN]` */ | ||
def appliedTo(targs: List[TypeRepr]): TypeRepr | ||
|
||
/** This type as seen from prefix `pre` and class `clazz`. This means: | ||
* Replace all thistypes of `clazz` or one of its subclasses | ||
* by `pre` and instantiate all parameters by arguments of `pre`. | ||
* Proceed analogously for thistypes referring to outer classes. | ||
* | ||
* Example: | ||
* class D[T] { def m: T } | ||
* class C extends p.D[Int] | ||
* T.asSeenFrom(ThisType(C), D) (where D is owner of m) | ||
* = Int | ||
*/ | ||
def asSeenFrom(pre: TypeRepr, clazz: Symbol): TypeRepr | ||
|
||
end extension | ||
} | ||
|
||
|
@@ -2994,6 +3008,40 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
end extension | ||
end MatchCaseMethods | ||
|
||
/** Type of the definition of a type lambda taking a list of type parameters. It's return type may be a TypeLambda. */ | ||
type ClassInfo <: TypeRepr | ||
|
||
/** `TypeTest` that allows testing at runtime in a pattern match if a `TypeRepr` is a `TypeLambda` */ | ||
given ClassInfoTypeTest: TypeTest[TypeRepr, ClassInfo] | ||
|
||
/** Module object of `type ClassInfo` */ | ||
val ClassInfo: ClassInfoModule | ||
|
||
/** Methods of the module object `val ClassInfo` */ | ||
trait ClassInfoModule { this: ClassInfo.type => | ||
def unapply(x: ClassInfo): (TypeRepr, Symbol, List[TypeRepr], List[Symbol], Option[TypeRepr]) | ||
} | ||
|
||
/** Makes extension methods on `ClassInfo` available without any imports */ | ||
given ClassInfoMethods: ClassInfoMethods | ||
|
||
/** Extension methods of `ClassInfo` */ | ||
trait ClassInfoMethods: | ||
extension (self: ClassInfo) | ||
/** The qualifier on which parents, decls, and selfType need to be rebased. */ | ||
def qualifier: TypeRepr | ||
/** The symbols defined directly in this class. */ | ||
def decls: List[Symbol] | ||
/** The parent types of this class. | ||
* These are all normalized to be TypeRefs by moving any refinements | ||
* to be member definitions of the class itself. | ||
* Unlike `parents`, the types are not seen as seen from `prefix`. | ||
*/ | ||
def declaredParents: List[TypeRepr] | ||
/** The type of `this` in this class, if explicitly given, None otherwise. */ | ||
def selfInfo: Option[TypeRepr] | ||
end extension | ||
end ClassInfoMethods | ||
|
||
// ----- TypeBounds ----------------------------------------------- | ||
|
||
|
@@ -3437,6 +3485,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
trait SymbolMethods { | ||
extension (self: Symbol) | ||
|
||
/** TypeRepr of the definitions of this symbol */ | ||
def info: TypeRepr | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we expose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a document here saying
What about a method like |
||
/** Owner of this symbol. The owner is the symbol in which this symbol is defined. Throws if this symbol does not have an owner. */ | ||
def owner: Symbol | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
a: | ||
scala.Int | ||
TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int") | ||
b: | ||
scala.Int | ||
TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int") | ||
c: | ||
=> scala.Int | ||
ByNameType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")) | ||
f1: | ||
()scala.Int | ||
MethodType(Nil, Nil, TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")) | ||
f2: | ||
(i: scala.Int)scala.Int | ||
MethodType(List(i), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")) | ||
f3: | ||
(i: scala.Int)(j: scala.Int)scala.Int | ||
MethodType(List(i), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")), MethodType(List(j), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int"))) | ||
f4: | ||
[T >: scala.Nothing <: scala.Any] => (x: T)scala.Int | ||
PolyType(List(T), List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))), MethodType(List(x), List(ParamRef(binder, 0)), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int"))) | ||
f5: | ||
(i: scala.Int)(x$2: scala.Int)scala.Int | ||
MethodType(List(i), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")), MethodType(List(x$2), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int"))) | ||
T1: | ||
_ >: scala.Nothing <: scala.Any | ||
TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any")) | ||
T2: | ||
_ >: scala.Int <: scala.Int | ||
TypeBounds(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")) | ||
T3: | ||
_ >: scala.Nothing <: scala.Int | ||
TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"), "Int")) | ||
T4: | ||
_ >: scala.Nothing <: [X >: scala.Nothing <: scala.Any] => scala.Any | ||
TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeLambda(List(X), List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))) | ||
A: | ||
A | ||
TypeRef(NoPrefix(), "A$") | ||
A$: | ||
class Test$._$_$A$ extends java.lang.Object { _: A => } | ||
ClassInfo(NoPrefix(), cls/*Test$._$_$A$*/, List(TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Object")), decls, Some(TermRef(NoPrefix(), "A"))) | ||
A: | ||
trait Test$._$_$A extends java.lang.Object | ||
ClassInfo(NoPrefix(), cls/*Test$._$_$A*/, List(TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Object")), decls, None) | ||
B: | ||
class Test$._$_$B extends java.lang.Object with A | ||
ClassInfo(NoPrefix(), cls/*Test$._$_$B*/, List(TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Object"), TypeRef(NoPrefix(), "A")), decls, None) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import quoted.* | ||
|
||
inline def printInfos(inline x: Any): Unit = ${ infoExpr('x) } | ||
|
||
private def infoExpr(x: Expr[Any])(using Quotes): Expr[Unit] = { | ||
import quotes.reflect.* | ||
val sb = StringBuilder() | ||
new TreeTraverser { | ||
override def traverseTree(tree: Tree)(owner: Symbol): Unit = | ||
tree match | ||
case tree: Definition => | ||
sb.append( | ||
s"""${tree.name}: | ||
| ${tree.symbol.info.show} | ||
| ${tree.symbol.info.show(using Printer.TypeReprStructure)} | ||
|""".stripMargin) | ||
case _ => | ||
super.traverseTree(tree)(owner) | ||
}.traverseTree(x.asTerm)(Symbol.spliceOwner) | ||
val infos = Expr(sb.toString) | ||
'{ println($infos) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
object Test { | ||
def main(args : Array[String]) : Unit = | ||
printInfos { | ||
val a: Int = 1 | ||
var b: Int = 1 | ||
def c: Int = 1 | ||
def f1(): Int = 1 | ||
def f2(i: Int): Int = 1 | ||
def f3(i: Int)(j: Int): Int = 1 | ||
def f4[T](x: T): Int = 1 | ||
def f5(i: Int)(using Int): Int = 1 | ||
type T1 | ||
type T2 = Int | ||
type T3 <: Int | ||
type T4[X] | ||
object A | ||
trait A | ||
class B[X] extends A | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already expose
memberInfo
which calls asSeenFrom, in what situation would you need the raw asSeenFrom?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@liufengyun ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we expose
symbol.info
, withoutasSeenFrom
, the info cannot be safely used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
symbol.info is the info of the symbol itself, I don't see what's unsafe about it, and if you want to see the info from some specific prefix you can do
prefix.memberType(symbol)
. Are there situations where you need to useasSeenFrom
directly instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concern is that users use
symbol.info
without rebasing, resulting in error-prone macros. I see that we already haveTypeEpr.memberType
, thus we don't needasSeenFrom
. However, it is good to add the following doc toSymbol.info
: