Skip to content

[BUG] macro expansion fails when removing duplicate #[external] annotations #20525

@mauricetjmurphy

Description

@mauricetjmurphy

Aztec Compiler Bug Report

Bug Description

When duplicate #[external("public")] annotations are present on a function, the compiler reports "Public function selector collision detected". However, removing one of the duplicate annotations causes the entire contract's macro expansion to fail with 70+ additional errors about storage members not being found.

This creates a catch-22 situation where:

  • Keeping the duplicates causes selector collision errors
  • Removing the duplicates breaks the entire contract compilation

Environment

  • Aztec version: v4.0.0-devnet.1-patch.0
  • Component: aztec-nargo compile
  • Noir contracts: Using aztec-nr dependency from tag v4.0.0-devnet.1-patch.0
  • Platform: macOS (Darwin 24.6.0)

Steps to Reproduce

  1. Create a contract function with duplicate #[external("public")] annotations:

#[external("public")]
#[external("public")]
fn some_function(value: Field) {
let current = storage.some_field.read();
storage.some_field.write(current + value);
}

  1. Run aztec-nargo compile

  2. Observe error:
    error: Public function selector collision detected between functions 'some_function' and 'some_function'

  3. Remove one #[external("public")] annotation to fix the collision

  4. Run aztec-nargo compile again

  5. Observe ~70+ new errors like:
    error: Type fn(TypeDefinition) -> Quoted has no member named some_field
    error: Type fn(TypeDefinition) -> Quoted has no member named [other_storage_fields]
    error: cannot find 'context' in this scope

Expected Behavior

Removing the duplicate #[external("public")] annotation should:

  1. Fix the selector collision error
  2. Allow the contract to compile successfully
  3. Not affect the #[aztec] macro expansion or storage access

Actual Behavior

Removing the duplicate annotation causes the #[aztec] macro to fail to properly expand, resulting in:

  • Storage fields becoming inaccessible throughout the entire contract
  • context variable not being available in function scope
  • All storage operations failing with type errors
  • The entire contract becoming uncompilable

Workaround

Currently, the only way to compile is to keep the duplicate annotations, which results in "selector collision" errors but allows the contract to otherwise compile successfully.

Additional Context

  • Other functions in the same files with single #[external("public")] annotations work correctly
  • This appears to be a bug in the macro system where the duplicate annotations somehow affect the macro expansion process
  • The issue is reproducible across different functions and contracts

Attempted Fix Using Official Pattern

According to official Aztec examples (aztec-examples repository), internal helper functions should use the #[only_self] annotation pattern:

#[only_self]
#[external("public")]
fn _helper_function() {
// function body
}

This pattern is used consistently across official examples including:

  • prediction-market contract (_process_buy function)
  • recursive_verification contract (_increment_public function)
  • account-contract (set_hashed_password function)

However, when this pattern is applied to v4.0.0-devnet.1-patch.0:

  1. Replacing duplicate annotations with #[only_self] + single #[external("public")]
  2. Results in 100+ compilation errors across the entire codebase
  3. Macro expansion fails for multiple unrelated functions
  4. Errors include storage member access failures and context scope issues

This suggests:

  • The #[only_self] annotation may not be supported in v4.0.0-devnet.1-patch.0
  • There may be a version incompatibility between the official examples and this devnet release
  • The duplicate annotation workaround may be necessary for this specific version

Impact

  • Severity: High - Blocks proper compilation
  • Workaround: Available but produces compilation errors
  • Scope: Affects contracts with duplicate external annotations

Minimal Reproducible Example

use ::aztec::macros::aztec;

#[aztec]
pub contract TestContract {
use ::aztec::{
macros::{functions::{external, initializer}, storage::storage},
};
use ::aztec::state_vars::{PublicMutable};

#[storage]
struct Storage<Context> {
    counter: PublicMutable<Field, Context>,
}

#[external("public")]
#[initializer]
fn constructor() {
    storage.counter.write(0);
}

// This function has the problematic duplicate annotation
#[external("public")]
#[external("public")]
fn increment() {
    let current = storage.counter.read();
    storage.counter.write(current + 1);
}

}

Result:

  • With duplicate annotations: Selector collision error
  • Without duplicate (correct code): 70+ macro expansion errors

Suggested Fix

The compiler should:

  1. Detect and reject duplicate #[external] annotations with a clear error message
  2. Not require duplicate annotations for proper macro expansion
  3. Ensure macro expansion is independent of annotation duplication
  4. Support the #[only_self] annotation pattern from official examples in devnet versions
  5. Provide clear migration guidance for version-specific annotation patterns

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-bugType: Bug. Something is broken.from-communityThis originated from the community :)

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions