Repository navigation
feat(ts): keep a Cow<str>'s variant - #567
Conversation
Quest-Claim: 33f9f96df25aa927
A Cow<str> is an rt.StrCow: the string it owns, or an rt.Borrowed of the one it borrows, as a Cow<[T]> keeps its variant. Matching Borrowed and Owned, to_mut, into_owned, +, From, Clone, and from_utf8_lossy keep std's variant. A Result rebuilt around a written payload is an object literal, so TS keeps the variant its test narrowed it to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ariant # Conflicts: # ts/MIGRATE.md
A str or slice a Cow-keyed collection is looked up or ranged by stands in as it is, which the Cow's comparisons read as an owned Cow. A hash collection hashes every Cow<str> key, so an owned one, a JS string, and an equal borrowed one are one key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This run was cancelled before it finished. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
quest land watch: the merge queue removed this pull request at b4fc880 because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |
|
quest land watch: armed auto-merge on this pull request at ca4e6e3, so it joins the back of the merge queue once its required checks pass, with the land fixer's fix ( (written by quest land watch) |

Problem
rust2-ts kept a
Cow<str>as a plain JS string, so it kept no variant: matchingCow::Borrowed/Ownedwas refused,to_mutof a borrowed one stayed borrowed, andString::from_utf8_lossylost whether std borrowed or replaced. Quest:quest/m3/ts-cow-str-variant.md, found translatingfrom_utf8_lossyin #537.Approach
A runtime wrapper, as the maintainer's 2026-11-03 direction in #555 favors over a whole-program pass. It has the same shape as
Cow<[T]>:rt.StrCow = string | rt.Borrowed<string>. The owned variant is the plain string aStringalready is, and the borrowed one is wrapped.rt.Borrowed<S>now takes the slice type, so one class serves both.Cow<[T]>paths (cow_borrowed,cow_made,cow_borrows,cow_deref).to_mutstores the text, now owned, in theCow's place, then lends that place as theString.into_ownedandDerefusert.strCowStr,Cloneusesrt.strCowClone, and+/+=usert.strCowAdd, which takes either side whole when the other is empty, as std does.Fromborrows the text of a&str/&Stringand owns aString.Defaultandcollectgive owned values.String::from_utf8_lossyborrows valid UTF-8 and owns replaced text. The unchecked decodes moved tort.strFromUtf8Unchecked.Display/Debug,join/concat,String'sextend/collect/unzipofCow<str>items, andto_stringderef first.Cow-keyed collection by astr(or slice) pass that value as is: theCowcomparisons read a plain string or array as an ownedCow. A hash collection hashes everyCow<str>key, so an owned key (a primitive) and an equal borrowed one are one key.Cow::is_borrowed/is_ownedare unstable and stay refused, now by the generic unsupported-call refusal, as the slice ones already were.Resultrebuilt around a written payload lost TS narrowing, so a second write through one&mutfailedtsc. This was already true on main, for example twopushes into aResult<String, _>'sOk.to_mutmade it show up in the existingexclaimedtest. That store now emits{ ok: true, value }instead ofrt.Ok(value), which keeps the narrowing. Typingrt.Oknarrowly instead also narrowed every initializer and brokelang/shapes.Differential tests against native Rust: a new
cows::variantscase covers construction, conversions, clones,to_mut,+/+=across empty and non-empty sides of each variant, matches by reference, in place, by&mutpassed on, and by value,Option,Vec, fields,join/concat/collect/extend/unzip, and B-tree and hash keys with lookups and ranges by&str.utf8::lossynow checksfrom_utf8_lossy's variant. The reject fixture'scow_matchedcase moved intolang/cows.Measurements
moq's translated codec (
just ts moqat the pin) and the bench's codec and task translations are byte-identical to main apart from the runtime, since none of them usesCow<str>. Bundle size is 65,586 B on both.just ts bench --route main=<main's translation>, geometric mean over 68 codec workloads:The cost lands only on code that uses a
Cow<str>: anrt.Borrowedallocation for each borrowed one made, and aninstanceofon each read.Impact
Cow<str>in the translated API is anrt.StrCowrather thanstring. Read it withrt.strCowStr(c), and passnew rt.Borrowed(s)or a string.rt.Borrowed<S>takes the borrowed slice type, so aCow<[T]>'s isrt.Borrowed<T[]>.rt.Cow<T>is unchanged.rt.stringFromUtf8Lossyreturns anrt.StrCow. The plain decode isrt.strFromUtf8Unchecked.rt.StrCow,rt.strCowStr,rt.strCowClone,rt.strCowAdd,rt.STRING_COW_PART.rt.cowByandrt.hashCoware generic over the borrowed type.ts/MIGRATE.mdhas the entry.Alternatives
to_mut's&mut Stringwould then need a place inside the wrapper, which would diverge fromCow<[T]>. With measured parity on moq, the uniform shape won.Follow-ups
None.
Review
mechanical fix by codex / Codex:
Merged base and mechanically re·solved four conflicts, preserving Cow<str> and byte-helper changes; passed cargo build --locked -p rust2-ts, cargo test --locked -p rust2-ts (104 tests), cargo fmt --all --check in ts/ with pinned nightly, and tsc -p ts/runtime.(head ca4e6e3)Codex (
quest review), gpt-6.1-sol, medium effort, headb4fc8806566e8e572a1d72ca39d7f5d06abd3b46, round 2: no findings.range/range_mutbystrrefused". Fixed: lookup values and range bounds pass as is. Also fixed the hash-key split between variants that this surfaced. Both are covered differentially.Checks run locally:
just cipasses on the reviewed head, andjust ts moqpasses.🤖 Generated with Claude Code