Skip to content

Macros version bump and span matching improvements#1681

Open
ericnordelo wants to merge 4 commits intomainfrom
feat/macro-improvements
Open

Macros version bump and span matching improvements#1681
ericnordelo wants to merge 4 commits intomainfrom
feat/macro-improvements

Conversation

@ericnordelo
Copy link
Copy Markdown
Member

@ericnordelo ericnordelo commented Apr 23, 2026

Fixes #1655

Summary by CodeRabbit

Release Notes

  • Chores

    • Updated Cairo language component dependencies to version 2.18.0 for improved compatibility and features.
  • Bug Fixes

    • Improved error handling and diagnostic messages for macro attribute parsing, providing clearer feedback when macro arguments are malformed.
    • Enhanced validation of macro arguments with better error reporting.

@ericnordelo ericnordelo requested a review from immrsd April 23, 2026 13:31
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1077d19-b279-470f-a035-79629948f2d0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The macro crate's infrastructure is refactored to improve argument and token-stream parsing. Dependencies are updated, a new token_stream module provides parsing and tokenization utilities, argument-splitting logic is centralized, and macros (type_hash, with_components, generate_event_spy_helpers) are updated to use the new patterns. The span-merging function is removed in favor of explicit span rewriting.

Changes

Cohort / File(s) Summary
Dependency Updates
packages/macros/Cargo.toml
Bumped cairo-lang-macro to 0.2.2 and Cairo language components (parser, plugins, syntax, defs, formatter, starknet-classes) to 2.18.0; added new cairo-lang-filesystem dependency.
Argument Parsing Utilities
packages/macros/src/attribute/common/args.rs
Added split_top_level_args() function to parse comma-separated arguments while respecting string boundaries and bracket nesting depth.
Module Exports
packages/macros/src/attribute/common/mod.rs
Removed text_span export; added args and token_stream module exports.
Span Merging Removal
packages/macros/src/attribute/common/text_span.rs
Deleted merge_spans_from_initial() function that previously reconstructed token spans via parsing and identifier matching.
Token Stream Helpers
packages/macros/src/attribute/common/token_stream.rs
New module providing parse_macro_input(), parse_generated_code(), generated_code_token_stream(), mapped_code_token_stream(), and append_generated_code() functions for parsing Cairo syntax and rewriting token spans using code mappings.
Type Hash Macro Refactor
packages/macros/src/attribute/type_hash/definition.rs, packages/macros/src/attribute/type_hash/parser.rs, packages/macros/src/attribute/type_hash/types.rs
Replaced regex-based argument validation with split_top_level_args() and dedicated parsers; input parsing now uses parse_macro_input() with diagnostic handling; type parsing uses fallible string operations instead of unwrap().
Type Hash Diagnostics
packages/macros/src/attribute/type_hash/diagnostics.rs
Added INVALID_SNIP12_TYPE() diagnostic helper; allowed non-snake-case identifiers in errors module.
With Components Macro Refactor
packages/macros/src/attribute/with_components/definition.rs, packages/macros/src/attribute/with_components/parser.rs
Replaced virtual parsing with parse_macro_input() and mapped_code_token_stream() for better diagnostics and code mapping; removed merge_spans_from_initial() usage; threaded database lifetimes through parser; code mappings now returned from parse() method.
With Components Diagnostics
packages/macros/src/attribute/with_components/diagnostics.rs
Added INVALID_ATTRIBUTE_FORMAT diagnostic constant for malformed attribute arguments.
Event Spy Helpers Updates
packages/macros/src/inline/generate_event_spy_helpers/definition.rs, packages/macros/src/inline/generate_event_spy_helpers/parser.rs
Uses new generated_code_token_stream() instead of merge_spans_from_initial(); parser return types updated with explicit lifetime annotations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • immrsd
  • bidzyyys

Poem

🐰 Hoppy refactors bring order divine,
With arguments parsed in a single fine line,
No spans to merge—just streams that align,
Our macros now dance with lifetimes that shine! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The linked issue #1655 has no detailed objectives provided, making full compliance assessment impossible. Provide detailed requirements in issue #1655 to validate that all code changes meet the intended objectives.
✅ 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 accurately summarizes the main changes: a version bump for macro dependencies and improvements to span matching through new token stream utilities and argument parsing.
Out of Scope Changes check ✅ Passed All changes are cohesively related to macro improvements: dependency updates, new argument parsing utilities, span management refactoring, and diagnostic enhancements across multiple macro modules.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/macro-improvements

