fix(runtime): for-of over a dynamically-typed string drives the String iterator (#4892)#4893
Merged
Merged
Conversation
… js_get_iterator (#4892) for-of over a dynamically-typed string value (`for (c of v)` where v: any, or a segmenter-/destructure-derived string) routed through the generic iterator protocol via js_get_iterator. The generic [Symbol.iterator] lookup only resolves the method off an object, so for a string PRIMITIVE it found nothing and js_get_iterator returned the string unchanged; the lazy loop then called .next() on the string itself, throwing "(string).next is not a function". Short-circuit string primitives (heap STRING_TAG + inline SSO) to string_values_iter, mirroring the array short-circuit at the top.
c9746b9 to
242beec
Compare
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
Fixes #4892.
for…of(and spread /Array.from/ array-destructuring) over a string value whose static type is unknown —v: any, or a value flowing out of a segmenter result / destructure — threwTypeError: (string).next is not a function. Statically-typed string for-of was unaffected.Root cause
The dynamic path routes through the generic iterator protocol via
js_get_iterator(lazy for-of, #4786). That helper resolves[Symbol.iterator]by reading the method off an object. For a string primitive there is no object slot, sojs_object_get_symbol_propertyfound nothing andjs_get_iteratorfell through toreturn val_f64— handing the string itself back as the "iterator". The loop then called.next()on a plain string and threw.Strings already have a correct String iterator object (
string/iter_object.rs::string_values_iter, code-point iteration) installed onString.prototype[Symbol.iterator]; the dynamic path just never reached it.Fix
One short-circuit in
js_get_iterator(crates/perry-runtime/src/symbol.rs): when the value is a string primitive (heapSTRING_TAGor inline SSO short string), build the real String iterator viastring_values_iter, mirroring the array short-circuit at the top of the function. SSO strings are materialized to a*const StringHeadervia the existingjs_get_string_pointer_unified.Verification
Against
node --experimental-strip-types:f(v: any){ for (const c of v) n++ }→f("ab")= 2 ✅for (const {segment} of seg.segment("a👍b")) for (const ch of segment) ...= 3 ✅[...v],Array.from(v),const [a,b]=vover dynamic strings — all code-point correct ✅37) still throwsTypeError✅; arrays still iterate ✅End-to-end (the live ink blocker): with #4891 also on
main,string-width@7+now matches Node for mixed strings —stringWidth("a👍b")= 4 (was thehangulClusterWidth→for (const character of visibleSegment)throw).stringWidthof"abc"/"👍"/"👨👩👧"= 3/2/2, all matching Node.cargo test -p perry-runtime(RUST_TEST_THREADS=1): 1007 passed; the only failure is the pre-existing TZ-dependentdate::tests::test_full_year_setters_revive_invalid_date_only, which fails on pristinemaintoo.Related
\p{RGI_Emoji}(property of strings, /v flag) rejected — Rust regex crate has no string-properties; blocks string-width/ink #4889 (previous string-width wall —\p{RGI_Emoji}, merged)ink(React-based TUI framework) end-to-end viaperry.compilePackages#348 (ink end-to-end umbrella)