[
https://issues.apache.org/jira/browse/GROOVY-12747?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King resolved GROOVY-12747.
--------------------------------
Fix Version/s: 7.0.0-alpha-1
Assignee: Paul King
Resolution: Fixed
> STC: assigning to a closure or lambda parameter typed from a functional
> interface converts the value to the inferred type
> -------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12747
> URL: https://issues.apache.org/jira/browse/GROOVY-12747
> Project: Groovy
> Issue Type: Bug
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
> Fix For: 7.0.0-alpha-1
>
>
> Under {{@TypeChecked}} and {{@CompileStatic}}, an untyped closure or lambda
> parameter whose type is inferred from a functional-interface parameter
> behaves as a declared, typed parameter. Assigning a value of another type to
> the parameter therefore converts the value to the parameter's type, silently
> or with a {{GroovyCastException}}. Dynamic Groovy keeps the value.
> {code:groovy}
> @groovy.transform.TypeChecked // or @CompileStatic
> def test() {
> List<Integer> numbers = [1, 2]
> numbers.stream().map { n -> n = n.toString(); n }.toList()
> }
> assert test() == [1, 2] // fails: [49, 50]
> {code}
> ||Closure (for {{Stream.map}} on {{List<Integer>}} [1,
> 2])||Dynamic||{{@TypeChecked}} / {{@CompileStatic}}||
> |{{\{ n -> n = n.toString(); n \}}}|[1, 2]|*[49, 50]*, the char codes of "1"
> and "2"|
> |{{\{ n -> n = n + 0.5; n \}}}|[1.5, 2.5]|*[1, 2]*, truncated|
> |{{n -> \{ n = 'x'; n \}}} (lambda)|[x, x]|*[120, 120]*|
> |{{Function<Integer, Object> f = \{ n -> n = 'x'; n \}}}|[x, x]|*[120, 120]*|
> |{{\{ n -> n = 'ab'; n \}}}|[ab, ab]|{{GroovyCastException}}: Cannot cast
> object 'ab' with class 'java.lang.String' to class 'java.lang.Integer'|
> |{{\{ it = 'x'; it \}}} (implicit parameter)|[x, x]|[x, x]|
> |DGM {{numbers.collect \{ n -> n = n.toString(); n \}}}
> ({{@ClosureParams}})|[1, 2]|[1, 2]|
> So the same closure body behaves differently depending on how its parameter
> type was inferred. A parameter typed through {{@ClosureParams}}, or an
> implicit {{it}}, keeps the assigned value, as dynamic Groovy does. A
> parameter typed from a functional interface converts it.
> h3. Cause
> {{StaticTypeCheckingVisitor.inferParameterAndReturnTypesOfClosureOnRHS}} sets
> the inferred type on an untyped parameter ({{parameter.setType(type)}},
> commented "GROOVY-11083, GROOVY-11085, et al."). The parameter is then
> declared with that type, so an assignment to it goes through a cast to the
> parameter type: a one-character String becomes its char code, a
> {{BigDecimal}} is narrowed to {{Integer}}, and other Strings fail. The
> {{@ClosureParams}} path ({{processClosureParams}}) records the inferred types
> without changing the parameters' declared type, so flow typing applies.
> h3. Expected
> The assignment must not silently change the value. Either:
> * treat the parameter as the {{@ClosureParams}} path does, so it keeps the
> assigned value as in dynamic Groovy; or
> * report a static type error for an assignment that is incompatible with the
> inferred type, as Java does for a lambda parameter.
> The first is consistent with the rest of Groovy's closure handling. Whichever
> is chosen, the {{@ClosureParams}} and functional-interface paths should agree.
> h3. Notes
> * Seen on master. {{setType}} dates from GROOVY-11083 and GROOVY-11085
> (3.0.18 / 4.0.13); earlier versions were not checked.
> * GROOVY-12746 types the parameters of closures passed to extension methods
> with functional-interface parameters, such as {{collectParallel}}, from the
> receiver. Those closures now have this behaviour too:
> {{numbers.collectParallel \{ n -> n = n.toString(); n \}}} gives [49, 50].
> Before GROOVY-12746 their parameters fell back to {{Object}}.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)