Paul King created GROOVY-12536:
----------------------------------

             Summary: SC: a lambda or method reference taking a String throws 
ClassCastException for a GString element of a List<String>
                 Key: GROOVY-12536
                 URL: https://issues.apache.org/jira/browse/GROOVY-12536
             Project: Groovy
          Issue Type: Bug
            Reporter: Paul King


A {{List<String>}} filled by dynamic code may hold {{GString}} elements, 
because generic types are not checked at runtime. In statically compiled code, 
Groovy converts such an element to a {{String}} wherever a {{String}} is 
required. That includes assignments, arguments, calls to {{String}}-only 
methods, and closure parameters ({{list.collect { it.toUpperCase() }}}). Native 
lambdas and method references are the exception: they throw a 
{{ClassCastException}}, even when the body only uses {{CharSequence}} or 
{{Object}} members:

{code:groovy}
import groovy.transform.CompileStatic

@CompileStatic
class L {
    static a(List<String> l) { l.stream().map(s -> 
s.toUpperCase()).findFirst().get() }
    static b(List<String> l) { 
l.stream().map(String::toUpperCase).findFirst().get() }
    static c(List<String> l) { l.stream().map(s -> 
s.length()).findFirst().get() }
    static d(List<String> l) { l.stream().map(s -> 
s.toString()).findFirst().get() }
    static e(List<String> l) { l.stream().map { it.toUpperCase() 
}.findFirst().get() }
}

def n = 'ab'
List<String> gs = ["$n"]   // element is a GString
{code}

||Call||@CompileStatic (4.0.29, 5.0.5, 6.0.0, master)||Dynamic||
|{{a}}: {{s -> s.toUpperCase()}}|ClassCastException|{{AB}}|
|{{b}}: {{String::toUpperCase}}|ClassCastException|MissingMethodException|
|{{c}}: {{s -> s.length()}}|ClassCastException|{{2}}|
|{{d}}: {{s -> s.toString()}}|ClassCastException|{{ab}}|
|{{e}}: closure {{{ it.toUpperCase() }}}|{{AB}} on master (CCE before 
GROOVY-12469 follow-up)|{{AB}}|

The lambda's parameter is compiled as {{String}}, so the cast happens in the 
bridge that {{LambdaMetafactory}} generates for {{Function.apply(Object)}}, 
before any generated code can convert the value. A possible fix is to give the 
synthetic lambda method an erased parameter type when the parameter type comes 
from generics, then convert a {{GString}} to {{String}} inside the lambda, as 
is now done for closure parameters. The method reference case would need the 
same treatment through a synthetic lambda.

Related observation: in dynamic code, {{String::toUpperCase}} applied to a 
{{GString}} throws {{MissingMethodException}}, while {{s -> s.toUpperCase()}} 
works. That may be worth handling separately.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to