Paul King created GROOVY-12184:
----------------------------------

             Summary: unsound cast for property access on union type 
(instanceof A || instanceof B) throws ClassCastException (fix for GROOVY_5_0_X)
                 Key: GROOVY-12184
                 URL: https://issues.apache.org/jira/browse/GROOVY-12184
             Project: Groovy
          Issue Type: Bug
            Reporter: Paul King


Under {{@CompileStatic}} (and {{@TypeChecked}}), accessing a property on a 
receiver whose flow-typed type is a union from {{instanceof A || instanceof B}} 
narrows the receiver to the *first* type and emits a hard cast to it. When the 
object is the other type, this throws {{ClassCastException}} at runtime.

{code:groovy}
import groovy.transform.CompileStatic

@CompileStatic
class X1 { Long id }
@CompileStatic
class X2 { Long id }

@CompileStatic
Long computeId(Object test) {
    if (test instanceof X1 || test instanceof X2) {
        return test.id   // narrowed to X1, cast to X1
    }
    null
}

assert computeId(new X1(id: 11)) == 11L   // ok
assert computeId(new X2(id: 22)) == 22L   // java.lang.ClassCastException: 
class X2 cannot be cast to class X1
{code}

*Expected:* both calls return the {{id}} (12 / 22).
*Actual:* the second call throws {{ClassCastException}}. Reordering the 
{{instanceof}} clauses flips which type fails, confirming the receiver is 
narrowed to a single delegate rather than their common supertype.

The subscript form ({{test['id']}}) is sound because it routes through dynamic 
{{getAt}} at runtime.

Affects 4.0.x and 5.0.x (not a regression). Passes on 6.0 (master), fixed 
incidentally by the union/intersection type work in GROOVY-11998.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to