Paul King created GROOVY-12644:
----------------------------------
Summary: make method return follow similar rules to assignment for
literals
Key: GROOVY-12644
URL: https://issues.apache.org/jira/browse/GROOVY-12644
Project: Groovy
Issue Type: Improvement
Reporter: Paul King
h3. Problem
Under {{@TypeChecked}} and {{@CompileStatic}}, a list or map literal *returned*
from a method is accepted only when the literal's own class ({{ArrayList}} or
{{LinkedHashMap}}) is assignable to the declared return type. The rules that
apply when the same literal is assigned to a variable of that type (the "list
and map constructors" section of the type checking documentation, and
GROOVY-12643) do not apply to return values. Dynamic code converts the returned
value to the return type, as it does for an assignment.
Results on 6.0.0 (the same on 3.0.25 and 4.0.33, with or without an explicit
{{return}}):
||Method||Dynamic||{{@TypeChecked}} / {{@CompileStatic}}||
|{{List m() \{ [3, 1] \}}}|{{ArrayList [3, 1]}}|{{ArrayList [3, 1]}}|
|{{HashMap m() \{ [a: 1] \}}}|{{LinkedHashMap [a:1]}}|{{LinkedHashMap [a:1]}}|
|{{Set m() \{ [3, 1, 3] \}}}|{{LinkedHashSet [3, 1]}}|Cannot return value of
type java.util.ArrayList<java.lang.Integer> for method returning java.util.Set|
|{{Set m(boolean b) \{ b ? [3, 1] : [2] \}}}|{{LinkedHashSet [3, 1]}}|same
error|
|{{Set m(boolean b) \{ if (b) return [3, 1]; [2] \}}}|{{LinkedHashSet [3,
1]}}|same error|
|{{LinkedHashSet m() \{ [3, 1] \}}}|{{LinkedHashSet [3, 1]}}|same error|
|{{TreeMap m() \{ [b: 2, a: 1] \}}}|{{TreeMap [a:1, b:2]}}|Cannot return value
of type java.util.LinkedHashMap<...> for method returning java.util.TreeMap|
The same literals compile when assigned instead:
{code:groovy}
@CompileStatic
Set m() {
Set s = [3, 1, 3] // compiles: LinkedHashSet
s
}
{code}
So the common workaround is a local variable, or an explicit {{as Set}}.
With GROOVY-12643, more declaration targets are accepted ({{SortedSet}},
{{Deque}}, {{SortedMap}}, {{LinkedList}}, ...), which widens the gap between
what a declaration and a return accept.
h3. Closures and lambdas are different
A closure or lambda with a declared SAM return type rejects the same literals:
||Expression||Dynamic||{{@TypeChecked}} / {{@CompileStatic}}||
|{{Supplier<Set<Integer>> s = () -> [3, 1, 3]}}|{{s.get()}} returns {{ArrayList
[3, 1, 3]}}|Cannot return value of type java.util.ArrayList<...>|
|{{Supplier<Set<Integer>> s = \{ -> [3, 1, 3] \}}}|{{s.get()}} returns
{{ArrayList [3, 1, 3]}}|same error|
|{{Closure<Set<Integer>> c = \{ -> [3, 1, 3] \}}}|{{c()}} returns {{ArrayList
[3, 1, 3]}}|same error|
|{{Supplier<TreeMap<String, Integer>> s = () -> [b: 2, a: 1]}}|{{s.get()}}
returns {{LinkedHashMap}}|same error|
Here dynamic code does *not* convert the return value, so a
{{Supplier<Set<Integer>>}} hands back an {{ArrayList}}. The static rejection
protects against a real mismatch, and accepting these literals would need the
closure or lambda to convert its result. So this issue proposes no change for
them.
h3. Question for feedback
Should a literal returned from a method follow the same rules as a literal
assigned to a variable of the method's return type?
If yes, the proposal is to check a returned list or map literal as an
assignment to the declared return type is checked: the {{Set}} exemption, the
constructor shorthand, and the GROOVY-12643 interface defaults and copy rule.
Under {{@CompileStatic}}, a value that is not an instance of the return type
should then reach the return type through the existing cast, which calls
{{DefaultTypeTransformation.castToType}}, as an assignment does. This is not
yet verified, and needs a test for each kind of target.
If no, the type checking documentation should say that the list and map
constructor rules apply to declarations and assignments only.
h3. Out of scope
* Closure and lambda return values (see above).
* Method arguments: a list or map argument is not converted to the parameter
type, in any mode, by design.
* Non-literal return values, such as {{Set m() \{ aList \}}}, stay an error
under static checking, as in Java.
h3. Related
* GROOVY-12643: the declaration rules for list and map literals assigned to
collection and map types.
* GROOVY-12585: the runtime default implementations used by the cast.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)