[
https://issues.apache.org/jira/browse/GROOVY-12559?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18134511#comment-18134511
]
ASF GitHub Bot commented on GROOVY-12559:
-----------------------------------------
jdaugherty commented on PR #3087:
URL: https://github.com/apache/groovy/pull/3087#issuecomment-6511117232
Thanks both. Your review and my push crossed, @daniellansun: everything you
and @paulk-asert asked for is in 3cf5e647f5, and the JIRA note is up.
**Test.** `testNestedTernaryOfClassLiteralsIsJavaLangClass` now covers the
Elvis operands, a class literal against a `Marker` instance, the array class
literals and, on master, the `??` form. The static import sits below the
regular imports. The same additions without `??` are on the backport branches:
[d87b7f831f](https://github.com/jdaugherty/groovy/commit/d87b7f831fec93e44cbd8e5a99c85e8a103d33c0)
on `GROOVY-12559-6_0_X` and
[af5d739a4c](https://github.com/jdaugherty/groovy/commit/af5d739a4c32bc73f765e726f05686ad7c95220f)
on `GROOVY-12559-5_1_X`; the description links them.
**Verification.** With the pre-fix `TernaryExpression.java` swapped in, the
method fails on the first nested shape with `ClassCastException: Cannot cast
java.lang.Class to Marker`; with the fix, `TernaryOperatorsTest` passes on all
three branches. Paul's table reproduces exactly against the released 4.0.33,
5.1.3 and 6.0.0 jars with indy on and off, and a master build with
`groovy-callsite` passes all seven shapes under both
`-Dgroovy.target.indy=true` and `false`.
**Why the last two rows fail with classic call sites too.** In the first
three shapes both branches of the outer ternary already have the common
supertype, so the only conversion written is at the use site, from `Marker` to
`Class`: classic `ShortTypeHandling.castToClass(Object)` accepts the `Class`
object, while indy's `cast:(LMarker;)Ljava/lang/Class;` casts the argument to
`Marker` first. In the last two, the conversion *to* the common supertype
(`Marker`, or `Serializable[]` for the array literals) is written inside the
ternary, on the branch that is the inner ternary, through `castToType`, which
fails either way. That is now on GROOVY-12559 with the table:
https://issues.apache.org/jira/browse/GROOVY-12559?focusedCommentId=18134510
I left the test additions as a second commit so the delta is easy to review;
happy to squash if you'd rather have one.
> Ternary of two ternaries of class literals is cast to the classes' common
> supertype under invokedynamic
> -------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12559
> URL: https://issues.apache.org/jira/browse/GROOVY-12559
> Project: Groovy
> Issue Type: Bug
> Components: Compiler
> Affects Versions: 5.1.3, 6.0.0
> Reporter: James Daugherty
> Priority: Major
>
> Since 5.0.0-alpha-4 (GROOVY-10936) the type of a ternary expression is the
> common supertype of its branches' types. A class literal's type is the class
> it names, not {{java.lang.Class}}, so a ternary that chooses between two
> ternaries of class literals
> {code:groovy}
> interface Marker {}
> class First implements Marker {}
> class Second implements Marker {}
> class Third implements Marker {}
> class Fourth implements Marker {}
> class Chooser {
> Class<?> choose(boolean a, boolean b, boolean c) {
> a ? (b ? First : Second) : (c ? Third : Fourth)
> }
> }
> assert new Chooser().choose(false, true, true) == Third
> {code}
> is typed as the classes' common supertype, {{Marker}}. The chosen {{Class}}
> object is then converted _from_ {{Marker}} wherever it is used: returned,
> passed as an argument, or assigned to a typed variable. With invokedynamic
> that conversion is a {{cast}} call site declared as {{(Marker)Class}}, and
> linking it casts the {{Class}} object to {{Marker}}:
> {noformat}
> java.lang.ClassCastException: Cannot cast java.lang.Class to Marker
> {noformat}
> Groovy 4, and Groovy 5 and 6 with classic call sites, return the chosen
> class. A single-level ternary of class literals, {{a ? First : Second}}, is
> unaffected, because the bytecode writer resolves a class literal operand to
> {{Class<First>}} itself; a ternary's own type is consulted only when it is an
> operand of another ternary. A Grails application moving from classic call
> sites to invokedynamic (the Grails 9 default) meets this in code that picks a
> class by conditions.
> *Reproducer:*
> https://github.com/jdaugherty/groovy-ternary-class-literal-reproducer
> ({{./run.sh 4.0.33 5.1.3 6.0.0}}): returning the chosen class and passing it
> as an argument both fail on 5.1.3 and 6.0.0 with indy, and pass on 4.0.33 and
> with classic call sites.
> *Cause:* {{TernaryExpression.getType()}} returns
> {{WideningCategories.lowestUpperBound()}} of its branches' {{getType()}}, and
> {{ClassExpression.getType()}} is the class the literal names.
> {{BinaryExpressionHelper.evaluateTernaryExpression()}} types each branch with
> {{StatementMetaTypeChooser.resolveType()}}, which answers {{Class<T>}} for a
> class expression but {{getType()}} for a nested ternary. So the inner
> ternaries are typed {{Marker}}, the outer ternary's common type is
> {{Marker}}, and the operand stack carries {{Marker}} for a value that is a
> {{Class}}. The conversion to {{Class}} is then written as {{invokedynamic
> cast:(LMarker;)Ljava/lang/Class;}}, whose method type makes the JVM cast the
> argument to {{Marker}} before the call site sees it. Classic call sites
> convert the value to {{Class}} directly and accept it.
> *Proposed fix:* in {{TernaryExpression.getType()}}, give a class literal
> operand the type {{Class<T>}}, as {{StatementMetaTypeChooser.resolveType()}}
> does, so a ternary of class literals is typed {{Class}} wherever it appears.
> A patch with a test follows as a pull request against master, with backports
> for the 6.0.x and 5.1.x lines.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)