Summary
for...of over a string value whose static type is unknown (any, or a value flowing out of an iterator/destructure) throws at runtime:
TypeError: (string).next is not a function
Statically-typed for-of over strings works fine — the failure needs the dynamic path, where for-of routes through the generic iterator protocol (js_get_iterator, #4786 lazy for-of) and apparently gets the string itself back as the "iterator", so the subsequent .next() call lands on a plain string. Same family as the pre-existing ".entries()-on-any → next is not a function" note from the #4800 investigation.
Repro (fails on the #4889 / PR #4891 build)
function f(v: any) {
let n = 0;
for (const ch of v) { n++; }
return n;
}
console.log(f("ab")); // Node: 2 — Perry: TypeError: (string).next is not a function
Also reachable without any, via destructured iterator results:
const seg = new Intl.Segmenter();
for (const { segment } of seg.segment("a👍b")) {
for (const ch of segment) {} // throws on the first segment
}
A statically-typed control works (so this is the dynamic-value dispatch, not string iteration itself):
function g(seg: string) {
const v = seg.replace(/^x+/, "");
for (const ch of v) {} // OK
}
Impact
With #4889 fixed (PR #4891), string-width@7+ passes module init and returns correct widths for pure-ASCII and pure-emoji inputs (stringWidth("abc")=3, stringWidth("👍")=2, stringWidth("👨👩👧")=2 ✅). But any mixed string (stringWidth("a👍b")) reaches hangulClusterWidth(), which does for (const character of visibleSegment) on a segmenter-derived (dynamically-typed) string → throws. So this is now the live string-width → wrap-ansi / cli-truncate / slice-ansi → ink (#348) blocker on the string-handling side.
Expected
For-of over any string value drives the String iterator (code points): f("ab") → 2, and stringWidth("a👍b") → 4 (Node-verified).
Related
Summary
for...ofover a string value whose static type is unknown (any, or a value flowing out of an iterator/destructure) throws at runtime:Statically-typed for-of over strings works fine — the failure needs the dynamic path, where for-of routes through the generic iterator protocol (
js_get_iterator, #4786 lazy for-of) and apparently gets the string itself back as the "iterator", so the subsequent.next()call lands on a plain string. Same family as the pre-existing ".entries()-on-any → next is not a function" note from the #4800 investigation.Repro (fails on the #4889 / PR #4891 build)
Also reachable without
any, via destructured iterator results:A statically-typed control works (so this is the dynamic-value dispatch, not string iteration itself):
Impact
With #4889 fixed (PR #4891),
string-width@7+passes module init and returns correct widths for pure-ASCII and pure-emoji inputs (stringWidth("abc")=3,stringWidth("👍")=2,stringWidth("👨👩👧")=2 ✅). But any mixed string (stringWidth("a👍b")) reacheshangulClusterWidth(), which doesfor (const character of visibleSegment)on a segmenter-derived (dynamically-typed) string → throws. So this is now the livestring-width→ wrap-ansi / cli-truncate / slice-ansi → ink (#348) blocker on the string-handling side.Expected
For-of over any string value drives the String iterator (code points):
f("ab")→ 2, andstringWidth("a👍b")→ 4 (Node-verified).Related
\p{RGI_Emoji}(property of strings, /v flag) rejected — Rust regex crate has no string-properties; blocks string-width/ink #4889 / PR fix(regex): expand \p{RGI_Emoji} & friends (properties of strings) to supported emoji primitives (#4889) #4891 (\p{RGI_Emoji}— previous wall, fixed)ink(React-based TUI framework) end-to-end viaperry.compilePackages#348 (ink end-to-end umbrella)