Repository navigation
Implement py2rust: Python-to-Rust subset compiler - #1
Conversation
- Frontend parser: supports typed Python subset (int/float/bool/str/list, functions with mandatory type hints, if/elif/else, while, for-range, return, print, arithmetic/comparison/bool ops) - Middle-end: symbol table with scoping, type checker, type inferencer, IR builder lowering Python AST to typed IR - IR: typed intermediate representation nodes - Backend: Rust code generator + optional rustfmt formatter - Utils: error hierarchy, visitor pattern, logger - CLI: py2rust command-line tool - Tests: 66 tests (parser, semantic, IR, codegen) — all passing - Examples: fibonacci and simple_math in Python and Rust Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: djmahe4 <137691824+djmahe4@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: djmahe4 <137691824+djmahe4@users.noreply.github.com>
Agent-Logs-Url: https://github.com/djmahe4/py2rust/sessions/d17dda38-d5b8-400f-9f1a-1f6293c5004e Co-authored-by: djmahe4 <137691824+djmahe4@users.noreply.github.com>
… variable Agent-Logs-Url: https://github.com/djmahe4/py2rust/sessions/d17dda38-d5b8-400f-9f1a-1f6293c5004e Co-authored-by: djmahe4 <137691824+djmahe4@users.noreply.github.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements py2rust, a subset compiler for translating statically typed Python into Rust, featuring a complete pipeline from parsing to code generation. The review identifies several functional and semantic issues: the AST visitor fails to traverse nodes stored in tuples, and the Rust code generation incorrectly handles floor division, negative range steps, and operator precedence. Furthermore, the type checker lacks necessary validations for augmented assignments and range arguments, and the print implementation will cause compilation errors for list types. Refactoring the type-dispatching logic to use isinstance is also recommended for better robustness.
…numbers, op precedence, negative range steps,list printing and minor type checking enhancements
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
Reviewed by laguna-m.1-20260312:free · 600,295 tokens |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces py2rust, a subset compiler that translates statically typed Python into Rust. The implementation covers the full compilation pipeline, including an AST-based frontend, a middle-end for type checking and IR construction, and a backend for Rust code generation. While the architecture is solid, several issues were identified in the code generation logic: it fails to account for the scoping differences between Python and Rust, lacks support for mutable loop variables, and incorrectly handles string indexing and move semantics for list elements. Furthermore, the type checker needs to be extended to validate condition expressions in control flow statements, and the backend should provide clearer error markers for unknown types instead of defaulting to i32.
… indexing and list move, contrrol flow condition validation
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces py2rust, a compiler that translates a statically typed subset of Python into Rust through a pipeline of parsing, type checking, and IR-based code generation. While the architecture is sound, several critical issues were identified in the backend. The variable pre-declaration logic is incomplete, missing variables from assignments and loop targets, which will lead to Rust compilation errors. Furthermore, there are semantic discrepancies in loop variable scoping, a lack of support for Python's negative indexing, and potential runtime panics related to usize casting and step_by operations.
| elif isinstance(expr, IRSubscript): | ||
| val = self._gen_expr(expr.value) | ||
| idx = self._gen_expr(expr.index) | ||
| if isinstance(expr.value_type, IRStrType): | ||
| return f"({val}.chars().nth(({idx}) as usize).unwrap().to_string())" | ||
|
|
||
| res = f"{val}[({idx}) as usize]" | ||
| if isinstance(expr.result_type, (IRStrType, IRListType)): | ||
| return f"({res}).clone()" | ||
| return res |
There was a problem hiding this comment.
Python's negative indexing (e.g., s[-1]) is not supported by the current implementation. Casting a negative i32 to usize in Rust results in a large positive value due to wrap-around, which will lead to an out-of-bounds panic at runtime. To support idiomatic Python behavior, the generated code should check if the index is negative and adjust it relative to the collection's length.
… semantic validation and functional stability ensured
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces py2rust, a compiler that translates a statically typed subset of Python into Rust. The implementation includes a full pipeline consisting of a frontend parser, a middle-end for semantic analysis and IR building, and a backend for Rust code generation and formatting. The review feedback identifies several critical improvements: ensuring Python semantics for range() loops by evaluating bounds only once, generating more idiomatic Rust by selectively applying the mut keyword, expanding the parser to support general function calls as standalone statements, and maintaining consistent error handling by using internal exception types instead of raw Python errors.
| start = self._gen_expr(stmt.start) | ||
| stop = self._gen_expr(stmt.stop) | ||
| step = self._gen_expr(stmt.step) if stmt.step is not None else "1" | ||
|
|
||
| # Use while loop to emulate Python's loop variable scoping and persistence | ||
| self._emit(f"{stmt.target} = {start};") | ||
| # Step can be negative, so we need a robust condition | ||
| self._emit(f"while if ({step}) > 0 {{ {stmt.target} < ({stop}) }} else {{ {stmt.target} > ({stop}) }} {{") | ||
| self._indent += 1 | ||
| for s in stmt.body: | ||
| self._gen_stmt(s) | ||
| self._emit(f"{stmt.target} += {step};") | ||
| self._indent -= 1 | ||
| self._emit("}") |
There was a problem hiding this comment.
In Python, the stop and step arguments of range() are evaluated exactly once when the loop starts. The current implementation evaluates these expressions on every iteration of the while loop. This can lead to incorrect behavior if the expressions have side effects or if the variables they depend on are modified within the loop body.
Consider evaluating these into temporary variables before the loop starts to ensure correct Python semantics.
| start = self._gen_expr(stmt.start) | |
| stop = self._gen_expr(stmt.stop) | |
| step = self._gen_expr(stmt.step) if stmt.step is not None else "1" | |
| # Use while loop to emulate Python's loop variable scoping and persistence | |
| self._emit(f"{stmt.target} = {start};") | |
| # Step can be negative, so we need a robust condition | |
| self._emit(f"while if ({step}) > 0 {{ {stmt.target} < ({stop}) }} else {{ {stmt.target} > ({stop}) }} {{") | |
| self._indent += 1 | |
| for s in stmt.body: | |
| self._gen_stmt(s) | |
| self._emit(f"{stmt.target} += {step};") | |
| self._indent -= 1 | |
| self._emit("}") | |
| elif isinstance(stmt, IRForRange): | |
| start = self._gen_expr(stmt.start) | |
| stop = self._gen_expr(stmt.stop) | |
| step = self._gen_expr(stmt.step) if stmt.step is not None else "1" | |
| # Evaluate bounds once to match Python semantics | |
| self._emit(f"let __stop = {stop};") | |
| self._emit(f"let __step = {step};") | |
| self._emit(f"{stmt.target} = {start};") | |
| self._emit(f"while if __step > 0 {{ {stmt.target} < __stop }} else {{ {stmt.target} > __stop }} {{") | |
| self._indent += 1 | |
| for s in stmt.body: | |
| self._gen_stmt(s) | |
| self._emit(f"{stmt.target} += __step;") | |
| self._indent -= 1 | |
| self._emit("}") |
…n func calls as statements in parser, use SemanticError class for reporting errors
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces py2rust, a compiler that translates a statically typed subset of Python into Rust. The project features a complete pipeline including a custom AST parser, a bidirectional type checker, an intermediate representation (IR) builder, and a Rust code generator. The review feedback highlights several logic issues in the backend, specifically regarding variable shadowing in nested loops, redundant expression evaluation in subscripts, and the need for explicit type casting in arithmetic operations. Additionally, an improvement was suggested for the type checker to prevent conflicting type definitions for loop targets.
…op target type safety fixed
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces py2rust, a compiler that translates a statically typed subset of Python into Rust. The implementation follows a standard compiler architecture, including a frontend parser, a middle-end for type checking and IR construction, and a backend for Rust code generation. Feedback focuses on refining the Rust code generation to correctly handle the _ discard pattern and ensuring that unknown types trigger explicit errors rather than producing invalid code. Additionally, it was noted that print statements should be explicitly type-checked to ensure the semantic validity of the expressions being printed.
There was a problem hiding this comment.
Code Review
This pull request introduces significant updates to the py2rust compiler, including a new semantic validation and pattern learning system, improved support for multi-module repository compilation, and enhanced code generation for complex features like context managers and generators. The review identified critical issues in ir_builder.py regarding SubscriptAssign type inference and method fallibility, as well as an AttributeError in type_checker.py during circular dependency checks. I have kept these critical feedback comments and removed comments that were purely validation or non-actionable.
| if isinstance(target_type, ListType): | ||
| value_type = _to_ir_type(target_type.element_type) | ||
| target_type = self.inferencer.infer(stmt.target) | ||
| trait_info = None | ||
| if isinstance(target_type, DictType): | ||
| value_type = _to_ir_type(target_type.value_type) | ||
| elif isinstance(target_type, StrType): | ||
| value_type = IRStrType() | ||
| elif isinstance(target_type, ClassType): | ||
| # Check for __setitem__ | ||
| if self.st.lookup_method(target_type.name, "__setitem__", 2): | ||
| trait_info = ("IndexMut", "index_mut") | ||
| value_type = IRIntType() # Default | ||
| else: | ||
| value_type = IRIntType() |
There was a problem hiding this comment.
The value_type for SubscriptAssign is incorrectly overwritten. If target_type is a ListType, the first if block sets value_type, but the subsequent else block (line 1031) always overwrites it with IRIntType() because a ListType is neither a DictType nor a ClassType. This will break assignments to lists of non-integer types.
| if isinstance(target_type, ListType): | |
| value_type = _to_ir_type(target_type.element_type) | |
| target_type = self.inferencer.infer(stmt.target) | |
| trait_info = None | |
| if isinstance(target_type, DictType): | |
| value_type = _to_ir_type(target_type.value_type) | |
| elif isinstance(target_type, StrType): | |
| value_type = IRStrType() | |
| elif isinstance(target_type, ClassType): | |
| # Check for __setitem__ | |
| if self.st.lookup_method(target_type.name, "__setitem__", 2): | |
| trait_info = ("IndexMut", "index_mut") | |
| value_type = IRIntType() # Default | |
| else: | |
| value_type = IRIntType() | |
| trait_info = None | |
| if isinstance(target_type, ListType): | |
| value_type = _to_ir_type(target_type.element_type) | |
| elif isinstance(target_type, DictType): | |
| value_type = _to_ir_type(target_type.value_type) | |
| elif isinstance(target_type, StrType): | |
| value_type = IRStrType() | |
| elif isinstance(target_type, ClassType): | |
| # Check for __setitem__ | |
| if self.st.lookup_method(target_type.name, "__setitem__", 2): | |
| trait_info = ("IndexMut", "index_mut") | |
| value_type = IRIntType() # Default | |
| else: | |
| value_type = IRIntType() |
| names.append(t.name) | ||
| elif isinstance(t, OptionalType): | ||
| names.extend(self._extract_class_names(t.element_type)) | ||
| elif isinstance(t, UnionType): |
There was a problem hiding this comment.
Incorrect attribute access in _extract_class_names. OptionalType uses inner_type instead of element_type, and UnionType uses variants instead of types. This will cause a AttributeError during circular dependency checks for classes containing these types.
| names.append(t.name) | |
| elif isinstance(t, OptionalType): | |
| names.extend(self._extract_class_names(t.element_type)) | |
| elif isinstance(t, UnionType): | |
| elif isinstance(t, OptionalType): | |
| names.extend(self._extract_class_names(t.inner_type)) | |
| elif isinstance(t, UnionType): | |
| for et in t.variants: |
…rns, and HITL interactive triage
… recovery and HITL triage dashboard
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request significantly expands the py2rust compiler, transforming it into a repository-scale Python-to-Rust subset compiler with an advanced LLM-backed semantic validation and pattern learning system. Key additions include a multi-pass frontend/middle-end/backend pipeline, a comprehensive plugin system for standard libraries, and a robust project management layer for handling cross-module imports and circular dependencies. The feedback highlights several critical areas for improvement: a command injection vulnerability in the PowerShell context gathering logic, fragility in the Rust function extraction and LLM response parsing routines, and performance concerns regarding SQLite connection management. Additionally, the reviewer noted that the Ollama host should be configurable and that the recursive topological sort could lead to stack overflows in complex projects.
| cmd = None | ||
| if sys.platform == "win32": | ||
| # Check Select-String via powershell | ||
| if shutil.which("powershell"): |
There was a problem hiding this comment.
Constructing a PowerShell command string using an f-string with symbol_name is vulnerable to command injection. If a symbol name contains single quotes or other shell metacharacters, arbitrary commands could be executed. It is safer to use shlex.quote for Unix-like systems or ensure proper escaping for Windows/PowerShell environments.
| def extract_rust_fn(rust_code: str, func_name: str) -> str: | ||
| # Find start of function definition in rust code | ||
| pattern = re.compile(rf"\bfn\s+{func_name}\b") | ||
| match = pattern.search(rust_code) | ||
| if not match: | ||
| return None | ||
| start_idx = match.start() | ||
|
|
||
| # Simple brace matching from start_idx | ||
| brace_count = 0 | ||
| started = False | ||
| end_idx = start_idx | ||
| for idx in range(start_idx, len(rust_code)): | ||
| char = rust_code[idx] | ||
| if char == '{': | ||
| brace_count += 1 | ||
| started = True | ||
| elif char == '}': | ||
| brace_count -= 1 | ||
|
|
||
| if started and brace_count == 0: | ||
| end_idx = idx + 1 | ||
| break | ||
| else: | ||
| end_idx = len(rust_code) | ||
|
|
||
| return rust_code[start_idx:end_idx] |
There was a problem hiding this comment.
The extract_rust_fn function is fragile as it relies on regular expressions and manual brace counting to extract Rust function bodies. This approach will fail if braces appear within comments or string literals, or if the code contains nested structures like closures. Consider using a more robust state-tracking scanner or a proper Rust lexer to identify function boundaries.
There was a problem hiding this comment.
The user is asking if the extract_rust_fn function in py2rust/main.py has been fixed. Based on the current code provided in the patch, the function still uses the same regex and brace-counting logic that was previously identified as fragile. No changes were made to this function in the provided diffs. I recommend replacing this approach with a more robust parser or a dedicated Rust lexer/parser library to correctly handle nested structures, comments, and string literals.
|
|
||
| class OllamaClient: | ||
| def __init__(self, model: str = "deepseek-coder", host: str = "http://localhost:11434"): | ||
| self.model = model |
| " Break,", | ||
| " Continue,", | ||
| "}", | ||
| "" |
There was a problem hiding this comment.
WARNING: Suppressing all warnings (#![allow(warnings)] #![allow(clippy::all)]) may hide genuine issues in the generated code. Consider fixing the root cause instead of suppressing warnings.
Same pattern appears on lines 157, 166, 182, and 201 in this file.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the core implementation of the py2rust compiler, a Python-to-Rust subset compiler featuring a recursive descent parser, type checker, IR builder, and Rust code generator, alongside extensive documentation and examples. The review feedback identifies several critical bugs in the code generation backend that must be addressed: an AttributeError when processing IRIf statements in generators, a silent no-op mutation bug during struct field assignments due to unnecessary cloning, a type-tracking failure in tuple unpacking, a precedence bug in floor division type casting, and a runtime panic risk in dictionary subscripting where a catchable KeyError should be propagated instead of using .unwrap().
| elif isinstance(stmt, IRIf): | ||
| branch_states = [] | ||
| for _ in stmt.branches: | ||
| branch_states.append(next_free_state) | ||
| next_free_state += 1 |
There was a problem hiding this comment.
The IRIf node does not have a branches attribute (it has condition, then_body, elif_clauses, and else_body). Attempting to access stmt.branches here will cause a compile-time AttributeError crash whenever a generator contains an if statement. We should reconstruct the list of branches dynamically.
| elif isinstance(stmt, IRIf): | |
| branch_states = [] | |
| for _ in stmt.branches: | |
| branch_states.append(next_free_state) | |
| next_free_state += 1 | |
| elif isinstance(stmt, IRIf): | |
| branches = [(stmt.condition, stmt.then_body)] + list(stmt.elif_clauses) | |
| if stmt.else_body is not None: | |
| branches.append((None, stmt.else_body)) | |
| branch_states = [] | |
| for _ in branches: | |
| branch_states.append(next_free_state) | |
| next_free_state += 1 |
| val_t = self._get_rust_type(expr.value_type.value_type) | ||
| return f"{val}.get(&{idx}).unwrap().clone()" | ||
|
|
||
| # Handle ExternalObject indexing (e.g., json data) |
There was a problem hiding this comment.
Using .unwrap() on dictionary lookups will cause an unrecoverable runtime panic if the key is missing. To preserve Python's exception handling model (where a missing key raises a catchable KeyError), we should return a fallible Result using ok_or_else and propagate it with ?.
| val_t = self._get_rust_type(expr.value_type.value_type) | |
| return f"{val}.get(&{idx}).unwrap().clone()" | |
| # Handle ExternalObject indexing (e.g., json data) | |
| # Handle dict subscript: d[key] -> __d.get(&key).ok_or_else(...)? | |
| if isinstance(expr.value_type, IRDictType): | |
| val_t = self._get_rust_type(expr.value_type.value_type) | |
| return f"{val}.get(&{idx}).ok_or_else(|| PyError::KeyError(format!(\"{{:?}}\", {idx})))?.clone()" |
… IRTupleUnpack names, floor-div precedence, dict KeyError propagation) - generator_codegen.py: fix AttributeError on IRIf.branches - rust_codegen.py: add IRStructAccess in get_mut_target (prevents silent clone no-ops) - rust_codegen.py: replace get_mut(...).unwrap() with ok_or_else on mutable dict subscript - rust_codegen.py: remove dead target_is_dict variable and stale design comments - codegen_helpers.py: extract .name string from IRTupleUnpack targets - expr_codegen.py: use _gen_expr_as_float for floor-div operands (fixes precedence) - expr_codegen.py: replace dict subscript unwrap() with ok_or_else on read path - tests: update assertions; add regression tests for floor-div and dict nested write
406883d to
ac77808
Compare
…32 on tmpdir cleanup PatternStore used which only manages transactions, not connection lifetime. On Windows, SQLite holds an exclusive OS-level file lock until the connection object is GC'd -- which may not happen before TemporaryDirectory.__exit__ tries to delete patterns.db. Changes: - pattern_store.py: replace _connect() + with _use_conn() contextmanager that always calls conn.close() in finally block. - Add __enter__/__exit__/close() to PatternStore so callers can use to guarantee cleanup ordering. - test_wave33.py: wrap store in in test_pattern_store_persistence and test_pattern_extractor. - test_hardening.py: wrap store in in test_pattern_store_sqlite_migration and test_pattern_store_concurrent_writes.
|
|
||
| ## Installation | ||
|
|
||
| ## Installation |
There was a problem hiding this comment.
SUGGESTION: Duplicate heading on line 190 - the ## Installation header already exists on line 188. Remove the duplicate to fix markdown structure.
| ## Installation | |
| You can install py2rust directly from PyPI: |
Builds
py2rustfrom scratch — a compiler that translates a statically typed Python subset into idiomatic, safe Rust following a formal pipeline: Python AST → Custom AST → Symbol Table + Type Checking → High-Level IR → Rust codegen.Pipeline stages
frontend/): Pythonast→ frozen-dataclass custom AST with source locations; rejects unsupported features (classes, imports, lambdas, missing type hints,eval/exec, etc.) withUnsupportedFeatureErrormiddleend/): scoped symbol table, strict bidirectional type checker, literal type inferencer, IR builder (custom AST → typed IR)ir/): strongly-typed frozen-dataclass nodes —IRFunction,IRAssign,IRIf,IRWhile,IRForRange,IRReturn,IRPrint, etc.backend/): Rust codegen +rustfmtintegrationType mapping
inti32floatf64boolboolstrStringlist[T]Vec<T>Codegen correctness
Three non-obvious correctness issues fixed vs. naïve output:
let mutinference — codegen scans the full function body (including nested loops/branches) forIRAssign/IRAugAssigntargets and emitslet mutonly for variables that are actually reassigned after declaration.fn main()return type — Rust'smainmust return()or aTerminationimpl. If the user writesdef main() -> int, codegen emitsfn main()(no return type) and convertsreturn <expr>;toreturn;.--verifytemp file —rustc -o /dev/nullfails in sandboxed environments; replaced with a propertempfile.NamedTemporaryFile.Example
CLI
Original prompt
🛠️ SYSTEM ROLE
You are a senior compiler engineer and Rust systems programmer with extensive experience building production compilers (similar to rustc frontend or mypy + codegen pipelines).
Your task is to build py2rust — a correct, safe, and idiomatic Python-to-Rust subset compiler.
You must treat this as a real compiler project, not a quick transpiler. Every stage must follow formal compiler design principles: frontend, middle-end, backend, with clear separation of concerns.
🚨 CRITICAL RULES — YOU MUST OBEY THESE AT ALL TIMES
rustc(stable), contain nounsafe, and follow idiomatic Rust style.UnsupportedFeatureError.ast,dataclasses, etc.).🎯 PROJECT NAME & GOAL
Project:
py2rustGoal: Translate a strictly defined, statically typed subset of Python into clean, safe, zero-unsafe Rust code.
Pipeline (mandatory):
Python Source → Python AST → Custom AST → Symbol Table + Semantic Analysis → Type Checking & Inference → High-Level IR → Rust Code Generation → Formatted Rust
📁 PROJECT STRUCTURE (EXACT — NO CHANGES ALLOWED)
🔒 STRICTLY SUPPORTED PYTHON SUBSET (Phase 1 MVP)
Only these features are allowed. Anything else must raise
UnsupportedFeatureErrorwith line number and helpful message.✅ Supported:
int,float,bool,str+,-,*,/,//,%and,or,not)if/elif/elsewhileloopsforloops only in the formfor i in range(start, stop)orrange(start, stop, step)where arguments are simplereturnstatementslist[int],list[float], etc. →Vec<T>print(expr)with simple arguments❌ Forbidden (immediate error):
Any,typing.Anyeval,exec,globals,localsType Mapping:
int→i32float→f64bool→boolstr→Stringlist[T]→Vec<T>🧩 DETAILED PHASE REQUIREMENTS
Frontend
astmodule + Visitor pattern (utils/visitor.py)frontend/ast_nodes.pywith source locationMiddle-end
IR (High-Level)
ir/ir_nodes.pyIRFunction,IRBlock,IRAssign,IRBinaryOp,IRIf,IRWhile,IRForRange,IRReturn,IRPrintBackend (Rust Codegen)
letbindings when neededif,while,for i in start..end)println!forprint()