Skip to content

Allow lone-surrogate keyword keys in f(**d) - #8409

Merged
youknowone merged 2 commits into
RustPython:mainfrom
HyoJongPark:fix/kwargs-surrogate-key
Jul 30, 2026
Merged

Allow lone-surrogate keyword keys in f(**d)#8409
youknowone merged 2 commits into
RustPython:mainfrom
HyoJongPark:fix/kwargs-surrogate-key

Conversation

@HyoJongPark

@HyoJongPark HyoJongPark commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Unpacking a dict via ** raised TypeError: keywords must be strings when a key contained a lone surrogate, even though the key is a valid str. CPython accepts any str key on the ** call path, so RustPython should behave the same.

collect(**{'\udc81': 2}):

result
CPython 3.14 {'\udc81': 2}
RustPython (before) TypeError: keywords must be strings
RustPython (after) {'\udc81': 2}

RustPython stores kwargs keys as Rust String, which holds only UTF-8 and cannot represent surrogates.
collect_ex_args rejects a surrogate key when it downcasts it to PyUtf8Str (a str guaranteed to be valid UTF-8).
Relaxing only that check isn't enough: putting a surrogate key into a String then panics in as_str(), so the storage type itself must change to one that can hold surrogates.

Changes

Changed KwArgs's key type from String to Wtf8Buf. (vm/function/argument.rs:417)
Wtf8Buf is the WTF-8 storage PyStr already uses internally, so it holds surrogates while reusing the existing hashing/equality logic.

  • Relax the key downcast from PyUtf8Str to PyStr, so a value only needs to be a str (as in CPython).
    • collect_ex_argsvm/frame.rs:7118
    • from_vectorcall / from_vectorcall_ownedvm/function/argument.rs:162, :193
    • functools.partial merge — vm/stdlib/_functools.rs:441
    • C-API dict_to_kwargscapi/abstract_.rs:31
  • Add inherent get/contains_key/swap_remove/shift_remove(&str) to KwArgs. (vm/function/argument.rs:457)
    • Wtf8Buf can't be looked up by &str directly, so these keep existing lookup call sites compiling unchanged.
  • Widen FromIterator to accept Into<Wtf8Buf>, keeping existing code that builds kwargs from String literals. (vm/function/argument.rs:480)
  • Update the remaining code that iterates kwargs and consumes keys (_ctypes, _operator, _asyncio, structseq, etc.) to the new key type (StringWtf8Buf); behavior is unchanged.
  • Update the two spots in wasm (wasm/src/convert.rs) that pass kwargs to/from JS.
    • On the Rust→JS side, wasm-bindgen only accepts UTF-8, so a surrogate key is replaced with U+FFFD.

Test

  • Behavior (all match CPython 3.14):
    • collect(**{'\udc81': 2}){'\udc81': 2}
    • dict(**{'\udc81': 1}){'\udc81': 1}
    • functools.partial(lambda **kw: kw, **{'\udc81': 1})(){'\udc81': 1}
  • cargo run --release -- -m test test_extcall test_call test_functools test_typing test_ctypes test_asyncio test_astTests result: SUCCESS (no unexpected successes).
  • rustpython-vm unit tests pass. cargo fmt / cargo clippy clean.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Python keyword argument handling for names containing lone surrogate characters, ensuring correct matching and round-tripping.
    • Preserved non-UTF-8/WTF-8 keyword names across function calls, async scheduling/cancellation, and functools.partial.
    • Corrected keyword-name processing in _ast, ctypes (structures/unions), structseq, operator.methodcaller, and WebAssembly interop.
    • Updated keyword validation and JIT argument matching to gracefully reject non-UTF-8 keyword names instead of misbehaving.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: f256011f-22ab-4efd-8e88-a524d35a45e9

📥 Commits

Reviewing files that changed from the base of the PR and between daf72d7 and de4f2d1.

📒 Files selected for processing (1)
  • crates/vm/src/stdlib/_ast/python.rs

📝 Walkthrough

Walkthrough

Keyword argument keys now use WTF-8 storage and conversion throughout RustPython. Function calls, dictionary unpacking, standard-library forwarding, error formatting, and WASM boundaries preserve or handle surrogate-containing string keys.

Changes

WTF-8 keyword argument handling

