From a920aa79fc8b80bb988bbe8428e5bc85ca0b403b Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 23:00:03 -0800 Subject: [PATCH] chore(quest): plan function pointer patterns and char methods Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 2 ++ quest/m3/ts-char-methods.md | 36 ++++++++++++++++++++++++++++++++++++ quest/m3/ts-fn-patterns.md | 29 +++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 quest/m3/ts-char-methods.md create mode 100644 quest/m3/ts-fn-patterns.md diff --git a/quest/m3/README.md b/quest/m3/README.md index 3dbf54e10..fab1cde9b 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -26,6 +26,8 @@ generic std gap is planned here even when moq never calls it. - [Drop order in std's folds and iterators](/quest/m3/ts-drop-order.md) - the values that drop still refused in std's consumers and collects drop as std's do - [A slice of one value behind any &mut](/quest/m3/ts-slice-of-one.md) - `slice::from_mut` and `array::from_mut` of a `&mut u8`, or of an object no place names, translate - [std's calls of a closure that have no mapping](/quest/m3/ts-closure-calls.md) - `try_fold`, `partition`, `repeat_with`, `sort_by_cached_key`, and `LazyCell::new` translate, dropping what their closures own as std's do +- [A function pointer or a boxed closure as a string pattern](/quest/m3/ts-fn-patterns.md) - `s.split(p)` of a `fn(char) -> bool` or a `Box bool>` translates, dropping a box it owns where std does +- [std's char methods that have no mapping](/quest/m3/ts-char-methods.md) - `is_alphabetic`, `to_lowercase`, `len_utf8`, the escapes, and the rest of `char` translate, called or as values, by std's Unicode version - [Waiting: a slice object for a mutable part of other values](/quest/m3/ts-slice-object.md) - waits on a moq profile showing the `Proxy` view's traps matter, then decides on an `rt.ByteSlice`-like form - [Port the workflow guard to kixelated/quest](/quest/m3/quest-workflow-guard.md) - quest's environment guard parses YAML as this repository's does - [A crate's own find and for_each](/quest/m3/ts-iter-own-calls.md) - a crate's iterator that overrides `find`, `rfind`, `find_map`, or `for_each` translates, each called where std's adaptors and consumers call it diff --git a/quest/m3/ts-char-methods.md b/quest/m3/ts-char-methods.md new file mode 100644 index 000000000..29c10517c --- /dev/null +++ b/quest/m3/ts-char-methods.md @@ -0,0 +1,36 @@ +# [M] std's char methods that have no mapping + +## Goal + +[rust2-ts](/ts/README.md) translates every stable `char` method and conversion, called or +named as a function value, as in `s.split(char::is_alphabetic)` or +`chars.map(char::len_utf8)`. Each returns what std's does for every `char`, by std's +Unicode version, proven by differential tests against native Rust over every code point. + +## Plan + +Found in [#495](https://github.com/moq-dev/rust2/pull/495). Probed on main at +`7c41f2b1`, each of these fails as "unsupported call", both called and as a value: + +- Classes: `is_alphabetic`, `is_lowercase`, `is_uppercase`, `is_alphanumeric`, + `is_control`, `is_numeric`, and `is_digit`. +- Case: `to_lowercase` and `to_uppercase`, and `Display` of the `ToLowercase` and + `ToUppercase` they return. +- Escapes: `escape_unicode`, `escape_debug`, and `escape_default`, and `Display` of + what each returns. +- Encoding: `len_utf8`, `len_utf16`, `encode_utf16`, and `char::decode_utf16`. +- Conversions: `char::from_digit` and `char::try_from(u32)`. + +`encode_utf8` into a `[u8; 4]` fails instead as "a mutable reference to a value JS copies". + +`is_whitespace`, `from_u32`, `to_digit`, `make_ascii_*`, the `is_ascii_*` family, +`From`, and `u32::from` already translate, and a mapped method already translates as a +value: the gap is the call's mapping, not the function value. + +JS's own Unicode data (`\p{...}` regexes, `toLowerCase`) follows the engine's Unicode +version, which may differ from std's `char::UNICODE_VERSION`, and its case mapping is of +strings, not chars. Compare that against lowering core's own tables before choosing, and +measure the cost of either. The case and escape iterators are std iterators, which may +need their own `rt.Iter` and `Display`. + +Public API: the translator's output, and the runtime's functions each needs. diff --git a/quest/m3/ts-fn-patterns.md b/quest/m3/ts-fn-patterns.md new file mode 100644 index 000000000..e9bc47572 --- /dev/null +++ b/quest/m3/ts-fn-patterns.md @@ -0,0 +1,29 @@ +# [S] A function pointer or a boxed closure as a string pattern + +## Goal + +[rust2-ts](/ts/README.md) translates a `str` search whose pattern is a function pointer, +`fn(char) -> bool`, or a boxed closure, `Box` or `Box bool>`, by +value or behind a reference. The search calls it as std's does, and drops a box it owns +where std drops it: as a search that returns a value returns, or with the iterator it +returns. Differential tests against native Rust prove it for every `str` search taking a +pattern. + +## Plan + +Found in [#495](https://github.com/moq-dev/rust2/pull/495), which made a reference to a +closure translate as a pattern. On main at `7c41f2b1`, each of these fails as "unsupported +call to `core::str::::split::<...>`" (or `contains`): + +- `s.contains(p)` and `s.split(p)` of `p: fn(char) -> bool`, and `s.split(&p)`. +- `s.split(b)` and `s.contains(b)` of `b: Box bool>`, and + `s.contains(&mut b)`. +- `s.split(b)` and `s.contains(b)` of `b: Box<{closure}>`. + +`pattern` in `ts/src/lower/library.rs` matches no `ty::FnPtr` and no `Box`. +`called_through` already sees through a `Box` to the closure it owns, and +`called_closures` drops an owned closure where std does. Check how a `Box` is +called and dropped elsewhere before mapping it. A boxed closure that owns a value that +drops is the case to test hardest. + +Public API: the translator's output.