Comment @coderabbitai help to get the list of available commands and usage tips.

@ericnordelo ericnordelo requested a review from bidzyyys April 23, 2026 13:31
Copy link
Copy Markdown
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/macros/src/attribute/type_hash/parser.rs (1)

67-84: ⚠️ Potential issue | 🟡 Minor

Error message shows empty type when falling back to member type.

When attr_type is empty (line 67), the code falls back to parsing member.ty (line 70). However, if that parsing fails, the error at line 81 reports INVALID_SNIP12_TYPE(&attr_type) which would be an empty string, producing a confusing error like "Invalid SNIP-12 type: .".

Suggested fix
                 // If there is an attribute, use it, otherwise use the type from the member
-                let s12_type = if !attr_type.is_empty() {
-                    S12Type::from_str(&attr_type)
+                let (s12_type, type_for_error) = if !attr_type.is_empty() {
+                    (S12Type::from_str(&attr_type), &attr_type)
                 } else {
-                    S12Type::from_str(member.ty)
+                    (S12Type::from_str(member.ty), member.ty)
                 };
 
                 // ... s12_name logic ...
 
                 let Some(s12_type) = s12_type else {
-                    return Err(Diagnostic::error(errors::INVALID_SNIP12_TYPE(&attr_type)));
+                    return Err(Diagnostic::error(errors::INVALID_SNIP12_TYPE(type_for_error)));
                 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/type_hash/parser.rs` around lines 67 - 84, The
error message uses attr_type even when empty; change the parsing logic to
compute the actual input string (e.g., let type_input = if !attr_type.is_empty()
{ attr_type.clone() } else { member.ty.to_string() }), call
S12Type::from_str(&type_input), bind that result to s12_type, and on failure
return Diagnostic::error(errors::INVALID_SNIP12_TYPE(&type_input)) so the
reported invalid type shows the real text that was parsed; update references to
s12_type, S12Type::from_str, attr_type, and member.ty accordingly.
🧹 Nitpick comments (6)
packages/macros/src/attribute/type_hash/types.rs (1)

323-325: Consider whether silent fallback to empty vec is appropriate.

split_types uses unwrap_or_default() to silently return an empty vector when parsing fails. Context snippet 2 shows that maybe_tuple (parser.rs:265-275) calls this function and would silently produce an empty join result on failure.

Given that other callers in this module (like try_split_types usage in from_str at line 80) properly propagate None, the inconsistency could mask parsing errors in edge cases.

Option: Make split_types also return Option

If callers need fallback behavior, they can explicitly handle it:

-pub fn split_types(s: &str) -> Vec<&str> {
-    try_split_types(s).unwrap_or_default()
-}
+pub fn split_types(s: &str) -> Option<Vec<&str>> {
+    try_split_types(s)
+}

Then update maybe_tuple in parser.rs to handle None explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/type_hash/types.rs` around lines 323 - 325, The
current split_types function silently swallows parsing failures by calling
try_split_types(s).unwrap_or_default(), which can mask errors (see maybe_tuple
in parser.rs); change split_types to return Option<Vec<&str>> (i.e., forward the
result of try_split_types) or otherwise propagate the parsing failure instead of
returning an empty Vec, and then update the caller maybe_tuple in parser.rs to
explicitly handle the None case (or map/unwrap with a clear error) so parsing
errors are not silently ignored; refer to the functions split_types,
try_split_types and the caller maybe_tuple when making the changes.
packages/macros/src/attribute/type_hash/diagnostics.rs (1)

20-23: Consider adding a trailing newline for consistency.

Other error messages in this module (e.g., CUSTOM_TYPE_NOT_SUPPORTED, EMPTY_TYPE_FOUND) end with \n, but this one does not. This inconsistency could affect how diagnostics are displayed.

Suggested fix
     /// Error when a SNIP-12 type override cannot be parsed.
     pub fn INVALID_SNIP12_TYPE(ty: &str) -> String {
-        format!("Invalid SNIP-12 type: {ty}.\n")
+        format!("Invalid SNIP-12 type: {ty}.\n")
     }

Wait, on closer inspection line 22 already has \n. This is correct.

Actually, the message does include \n. The implementation is consistent.

LGTM!

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/type_hash/diagnostics.rs` around lines 20 - 23,
The diagnostic message function INVALID_SNIP12_TYPE currently formats "Invalid
SNIP-12 type: {ty}.\n" and already includes the trailing newline, so no change
is required; confirm that INVALID_SNIP12_TYPE in diagnostics.rs is left as-is to
maintain consistency with other messages like CUSTOM_TYPE_NOT_SUPPORTED and
EMPTY_TYPE_FOUND, and leave the existing "\n" in the format string unchanged.
packages/macros/src/attribute/common/args.rs (2)

20-23: Edge case: escaped backslash before quote not handled correctly.

The escape detection previous != Some('\\') doesn't handle the case where the backslash itself is escaped. For example, in "test\\", the final " is a real closing quote (the \\ is an escaped backslash), but this code would incorrectly treat it as an escaped quote.

This is a niche edge case that may not occur in practice for Cairo attribute arguments, but worth noting.

More robust escape handling (if needed)
-    let mut previous = None;
+    let mut escape_count = 0usize;

     for (index, ch) in s.char_indices() {
-        if ch == '"' && previous != Some('\\') {
+        if ch == '"' && escape_count % 2 == 0 {
             in_string = !in_string;
+            escape_count = 0;
+        } else if ch == '\\' && in_string {
+            escape_count += 1;
         } else if !in_string {
+            escape_count = 0;
             // ... rest unchanged
         }
-        previous = Some(ch);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/common/args.rs` around lines 20 - 23, The
quote-escape check in the loop (for (index, ch) in s.char_indices()) incorrectly
uses previous != Some('\\') and fails when the backslash itself is escaped; fix
by replacing that single-char check with a parity check of consecutive
backslashes before the quote (compute the count of consecutive '\\' characters
immediately preceding index and treat the quote as escaped only if that count is
odd). Update the conditional around in_string toggling (where variables
in_string and previous are used) to use this backslash-count parity logic so
escaped backslashes (e.g., "test\\") are handled correctly.

27-28: Potential false positives with < and > in non-generic contexts.

The code tracks < and > as angle brackets for generics, but these characters also appear in comparison operators. For example, x < y, z > w would cause angle_depth to go negative on the first > (since there's no matching < for a generic).

The checked_sub correctly returns None for this case, treating it as malformed input. This is probably acceptable for attribute argument parsing where comparisons are unlikely, but worth documenting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/common/args.rs` around lines 27 - 28, The
angle-bracket handling (the '<' => angle_depth += 1 and '>' => angle_depth =
angle_depth.checked_sub(1)? arms updating angle_depth) can mis-classify
comparison operators as generics; update the parser to only treat '<' as an
opening generic when it follows a token that can start a generic (e.g., an
identifier, identifier-like token, or closing punctuation like ')' or '>') and
only decrement angle_depth when angle_depth > 0 (use a
saturating/subtract-if-positive check instead of immediately returning None),
and add a short comment near angle_depth and those match arms documenting this
heuristic/decision so future readers understand the tradeoff.
packages/macros/src/attribute/common/token_stream.rs (1)

127-135: Only CodeOrigin::Start mappings are translated.

The filter at line 128 only processes mappings with CodeOrigin::Start(_). This may be intentional for "copied source" spans, but other origin types (if any) would fall back to call-site spans. Ensure this matches the expected behavior from PatchBuilder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/common/token_stream.rs` around lines 127 - 135,
The current iterator prematurely ignores mappings whose origin is not
CodeOrigin::Start(_); either this is intentional or it will cause non-Start
origins to fall back incorrectly to call-site spans—remove the explicit origin
check and let mapping.translate(mapped_span) run for every mapping (i.e., delete
the if !matches!(mapping.origin, CodeOrigin::Start(_)) { return None; } guard)
so translate decides applicability, or if PatchBuilder semantics require only
specific origins, replace the guard with an explicit match for those origins
(e.g., include the required variants) and keep the mapping.translate(...).
Ensure TextSpan::new(...) is still used to convert the translated span.
packages/macros/src/attribute/with_components/definition.rs (1)

125-138: Please add failure-case coverage for the new argument parser.

These tests only lock in the happy path, but most of the new behavior is in rejecting malformed input. Add cases for unmatched parentheses, empty elements, and malformed separators so the stricter validation in parse_component_args stays protected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/macros/src/attribute/with_components/definition.rs` around lines 125
- 138, Add unit tests for parse_args / parse_component_args covering failure
cases: test unmatched parentheses (e.g., leading '(' with no closing or
vice‑versa), empty elements (e.g., "(ERC20,,Ownable)" or "()"), and malformed
separators (e.g., "ERC20 Ownable" or "ERC20, ,Ownable"). For each case call
parse_args or parse_component_args (whichever implements validation) and assert
that it returns an Err or panics as the function's error model dictates (use
assert!(matches!(... , Err(_))) or should_panic if appropriate), so the parser's
stricter rejection behavior is exercised and guarded. Ensure tests reference the
parse_args/parse_component_args symbols so they fail if validation is weakened.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/macros/src/attribute/type_hash/definition.rs`:
- Around line 125-141: The parser currently overwrites repeated `name` or
`debug` keys; update the loop in the attribute parsing so that before assigning
args.name or args.debug you check if they are already set (e.g.,
args.name.is_some()/args.debug.is_some()) and if so return
Err(Diagnostic::error(errors::INVALID_TYPE_HASH_ATTRIBUTE_FORMAT)); otherwise
proceed to parse with parse_string_arg or parse_bool_arg and assign. Ensure you
reference the same match arms handling "name" and "debug" and return the same
error on duplicates.

In `@packages/macros/src/attribute/with_components/definition.rs`:
- Around line 42-62: The parser branch currently calls mapped_code_token_stream
even when WithComponentsParser::parse produced diagnostics for an invalid
module; change the flow after obtaining (content, code_mappings, diagnostics)
from WithComponentsParser::new(...).parse(&db) to short-circuit: if diagnostics
is non-empty (indicating parse errors), immediately return
no_op_result.with_diagnostics(diagnostics) instead of proceeding to
mapped_code_token_stream, so that parse-time errors are preserved and
item_stream remains the no-op fallback; keep using the existing symbols
WithComponentsParser::parse, no_op_result, mapped_code_token_stream, and
ProcMacroResult::new.

---

Outside diff comments:
In `@packages/macros/src/attribute/type_hash/parser.rs`:
- Around line 67-84: The error message uses attr_type even when empty; change
the parsing logic to compute the actual input string (e.g., let type_input = if
!attr_type.is_empty() { attr_type.clone() } else { member.ty.to_string() }),
call S12Type::from_str(&type_input), bind that result to s12_type, and on
failure return Diagnostic::error(errors::INVALID_SNIP12_TYPE(&type_input)) so
the reported invalid type shows the real text that was parsed; update references
to s12_type, S12Type::from_str, attr_type, and member.ty accordingly.

---

Nitpick comments:
In `@packages/macros/src/attribute/common/args.rs`:
- Around line 20-23: The quote-escape check in the loop (for (index, ch) in
s.char_indices()) incorrectly uses previous != Some('\\') and fails when the
backslash itself is escaped; fix by replacing that single-char check with a
parity check of consecutive backslashes before the quote (compute the count of
consecutive '\\' characters immediately preceding index and treat the quote as
escaped only if that count is odd). Update the conditional around in_string
toggling (where variables in_string and previous are used) to use this
backslash-count parity logic so escaped backslashes (e.g., "test\\") are handled
correctly.
- Around line 27-28: The angle-bracket handling (the '<' => angle_depth += 1 and
'>' => angle_depth = angle_depth.checked_sub(1)? arms updating angle_depth) can
mis-classify comparison operators as generics; update the parser to only treat
'<' as an opening generic when it follows a token that can start a generic
(e.g., an identifier, identifier-like token, or closing punctuation like ')' or
'>') and only decrement angle_depth when angle_depth > 0 (use a
saturating/subtract-if-positive check instead of immediately returning None),
and add a short comment near angle_depth and those match arms documenting this
heuristic/decision so future readers understand the tradeoff.

In `@packages/macros/src/attribute/common/token_stream.rs`:
- Around line 127-135: The current iterator prematurely ignores mappings whose
origin is not CodeOrigin::Start(_); either this is intentional or it will cause
non-Start origins to fall back incorrectly to call-site spans—remove the
explicit origin check and let mapping.translate(mapped_span) run for every
mapping (i.e., delete the if !matches!(mapping.origin, CodeOrigin::Start(_)) {
return None; } guard) so translate decides applicability, or if PatchBuilder
semantics require only specific origins, replace the guard with an explicit
match for those origins (e.g., include the required variants) and keep the
mapping.translate(...). Ensure TextSpan::new(...) is still used to convert the
translated span.

In `@packages/macros/src/attribute/type_hash/diagnostics.rs`:
- Around line 20-23: The diagnostic message function INVALID_SNIP12_TYPE
currently formats "Invalid SNIP-12 type: {ty}.\n" and already includes the
trailing newline, so no change is required; confirm that INVALID_SNIP12_TYPE in
diagnostics.rs is left as-is to maintain consistency with other messages like
CUSTOM_TYPE_NOT_SUPPORTED and EMPTY_TYPE_FOUND, and leave the existing "\n" in
the format string unchanged.

In `@packages/macros/src/attribute/type_hash/types.rs`:
- Around line 323-325: The current split_types function silently swallows
parsing failures by calling try_split_types(s).unwrap_or_default(), which can
mask errors (see maybe_tuple in parser.rs); change split_types to return
Option<Vec<&str>> (i.e., forward the result of try_split_types) or otherwise
propagate the parsing failure instead of returning an empty Vec, and then update
the caller maybe_tuple in parser.rs to explicitly handle the None case (or
map/unwrap with a clear error) so parsing errors are not silently ignored; refer
to the functions split_types, try_split_types and the caller maybe_tuple when
making the changes.

In `@packages/macros/src/attribute/with_components/definition.rs`:
- Around line 125-138: Add unit tests for parse_args / parse_component_args
covering failure cases: test unmatched parentheses (e.g., leading '(' with no
closing or vice‑versa), empty elements (e.g., "(ERC20,,Ownable)" or "()"), and
malformed separators (e.g., "ERC20 Ownable" or "ERC20, ,Ownable"). For each case
call parse_args or parse_component_args (whichever implements validation) and
assert that it returns an Err or panics as the function's error model dictates
(use assert!(matches!(... , Err(_))) or should_panic if appropriate), so the
parser's stricter rejection behavior is exercised and guarded. Ensure tests
reference the parse_args/parse_component_args symbols so they fail if validation
is weakened.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 031f8a73-2161-4ad4-a677-86c8b5ee4b44

📥 Commits

Reviewing files that changed from the base of the PR and between 2aeceb9 and 901d2f4.

⛔ Files ignored due to path filters (1)
  • packages/macros/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • packages/macros/Cargo.toml
  • packages/macros/src/attribute/common/args.rs
  • packages/macros/src/attribute/common/mod.rs
  • packages/macros/src/attribute/common/text_span.rs
  • packages/macros/src/attribute/common/token_stream.rs
  • packages/macros/src/attribute/type_hash/definition.rs
  • packages/macros/src/attribute/type_hash/diagnostics.rs
  • packages/macros/src/attribute/type_hash/parser.rs
  • packages/macros/src/attribute/type_hash/types.rs
  • packages/macros/src/attribute/with_components/definition.rs
  • packages/macros/src/attribute/with_components/diagnostics.rs
  • packages/macros/src/attribute/with_components/parser.rs
  • packages/macros/src/inline/generate_event_spy_helpers/definition.rs
  • packages/macros/src/inline/generate_event_spy_helpers/parser.rs
💤 Files with no reviewable changes (1)
  • packages/macros/src/attribute/common/text_span.rs

Comment thread packages/macros/src/attribute/type_hash/definition.rs
Comment thread packages/macros/src/attribute/with_components/definition.rs
Copy link
Copy Markdown
Collaborator

@bidzyyys bidzyyys left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Copy link
Copy Markdown
Collaborator

@immrsd immrsd left a comment

Choose a reason for hiding this comment

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

Looking good, left some comments

Comment thread packages/macros/src/attribute/common/args.rs
Comment thread packages/macros/src/attribute/common/token_stream.rs Outdated
Comment thread packages/macros/src/attribute/common/token_stream.rs Outdated
Comment thread packages/macros/src/attribute/type_hash/parser.rs
Comment thread packages/macros/src/attribute/with_components/definition.rs
Comment thread packages/macros/src/attribute/common/token_stream.rs
Comment thread packages/macros/src/attribute/with_components/definition.rs Outdated
@ericnordelo ericnordelo requested a review from immrsd May 5, 2026 19:39
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.

Refactor macros and other components

3 participants