1 unstable release

0.1.0 Aug 22, 2022

#732 in Procedural macros

MIT license

13KB
152 lines

accio

This crate is mostly an experiment, and is not intended for production!

accio retrieves code blocks distributed to multiple sites in the build path, and puts them together in the call site.

It does this at the compile time; therefore no code execution takes place in load-time (ctor) or runtime (any kind of lazy initialization).

It allows for partial implementations of functions, enums, struct fields, match statements and more.

There are still many quirks with it; and they are not likely to be solved in the future. Just to name a few;

  • Macro execution paths are not checked AT ALL,
  • Conditional compilation flags are blatantly disregarded,
  • Incremental compilation might cause some problems with when the macros are re-evaluated,
  • Error messages will point to incorrect places,
  • The accio_emit macro is only matched by name; and name overlaps are not checked for,
  • None of the macros in this crate can be nested,
  • There is no way for an accio_emit statement to dynamically refer to its own path,
  • The source path is simply recursively traversed for .rs files; instead of verifying that any of these files are imported somewhere,
  • There are many possible errors during parsing that are silently omitted,
  • It does not work with documentation examples! (Yes, every example in the docs is no_run.)
  • I simply didn't test how it would behave with multiple crates.

For most use-cases; I can recommend inventory, linkme, or ctor crates.

For automatically importing all modules in a directory, you can use automod.

Contribution

I'm unlikely to respond to issues in this crate; but if you think you can make this usable in any way; go for it. So PRs are welcome I guess.

Use cases

accio can be used for collecting statements in a function body as follows:

use accio::*;

// some_file.rs
accio_emit! {
    // accio_emit's block must be written
    // as `scope_name { code_block }` pairs.
    // multiple blocks can be listed as follows:
    first_scope {
        val = 1;
    }
    second_scope {
        val += 2;
    }
}

// some_other_file.rs
accio_emit! {
    // the same scope name can have multiple
    // blocks; these are merged in an
    // undetermined order.
    second_scope {
        val += 3;
    }
}

// another_file.rs
let mut val = 0;
assert_eq!(val, 0);

// include the first scope:
// this will evaluate to `val = 1;`
accio!(first_scope);
assert_eq!(val, 1);

// include the second scope:
// this will evaluate to either
// `val += 2; val += 3;` or
// `val += 3; val += 2;`.
accio!(second_scope);
assert_eq!(val, 6);

A more meaningful use case is automatically gathering enum variants. However, due to Rust's grammar rules, the accio! macro cannot be placed inside the braces of an enum or a struct. The following would not compile:

use accio::*;

enum SomeEnum { 
    accio!(enum_variants)
}

This would yield:

error: expected one of `(`, `,`, `=`, `{`, or `}`, found `!`
 --> src/lib.rs:60:10
  |
5 |     accio!(enum_variants)    
  |          ^ expected one of `(`, `,`, `=`, `{`, or `}`

Instead, we can use the accio_body attribute macro:

use accio::*;

#[accio_body(enum_variants)]
enum SomeEnum {
    // this part MUST be empty!
}

#[accio_body(struct_fields)]
struct SomeStruct {
    /* this part MUST be empty! */
}

#[accio_body(array_elems)]
static SOME_ARRAY: &[i32] = &[];

accio_emit! {
    enum_variants {
        FirstVariant,
    }
    enum_variants {
        SecondVariant(String),
    }
    struct_fields {
        pub name: String,
    }
    array_elems: {
        42,
    }
}
// ...and so on

Note that accio_body implementation places the code into the first empty curly brace ({}) or square bracket ([]) scope. Therefore the following variants will fail:

use accio::*;

#[accio_body(enum_variants)]
enum FailingEnum {
    // there is code within the braces
    AlreadySomeVariant,
}

#[accio_body(struct_fields)]
struct FailingStruct; // no braces!

You can still add comments, they do not cause issues.

See examples/ for more detailed examples.

Dependencies

~1.5MB
~34K SLoC