Skip to content

Fix genericalias parameters #5841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

youknowone
Copy link
Member

@youknowone youknowone commented Jun 26, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced generic type parameter handling with support for default parameters and expanded tuple substitutions, improving generic alias behavior.
    • Added iteration support for TypeVarTuple to match standard Python unpacking behavior.
  • Tests

    • Removed expected failure markers from multiple test cases to reflect improved functionality and stability.
    • Adjusted expected failure status for specific typing tests related to variable substitution and valid uses of Callable and ParamSpec.
    • Updated tests for generic alias registration with functools.singledispatch to pass as expected.
  • Chores

    • Added internal support for the __typing_subst__ attribute to align substitution logic with standard Python behavior.
    • Extended internal constants with __typing_subst__ for improved VM attribute handling.
    • Improved typing module integration by delegating generic class item and subclass initialization to internal typing functions.
    • Added clarifying documentation for attribute access method parameter types.

Copy link

coderabbitai bot commented Jun 26, 2025

Warning

Rate limit exceeded

@youknowone has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 5 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between af2d36c and bd133fd.

📒 Files selected for processing (5)
  • Lib/_collections_abc.py (3 hunks)
  • Lib/test/test_exception_group.py (1 hunks)
  • Lib/test/test_functools.py (0 hunks)
  • Lib/test/test_typing.py (2 hunks)
  • vm/src/builtins/genericalias.rs (7 hunks)

