James Daugherty created GROOVY-12559:
----------------------------------------

             Summary: 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


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)

Reply via email to