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)