Skip to content

dict: match CPython errors for invalid update sequence elements - #8338

Merged
youknowone merged 5 commits into
RustPython:mainfrom
2jiyong:fix-dict-update-type-error
Jul 29, 2026
Merged

dict: match CPython errors for invalid update sequence elements#8338
youknowone merged 5 commits into
RustPython:mainfrom
2jiyong:fix-dict-update-type-error

Conversation

@2jiyong

@2jiyong 2jiyong commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Codex:gpt-5.6-Sol

Summary

Match CPython's error reporting when dict() or dict.update() receives an invalid sequence element.

  • Add a contextual exception note when an element cannot be converted to a sequence, enabling test_update_type_error.
  • Report the zero-based element index and actual sequence length when an element does not contain exactly two items.

Implementation details

Previously, merge_from_seq2 extracted the first two items and only checked whether a third item existed. As a result, it could only raise the generic error:

Iterator must have exactly two elements

CPython converts each dictionary update element to a sequence before validating its length. This allows it to report both the index of the failing element and its actual length:

dictionary update sequence element #0 has length 3; 2 is required

This change follows the same approach:

  • Exact PyList and PyTuple instances use their underlying slices directly, avoiding an unnecessary allocation.
  • Other iterable objects are collected into a Vec so that their actual length can be determined.
  • Non-iterable elements raise TypeError: object is not iterable.
  • TypeErrors raised while converting or iterating an element are preserved and receive the contextual note:
Cannot convert dictionary update sequence element #0 to a sequence

PyList is imported to support the exact-list fast path, corresponding to CPython's PySequence_Fast handling of exact lists and tuples.

Summary by CodeRabbit

  • Bug Fixes
    • Improved dictionary updates from sequence items with consistent, per-element error context when entries don’t have exactly two elements.
    • Better handling of invalid update inputs, including clearer messaging for non-iterable items.
    • Added optimized fast paths for list/tuple-based update entries while keeping behavior the same for other iterables.
    • Clarified the error message in exception notes when __notes__ exists but isn’t a list.

@coderabbitai

coderabbitai Bot commented Jul 20, 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: a494d410-88d2-4e77-903e-95431919e855

📥 Commits

Reviewing files that changed from the base of the PR and between a0817f9 and f012d4f.

📒 Files selected for processing (2)
  • crates/vm/src/builtins/dict.rs
  • crates/vm/src/exceptions.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/vm/src/exceptions.rs
  • crates/vm/src/builtins/dict.rs

📝 Walkthrough

Walkthrough

Dict update-sequence processing now centralizes conversion, length validation, and contextual error handling for each element. merge_from_seq2 enumerates input elements and delegates key-value pair parsing to the new helpers.

Changes

Dict update sequence handling

Layer / File(s) Summary
Centralized update-element parsing
crates/vm/src/builtins/dict.rs, crates/vm/src/exceptions.rs
Adds list and tuple fast paths, generic iterable handling, exact two-element validation, contextual TypeError notes, and updated add_note error text.
Merge integration
crates/vm/src/builtins/dict.rs
Updates merge_from_seq2 to enumerate elements and use the centralized pair parser.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: moreal, copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 summarizes the main change: aligning dict update-sequence errors with CPython.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_dict.py (TODO: 5)
[x] test: cpython/Lib/test/test_dictcomps.py (TODO: 1)
[x] test: cpython/Lib/test/test_dictviews.py (TODO: 1)
[x] test: cpython/Lib/test/test_userdict.py
[x] test: cpython/Lib/test/mapping_tests.py

dependencies:

dependent tests: (no tests depend on dict)

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone
youknowone requested a review from moreal July 22, 2026 11:28

@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.

other parts looks good, thank you!

Comment thread crates/vm/src/builtins/dict.rs Outdated
@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Jul 24, 2026
Copilot AI review requested due to automatic review settings July 25, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates RustPython’s dict() / dict.update() sequence-merge path to more closely match CPython’s error reporting for invalid update-sequence elements, and enables the upstream Lib/test.test_dict.test_update_type_error by removing the RustPython-only expectedFailure.

Changes:

  • Reworked PyDict::merge_from_seq2 to convert each update element into a sequence-like form first, so it can report the failing element index and its actual length when not exactly 2.
  • Added contextual exception notes for TypeError failures during element conversion/iteration.
  • Removed the @unittest.expectedFailure marker from Lib/test/test_dict.py::test_update_type_error.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Lib/test/test_dict.py Unmarks test_update_type_error as expected-failing now that the runtime behavior is implemented.
