-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Implement Reflection API directly #9818
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
Implement Reflection API directly #9818
Conversation
34e1f18
to
bf2ac88
Compare
There are further improvements possible on the definitions and implementation of the |
bf2ac88
to
d2400b4
Compare
Revert to the original design with abstract modules that are implemented directly. We had to move the implementations to the compiler interface with one of the iterations of extension methods. The current extension methods are again compatible with this design. CompilerInterface is kept for the internal methods that are only available in the stdlib. * Now Reflection contains the full user-facing API * CompilerInterface only contains internal methods used in the stdlib * tasty.refelct.Types was merged into Reflection * QuoteContextImpl implements every member directly * All members of tasty are lazily initialized using object * Fix defn.NullClass * Fix typo in Flags.Accessor * Rename Scala2X to Scala2x to follow dotty conventions
d2400b4
to
9b9c9f8
Compare
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.
I am generally in favor of the architectural changes. However, I have a few questions and remarks:
- I am not sure 100% sure whether it is a good idea to inline
Types
intoReflection
. Maybe it is, since this is a very fine grained interface, only suitable for Reflection anyways. - Why is
val XXX: XXXModule
defined as aval
? Defining it as aval
has the disadvantage that implementors cannot easily stub aspects they do not care about (i.e.,def XXX = ???
). Is the reason that you want to use it as a self-type? If yes: I couldn't immediately find a reason why the self-type is necessary. Same reasoning applies toXXXMethods
andXXXTypeTest
.
case _ => false | ||
end extension | ||
|
||
extension [T](tree: Tree) |
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.
This has nothing to do with this PR, but it is sad that you are forced to open a second extension block, here.
Some((cdef.name, cdef.constructor, cdef.parents, cdef.derived, cdef.self, cdef.body)) | ||
end ClassDef | ||
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree /* Term | TypeTree */], derived: List[TypeTree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef | ||
def unapply(cdef: ClassDef): Option[(String, DefDef, List[Tree /* Term | TypeTree */], List[TypeTree], Option[ValDef], List[Statement])] |
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.
As discussed already, the apply
is still missing here.
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.
There is a TODO to remind us to do it just above.
|
||
/** Class name of the current CompilationUnit */ | ||
def compilationUnitClassname: String = reflectSelf.Source_compilationUnitClassname | ||
trait TreeModule { this: Tree.type => } |
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.
Do you plan to add methods here?
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.
Possibly. If we do, it is better to have it already defined to avoid breaking user code if we add it later. This happened in the past with the Position
module.
It would be good to spell out the design trade-offs here. I try to reproduce the main points of an offline discussion with @nicolasstucki below. The reflection library has two sets of APIs:
Conceptually, the two contracts have different stakeholders and have different requirements. For compiler contracts, the APIs need to be minimal and powerful. Coarse-grained APIs are ideal, friendliness and type safety are not needed. For meta-programming APIs, friendliness is a concern, and some-level of type safety can prevent meta-programmers from accidental mistakes. Fine-grained APIs seem to be a good fit for the latter use case. Seen from that perspective, there are two reasonable design points:
Both the two design points are arguably local optima, but design points between them are not. The first design is simpler in implementation efforts, as there is only one interface. It has the disadvantage of complicating/polluting the contract with the compiler. If there is going to be another compiler to support the Reflection API, it will be a nightmare. The second design allows the two interfaces to evolve differently, which might help with maintainability, in particular, when there are multiple compilers to support the Reflect library. But it has the disadvantage of creating another abstraction layer, which might cause a maintenance problem. However, if the reflection library always evolves together with the Dotty compiler and only with the Dotty compiler, the 2nd approach seems to be an over-design. That seems to be the assumption of this PR, which sounds reasonable. There is a minor issue with the current PR. While it approaches the 2nd design point, it still keeps a tiny compiler interface. Is it possible to eliminate |
@liufengyun I prefer separating the user facing reflection API from compiler internals. For other potential implementors of a reflection API having compiler internals in the trait would be a blocker. |
If I were correct, it is impossible for another compiler to only implement reflection API but not CompilerInterface. Both are necessary contracts with compilers for meta-programming to work. |
You are probably right and in the current design compilers would also need to implement for example You say "meta-programming" but eventually, it might be relevant to separate different notions of meta-programming (quoting, quoted pattern matching, reflection). Compilers (or DSL authors) might implement quoting, but not reflection. But I'd leave that for another PR / discussion |
Regarding tradeoffs:
|
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.
LGTM
As user-facing API keeps the same, I think it's fine to delay the further refactoring of CompilerInterface
to a different PR.
As a final note for users of the Reflection API. This PR does not change the user-facing API (modulo couple of small fixes). The changes in the way we implement this interface internally. |
Revert to the original design with abstract modules that are implemented directly.
We had to move the implementations to the compiler interface with one of the iterations of extension methods.
The current extension methods are again compatible with this design.
CompilerInterface is kept for the internal methods that are only available in the stdlib.