feat(slang): recursive structs - #699
hedgar2017 wants to merge 1 commit into
Conversation
Coverage Summary
|
793d8f8 to
06722d7
Compare
2634941 to
245f83c
Compare
06722d7 to
ffc030e
Compare
245f83c to
b75bf84
Compare
ffc030e to
c461566
Compare
b75bf84 to
9c10e30
Compare
5cbdd13 to
75cffc7
Compare
7697cac to
9f48b04
Compare
75cffc7 to
36d0404
Compare
9f48b04 to
7fb5efe
Compare
f70d347 to
747aa5e
Compare
765d1d6 to
b01d1a6
Compare
747aa5e to
0998552
Compare
b01d1a6 to
faa76df
Compare
0998552 to
d08e50d
Compare
faa76df to
8e9807a
Compare
d08e50d to
2f7993d
Compare
f0e294a to
55a3c9a
Compare
2f7993d to
67561e6
Compare
92ad592 to
75bcc83
Compare
175a65e to
d4479be
Compare
54ba97c to
ca59040
Compare
e32600b to
08301b8
Compare
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Recursive compiler type construction and new unsafe Rust/C++ FFI boundaries warrant final human review.
Review effort: Balanced
Findings: None
What changed in this PR
Adds recursive struct lowering across Slang and MLIR, including cycles through arrays, mappings, nested structs, and function types.
Changes:
- Introduces identified MLIR struct types with deferred bodies.
- Tracks recursive type resolution state and data locations.
- Adds extensive MLIR and runtime regression coverage.
| File | Description |
|---|---|
Cargo.toml |
Updates the Slang revision. |
Cargo.lock |
Locks updated Slang packages. |
solx-utils/src/data_location.rs |
Makes data locations hashable. |
solx-slang/src/type.rs |
Resolves recursive structs through identified types. |
solx-slang/src/scope/source_unit.rs |
Tracks recursive resolution state. |
solx-slang/src/scope/function.rs |
Permits mutable type resolution. |
solx-slang/src/contract/getter/mod.rs |
Passes mutable source-unit scope. |
solx-slang/src/contract/function/expression/call/external_callee.rs |
Supports mutable signature resolution. |
solx-mlir/src/ir/type/mod.rs |
Exposes identified struct operations. |
solx-mlir/src/ffi.rs |
Declares recursive struct FFI functions. |
solx-mlir/dialect_stubs.cpp |
Implements identified struct FFI wrappers. |
solx-mlir/tests/lit/recursive_struct.sol |
Covers recursive MLIR type shapes and operations. |
tests/solidity/simple/recursion/struct_function_type_cycle.sol |
Tests function-type recursion behavior. |
tests/solidity/simple/recursion/struct_doubling_chain.sol |
Tests bounded resolution of branching cycles. |
tests/solidity/simple/recursion/struct_by_value_member_offset.sol |
Tests recursive storage offsets. |
tests/solidity/complex/recursive_struct_per_file/test.json |
Configures cross-file runtime assertions. |
tests/solidity/complex/recursive_struct_per_file/first.sol |
Exercises local and imported recursive structs. |
tests/solidity/complex/recursive_struct_per_file/second.sol |
Defines the imported recursive struct. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
08301b8 to
51801e5
Compare
| /// Whether a struct at the current position may stay opaque: behind an array, a mapping or a | ||
| /// function reference. | ||
| pub breaks_cycle: bool, |
There was a problem hiding this comment.
I'd recommend to use a enum instead of plain bool for to improve readability, as this field plays critical role for proper storage layout of recursive structures. For instance, something like:
/// How the position being resolved holds a struct named at it, deciding whether an
/// in-progress recursive struct may stay opaque there. The distinction guards storage
/// layout, not termination: setting a body computes and caches the holder's layout from
/// its members' sizes at that moment.
pub enum Position {
/// The member is embedded field-by-field, so its size enters the holder's layout: an
/// in-progress struct met here has its body completed before the holder's is set, and
/// the suspended frame's later identical `setBody` is a no-op.
ByValue,
/// A dynamic array, a mapping or a function reference occupies a fixed footprint
/// independent of what it refers to, so an in-progress struct stays an opaque
/// reference: the one place the recursive knot ties.
Breaking,
}
Mis-handling of break_cycles (in case of a refactoring) can easily lead to text MLIR that look correct, but compiler to a wrong bytecode. That's because we do not print in MLIR dialect a structure layout.
To handle that, I'll force checking for ill formed structures on MLIR level by:
LogicalResult StructType::setBody(ArrayRef<Type> memberTypes) {
assert(isIdentified() && "cannot set the body of a literal struct");
for (Type memTy : memberTypes)
if (StructType opaque = findIllFoundedOpaqueRef(memTy))
llvm::report_fatal_error("struct body set while by-value member '" +
opaque.getName() + "' is still opaque");
return Base::mutate(memberTypes);
}
Supports recursive structs: those on a cycle through a dynamic array, a mapping, a nested struct or a function type.