[ 
https://issues.apache.org/jira/browse/GROOVY-12412?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18115591#comment-18115591
 ] 

ASF GitHub Bot commented on GROOVY-12412:
-----------------------------------------

codecov-commenter commented on PR #2937:
URL: https://github.com/apache/groovy/pull/2937#issuecomment-5680729270

   ## 
[Codecov](https://app.codecov.io/gh/apache/groovy/pull/2937?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
 Report
   :white_check_mark: All modified and coverable lines are covered by tests.
   :white_check_mark: Project coverage is 71.6104%. Comparing base 
([`0041ce8`](https://app.codecov.io/gh/apache/groovy/commit/0041ce8c3a6364687a59aaad943a3264be4a2237?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache))
 to head 
([`86fa154`](https://app.codecov.io/gh/apache/groovy/commit/86fa154c37d84e187ce82ced86bbd828ce09e070?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   [![Impacted file tree 
graph](https://app.codecov.io/gh/apache/groovy/pull/2937/graphs/tree.svg?width=650&height=150&src=pr&token=1r45138NfQ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)](https://app.codecov.io/gh/apache/groovy/pull/2937?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   
   ```diff
   @@                Coverage Diff                 @@
   ##               master      #2937        +/-   ##
   ==================================================
   + Coverage     71.5979%   71.6104%   +0.0126%     
   - Complexity      38043      38060        +17     
   ==================================================
     Files            1588       1588                
     Lines          137046     137068        +22     
     Branches        25493      25505        +12     
   ==================================================
   + Hits            98122      98155        +33     
   + Misses          30018      30009         -9     
   + Partials         8906       8904         -2     
   ```
   
   | [Files with missing 
lines](https://app.codecov.io/gh/apache/groovy/pull/2937?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
 | Coverage Δ | |
   |---|---|---|
   | 
[src/main/java/groovy/lang/NumberRange.java](https://app.codecov.io/gh/apache/groovy/pull/2937?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fgroovy%2Flang%2FNumberRange.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9ncm9vdnkvbGFuZy9OdW1iZXJSYW5nZS5qYXZh)
 | `80.1980% <100.0000%> (+0.1980%)` | :arrow_up: |
   | 
[...rg/codehaus/groovy/runtime/ArrayGroovyMethods.java](https://app.codecov.io/gh/apache/groovy/pull/2937?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FArrayGroovyMethods.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvQXJyYXlHcm9vdnlNZXRob2RzLmphdmE=)
 | `87.3247% <100.0000%> (+0.0549%)` | :arrow_up: |
   | 
[.../codehaus/groovy/runtime/DefaultGroovyMethods.java](https://app.codecov.io/gh/apache/groovy/pull/2937?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FDefaultGroovyMethods.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvRGVmYXVsdEdyb292eU1ldGhvZHMuamF2YQ==)
 | `75.6757% <100.0000%> (+0.0186%)` | :arrow_up: |
   | 
[...g/codehaus/groovy/runtime/StringGroovyMethods.java](https://app.codecov.io/gh/apache/groovy/pull/2937?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FStringGroovyMethods.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvU3RyaW5nR3Jvb3Z5TWV0aG9kcy5qYXZh)
 | `82.6456% <100.0000%> (+0.1916%)` | :arrow_up: |
   
   ... and [12 files with indirect coverage 
changes](https://app.codecov.io/gh/apache/groovy/pull/2937/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   </details>
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test 
Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, 
report on failures, and find test suite problems.
   - :package: [JS Bundle 
Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save 
yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>




> Improve consistency of numerous Range indexing edge cases
> ---------------------------------------------------------
>
>                 Key: GROOVY-12412
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12412
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> Range indexing behaves differently depending on the receiver, on which 
> {{Range}} implementation
> the bounds happen to produce, and on whether the range is the only index or 
> one of several. The
> differences are not documented anywhere and no test pins them. This issue 
> brings all of them into
> line and adds the missing tests.
> Six receivers support subscripting by index: {{List}}, {{ListWithDefault}}, 
> object arrays,
> primitive arrays, {{CharSequence}} and {{Matcher}}. Measured over eighteen 
> subscript forms, they
> disagree on twelve of them today.
> h3. The rule that should hold
> A range used as an index has its bounds resolved against the size of the 
> receiver, whether it is
> the only index or one of several: a negative bound counts back from the end, 
> both exclusive forms
> drop the right element, a range written high to low reverses the result, and 
> an empty range
> contributes nothing. Anything that is not a range, including a nested list of 
> indices, is walked
> element by element, with negative values still counting from the end.
> h3. Symptoms
> {code:groovy}
> def list = (1..8).toList()
> def ints = (1..8) as int[]
> def objs = (1..8) as Integer[]
> def m = 'a1b2c3d4' =~ /[a-z]\d/
> // 1. a nested range on a List ignores a negative bound (regression, 5.0.0)
> assert list[0..2, 3..-1] == [1, 2, 3, 4, 5, 6, 7, 8]   // actual [1, 2, 3, 4, 
> 3, 2, 1, 8]
> // 2. a primitive array resolves a sole range only when it is an IntRange
> assert ints[3..-1]   == [4, 5, 6, 7, 8]                // ok
> assert ints[3L..-1L] == [4, 5, 6, 7, 8]                // actual [4, 3, 2, 1, 
> 8]
> assert ints[0, 3..-1] == [1, 4, 5, 6, 7, 8]            // actual [1, 4, 3, 2, 
> 1, 8]
> // 3. a NumberRange cannot tell a negative bound from a reversed range
> assert list[3L..-1L] == [4, 5, 6, 7, 8]                // actual [8, 7, 6, 5, 
> 4]
> assert list[2L..0L]  == [3, 2, 1]                      // actual [1, 2, 3]
> // 4. Matcher loses an element on every exclusive form, ignores reversal,
> //    and rejects a nested collection of indices
> assert m[1..<3] == ['b2', 'c3']                        // actual ['b2']
> assert m[3..1]  == ['d4', 'c3', 'b2']                  // actual ['b2', 'c3', 
> 'd4']
> m[0, [1, 3]]                                           // GroovyCastException
> // 5. a nested empty range adds a stray element on object arrays and strings
> assert objs[0..1, 2..<2]        == [1, 2]              // actual [1, 2, 3]
> assert 'abcdefgh'[0..2, 2..<2]  == 'abc'               // actual 'abcc'
> {code}
> h3. Root causes
> *1. {{DefaultGroovyMethods.getAt(List, Collection)}} lost its dynamic 
> dispatch.* Commit
> 87b4109418 ("minor refactor - no need to funnel through InvokerHelper here", 
> 2022-08-24, no JIRA
> reference) replaced {{InvokerHelper.invokeMethod(self, "getAt", value)}} with 
> a direct call to the
> {{Collection}} overload. The dynamic call let the MOP select {{getAt(List, 
> Range)}} for a nested
> range; the direct call recurses and walks the range's own elements as 
> indices. This is the only
> regression in this issue: it is not on {{GROOVY_4_0_X}} and first shipped in 
> 5.0.0. The sibling
> {{getAt(ListWithDefault, Collection)}} still uses the dynamic form, which is 
> why
> {{ListWithDefault}} was never affected.
> *2. Primitive arrays resolve only an {{IntRange}}.* The sixteen
> {{getAt(prim[], Range<?>)}} and {{getAt(prim[], ObjectRange)}} overloads, and 
> the shared helper
> behind the {{Collection}} overloads, walk the range literally. Only the typed
> {{getAt(prim[], IntRange)}} overloads resolve bounds. Present in every 4.x 
> and 5.x release.
> *3. {{NumberRange.subListBorders}} reads its normalised bounds.* The 
> constructor computes
> {{reverse}} and stores {{from}} and {{to}} in ascending order, so by the time 
> borders are computed
> {{3L..-1L}} is indistinguishable from {{-1L..3L}}. {{IntRange}} keeps the raw 
> order in its fields
> and presents the normalised view through {{getFrom()}} and {{getTo()}}, which 
> is exactly why it
> gets negative indexing right. Both then share {{IntRange.subListBorders}}. 
> Any bound that is not
> an {{Integer}} produces a {{NumberRange}}, so this covers long, short, byte, 
> {{BigInteger}} and
> decimal bounds. Present in every 4.x and 5.x release.
> *4. {{StringGroovyMethods.getAt(Matcher, Collection)}} rebuilds a range from 
> resolved borders.*
> It resolves borders only when the whole index argument is an {{IntRange}}, 
> then does
> {{new IntRange(((IntRange) indices).getInclusive(), info.from, info.to - 
> 1)}}. Re-applying the
> original inclusivity to an already resolved bound drops an element, and 
> {{info.reverse}} is
> discarded. It also has no branch for a nested collection. Present in every 
> 4.x and 5.x release.
> *5. {{EmptyRange}} implements {{Range}}, so a nested empty range reaches
> {{subListBorders(int, Range)}}, which returns a one element window.* This 
> affects object arrays
> and {{CharSequence}}. The {{List}} path avoided it only by accident of the 
> code in cause 1, so the
> guard is needed there too once that is fixed.
> h3. Fix
> || file || change ||
> | {{groovy/lang/NumberRange.java}} | recover the written bound order from the 
> {{reverse}} flag before computing borders |
> | {{runtime/ArrayGroovyMethods.java}} | new private {{primitiveArraySubList}} 
> resolves a range against the array length and handles {{EmptyRange}}; the 
> sixteen sole-index overloads and the nested path both use it |
> | {{runtime/DefaultGroovyMethods.java}} | {{getAt(List, Collection)}} 
> dispatches a nested range to the {{Range}} overload, with an {{EmptyRange}} 
> guard |
> | {{runtime/StringGroovyMethods.java}} | {{EmptyRange}} guard on the 
> {{CharSequence}} path; {{getAt(Matcher, Collection)}} rewritten to read the 
> resolved window and to accept nested collections |
> The eight {{getAt(prim[], IntRange)}} overloads are untouched: they rebuild a 
> range through
> {{subListRange}}, which does correctly undo the inclusivity flags, and they 
> handle an empty window
> correctly.
> The literal walk remains available on every receiver by passing a plain list 
> of indices, for
> example {{a[(3..-1).toList()]}}, which still yields {{[4, 3, 2, 1, 8]}}.
> h3. Behaviour changes that are not regression fixes
> * A {{NumberRange}} index now behaves like the equivalent {{IntRange}} on 
> every receiver. Code
>   relying on {{list[3L..-1L]}} returning a reversed window changes.
> * A sole {{NumberRange}} or {{ObjectRange}} index on a primitive array is now 
> resolved rather than
>   walked.
> * {{matcher[0, [1, 2]]}} now returns a result. {{RegularExpressionsTest}} 
> carried
>   {{shouldFail \{ matcher[0, [1, 2]] \}}}, added in 2008 under GROOVY-3028, 
> which reads as
>   characterising the behaviour of the day rather than arguing the form should 
> be rejected. It has
>   been replaced with a positive assertion, since every other receiver accepts 
> it.
> * The deprecated protected {{primitiveArrayGet}} helpers in 
> {{DefaultGroovyMethods}} still hold
>   the old literal logic. Nothing in Groovy calls them; they are kept for 
> binary compatibility.
> h3. Verification
> * Eighteen subscript forms measured against all six receivers, results 
> normalised to the positions
>   selected: twelve forms disagreed before, none disagree after.
> * Core {{:test}}: 18342 passed, 68 skipped, 0 failed. All 37 module suites: 
> 5691 passed, 0 failed.
> * New tests: {{GroovyMethodsTest.testGetAtMultipleIndicesIncludingRanges}} and
>   {{testGetAtSingleRangeResolvesBoundsOnEveryReceiver}} cover {{List}}, 
> {{ListWithDefault}},
>   {{Integer[]}}, all eight primitive array types and {{CharSequence}} over 
> negative, exclusive,
>   reversed, empty and {{ObjectRange}} bounds in both positions.
>   {{RegularExpressionsTest.testMatcherRangeIndicesMatchOtherAggregates}} does 
> the same for
>   {{Matcher}} and asserts that each index expression selects the same 
> positions as from a list of
>   the same size.
> * New executed javadoc examples on {{getAt(int[], Collection)}}, 
> {{getAt(int[], Range)}} and
>   {{getAt(Matcher, Collection)}}, so the corrected behaviour is documented as 
> well as tested.
> h3. Why this belongs in 6.0.0
> Item 1 alone is a regression against 4.x and would justify a fix on its own. 
> The remaining items
> are long standing, individually obscure, and each looked too marginal to 
> disturb an RC. Taken
> together they are what makes the first item a regression rather than a quirk: 
> the same subscript
> written three ways over the same eight elements returns three different 
> answers, and which answer
> you get depends on whether a bound happened to be an {{int}} or a {{long}}, 
> and on whether the
> range stood alone or sat beside another index. Fixing them as one change is 
> what produces a rule
> that can be stated in a sentence and documented. Deferring the rest to 7 
> would mean shipping 6
> with the regression fixed and the surrounding inconsistencies intact, then 
> changing the same
> methods again a major version later.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to