Layer / File(s) Summary
WTF-8 kwargs contract
crates/vm/src/function/argument.rs
FuncArgs and KwArgs now extract, store, iterate, and look up keyword keys using Wtf8Buf.
Argument collection and matching
crates/vm/src/frame.rs, crates/vm/src/builtins/function.rs, crates/vm/src/builtins/function/jit.rs, crates/capi/src/abstract_.rs
Dictionary unpacking and function binding accept PyStr keys and avoid UTF-8-only matching for surrogate-containing names.
Standard-library kwargs forwarding
crates/stdlib/src/_asyncio.rs, crates/vm/src/stdlib/_ast/python.rs, crates/vm/src/stdlib/_ctypes/*, crates/vm/src/stdlib/_functools.rs
Standard-library keyword construction, merging, interning, and attribute assignment use WTF-8 keys.
Keyword error and representation paths
crates/vm/src/stdlib/_operator.rs, crates/vm/src/stdlib/_typing.rs, crates/vm/src/types/structseq.rs
Keyword representations and unexpected-keyword validation use WTF-8-aware operations.
WASM keyword conversion
crates/wasm/src/convert.rs
JavaScript-to-Python and Python-to-JavaScript keyword conversion uses explicit string conversion at each boundary.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonCall
  participant FuncArgs
  participant KwArgs
  participant FunctionBinding
  PythonCall->>FuncArgs: pass keyword names
  FuncArgs->>KwArgs: store names as Wtf8Buf
  KwArgs->>FunctionBinding: provide keyword entries
  FunctionBinding->>PythonCall: bind valid UTF-8 names or report unexpected keys
Loading

Possibly related issues

  • #8228: Fixes surrogate-containing str keys rejected during dictionary-unpacked function calls.
  • #8410: Covers related surrogate-safe string conversion paths in WASM keyword handling.

Possibly related PRs

Suggested reviewers: youknowone

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: accepting lone-surrogate keyword keys in f(**d).
Linked Issues check ✅ Passed The changes address #8228 end-to-end by accepting PyStr keys, storing them as Wtf8Buf, and updating affected call paths.
Out of Scope Changes check ✅ Passed All edits stay within surrogate-safe keyword handling and related call-convention updates; no unrelated functionality appears added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/wasm/src/convert.rs`:
- Around line 122-127: Preserve lone surrogates at both WASM conversion sites:
in crates/wasm/src/convert.rs lines 122-127, convert incoming JavaScript keyword
names directly from UTF-16 into Wtf8Buf before inserting them into kwargs
instead of routing through String; in lines 235-237, make the
Python-to-JavaScript key conversion explicitly lossless and avoid to_string()
replacement with U+FFFD, using the established Wtf8Buf/UTF-16 conversion
contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 37ecacc2-945c-45a2-ba8e-4939ef17159a

📥 Commits

Reviewing files that changed from the base of the PR and between 1613741 and 62bcc01.

📒 Files selected for processing (13)
  • crates/capi/src/abstract_.rs
  • crates/stdlib/src/_asyncio.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/frame.rs
  • crates/vm/src/function/argument.rs
  • crates/vm/src/stdlib/_ast/python.rs
  • crates/vm/src/stdlib/_ctypes/structure.rs
  • crates/vm/src/stdlib/_ctypes/union.rs
  • crates/vm/src/stdlib/_functools.rs
  • crates/vm/src/stdlib/_operator.rs
  • crates/vm/src/stdlib/_typing.rs
  • crates/vm/src/types/structseq.rs
  • crates/wasm/src/convert.rs

Comment on lines +122 to +127
py_func_args.kwargs.insert(
// JS strings coming in are UTF-16; go through Rust `String`
// (kwargs keys are now WTF-8, so convert String -> Wtf8Buf).
String::from(js_sys::JsString::from(key)).into(),
js_to_py(vm, val),
);

@coderabbitai coderabbitai Bot Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files around wasm convert =="
git ls-files | rg '(^|/)crates/wasm/src/convert\.rs$|Cargo\.toml$' | sed -n '1,80p'

echo
echo "== convert.rs outline =="
ast-grep outline crates/wasm/src/convert.rs --view expanded 2>/dev/null | sed -n '1,160p'

echo
echo "== relevant snippets =="
cat -n crates/wasm/src/convert.rs | sed -n '1,280p'

echo
echo "== WASM/wtfs/JsString references =="
rg -n "Wtf8Buf|JsString|Reflect::set|to_string|keyword|kwargs|wtf" crates/wasm crates -S | sed -n '1,220p'

Repository: RustPython/RustPython

Length of output: 35443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== crate manifests and workspace config =="
cat -n crates/wasm/Cargo.toml | sed -n '1,220p'
echo
cat -n crates/wtf8/Cargo.toml | sed -n '1,160p'
echo
cat -n crates/common/Cargo.toml | sed -n '1,220p'
echo
sed -n '1,120p' Cargo.toml

echo
echo "== wtf8 API =="
rg -n "pub (struct|impl|fn|trait)|Wtf8Buf|Wtf8|from.*utf|utf.*16|TryFrom|From<JsString>|JsValue" crates/wtf8/src crates -S | sed -n '1,260p'

echo
echo "== Python wtf8/string APIs =="
rg -n "impl \.Into.*Wtf8Buf|Wtf8Buf|as_wtf8|into_wtf8|From<.*Wtf8Buf.*>.*PyStr|PyStr" crates/vm crates/common crates/literal -S | sed -n '1,260p'

Repository: RustPython/RustPython

Length of output: 50377


🌐 Web query:

Rust String::from JsString js_sys lone surrogates UTF-16

💡 Result:

When converting a JavaScript string to a Rust String using String::from(js_string) (where js_string is a js_sys::JsString), the conversion is lossy if the JavaScript string contains lone (unpaired) surrogates [1][2]. JavaScript strings are encoded as UTF-16 and can contain unpaired surrogates, whereas Rust strings are strictly required to be valid UTF-8 [2]. During conversion, the wasm-bindgen library utilizes the TextEncoder API to transform the string, which replaces any unpaired surrogates with the Unicode replacement character (U+FFFD,) [2]. Consequently, the resulting Rust string will not be identical to the original JavaScript string [2]. To check if a js_sys::JsString contains lone surrogates before conversion, you can use the is_valid_utf16() method [1][3]. If this method returns false, the string is not valid UTF-16, and String::from will perform a lossy conversion [1][2]. For scenarios where a lossless representation is required, you must avoid converting the string to a Rust String [2]. Instead, you should: 1. Keep the data as a js_sys::JsString handle [2]. 2. Use the js_sys::JsString::iter() method or the char_code_at accessor to manually access the raw UTF-16 code units [1][2]. This allows you to handle the data without triggering the lossy UTF-8 conversion [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Wtf8Buf::from_wide/source =="
cat -n crates/wtf8/src/lib.rs | sed -n '327,372p'

echo
echo "== JsString code unit / UTF-16 docs search in local manifests =="
rg -n "JsString|TextEncoder|char_code_at|iter\\(|is_valid_utf16|from_wide|Wtf8Buf::from" crates wasm -S 2>/dev/null | sed -n '1,220p'

echo
echo "== behavioral model of lone surrogate UTF-16 to Rust String/Wtf8Buf =="
python3 - <<'PY'
import codecs
samples = [
    ("unpaired lead", 0xD800),
    ("unpaired trail", 0xDC00),
    ("surrogate pair", 0x1D306),
]
for name, cp in samples:
    bits = cp if cp <= 0xFFFF else 0xD800 | ((cp - 0x10000) >> 10), 0xDC00 | ((cp - 0x10000) & 0x3ff)
    units = [x for cp in [cp] for x in ([x] if cp <= 0xFFFF else bits)]
    enc_utf8 = codecs.utf_16_encode(bytes().join(i.to_bytes(2, 'big') for i in units))[0] if False else None
    try:
        rust_like = "".join(chr(i) for i in units).encode("utf-8").decode("utf-8")
        print(name, "units", [hex(i) for i in units], "bytes", enc_utf8, "roundtrip", repr(rust_like), "back to units", [ord(c) for c in rust_like])
    except UnicodeDecodeError as e:
        print(name, "Rust UTF-8 decode fails", e)
    else:
        pass
PY

Repository: RustPython/RustPython

Length of output: 20796


Preserve lone-surrogate JavaScript keyword names through the WASM boundary. String::from(js_sys::JsString::from(key)) loses unpaired surrogates before kwargs key insert, and k.to_string().into() substitutes U+FFFD on round trip; store incoming JS keys via a UTF-16-aware Wtf8Buf conversion, and make the outbound Python→JS boundary contract explicit/lossless as well.

📍 Affects 1 file
  • crates/wasm/src/convert.rs#L122-L127 (this comment)
  • crates/wasm/src/convert.rs#L235-L237
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/wasm/src/convert.rs` around lines 122 - 127, Preserve lone surrogates
at both WASM conversion sites: in crates/wasm/src/convert.rs lines 122-127,
convert incoming JavaScript keyword names directly from UTF-16 into Wtf8Buf
before inserting them into kwargs instead of routing through String; in lines
235-237, make the Python-to-JavaScript key conversion explicitly lossless and
avoid to_string() replacement with U+FFFD, using the established Wtf8Buf/UTF-16
conversion contract.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

py_serde and the other str→JS conversions here also panic on surrogates, so fixing only this site would be inconsistent. I'd handle the whole boundary in a separate issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HyoJongPark, agreed. Fixing only keyword names would create an inconsistent partial boundary policy while other PyStr → JS conversion paths still fail on lone surrogates. This is better handled comprehensively in a separate follow-up; I won’t treat it as a blocker for this PR.

Would you like me to open the GitHub issue for the broader WASM string/surrogate boundary work?


✏️ Learnings added
Learnt from: HyoJongPark
Repo: RustPython/RustPython PR: 8409
File: crates/wasm/src/convert.rs:122-127
Timestamp: 2026-07-29T01:10:00.591Z
Learning: In `crates/wasm/src/convert.rs`, Python-string-to-JavaScript conversion is not yet surrogate-safe across the whole WASM boundary: `py_serde` and other `PyStr` → JS conversion paths can panic on lone surrogates. Changes to surrogate behavior in the WASM bridge should define and implement a consistent boundary-wide policy rather than fixing only kwargs key conversions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@HyoJongPark HyoJongPark Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="RustPython/RustPython"
title="Define surrogate-safe Python↔JavaScript string conversion at the WASM boundary"
body="$(cat <<'EOF'
## Summary

Define and implement one consistent, surrogate-safe conversion policy for Python strings crossing the RustPython WASM JavaScript boundary.

RustPython keyword arguments now retain lone surrogates internally using WTF-8, but converting only WASM keyword names would be incomplete: other `PyStr` → JavaScript paths (including `py_serde`) can still fail or perform lossy conversion when a string contains an unpaired surrogate.

## Rationale

JavaScript strings are UTF-16 and may contain lone surrogate code units. Python `str` values in RustPython can represent those values through WTF-8. Rust `String` and several wasm-bindgen conversion paths are not a lossless representation for this data, so ad hoc conversions can replace lone surrogates with U+FFFD or panic.

The whole WASM bridge needs an explicit policy that applies consistently to both directions and all relevant string conversion paths.

## Affected areas

- `crates/wasm/src/convert.rs`
  - JavaScript object/keyword keys converted into Python `FuncArgs`
  - Python function keyword keys converted into JavaScript object properties
  - general object conversion paths
- Python-string-to-JavaScript serialization via `py_serde` / `serde_wasm_bindgen`
- Any shared WASM helpers used for `PyStr`, WTF-8, `js_sys::JsString`, or JavaScript UTF-16 conversion

## Required changes

1. Establish and document the intended surrogate policy for Python↔JavaScript string conversion at the WASM boundary.
2. Implement shared, UTF-16-aware conversion helpers where needed so JavaScript strings with lone surrogates can enter the Python/WTF-8 representation without loss.
3. Make the Python→JavaScript conversion behavior match the policy across keyword names, object/string conversion, and serialization paths.
4. Remove or avoid isolated conversions that route surrogate-containing data through Rust `String` when that would be lossy or panic.
5. Add WASM-focused regression coverage for lone lead and trailing surrogates in relevant key and value conversion paths.

## Acceptance criteria

- No PyStr→JS conversion path in the WASM bridge panics solely because a Python string contains a lone surrogate.
- The behavior for lone surrogates is consistent and explicitly defined across kwargs, object properties, and serialized string values.
- JavaScript→Python conversion preserves UTF-16 lone surrogates where the boundary contract requires it.
- Tests cover both directions and demonstrate the agreed behavior for paired and unpaired surrogates.

## Backlinks

- PR `#8409`: https://github.com/RustPython/RustPython/pull/8409
- Review discussion: https://github.com/RustPython/RustPython/pull/8409#discussion_r3670087497
- Related issue: `#8228`

Requested by `@HyoJongPark`.
EOF
)"

gh issue create \
  --repo "$repo" \
  --title "$title" \
  --body "$body" \
  --assignee "HyoJongPark"

Length of output: 3207


@HyoJongPark, created and assigned the follow-up issue: #8410Define surrogate-safe Python↔JavaScript string conversion at the WASM boundary.

It links back to #8409 and this review discussion, and covers the boundary-wide policy and regression-test requirements. 🐇


🧠 Learnings used
Learnt from: HyoJongPark
Repo: RustPython/RustPython PR: 8409
File: crates/wasm/src/convert.rs:122-127
Timestamp: 2026-07-29T01:10:00.591Z
Learning: In the WASM bridge, ensure Python string (PyStr / py_serde) to JavaScript conversion is surrogate-safe across the full boundary. Lone Unicode surrogates must not cause panics in any PyStr → JS conversion path; instead of fixing behavior for only specific cases (e.g., kwargs key conversions), define and implement a single, consistent boundary-wide surrogate policy in the WASM string conversion layer.

@HyoJongPark
HyoJongPark marked this pull request as draft July 29, 2026 00:32
`f(**{'\udc81': 2})` raised `TypeError: keywords must be strings` even
though the key is a valid `str`, because `KwArgs` stored keys as Rust
`String` (strict UTF-8) and `collect_ex_args` narrowed each key to
`PyUtf8Str` (valid UTF-8 required). CPython only checks that the key is a
`str` (`Py_TPFLAGS_UNICODE_SUBCLASS`), never encoding validity.

Change `KwArgs<T>`'s key type from `String` to `Wtf8Buf` (the WTF-8
storage `PyStr` already uses) and relax the keyword-key downcast from
`PyUtf8Str` to `PyStr` in `collect_ex_args`, `from_vectorcall`/
`from_vectorcall_owned` (which previously panicked on surrogate keys),
the `functools.partial` keyword merge, and the C-API `dict_to_kwargs`.

`Wtf8Buf` borrows only as `Wtf8`, so inherent `get`/`contains_key`/
`swap_remove`/`shift_remove(&str)` on `KwArgs` restore the `&str` lookup
interface `String: Borrow<str>` used to provide (via the zero-cost
`Wtf8::new` cast), and a generic `FromIterator<(K: Into<Wtf8Buf>, T)>`
keeps construction sites unchanged. WTF-8 awareness stays localized to
`function/argument.rs`.

Fixes RustPython#8228

Assisted-by: Claude Code:claude-opus-4-8
Comment thread crates/vm/src/stdlib/_ast/python.rs Outdated

for (key, _value) in &args.kwargs {
let key_obj: PyObjectRef = vm.ctx.new_str(key.as_str()).into();
let key_obj: PyObjectRef = vm.ctx.new_str(key.clone()).into();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there must be a way not to use clone here. could you check? I will find out one if there isn't any way to do it yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

         for (key, _value) in &args.kwargs {
-            let key_obj: PyObjectRef = vm.ctx.new_str(key.clone()).into();
+            let key_obj: PyObjectRef = vm.ctx.new_str(key.as_ref()).into();

Replaced the clone with as_ref() to avoid an unnecessary copy.
new_str only needs a &Wtf8, so instead of cloning the key into an owned Wtf8Buf, I pass key.as_ref() to borrow it instead.

Does this match what you had in mind?

Comment thread crates/vm/src/stdlib/_ast/python.rs Outdated
}
for (key, value) in args.kwargs {
let key_obj: PyObjectRef = vm.ctx.new_str(key.as_str()).into();
let key_obj: PyObjectRef = vm.ctx.new_str(key.clone()).into();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another clone here

`new_str` only needs a `&Wtf8`, so pass `key.as_ref()` rather than
cloning the key into an owned `Wtf8Buf`. The key is reused afterwards
(error message, `intern_str`), so a borrow is the right fit.

Assisted-by: Claude Code:claude-opus-4-8
@HyoJongPark
HyoJongPark requested a review from youknowone July 30, 2026 01:48

@youknowone youknowone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@youknowone
youknowone merged commit 04c3ecf into RustPython:main Jul 30, 2026
26 checks passed
@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

f(**d) rejects str keys containing lone surrogates: TypeError: keywords must be strings

3 participants