"""

Walkthrough

This update removes expected failure decorators from several Python test methods, rewrites generic alias parameter handling in Rust to support default parameters and tuple expansion, and adds a new constant for __typing_subst__ in the VM context. The changes align parameter substitution logic with CPython's behavior and improve type parameter management.

Changes

File(s) Change Summary
Lib/test/test_genericalias.py, Removed @unittest.expectedFailure decorators and related comments from test methods.
Lib/test/test_dataclasses.py,
Lib/test/test_types.py,
Lib/test/test_typing.py, Removed @unittest.expectedFailure decorators and related comments from multiple typing-related test methods.
Lib/test/test_functools.py Removed @unittest.expectedFailure decorators and related comments from singledispatch generic alias registration tests.
vm/src/builtins/genericalias.rs Removed is_typevar; rewrote make_parameters; changed tuple_index to accept slices; updated subs_tvars and subs_parameters to handle default parameters, tuple expansion, and __typing_subst__ substitution.
vm/src/vm/context.rs Added __typing_subst__ constant to the ConstName struct.
vm/src/protocol/object.rs Added doc comment clarifying accepted types for get_attr parameter.
vm/src/stdlib/typing.rs Added Iterable implementation for TypeVarTuple; introduced helper to call typing module functions; modified Generic class methods to delegate to typing module functions.

Sequence Diagram(s)

sequenceDiagram
    participant PythonTest as Python Test
    participant RustVM as Rust VM (genericalias.rs)
    participant Context as VM Context

    PythonTest->>RustVM: Substitute generic alias parameters
    RustVM->>RustVM: Check for __typing_subst__ attribute
    alt Has __typing_subst__
        RustVM->>RustVM: Call __typing_subst__ on argument
    else No __typing_subst__
        RustVM->>RustVM: Use subs_tvars (with tuple expansion if needed)
    end
    RustVM->>Context: Access __typing_subst__ constant
    RustVM-->>PythonTest: Return substituted parameters tuple
Loading

Suggested reviewers

  • arihant2math

Poem

In fields of types where tuples grow,
A rabbit hops with code in tow.
No more failures marked in test,
Default params now handled best.
With __typing_subst__ in the mix,
Rust and Python’s types now click.
🐇✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
vm/src/builtins/genericalias.rs (3)

217-257: Consider optimizing vector resizing for better performance.

The current implementation resizes the vector multiple times during execution, which could lead to unnecessary allocations. Consider collecting parameters into a temporary structure first or using a more efficient growth strategy.

You could optimize by first collecting all parameters and then creating the final vector:

 pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
-    let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
-    let mut iparam = 0;
+    let mut seen = std::collections::HashSet::new();
+    let mut parameters = Vec::new();

     for arg in args {
         // We don't want __parameters__ descriptor of a bare Python class.
         if arg.class().is(vm.ctx.types.type_type) {
             continue;
         }

         // Check for __typing_subst__ attribute (like CPython)
         if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() {
-            // Use tuple_add equivalent logic
-            if tuple_index_vec(&parameters, arg).is_none() {
-                if iparam >= parameters.len() {
-                    parameters.resize(iparam + 1, vm.ctx.none());
-                }
-                parameters[iparam] = arg.clone();
-                iparam += 1;
+            if seen.insert(arg.as_object().id()) {
+                parameters.push(arg.clone());
             }
         } else if let Ok(subparams) = arg.get_attr(identifier!(vm, __parameters__), vm) {
             if let Ok(sub_params) = subparams.try_to_ref::<PyTuple>(vm) {
-                let len2 = sub_params.len();
-                // Resize if needed
-                if iparam + len2 > parameters.len() {
-                    parameters.resize(iparam + len2, vm.ctx.none());
-                }
                 for sub_param in sub_params {
-                    // Use tuple_add equivalent logic
-                    if tuple_index_vec(&parameters[..iparam], sub_param).is_none() {
-                        if iparam >= parameters.len() {
-                            parameters.resize(iparam + 1, vm.ctx.none());
-                        }
-                        parameters[iparam] = sub_param.clone();
-                        iparam += 1;
+                    if seen.insert(sub_param.as_object().id()) {
+                        parameters.push(sub_param.clone());
                     }
                 }
             }
         }
     }

-    // Resize to actual size
-    parameters.truncate(iparam);
     PyTuple::new_ref(parameters, &vm.ctx)
 }

261-269: Refactor to avoid code duplication between tuple_index and tuple_index_vec.

Both functions perform the same operation of finding an item by identity. Consider creating a generic helper function.

 #[inline]
-fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option<usize> {
-    tuple.iter().position(|element| element.is(item))
+fn find_by_identity<'a>(iter: impl Iterator<Item = &'a PyObjectRef>, item: &PyObjectRef) -> Option<usize> {
+    iter.position(|element| element.is(item))
 }

 #[inline]
-fn tuple_index_vec(vec: &[PyObjectRef], item: &PyObjectRef) -> Option<usize> {
-    vec.iter().position(|element| element.is(item))
+fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option<usize> {
+    find_by_identity(tuple.iter(), item)
+}
+
+#[inline]
+fn tuple_index_vec(vec: &[PyObjectRef], item: &PyObjectRef) -> Option<usize> {
+    find_by_identity(vec.iter(), item)
 }

375-392: Clarify error message for unsupported default values.

The comment indicates that CPython doesn't support default values in this context, but the error message doesn't make this clear to users.

             } else {
                 // CPython doesn't support default values in this context
                 return Err(vm.new_type_error(format!(
-                    "No argument provided for parameter at index {}",
-                    idx
+                    "No argument provided for parameter '{}' at index {} (default values not supported with __typing_subst__)",
+                    arg.repr(vm).unwrap_or_else(|_| "?".to_string()),
+                    idx
                 )));
             }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3031d5b and 7bc7887.

📒 Files selected for processing (3)
  • Lib/test/test_genericalias.py (0 hunks)
  • vm/src/builtins/genericalias.rs (4 hunks)
  • vm/src/vm/context.rs (1 hunks)
💤 Files with no reviewable changes (1)
  • Lib/test/test_genericalias.py
🧰 Additional context used
🧠 Learnings (1)
vm/src/builtins/genericalias.rs (2)
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: Use the macro system (such as 'pyclass', 'pymodule', 'pyfunction') when implementing Python functionality in Rust for RustPython.
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: When implementing a Python module in Rust for RustPython, use the #[pymodule] attribute and expose functions and classes using #[pyfunction], #[pyclass], and #[pymethod] as appropriate.
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Run tests under miri
  • GitHub Check: Check Rust code with rustfmt and clippy
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Ensure compilation on various targets
🔇 Additional comments (2)
vm/src/vm/context.rs (1)

226-226: LGTM!

The addition of __typing_subst__ constant follows the established pattern and is necessary for the enhanced generic alias parameter handling.

vm/src/builtins/genericalias.rs (1)

284-309: TypeVarTuple handling looks correct!

The implementation properly handles TypeVarTuple expansion by checking for the iterator slot and expanding tuple elements when appropriate. This aligns with CPython's behavior for type parameter substitution.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
vm/src/builtins/genericalias.rs (1)

217-234: Consider simplifying the vector management logic.

The index-based insertion with resize calls could be simplified. The current approach works but may be harder to maintain.

Consider this cleaner approach:

-    let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
-    let mut iparam = 0;
-
     for arg in args {
         // We don't want __parameters__ descriptor of a bare Python class.
         if arg.class().is(vm.ctx.types.type_type) {
             continue;
         }
 
         // Check for __typing_subst__ attribute (like CPython)
         if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() {
-            // Use tuple_add equivalent logic
-            if tuple_index_vec(&parameters, arg).is_none() {
-                if iparam >= parameters.len() {
-                    parameters.resize(iparam + 1, vm.ctx.none());
-                }
-                parameters[iparam] = arg.clone();
-                iparam += 1;
+            if !parameters.contains(arg) {
+                parameters.push(arg.clone());
             }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc7887 and b4f4ffa.

📒 Files selected for processing (5)
  • Lib/test/test_dataclasses.py (0 hunks)
  • Lib/test/test_genericalias.py (0 hunks)
  • Lib/test/test_types.py (0 hunks)
  • vm/src/builtins/genericalias.rs (4 hunks)
  • vm/src/vm/context.rs (1 hunks)
💤 Files with no reviewable changes (3)
  • Lib/test/test_genericalias.py
  • Lib/test/test_types.py
  • Lib/test/test_dataclasses.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • vm/src/vm/context.rs
🧰 Additional context used
🧠 Learnings (1)
vm/src/builtins/genericalias.rs (2)
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: Use the macro system (such as 'pyclass', 'pymodule', 'pyfunction') when implementing Python functionality in Rust for RustPython.
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: When implementing a Python module in Rust for RustPython, use the #[pymodule] attribute and expose functions and classes using #[pyfunction], #[pyclass], and #[pymethod] as appropriate.
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run tests under miri
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Check Rust code with rustfmt and clippy
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run snippets and cpython tests (windows-latest)
🔇 Additional comments (5)
vm/src/builtins/genericalias.rs (5)

266-269: LGTM! Clean helper function implementation.

The tuple_index_vec function correctly uses identity comparison with is() which is appropriate for Python object references.


256-258: Good optimization with truncate.

Using truncate instead of shrink_to_fit is more appropriate here since we know the exact final size needed.


235-254: Verify the nested parameter expansion logic.

The recursive parameter expansion with index management is complex and could be error-prone. The logic appears correct but warrants careful testing.

#!/bin/bash
# Search for tests that cover nested parameter expansion scenarios
rg -A 10 -B 5 "__parameters__.*__parameters__" --type py

284-314: Verify TypeVarTuple detection mechanism.

The TypeVarTuple detection using param.class().slots.iter.load().is_some() is interesting but may not be the most reliable way to identify TypeVarTuple objects.

#!/bin/bash
# Search for TypeVarTuple usage patterns and tests
rg -A 5 -B 5 "TypeVarTuple" --type py
ast-grep --pattern 'class TypeVarTuple($_) { $$$ }'

338-370: ```shell
#!/bin/bash
sed -n '330,370p' vm/src/builtins/genericalias.rs


</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 374 to 392
for arg in args.iter() {
// Check for __typing_subst__ attribute directly (like CPython)
if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) {
let idx = tuple_index(&parameters, arg).unwrap();
if idx < num_items {
// Call __typing_subst__ with the argument
let substituted = subst.call((arg_items[idx].clone(),), vm)?;
new_args.push(substituted);
} else {
subs_tvars(arg.clone(), &parameters, arg_items, vm)
// CPython doesn't support default values in this context
return Err(vm.new_type_error(format!(
"No argument provided for parameter at index {}",
idx
)));
}
})
.collect::<PyResult<Vec<_>>>()?;
} else {
new_args.push(subs_tvars(arg.clone(), &parameters, arg_items, vm)?);
}
}
Copy link

@coderabbitai coderabbitai bot Jun 26, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue with missing parameter handling.

The error handling for missing parameters when using __typing_subst__ may be too strict. CPython might handle this case differently, particularly with default values.

         } else {
-            // CPython doesn't support default values in this context
-            return Err(vm.new_type_error(format!(
-                "No argument provided for parameter at index {}",
-                idx
-            )));
+            // Use the parameter itself as default (CPython behavior)
+            new_args.push(arg.clone());
         }

This needs verification against CPython's actual behavior in similar scenarios.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for arg in args.iter() {
// Check for __typing_subst__ attribute directly (like CPython)
if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) {
let idx = tuple_index(&parameters, arg).unwrap();
if idx < num_items {
// Call __typing_subst__ with the argument
let substituted = subst.call((arg_items[idx].clone(),), vm)?;
new_args.push(substituted);
} else {
subs_tvars(arg.clone(), &parameters, arg_items, vm)
// CPython doesn't support default values in this context
return Err(vm.new_type_error(format!(
"No argument provided for parameter at index {}",
idx
)));
}
})
.collect::<PyResult<Vec<_>>>()?;
} else {
new_args.push(subs_tvars(arg.clone(), &parameters, arg_items, vm)?);
}
}
for arg in args.iter() {
// Check for __typing_subst__ attribute directly (like CPython)
if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) {
let idx = tuple_index(&parameters, arg).unwrap();
if idx < num_items {
// Call __typing_subst__ with the argument
let substituted = subst.call((arg_items[idx].clone(),), vm)?;
new_args.push(substituted);
} else {
// Use the parameter itself as default (CPython behavior)
new_args.push(arg.clone());
}
} else {
new_args.push(subs_tvars(arg.clone(), &parameters, arg_items, vm)?);
}
}
🤖 Prompt for AI Agents
In vm/src/builtins/genericalias.rs between lines 374 and 392, the current code
returns a type error if an argument index exceeds the number of items, assuming
no default values are supported like CPython. To address the potential issue,
verify CPython's behavior regarding default parameters with __typing_subst__ and
adjust the error handling accordingly. If CPython allows default values or
handles missing parameters differently, modify the code to support default
values or skip substitution for those parameters instead of returning an error.

Copy link
Member Author

Choose a reason for hiding this comment

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

python3.13 -c "
import typing
from typing import TypeVar, Generic

# Create a test case that would trigger the missing parameter scenario
T = TypeVar('T')
U = TypeVar('U', bound=str)

class MyGeneric(Generic[T, U]):
    pass

# Test various substitution scenarios
try:
    # This should help us understand how CPython handles missing parameters
    result = MyGeneric[int]  # Missing second parameter
    print('CPython allows partial substitution:', result)
except Exception as e:
    print('CPython error:', e)
"

CPython error: Too few arguments for <class '__main__.MyGeneric'>; actual 1, expected at least 2

Copy link

Choose a reason for hiding this comment

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

@youknowone Thank you for providing that test! Your output clearly shows that CPython raises an error with the format "Too few arguments for...; actual X, expected at least Y" when there are insufficient arguments for a generic type.

Looking at the current implementation in the diff, I can see that the RustPython code actually handles this correctly:

if num_items < min_required {
    return Err(vm.new_type_error(format!(
        "Too few arguments for {}; actual {}, expected at least {}",
        repr(vm)?,
        num_items,
        min_required
    )));
}

This matches CPython's behavior exactly. My original suggestion was incorrect - I was concerned that the error handling for the __typing_subst__ case might be too strict, but your test demonstrates that CPython does indeed raise errors for insufficient arguments.

The current implementation correctly:

  1. Validates argument counts upfront, considering parameters with defaults
  2. Uses error messages that match CPython's format
  3. Only reaches the "No argument provided for parameter at index" error in cases that should be unreachable if the earlier validation works properly

I retract my original suggestion. The current implementation appears to align properly with CPython's behavior.

🐰💭

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
vm/src/builtins/genericalias.rs (1)

374-392: This still needs verification against CPython behavior.

The error handling for missing parameters when using __typing_subst__ may still be too strict compared to CPython's actual behavior, as noted in the previous review.

The previous review comment about this exact issue remains valid. Please verify CPython's behavior for this scenario:

#!/bin/bash
# Verify CPython's behavior with __typing_subst__ and missing parameters
python3 -c "
import typing
from typing import TypeVar, Generic

# Create a test case that would trigger the missing parameter scenario
T = TypeVar('T')
U = TypeVar('U', bound=str)

class MyGeneric(Generic[T, U]):
    pass

# Test various substitution scenarios
try:
    # This should help us understand how CPython handles missing parameters
    result = MyGeneric[int]  # Missing second parameter
    print('CPython allows partial substitution:', result)
except Exception as e:
    print('CPython error:', e)
"
🧹 Nitpick comments (2)
vm/src/builtins/genericalias.rs (2)

220-264: Consider simplifying the complex parameter handling logic.

The rewritten make_parameters function introduces significant complexity with vector resizing, index tracking, and deduplication logic. While the approach appears to align with CPython's behavior, the implementation could be error-prone.

Consider these potential improvements:

  1. Simplify vector management: Instead of frequent resizing, use a Vec that grows naturally and truncate at the end.
  2. Optimize deduplication: Consider using a HashSet for O(1) duplicate detection instead of linear search via tuple_index.
  3. Add comprehensive comments: The complex logic would benefit from detailed comments explaining the CPython behavior being mimicked.
pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
-    let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
-    let mut iparam = 0;
+    let mut parameters: Vec<PyObjectRef> = Vec::new();
+    let mut seen: std::collections::HashSet<*const PyObject> = std::collections::HashSet::new();

    for arg in args {
        // We don't want __parameters__ descriptor of a bare Python class.
        if arg.class().is(vm.ctx.types.type_type) {
            continue;
        }

        // Check for __typing_subst__ attribute (like CPython)
        if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() {
-            // Use tuple_add equivalent logic
-            if tuple_index(&parameters, arg).is_none() {
-                if iparam >= parameters.len() {
-                    parameters.resize(iparam + 1, vm.ctx.none());
-                }
-                parameters[iparam] = arg.clone();
-                iparam += 1;
-            }
+            if seen.insert(arg.as_ptr()) {
+                parameters.push(arg.clone());
+            }
        } else if let Ok(subparams) = arg.get_attr(identifier!(vm, __parameters__), vm) {
            // ... similar simplification for sub_params handling
        }
    }

-    // Resize to actual size
-    parameters.truncate(iparam);
    PyTuple::new_ref(parameters, &vm.ctx)
}

284-308: Good TypeVarTuple handling, but add clarifying comments.

The logic for handling TypeVarTuple expansion is correct, but the implementation would benefit from clearer comments explaining the behavior.

                    for arg in sub_params.iter() {
                        if let Some(idx) = tuple_index(params.as_slice(), arg) {
                            let param = &params[idx];
                            let substituted_arg = &arg_items[idx];

-                            // Check if this is a TypeVarTuple (has tp_iter)
+                            // TypeVarTuple handling: Check if this parameter has iteration support
+                            // and the substituted argument is a tuple that should be expanded
                            if param.class().slots.iter.load().is_some()
                                && substituted_arg.try_to_ref::<PyTuple>(vm).is_ok()
                            {
-                                // TypeVarTuple case - extend with tuple elements
+                                // Expand tuple elements for TypeVarTuple substitution
                                if let Ok(tuple) = substituted_arg.try_to_ref::<PyTuple>(vm) {
                                    for elem in tuple.iter() {
                                        sub_args.push(elem.clone());
                                    }
                                    continue;
                                }
                            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd3525 and 433f24e.

📒 Files selected for processing (2)
  • Lib/test/test_typing.py (1 hunks)
  • vm/src/builtins/genericalias.rs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Lib/test/test_typing.py
🧰 Additional context used
🧠 Learnings (1)
vm/src/builtins/genericalias.rs (1)
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: Use the macro system (such as 'pyclass', 'pymodule', 'pyfunction') when implementing Python functionality in Rust for RustPython.
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Check Rust code with rustfmt and clippy
  • GitHub Check: Run tests under miri
🔇 Additional comments (3)
vm/src/builtins/genericalias.rs (3)

20-34: LGTM: Appropriate expansion of special attributes.

The addition of __class__, __bases__, __unpacked__, and __typing_unpacked_tuple_args__ to the attribute exceptions list is appropriate for comprehensive generic alias support.


266-269: LGTM: Clean utility function.

The tuple_index function is a straightforward utility that correctly uses identity comparison for Python objects.


338-370: Excellent default parameter handling implementation.

The logic for handling default parameters is well-implemented with proper validation and informative error messages. The approach of counting defaults from the end backwards aligns with Python's parameter handling conventions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
vm/src/stdlib/typing.rs (1)

937-959: Consider caching the typing module import for performance.

The helper function is well-structured and follows good practices for calling typing module functions. However, it imports the typing module on every call.

Consider caching the typing module reference if this function is called frequently:

fn call_typing_args_kwargs(
    name: &'static str,
    cls: PyTypeRef,
    args: FuncArgs,
    vm: &VirtualMachine,
) -> PyResult {
-   let typing = vm.import("typing", 0)?;
+   // Consider caching if performance becomes an issue
+   let typing = vm.import("typing", 0)?;
    let func = typing.get_attr(name, vm)?;
    
    // Prepare arguments: (cls, *args)
    let mut call_args = vec![cls.into()];
    call_args.extend(args.args);
    
    // Call with prepared args and original kwargs
    let func_args = FuncArgs {
        args: call_args,
        kwargs: args.kwargs,
    };
    
    func.call(func_args, vm)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdd7519 and af2d36c.

📒 Files selected for processing (5)
  • Lib/test/test_functools.py (0 hunks)
  • Lib/test/test_genericalias.py (0 hunks)
  • Lib/test/test_typing.py (2 hunks)
  • vm/src/protocol/object.rs (1 hunks)
  • vm/src/stdlib/typing.rs (5 hunks)
💤 Files with no reviewable changes (2)
  • Lib/test/test_functools.py
  • Lib/test/test_genericalias.py
✅ Files skipped from review due to trivial changes (2)
  • vm/src/protocol/object.rs
  • Lib/test/test_typing.py
🧰 Additional context used
🧠 Learnings (1)
vm/src/stdlib/typing.rs (2)
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: Use the macro system (such as 'pyclass', 'pymodule', 'pyfunction') when implementing Python functionality in Rust for RustPython.
Learnt from: CR
PR: RustPython/RustPython#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-06-26T12:52:11.109Z
Learning: When implementing a Python module in Rust for RustPython, use the #[pymodule] attribute and expose functions and classes using #[pyfunction], #[pyclass], and #[pymethod] as appropriate.
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run tests under miri
  • GitHub Check: Check Rust code with rustfmt and clippy
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run snippets and cpython tests (macos-latest)
🔇 Additional comments (5)
vm/src/stdlib/typing.rs (5)

16-17: LGTM! Clean import additions for new functionality.

The added imports (PyObjectRef, PyRef, PyTupleRef, and Iterable) are necessary for the new Iterable trait implementation and properly follow the existing import organization.

Also applies to: 20-20


610-610: Good addition of Iterable trait to TypeVarTuple.

Adding Iterable to the trait list enables iteration support for TypeVarTuple, which aligns with CPython's behavior for handling variadic generics.


670-682: Well-implemented Iterable trait for TypeVarTuple.

The implementation correctly handles the unpacking behavior by:

  1. Importing the typing module to access Unpack
  2. Creating Unpack[self] to represent the unpacked tuple
  3. Returning a list iterator containing the unpacked element

This aligns with CPython's behavior for Generic[*Ts] as mentioned in the comment.


971-978: Excellent refactor to delegate to Python typing module.

The change from directly creating PyGenericAlias to calling _generic_class_getitem via the helper function aligns with CPython's behavior and centralizes the logic in the Python typing module.


980-983: Good delegation to Python typing module for subclass initialization.

The refactor to use call_typing_args_kwargs for _generic_init_subclass is consistent with the class_getitem changes and properly delegates complex logic to the Python side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant