[code-reflection] RFR: Drop Quotable type
Maurizio Cimadamore
mcimadamore at openjdk.org
Wed Nov 19 13:15:35 UTC 2025
On Wed, 19 Nov 2025 13:00:51 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:
> The `Quotable` type is a marker interface that is used to mark lambda expressions and method reference for which javac should produce a code model (we call them quotable lambdas), as in:
>
>
> Runnable r = (Runnable & Quotable)() -> {}
>
>
> Since the lambda metafactory, nor `Op::opQuotable` only rely on this type in a superficial way, this PR simplifies the programming model by removing `Quotable` and use the `@CodeReflection` annotation (now turned into a type annotation) to mark quotable lambdas, as in:
>
>
> Runnable r = (@CodeReflection Runnable)() -> {}
>
>
> Supporting this is relatively straightforward. The most complex part is, perhaps, to make sure that (a) the type annotation we care about is visible in the AST (javac is very lazy when it comes to fill in information on type annotations) and (b) make sure we don't end up using type annotations that appear in positions other than a cast. The latter point is important; if we have this:
>
>
> void m(@CodeReflection Runnable r) { ... }
> m(() -> { .. });
>
>
> we don't want the lambda to magically become quotable simply because the target method parameter has been annotated.
>
> There's also other cases like:
>
>
> @CodeReflection
> Runnable m() {
> return () -> { .. };
> }
>
>
> If `@CodeReflection` is a type annotation, the above lambda has a target `@CodeReflection Runnable` and is also treated as a quotable lambda. What we want here instead is for `@CodeReflection` to just act as a regular declaration annotation on the method.
>
> To address these issues, I've augmented both `ReflectMethods` and its associated `BodyScanner` to keep track of the prviously visited node. This is done by having both classes extend a new simple scanner `TreeScannerPrev`, which "remembers" the node visited before the current one.
>
> Thanks to this, when we see a lambda we can see if the previous node was a cast, and if such a cast contained any type-annotated type. If so, we will treat the lambda as a quotable lambda.
>
> This implementation tactic guarantees that type annotations will only be consulted when needed.
There's some future work in this area, not addressed in this PR:
* (cosmetic) we plan to rename the `@CodeReflection` annotation to just `@Reflect`
* we want to tweak the logic for detecting quotable lambdas and make it stricter, so that _both_ an annotation at the use site, and one at the declaration side is needed. E.g.
@Reflect
interface QuotableRunnable extends Runnable { }
QuotableRunnable r = (@Reflect QuotableRunnable) () -> { ... }
This is a fussier model, but one that makes sure that reflectability of a lambda expression is always controlled by the client.
src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java line 451:
> 449: JavaFileObject prevSource = log.useSource(env.toplevel.sourcefile);
> 450: try {
> 451: annotate.queueScanTreeAndTypeAnnotate(tree.body, env, tree.sym);
Some adjustments were needed to make sure the type annotations were initialized correctly up to this point. I verified with our annotation processor in `crExamples` and everything still works.
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java line 118:
> 116: * reflection API (see jdk.internal.java.lang.reflect.code).
> 117: */
> 118: public class ReflectMethods extends TreeScannerPrev {
I've dropped the `TreeTranslator` supertype here, as it seems to me `ReflectMethods` doesn't need it; the only thing `ReflectMethod` does is generating models for quotable methods and lambdas. The models are stored in appropriate AST nodes, but we don't need to alter the structure of the AST (at least for now).
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java line 278:
> 276:
> 277: // @@@: Only used for quoted lambda, not quotable ones. Remove?
> 278: ListBuffer<JCExpression> quotedCapturedArgs(DiagnosticPosition pos, BodyScanner bodyScanner) {
This was dead code
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java line 305:
> 303: * code for capturing the bound method reference receiver twice.
> 304: */
> 305: JCExpression copyReferenceWithReceiverVar(JCMemberReference ref, JCVariableDecl recvDecl) {
This was used, but I think it was added long ago, before we cleaned up the compiler pipeline as part of flexible constructor bodies.
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java line 371:
> 369: }
> 370:
> 371: class BodyScanner extends TreeScannerPrev {
Note: `BodyScanner` also defines a `currentNode` field to keep track of the node being visited. Unfortunately, this field is also used _outside_ the scanning process -- e.g. before (at construction) and after (to finish up things). This means it is currently very hard to merge this field with `TreeScannerPrev.currentNode`. Maybe this is something we can look again in a separate PR.
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java line 2402:
> 2400:
> 2401: CoreOp.FuncOp scanMethod() {
> 2402: scan(body, currentNode);
These `scan` methods are tricky. When scanning a quotable method, `currentNode` points to the method decl (a `JCMethodDecl` node)., and `body` points at the method body (a `JCBlock` node).
But when scanning quotable lambdas, both `currentNode` and `body` point at a `JCLambda`. Hence the different "previous node" forced on the `TreeScannerPrev::scan`.
-------------
PR Comment: https://git.openjdk.org/babylon/pull/685#issuecomment-3552588276
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541923094
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541942829
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541944715
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541947678
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541934943
PR Review Comment: https://git.openjdk.org/babylon/pull/685#discussion_r2541956820
More information about the babylon-dev
mailing list