paulk-asert commented on PR #3085:
URL: https://github.com/apache/groovy/pull/3085#issuecomment-6532734438

   Verdict: ready to merge. I found no correctness or metaprogramming blocker. 
James has already answered Daniel's asType question in the PR thread: a cast 
site never selects asType, while the as operator goes through an ordinary 
method call site whose metaclass guard the PR leaves alone. The PR has 
blackdrag's approval and green CI.
   
   To test it I built the PR head and its parent and ran probes against both. 
The jars and probe scripts are in 
/private/tmp/claude-501/-Users-paulk-Projects-groovy/deaa34fd-22fc-4390-b220-52371f39f6b5/scratchpad/p3085/;
 I've removed the scratch worktree.
   
   Other ways it could break, and what I found:
   
   1. Is anything else in the cast metaclass-dependent? No. Besides asBoolean, 
a cast links one of four handles:
      - identity;
      - the HashSet/ArrayList constructors;
      - closure-to-SAM conversion;
      - castToType.
   
      The first three depend only on the value's class. castToType runs fresh 
on every call, so any metaclass lookup inside it (such as a constructor through 
invokeConstructorOf) never gets baked into the cached handle. The Boolean and 
boolean targets are caught by handleBoolean first, which keeps the guard.
   2. The metaclass guard was also quietly a null guard. isSameMetaClass(mc, 
null) returns false, so a null value forced a relink. With the PR, a cast from 
a final static type gets only the switch-point guard; I confirmed in the indy 
log that nothing else is added. A null-unsafe handle cached for a non-null 
value could therefore be reused for null. In practice that can't happen:
      - Identity, SAM and castToType handle null fine.
      - The collection-constructor fast path is effectively unreachable, 
because isAbstractClassOf(HashSet, Set) tests the subtype relationship the 
wrong way round. Those casts fall through to castToType instead. That's an old 
oddity, not something this PR introduced.
   
      I warmed a final class Bag extends ArrayList → Set cast past the optimize 
threshold, then passed null. It returned null on both builds, and the final 
Number → int case still throws GroovyCastException on both.
   3. Per-instance asBoolean still relinks. The flag is only set when 
handleBoolean selects through the metaclass, and a new selector is created on 
every relink, so stale state can't carry over. The PR's alternating 
truthy/falsy test covers this.
   4. Proxy truthiness is still not fixed, but it matches classic call sites. I 
used a Groovy subclass whose getMetaClass() throws, standing in for a scoped 
proxy:
   
   | Shape                            | Before (indy) | PR (indy) | PR 
(classic) |
   
|----------------------------------|---------------|-----------|--------------|
   | Report r = p, (Report) p         | throws        | works     | works       
 |
   | boolean b = p, if (p), p ? :, !p | throws        | throws    | throws      
 |
   | p == p                           | works         | works     | works       
 |
      It's worth one sentence on the JIRA so Grails users don't expect if 
(scopedProxy) to work outside the scope.
   5. Small performance gain. Casting a GroovyObject no longer calls 
getMetaClass() on every invocation. Values with per-instance metaclasses no 
longer fail the guard and relink the cast site each time.
   
   Backports:
   - The PR links 6.0.x and 5.1.x commits, and Daniel agrees backporting is 
warranted. I didn't review James's hand-applied 5.1.x version; it's worth 
running the new test on that branch before you pick it.
   - The PR says the bug reproduces on 4.0.33, and GROOVY_4_0_X has the same 
unconditional receiver instanceof GroovyObject guard in setGuards. Grails 7 
runs on Groovy 4.0.x. If the scoped-proxy report comes from a Grails 7 app, 
4.0.x may be the backport that actually matters, so ask James whether he wants 
it there too.
   
   Nits (none blocking):
   - The test uses static counters, which is fine as long as tests run 
sequentially in one JVM.
   - No test uses a real proxy subclass, but Watched covers the same path.
   - The existing "call site stays uncached" log line in doCallSiteTargetSet is 
misleading: the target is in fact set later.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to