fix(ir): keep CanonicalWords a fixed point on caseless capitals - #366
Open
OmarAlJarrah wants to merge 1 commit into
Open
fix(ir): keep CanonicalWords a fixed point on caseless capitals#366OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
CanonicalWords is documented as a fixed point and the fuzz target asserts
it, but feeding a canonical back in could still change it:
CanonicalWords("ℤℤA") == "ℤℤa"
CanonicalWords("ℤℤa") == "ℤ_ℤa"
The acronym-tail rule splits at the last capital of a run when the next
rune is lowercase. "ℤℤA" has no lowercase rune at all, so the first pass
produces one word and lowercases it -- and lowercasing is what supplies
the missing lowercase letter, since ℤ has no lowercase form and survives
as a capital while A becomes a. The second pass sees two capitals with a
lowercase following, fires the tail rule, and yields "ℤ_ℤa".
This is #187's mechanism at a position #187 did not reach: there the
caseless capital followed the cased one, here it precedes it. The
consequence is the same -- the segmentation depends on the casing of the
source spelling rather than on its words, and Canonical is ABI precisely
so an emitter never has to know which spelling produced it.
The tail rule now requires that lowercasing change at least one of the
two capitals either side of the split. Requiring it of both would be too
strong in either direction: asking it of the left rune loses ℤ_server,
asking it of the right loses http_ℤerver, and canonicalCases pins both.
Asking it of the pair keeps every pinned row, and makes the grammar a
fixed point for a reason that can be stated: one pass lowercases every
rune that carries case, so neither case rule has anything left to fire
on, and the letter/digit rule is case-independent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ir.CanonicalWordsis documented as a fixed point, andFuzzCanonicalWords_Properties/assertIdempotentasserts it. Feeding acanonical back in could still change it:
The acronym-tail rule in
wordBoundarysplits at the last capital of a run whenthe next rune is lowercase.
"ℤℤA"has no lowercase rune at all, so the firstpass produces one word and lowercases it — and lowercasing is exactly what
supplies the missing lowercase letter:
ℤhas no lowercase form and survives asa capital, while
Abecomesa. The second pass now sees two capitals with alowercase letter following, fires the tail rule, and yields
"ℤ_ℤa".This is #187's mechanism at a position #187 did not reach — there the caseless
capital followed the cased one, here it precedes it — and the consequence is the
same one #187 recorded: the segmentation depends on the casing of the source
spelling rather than on its words, and
Naming.Canonicalis ABI precisely so anemitter never has to know which spelling produced it.
ir/naming.go's own comment stated the invariant this broke, and stated it asthe reason the two rules were safe together:
The run does not produce it. The rune after the run does, by being lowercased.
The fix, and why it is this one
The tail rule now requires that lowercasing change at least one of the two
capitals either side of the split. Requiring it of both is too strong in either
direction, and each direction breaks a row
canonicalCasesalready pins:ℤServerℤserver✗ℤ_server✓ℤ_server✓HTTPℤerverhttp_ℤerver✓httpℤerver✗http_ℤerver✓ℤℤaℤℤa✓ℤℤa✓ℤℤa✓Asking it of the pair keeps every pinned row and makes the grammar a fixed point
for a reason that can be stated rather than observed: one pass lowercases every
rune that carries case, so both case rules — each of which now requires one at
the boundary — have nothing left to fire on, and the letter/digit rule is
case-independent and has already been applied wherever it applies.
Three spellings change, all of them a caseless run splitting itself:
"ℤℤa"→ℤℤa(wasℤ_ℤa),"aℤℤb"→aℤℤb(wasaℤ_ℤb), and the"ℤℤA"above. None is pinned by a table row today; all three are now.Test plan
canonicalCasespinningℤℤA,ℤℤaandℤℤAb, the lastholding that a caseless run still does end at a capital that carries case.
"ℤℤA"added toadversarialRuneswith the reason it is there: the seedbeside it,
"ℤℤa", was already present and passed, because the input thatbroke the property was the uppercase spelling one mutation away from it.
TestCanonicalWords_Conformance/so_the_lowercase_spelling_segments_the_same_wayfails
expected: "ℤℤa", actual: "ℤ_ℤa", and the fuzz seed fails withnot a fixed point / input: "ℤℤA" / once: "ℤℤa" / twice: "ℤ_ℤa".-fuzzfor 90s, 5,303,142 executions,228 interesting inputs, no failure.
No golden moved: no name in the corpus contains a letter with no lowercase form.
Full gate green:
gofmt,go vet ./...,golangci-lint run(0 issues),go build ./...,./scripts/check-coverage.sh(all 4947 statements covered).Closes #336