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

Paul King commented on GROOVY-12085:
------------------------------------

I have marked this as breaking. It is really a consistency fix, but marking it 
so that folks making heavy use of supplementary characters can do extra 
checking.

h4. TL;DR

For source that contains *no* supplementary (astral-plane) characters, the fix 
is provably a *no-op* — nothing changes. Any breaking potential is confined to 
(a) source that actually contains supplementary characters *and* (b) tooling 
that consumed the old, inconsistent {{lastColumnNumber}}.

h4. Could it break code with no supplementary characters?

*No — and this is guaranteed, not just likely.* Both changes reduce to the 
identity when there are no surrogate pairs:
* {{String.codePointCount(0, len) == len}} for any string with no surrogate 
pairs.
* {{String.offsetByCodePoints(0, n) == n}} when no surrogate pair precedes 
index {{n}}.

Only a *valid supplementary character* (a surrogate pair) ever changes a value; 
even a lone/malformed surrogate counts as 1 under both {{length()}} and 
{{codePointCount()}}. So ASCII/BMP source — effectively all existing code — 
produces *bit-for-bit identical* AST column numbers and power-assert output. 
The {{PositionConfigureUtils}} change touches every node's 
{{lastColumnNumber}}, but for BMP source that value is unchanged.

h4. Scope of each change

* *{{SourceText}}* — local to power-assert and anything reusing {{SourceText}}. 
Identity for BMP; for astral source it now captures the *complete* line instead 
of a truncated one.
* *{{PositionConfigureUtils}}* — global: it changes {{lastColumnNumber}} for 
*every* node. Identity for BMP; for astral source the end column is now 
code-point based.

h4. How it could break (only when supplementary characters are present)

* *Tools that treat {{lastColumnNumber}} as a UTF-16 offset* (IDE/editor 
integrations mapping AST positions to UTF-16 document offsets, source-snippet 
extractors doing {{line.substring(col-1, lastCol-1)}}). Previously the end 
column counted a supplementary char inside the final token as 2 (UTF-16 units); 
now it counts it as 1 (code point). Such a consumer will now land *earlier* by 
the number of supplementary chars in the final token. Note this only aligns the 
*end* with the *start*: {{columnNumber}} was already code-point based, so these 
tools were already off on the start side for astral source — the change makes 
both ends consistent rather than introducing a new basis.
* *Snapshot/golden tests* that pin exact column numbers, syntax-error caret 
positions, or power-assert message text for source containing emoji. The 
power-assert message in particular changes from truncated to complete, so any 
test asserting the old (buggy) output needs updating.
* *Downstream AST consumers* (linters, contract/spec frameworks, code-coverage 
source mapping) that derived ranges from {{lastColumnNumber}} over astral 
source — they shift from the old hybrid value to a consistent code-point value.

h4. Net characterization

This is best framed as a *consistency fix*: {{columnNumber}} (start) was 
already code-point based; {{lastColumnNumber}} (end) now matches. A consumer 
that correctly converts code-point columns to UTF-16 indices *benefits*; a 
consumer that relied on the old hybrid end as an accidental approximation could 
*regress* — but in both cases *only for source containing supplementary 
characters*. There is no behavioral change for code without them.

> SourceText slices UTF-16 with code-point AST columns, truncating source for 
> supplementary characters
> ----------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12085
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12085
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>              Labels: breaking
>
> Power-assert renders truncated source text for any assertion containing 
> supplementary (astral-plane) characters such as emoji. Anything reusing 
> {{SourceText}} (e.g. groovy-contracts/groovy-verify source capture) is 
> affected, and the same column mismatch mis-positions syntax-error carets.
> h4. Steps to reproduce
> {code:groovy}
> assert (true ? '🥤🐝' : 'z').length() == 999
> {code}
> The rendered expression is cut short by one character per emoji preceding the 
> end (e.g. {{== 999}} renders as {{== 9}}).
> h4. Root cause
> AST column numbers are *code-point*-based ({{GroovyLangLexer}} uses 
> {{CharStreams.fromReader}} -> ANTLR {{CodePointCharStream}}; 
> {{PositionConfigureUtils}} sets {{columnNumber = getCharPositionInLine() + 
> 1}}), but {{SourceText}} slices a *UTF-16* {{String}} from 
> {{SourceUnit.getSample()}} with {{substring()}} using those columns. Each 
> astral char before the slice boundary under-counts the UTF-16 index by one, 
> cutting the slice short.
> Compounding this, {{PositionConfigureUtils}} computes {{lastColumnNumber = 
> getCharPositionInLine() + 1 + token.getText().length()}} -- a code-point 
> start plus a UTF-16 length. The UTF-16 term accidentally compensates for 
> astral chars *inside* the final token (so "emoji as last token" works) but 
> not for those *before* it, which is why the bug looks intermittent.
> h4. Suggested fix
> Make column numbers uniformly code-point-based 
> ({{token.getText().codePointCount(0, len)}} in {{PositionConfigureUtils}}), 
> then convert code-point columns to UTF-16 indices at the slice sites via 
> {{String.offsetByCodePoints}} ({{SourceText}}, and the syntax-error message 
> renderers). Note: converting the hybrid {{lastColumnNumber}} without the 
> {{codePointCount}} fix first will overshoot/throw, so both changes are needed 
> together.
> h4. Affects
> All current versions (present on master). Affects power-assert rendering and 
> any consumer of {{SourceText}}/AST column positions for supplementary 
> characters.



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

Reply via email to