[ 
https://issues.apache.org/jira/browse/GROOVY-12643?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King resolved GROOVY-12643.
--------------------------------
    Fix Version/s: 7.0.0-alpha-1
         Assignee: Paul King
       Resolution: Fixed

> STC list/map literals to collection types
> -----------------------------------------
>
>                 Key: GROOVY-12643
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12643
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>             Fix For: 7.0.0-alpha-1
>
>
> h3. Problem
> Under {{@TypeChecked}} and {{@CompileStatic}}, a list or map literal assigned 
> to a collection or map type follows rules of its own (the "list and map 
> constructors" section of the type checking documentation). They differ from 
> the dynamic cast in two ways:
> * a literal assigned to a collection or map interface that the literal's own 
> class ({{ArrayList}}, {{LinkedHashSet}} or {{LinkedHashMap}}) does not 
> implement is a compile error;
> * a literal assigned to a concrete collection class is matched against a 
> constructor taking the *elements* as arguments, so a collection class is 
> either rejected or given the elements as constructor arguments, e.g. a 
> capacity.
> GROOVY-12585 gives dynamic code one table of default implementations, so the 
> gap between modes grows unless the type checker follows.
> h4. 1. Interface targets are rejected
> ||Declaration||Dynamic (with GROOVY-12585)||{{@TypeChecked}} / 
> {{@CompileStatic}}||
> |{{SortedSet s = [3, 1, 2]}}|{{TreeSet [1, 2, 3]}}|Cannot assign value of 
> type java.util.ArrayList<java.lang.Integer> to variable of type 
> java.util.SortedSet|
> |{{Deque d = [3, 1, 2]}}|{{LinkedList [3, 1, 2]}}|same error|
> |{{Queue q = []}}|{{LinkedList []}}|same error|
> |{{SortedMap m = [b: 2, a: 1]}}|{{TreeMap [a:1, b:2]}}|Cannot find matching 
> constructor java.util.SortedMap(java.util.LinkedHashMap<...>)|
> |{{ConcurrentMap m = [a: 1]}}|{{ConcurrentHashMap [a:1]}}|same error|
> {{Set}}, {{List}}, {{Collection}}, {{Iterable}} and {{Map}} work, because the 
> literal's class implements them or because of the {{LinkedHashSet}} exemption 
> added in GROOVY-10002.
> The interface targets have never worked by assignment: they fail in every 
> mode in every release checked (2.4.12, 2.5.23, 3.0.9, 3.0.25, 4.0.5, 4.0.33, 
> 5.0.8, 5.1.3, 6.0.0). Some releases (2.4.12, 3.0.9, 4.0.5) type check 
> {{SortedMap m = [...]}} and then fail at run time with 
> {{GroovyCastException}}; the others reject it at compile time.
> h4. 2. Concrete collection classes use the element constructor
> ||Declaration||Dynamic (with 
> GROOVY-12585)||{{@TypeChecked}}||{{@CompileStatic}}||
> |{{Vector v = [5]}}|{{[5]}}|{{[5]}}|{{[]}} (calls {{Vector(int)}})|
> |{{PriorityQueue q = [5]}}|{{[5]}}|{{[5]}}|{{[]}}|
> |{{ArrayDeque d = [5]}}|{{[5]}}|{{[5]}}|{{[]}}|
> |{{LinkedList l = [3, 1]}}|{{[3, 1]}}|Cannot find matching constructor 
> java.util.LinkedList(int, int)|same error|
> |{{Stack s = [3, 1, 2]}}|{{[3, 1, 2]}}|Cannot find matching constructor|same 
> error|
> {{@TypeChecked}} code runs the dynamic cast, so where it compiles it follows 
> the dynamic result. {{@CompileStatic}} code calls the matched constructor 
> directly.
> h3. Cause
> In {{StaticTypeCheckingVisitor}}:
> * {{addListAssignmentConstructorErrors}} exempts targets that are supertypes 
> of {{ArrayList}} or {{LinkedHashSet}}, applies the constructor shorthand to 
> concrete classes, and rejects any other interface or abstract class.
> * {{addMapAssignmentConstructorErrors}} exempts supertypes of 
> {{LinkedHashMap}} and applies the constructor shorthand to everything else, 
> which an interface cannot satisfy.
> * The inferred type of the declaration ({{getLiteralResultType}}) is 
> {{ArrayList}}, {{LinkedHashSet}} or {{LinkedHashMap}} for the exempt targets.
> Under {{@CompileStatic}} the literal is built as an {{ArrayList}} or 
> {{LinkedHashMap}}, and a target it is not an instance of is reached through 
> the indy cast, which calls {{DefaultTypeTransformation.castToType}}. {{Set s 
> = [3, 1]}} compiles to an {{ArrayList}} followed by {{invokedynamic 
> cast:(ArrayList)LinkedHashSet}}. So an interface target needs no new code 
> generation once the checker accepts it.
> h3. Proposal
> *Part 1: interface targets.* Accept a list literal assigned to {{SortedSet}}, 
> {{NavigableSet}}, {{Queue}}, {{Deque}} or {{BlockingQueue}}, and a map 
> literal assigned to {{SortedMap}}, {{NavigableMap}} or {{ConcurrentMap}}, as 
> {{Set}} is accepted today. The inferred type is the default implementation 
> from GROOVY-12585 ({{TreeSet}}, {{LinkedList}}, {{LinkedBlockingQueue}}, 
> {{TreeMap}}, {{ConcurrentHashMap}}), and the elements are checked against the 
> declared type arguments:
> {code:groovy}
> @CompileStatic
> void test() {
>     SortedSet<Integer> s = [3, 1, 2]      // TreeSet [1, 2, 3]
>     Deque<String> d = []                  // LinkedList
>     SortedMap<String, Integer> m = [b: 2, a: 1]  // TreeMap [a:1, b:2]
>     SortedSet<String> bad = [3, 1]        // error: Incompatible generic 
> argument types
> }
> {code}
> Every program this accepts is a compile error today, so no compiling program 
> changes meaning.
> *Part 2: concrete collection classes.* For a list literal assigned to a 
> concrete collection class that has a constructor taking a collection, or a 
> no-arg constructor, copy the elements, as the dynamic cast does, instead of 
> matching a constructor against them. The inferred type is the class, and the 
> elements are checked against its type arguments:
> {code:groovy}
> @CompileStatic
> void test() {
>     Vector<Integer> v = [5]               // [5], was an empty Vector
>     LinkedList<Integer> l = [3, 1]        // [3, 1], was a compile error
>     Stack<Integer> s = [3, 1, 2]          // was a compile error
> }
> {code}
> Other classes keep the constructor shorthand: {{Point p = [1, 2]}}, 
> {{Dimension d = [100, 200]}}, and a collection class whose only constructors 
> take the elements.
> h3. Behaviour changes
> * Part 1 only accepts programs that are rejected today.
> * Part 2 changes the result of compiling code. Under {{@CompileStatic}}, 
> {{Vector v = [5]}}, {{PriorityQueue q = [5]}} and {{ArrayDeque d = [5]}} hold 
> the element instead of being empty with that capacity. With type arguments, 
> using the literal as a capacity becomes a compile error, for example 
> {{ArrayDeque<String> d = [16]}}. Write {{new ArrayDeque(16)}} for a capacity.
> * 
> {{ArraysAndCollectionsSTCTest.testCollectionTypesInitializedByListLiteral2}} 
> pins {{ArrayDeque<String> deque = [123] // ArrayDeque(int numElements)}} 
> (GROOVY-10002). It would change to {{ArrayDeque<Integer> deque = [123]}} 
> holding {{123}}, with {{ArrayDeque<String> deque = [123]}} rejected.
> * The {{Deque}}/{{Queue}} cases in 
> {{testCollectionTypesInitializedByListLiteral3}} and {{SortedMap m = [:]}} in 
> {{ConstructorsSTCTest.testMapLiteral}} pin the current rejections, and would 
> assert the new results.
> Part 2 is the one to discuss: it changes behaviour that has been consistent 
> across modes, if unhelpful, and that one existing test treats as intended. 
> Part 1 can be done without it.
> h3. Out of scope
> * GROOVY-12644: a literal returned from a method under static checking is 
> rejected unless its own class is assignable to the return type. {{Set m() \{ 
> [3, 1] \}}} fails while {{Set s = [3, 1]}} compiles. Returns have never 
> followed the declaration rules, including the existing {{Set}} exemption.
> * Assigning a non-literal value, such as {{SortedSet s = aList}}, stays an 
> error under static checking, as in Java.
> * A list or map argument is not converted to a method's parameter type, in 
> any mode, by design.
> h3. Related
> * GROOVY-12585: the runtime default-implementation table this relies on.
> * GROOVY-10002: introduced the {{LinkedHashSet}} exemption and the 
> capacity-idiom test.



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

Reply via email to