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)

Reply via email to