Title: `f(**d)` rejects str keys containing lone surrogates: TypeError: keywords must be strings · Issue #8228 · RustPython/RustPython · GitHub
Open Graph Title: `f(**d)` rejects str keys containing lone surrogates: TypeError: keywords must be strings · Issue #8228 · RustPython/RustPython
X Title: `f(**d)` rejects str keys containing lone surrogates: TypeError: keywords must be strings · Issue #8228 · RustPython/RustPython
Description: Summary Unpacking a dict whose keys contain lone surrogates via ** raises TypeError: keywords must be strings, but the keys are str instances. CPython accepts any str key through the ** call path. def collect(**kw): return kw collect(**{...
Open Graph Description: Summary Unpacking a dict whose keys contain lone surrogates via ** raises TypeError: keywords must be strings, but the keys are str instances. CPython accepts any str key through the ** call path. ...
X Description: Summary Unpacking a dict whose keys contain lone surrogates via ** raises TypeError: keywords must be strings, but the keys are str instances. CPython accepts any str key through the ** call path. ...
Opengraph URL: https://github.com/RustPython/RustPython/issues/8228
X: @github
Domain: Github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"`f(**d)` rejects str keys containing lone surrogates: TypeError: keywords must be strings","articleBody":"# Summary\n\nUnpacking a dict whose keys contain lone surrogates via `**` raises `TypeError: keywords must be strings`, but the keys *are* `str` instances. CPython accepts any `str` key through the `**` call path.\n\n```python\ndef collect(**kw):\n return kw\n\ncollect(**{'\\udc81': 2}) # TypeError: keywords must be strings\ndict(**{'\\udc81': 1}) # TypeError: keywords must be strings\n```\n\nCPython 3.14.2:\n\n```\n\u003e\u003e\u003e collect(**{'\\udc81': 2})\n{'\\udc81': 2}\n```\n\n`'\\udc81'` is a real `str` (e.g. produced by `os.fsdecode` of undecodable bytes, `surrogateescape`-decoded data, etc.), so this rejects valid programs. Verified on `main` @ 7044fdcc8.\n\n# Cause\n\n`ExecutingFrame::collect_ex_args` (`crates/vm/src/frame.rs:6539`) validates kwargs keys with\n\n```rust\nkey.downcast_ref::\u003cPyUtf8Str\u003e()\n .ok_or_else(|| vm.new_type_error(\"keywords must be strings\"))?;\n```\n\nA str containing a lone surrogate is a `PyStr` whose payload is Wtf8, not `PyUtf8Str`, so the downcast fails even though the key is a `str`. The same pattern exists at `frame.rs:6521`, `crates/vm/src/stdlib/_functools.rs` (partial), and `crates/capi/src/abstract_.rs`.\n\nThe deeper constraint is that `KwArgs`/`FuncArgs` keyword names are Rust `String` (UTF-8), so a surrogate-carrying key currently has no representation in the calling convention; fixing this properly means keyword names need to be `Wtf8Buf` (or `PyStrRef`) end-to-end. The error message check should be `downcast_ref::\u003cPyStr\u003e()` (matching CPython, which only rejects non-`str` keys) once the storage can hold it.\n\n---\n*Found while running synthetic benchmark/compat suites; researched and reported by Claude on behalf of @youknowone.*\n","author":{"url":"https://github.com/youknowone","@type":"Person","name":"youknowone"},"datePublished":"2026-07-06T15:34:33.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/8228/RustPython/issues/8228"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:28036b9b-5eb1-e226-d842-1c52fdcf0c86 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9B70:D629A:8B6B:C82F:6A61F62F |
| html-safe-nonce | 79c9215b47374795f810302bd097b1e49f14d3cafb0faf8465604dcdd1cd36b1 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5QjcwOkQ2MjlBOjhCNkI6QzgyRjo2QTYxRjYyRiIsInZpc2l0b3JfaWQiOiIyMDQ2NzQ2Mjk1NjYzNzE5OTgzIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 87d1be2ed45d6e48f7e12aa36961d1f5cb3c4da14c708241cfb7a97b11f5fc95 |
| hovercard-subject-tag | issue:4820663945 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/RustPython/RustPython/8228/issue_layout |
| twitter:image | https://opengraph.githubassets.com/58ef520c3910a00b4b3ea903eac42e22bb008453f534f84f23a87c27dfd6c7cf/RustPython/RustPython/issues/8228 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/58ef520c3910a00b4b3ea903eac42e22bb008453f534f84f23a87c27dfd6c7cf/RustPython/RustPython/issues/8228 |
| og:image:alt | Summary Unpacking a dict whose keys contain lone surrogates via ** raises TypeError: keywords must be strings, but the keys are str instances. CPython accepts any str key through the ** call path. ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | youknowone |
| hostname | github.com |
| expected-hostname | github.com |
| None | d88d9dd41cd32712dc23ff01ba406271c88c4858198430c255d591be42a1166f |
| turbo-cache-control | no-preview |
| go-import | github.com/RustPython/RustPython git https://github.com/RustPython/RustPython.git |
| octolytics-dimension-user_id | 39710557 |
| octolytics-dimension-user_login | RustPython |
| octolytics-dimension-repository_id | 135201145 |
| octolytics-dimension-repository_nwo | RustPython/RustPython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 135201145 |
| octolytics-dimension-repository_network_root_nwo | RustPython/RustPython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | a7f024eda6b8ce06d29c9276168423bfe05bafae |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width