crates/vm/src/builtins/dict.rs Implements CPython-aligned per-element validation (index + length) and contextual notes during update-from-seq2 merges, with list/tuple fast paths.
Comments suppressed due to low confidence (1)

crates/vm/src/builtins/dict.rs:238

  • element.get_iter(vm) errors are currently rewritten to TypeError("object is not iterable") for all TypeErrors. This also rewrites TypeErrors raised by a user-defined __iter__ (or other iterator construction paths), which contradicts the goal of preserving element-conversion/iteration TypeErrors and can change observable error messages.

Consider only rewriting the specific non-iterable case (i.e. when get_iter fails with the default "'' object is not iterable" message), and otherwise propagating the original TypeError unchanged (still adding the contextual note).

        let elements = (|| {
            let elem_iter = element.get_iter(vm).map_err(|exc| {
                if exc.fast_isinstance(vm.ctx.exceptions.type_error) {
                    vm.new_type_error("object is not iterable")
                } else {
                    exc
                }
            })?;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/vm/src/builtins/dict.rs Outdated
Comment on lines +190 to +196
fn add_update_sequence_note(exc: &PyBaseExceptionRef, index: usize, vm: &VirtualMachine) {
if exc.fast_isinstance(vm.ctx.exceptions.type_error) {
let note =
format!("Cannot convert dictionary update sequence element #{index} to a sequence");
let _ = vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),));
}
}

@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 for contributing! and welcome to RustPython project

@youknowone
youknowone enabled auto-merge (squash) July 26, 2026 04:47
Comment thread crates/vm/src/builtins/dict.rs Outdated
if exc.fast_isinstance(vm.ctx.exceptions.type_error) {
let note =
format!("Cannot convert dictionary update sequence element #{index} to a sequence");
let _ = vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),));

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.

ah, one more thing to check. is it correct to ignore the result of vm.call_method? don't we need to propagate this error?
If we don't need to propagate this error, please add a comment why let _ = is justified here.

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.

I used let _ = to ignore the error, following the existing add_note handling in type.rs and codecs.rs.
However, CPython propagates an error raised while adding the note and chains the original exception as its __context__.
I’ll update this to match CPython’s behavior.

@youknowone
youknowone disabled auto-merge July 26, 2026 05:07
Copilot AI review requested due to automatic review settings July 29, 2026 06:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

crates/vm/src/exceptions.rs:706

  • BaseException.add_note() currently raises TypeError("Cannot add note: __notes__ is not a list") when __notes__ exists but is not a list. CPython’s error message here is "__notes__ must be a list"; keeping the CPython wording helps compatibility and avoids surprising message diffs for code/tests that assert on it.
        let notes = notes
            .downcast::<PyList>()
            .map_err(|_| vm.new_type_error("Cannot add note: __notes__ is not a list"))?;

Copilot AI review requested due to automatic review settings July 29, 2026 07:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

crates/vm/src/builtins/dict.rs:244

  • The element.get_iter(vm) error mapping converts any TypeError into TypeError("object is not iterable"). That also overwrites TypeErrors raised by an element’s __iter__ implementation (or other TypeErrors during iterator creation), which should be preserved (and only receive the contextual note). Consider only normalizing the specific “'T' object is not iterable” case produced by get_iter for non-iterables, and otherwise returning the original exception.
            let elem_iter = element.get_iter(vm).map_err(|exc| {
                if exc.fast_isinstance(vm.ctx.exceptions.type_error) {
                    vm.new_type_error("object is not iterable")
                } else {
                    exc

crates/vm/src/builtins/dict.rs:203

  • add_update_sequence_note currently returns the add_note failure (note_err) if adding the note fails. That can replace the original TypeError from dict update conversion (e.g., if user code mutates exc.__notes__ to a non-list), contradicting the intent to preserve the original error and making note attachment change the raised exception. Other call sites treat add_note as best-effort and ignore failures (e.g. crates/vm/src/codecs.rs:376, crates/vm/src/builtins/type.rs:2520-2522).

This issue also appears on line 240 of the same file.

        match vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),)) {
            Ok(_) => exc,
            Err(note_err) => {
                note_err.set___context__(Some(exc));
                note_err

@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.

Looks great, thank you!

@youknowone
youknowone merged commit ede18e4 into RustPython:main Jul 29, 2026
27 checks passed
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.

4 participants