Skip to content

feat: nargo expand to show code after macro expansions#7613

Merged
asterite merged 152 commits intomasterfrom
ab/macro-expander
Apr 30, 2025
Merged

feat: nargo expand to show code after macro expansions#7613
asterite merged 152 commits intomasterfrom
ab/macro-expander

Conversation

@asterite
Copy link
Collaborator

@asterite asterite commented Mar 6, 2025

Description

Problem

Resolves #7552

Summary

By pure coincidence, nargo expand works almost the same way as cargo expand:

  1. We type-check the code as usual
  2. All code, that which exists in source files and that which is created by macros, is stored in "def maps" and the node interner, so we can use those sources to rebuild the final code
  3. Because of this, comments (but not doc comments) are lost (this is the same in cargo expand)
  4. All of the generated code is printed to the output as a single string (again, exactly like cargo expand)

In a follow-up PR we could try to somehow put back comments (we can know where they are and insert them as we traverse the HIR).

For example, if you run it for this code:

fn main() {
    let _ = std::as_witness(1);
    println("Hello world");
}

#[foo]
comptime fn foo(f: FunctionDefinition) -> Quoted {
    quote {
        pub fn bar(x: i32) -> i32  {  
            let y = x + 1;
            y + 2
        }
    }
}

#[mutate_add_one]
fn add_one() {}

comptime fn mutate_add_one(f: FunctionDefinition) {
    f.set_parameters(&[(quote { x }, quote { Field }.as_type())]);
    f.set_return_type(quote { Field }.as_type());
    f.set_body(quote { x + 1 }.as_expr().unwrap());
}

we get this output:

fn main() {
    let _: () = std::as_witness(1);
    println("Hello world");
}

comptime fn foo(f: FunctionDefinition) -> Quoted {
    quote {
        pub fn bar(x: i32) -> i32 {
            let y = x + 1;
            y + 2
        }
    }
}

pub fn bar(x: i32) -> i32 {
    let y: i32 = x + 1;
    y + 2
}

fn add_one(x: Field) -> Field {
    x + 1
}

comptime fn mutate_add_one(f: FunctionDefinition) {
    f.set_parameters(&[(quote { x }, quote { Field }.as_type())]);
    f.set_return_type(quote { Field }.as_type());
    f.set_body(quote { x + 1 }.as_expr().unwrap());
}

Additional Context

Documentation

Check one:

  • No documentation needed.
  • Documentation included in this PR.
  • [For Experimental Features] Documentation to be submitted in a separate PR.

PR Checklist

  • I have tested the changes locally.
  • I have formatted the changes with Prettier and/or cargo fmt on default settings.

@jfecher
Copy link
Contributor

jfecher commented Mar 6, 2025

Eventually a directory with all the expanded code could be created, with one file per module to match the original source code.

How would we do this though? The reason we only have a CLI flag currently and only output the new code is that while we're elaborating we are losing information about the source code. We throw away all type and trait definitions for example. By monomorphization we're already left with only functions. How would we output any types/traits/imports/etc back into the program? I don't think just skipping past them is an option either since metaprogramming may modify a type definition such that it differs from the source.

@asterite
Copy link
Collaborator Author

asterite commented Mar 7, 2025

How would we do this though?

Oh, I just meant that if we have a function foo defined in src/main.nr we know that its location is in that file. Then we'd output it (after it has been potentially changed) in the same file, but a different directory. For code generated by macros... it would land on the file that has the macro attribute, I think.

We throw away all type and trait definitions for example. How would we output any types/traits/imports/etc back into the program?

They are in the NodeInterner, for example get_trait, get_type, etc. Imports are a bit more tricky but they are there in a module's scope (probably the ones that aren't in definitions). It's still tricky because if we need to output a type or a call to a function we might need to either fully-qualify it or use an existing import, but I think it's doable.

By monomorphization we're already left with only functions.

Right, we can do this before monomoprhization (or put another way: we don't monomorphize for this tool)

@asterite asterite requested review from a team and removed request for a team April 22, 2025 13:04
@asterite
Copy link
Collaborator Author

This is now ready for review. In most cases it produces code that can be compiled again, and executes like the original program, but in some cases it produces code that can't be compiled. That said, the code is always understandable, which is likely the use case for expanding macro code. We can fix the remaining bugs in subsequent PRs as this PR is already quite large.

Copy link
Contributor

@michaeljklein michaeljklein left a comment

Choose a reason for hiding this comment

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

LGTM

@asterite asterite added this pull request to the merge queue Apr 30, 2025
Merged via the queue into master with commit 8d8991a Apr 30, 2025
115 checks passed
@asterite asterite deleted the ab/macro-expander branch April 30, 2025 18:19
github-merge-queue bot pushed a commit to AztecProtocol/aztec-packages that referenced this pull request May 1, 2025
Automated pull of nightly from the
[noir](https://github.com/noir-lang/noir) programming language, a
dependency of Aztec.
BEGIN_COMMIT_OVERRIDE
feat: add `--debug-compile-stdin` to read `main.nr` from `STDIN` for
testing (noir-lang/noir#8253)
feat: better error message on unicode whitespace that isn't ascii
whitespace (noir-lang/noir#8295)
chore: update `quicksort` from iterative `noir_sort` version
(noir-lang/noir#7348)
fix: use correct meta attribute names in contract custom attributes
(noir-lang/noir#8273)
feat: `nargo expand` to show code after macro expansions
(noir-lang/noir#7613)
feat: allow specifying fuzz-related dirs when invoking `nargo test`
(noir-lang/noir#8293)
chore: redo typo PR by ciaranightingale
(noir-lang/noir#8292)
chore: Extend the bug list with issues found by the AST fuzzer
(noir-lang/noir#8285)
fix: don't disallow writing to memory after passing it to brillig
(noir-lang/noir#8276)
chore: test against zkpassport rsa lib
(noir-lang/noir#8278)
feat: omit element size array for more array types
(noir-lang/noir#8257)
chore: refactor array handling in ACIRgen
(noir-lang/noir#8256)
chore: document cast (noir-lang/noir#8268)
END_COMMIT_OVERRIDE

---------

Co-authored-by: AztecBot <tech@aztecprotocol.com>
Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
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.

Add a tool to show code after macro expansions

3 participants