Giter VIP home page Giter VIP logo

proc-macro-workshop's Introduction

Rust Latam: procedural macros workshop

This repo contains a selection of projects designed to learn to write Rust procedural macros — Rust code that generates Rust code.

Each of these projects is drawn closely from a compelling real use case. Out of the 5 projects here, 3 are macros that I have personally implemented in industrial codebases for work, and the other 2 exist as libraries on crates.io by other authors.


Contents


Suggested prerequisites

This workshop covers attribute macros, derive macros, and function-like procedural macros.

Be aware that the content of the workshop and the explanations in this repo will assume a working understanding of structs, enums, traits, trait impls, generic parameters, and trait bounds. You are welcome to dive into the workshop with any level of experience with Rust, but you may find that these basics are far easier to learn for the first time outside of the context of macros.


Projects

Here is an introduction to each of the projects. At the bottom, I give recommendations for what order to tackle them based on your interests. Note that each of these projects goes into more depth than what is described in the introduction here.

Derive macro: derive(Builder)

This macro generates the boilerplate code involved in implementing the builder pattern in Rust. Builders are a mechanism for instantiating structs, especially structs with many fields, and especially if many of those fields are optional or the set of fields may need to grow backward compatibly over time.

There are a few different possibilities for expressing builders in Rust. Unless you have a strong pre-existing preference, to keep things simple for this project I would recommend following the example of the standard library's std::process::Command builder in which the setter methods each receive and return &mut self to allow chained method calls.

Callers will invoke the macro as follows.

use derive_builder::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    #[builder(each = "arg")]
    args: Vec<String>,
    current_dir: Option<String>,
}

fn main() {
    let command = Command::builder()
        .executable("cargo".to_owned())
        .arg("build".to_owned())
        .arg("--release".to_owned())
        .build()
        .unwrap();

    assert_eq!(command.executable, "cargo");
}

This project covers:

  • traversing syntax trees;
  • constructing output source code;
  • processing helper attributes to customize the generated code.

Project skeleton is located under the builder directory.

Derive macro: derive(CustomDebug)

This macro implements a derive for the standard library std::fmt::Debug trait that is more customizable than the similar Debug derive macro exposed by the standard library.

In particular, we'd like to be able to select the formatting used for individual struct fields by providing a format string in the style expected by Rust string formatting macros like format! and println!.

use derive_debug::CustomDebug;

#[derive(CustomDebug)]
pub struct Field {
    name: String,
    #[debug = "0b{:08b}"]
    bitmask: u8,
}

Here, one possible instance of the struct above might be printed by its generated Debug impl like this:

Field { name: "st0", bitmask: 0b00011100 }

This project covers:

  • traversing syntax trees;
  • constructing output source code;
  • processing helper attributes;
  • dealing with lifetime parameters and type parameters;
  • inferring trait bounds on generic parameters of trait impls;
  • limitations of derive's ability to emit universally correct trait bounds.

Project skeleton is located under the debug directory.

Function-like macro: seq!

This macro provides a syntax for stamping out sequentially indexed copies of an arbitrary chunk of code.

For example our application may require an enum with sequentially numbered variants like Cpu0 Cpu1 Cpu2 ... Cpu511. But note that the same seq! macro should work for any sort of compile-time loop; there is nothing specific to emitting enum variants. A different caller might use it for generating an expression like tuple.0 + tuple.1 + ... + tuple.511.

use seq::seq;

seq!(N in 0..512 {
    #[derive(Copy, Clone, PartialEq, Debug)]
    pub enum Processor {
        #(
            Cpu~N,
        )*
    }
});

fn main() {
    let cpu = Processor::Cpu8;

    assert_eq!(cpu as u8, 8);
    assert_eq!(cpu, Processor::Cpu8);
}

This project covers:

  • parsing custom syntax;
  • low-level representation of token streams;
  • constructing output source code.

Project skeleton is located under the seq directory.

Attribute macro: #[sorted]

A macro for when your coworkers (or you yourself) cannot seem to keep enum variants in sorted order when adding variants or refactoring. The macro will detect unsorted variants at compile time and emit an error pointing out which variants are out of order.

#[sorted]
#[derive(Debug)]
pub enum Error {
    BlockSignal(signal::Error),
    CreateCrasClient(libcras::Error),
    CreateEventFd(sys_util::Error),
    CreateSignalFd(sys_util::SignalFdError),
    CreateSocket(io::Error),
    DetectImageType(qcow::Error),
    DeviceJail(io_jail::Error),
    NetDeviceNew(virtio::NetError),
    SpawnVcpu(io::Error),
}

This project covers:

  • compile-time error reporting;
  • application of visitor pattern to traverse a syntax tree;
  • limitations of the currently stable macro API and some ways to work around them.

Project skeleton is located under the sorted directory.

Attribute macro: #[bitfield]

This macro provides a mechanism for defining structs in a packed binary representation with access to ranges of bits, similar to the language-level support for bit fields in C.

The macro will conceptualize one of these structs as a sequence of bits 0..N. The bits are grouped into fields in the order specified by a struct written by the caller. The #[bitfield] attribute rewrites the caller's struct into a private byte array representation with public getter and setter methods for each field.

The total number of bits N is required to be a multiple of 8 (this will be checked at compile time).

For example, the following invocation builds a struct with a total size of 32 bits or 4 bytes. It places field a in the least significant bit of the first byte, field b in the next three least significant bits, field c in the remaining four most significant bits of the first byte, and field d spanning the next three bytes.

use bitfield::*;

#[bitfield]
pub struct MyFourBytes {
    a: B1,
    b: B3,
    c: B4,
    d: B24,
}
                               least significant bit of third byte
                                 ┊           most significant
                                 ┊             ┊
                                 ┊             ┊
║  first byte   ║  second byte  ║  third byte   ║  fourth byte  ║
╟───────────────╫───────────────╫───────────────╫───────────────╢
║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
╟─╫─────╫───────╫───────────────────────────────────────────────╢
║a║  b  ║   c   ║                       d                       ║
                 ┊                                             ┊
                 ┊                                             ┊
               least significant bit of d         most significant

The code emitted by the #[bitfield] macro for this struct would be as follows. Note that the field getters and setters use whichever of u8, u16, u32, u64 is the smallest while being at least as large as the number of bits in the field.

impl MyFourBytes {
    // Initializes all fields to 0.
    pub fn new() -> Self;

    // Field getters and setters:
    pub fn get_a(&self) -> u8;
    pub fn set_a(&mut self, val: u8);
    pub fn get_b(&self) -> u8;
    pub fn set_b(&mut self, val: u8);
    pub fn get_c(&self) -> u8;
    pub fn set_c(&mut self, val: u8);
    pub fn get_d(&self) -> u32;
    pub fn set_d(&mut self, val: u32);
}

This project covers:

  • traversing syntax trees;
  • processing helper attributes;
  • constructing output source code;
  • interacting with traits and structs other than from the standard library;
  • techniques for compile-time assertions that require type information, by leveraging the trait system in interesting ways from generated code;
  • tricky code.

Project skeleton is located under the bitfield directory.

Project recommendations

If this is your first time working with procedural macros, I would recommend starting with the derive(Builder) project. This will get you comfortable with traversing syntax trees and constructing output source code. These are the two fundamental components of a procedural macro.

After that, it would be equally reasonable to jump to any of derive(CustomDebug), seq!, or #[sorted].

  • Go for derive(CustomDebug) if you are interested in exploring how macros manipulate trait bounds, which is one of the most complicated aspects of code generation in Rust involving generic code like Serde. This project provides an approachable introduction to trait bounds and digs into many of the challenging aspects.

  • Go for seq! if you are interested in parsing a custom input syntax yourself. The other projects will all mostly rely on parsers that have already been written and distributed as a library, since their input is ordinary Rust syntax.

  • Go for #[sorted] if you are interested in generating diagnostics (custom errors) via a macro. Part of this project also covers a different way of processing input syntax trees; the other projects will do most things through if let. The visitor approach is better suited to certain types of macros involving statements or expressions as we'll see here when checking that match arms are sorted.

I would recommend starting on #[bitfield] only after you feel you have a strong grasp on at least two of the other projects. Note that completing the full intended design will involve writing at least one of all three types of procedural macros and substantially more code than the other projects.


Test harness

Testing macros thoroughly tends to be tricky. Rust and Cargo have a built-in testing framework via cargo test which can work for testing the success cases, but we also really care that our macros produce good error message when they detect a problem at compile time; Cargo isn't able to say that failing to compile is considered a success, and isn't able to compare that the error message produced by the compiler is exactly what we expect.

The project skeletons in this repository use an alternative test harness called trybuild.

The test harness is geared toward iterating on the implementation of a procedural macro, observing the errors emitted by failed executions of the macro, and testing that those errors are as expected.


Workflow

Every project has a test suite already written under its tests directory. (But feel free to add more tests, remove tests for functionality you don't want to implement, or modify tests as you see fit to align with your implementation.)

Run cargo test inside any of the 5 top-level project directories to run the test suite for that project.

Initially every projects starts with all of its tests disabled. Open up the project's tests/progress.rs file and enable tests one at a time as you work through the implementation. The test files (for example tests/01-parse.rs) each contain a comment explaining what functionality is tested and giving some tips for how to implement it. I recommend working through tests in numbered order, each time enabling one more test and getting it passing before moving on.

Tests come in two flavors: tests that should compile+run successfully, and tests that should fail to compile with a specific error message.

If a test should compile and run successfully, but fails, the test runner will surface the compiler error or runtime error output.

For tests that should fail to compile, we compare the compilation output against a file of expected errors for that test. If those errors match, the test is considered to pass. If they do not match, the test runner will surface the expected and actual output.

Expected output goes in a file with the same name as the test except with an extension of *.stderr instead of *.rs.

If there is no *.stderr file for a test that is supposed to fail to compile, the test runner will save the compiler's output into a directory called wip adjacent to the tests directory. So the way to update the "expected" output is to delete the existing *.stderr file, run the tests again so that the output is written to wip, and then move the new output from wip to tests.


Debugging tips

To look at what code a macro is expanding into, install the cargo expand Cargo subcommand and then run cargo expand in the repository root (outside of any of the project directories) to expand the main.rs file in that directory. You can copy any of the test cases into this main.rs and tweak it as you iterate on the macro.

If a macro is emitting syntactically invalid code (not just code that fails type-checking) then cargo expand will not be able to show it. Instead have the macro print its generated TokenStream to stderr before returning the tokens.

eprintln!("TOKENS: {}", tokens);

Then a cargo check in the repository root (if you are iterating using main.rs) or cargo test in the corresponding project directory will display this output during macro expansion.

Stderr is also a helpful way to see the structure of the syntax tree that gets parsed from the input of the macro.

eprintln!("INPUT: {:#?}", syntax_tree);

Note that in order for Syn's syntax tree types to provide Debug impls, you will need to set features = ["extra-traits"] on the dependency on Syn. This is because adding hundreds of Debug impls adds an appreciable amount of compile time to Syn, and we really only need this enabled while doing development on a macro rather than when the finished macro is published to users.


License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this codebase by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

proc-macro-workshop's People

Contributors

alexcrichton avatar dtolnay avatar ehsanmok avatar ericseppanen avatar felixrabe avatar jplatte avatar nindalf avatar pipsqueakh avatar robbepop avatar skaunov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proc-macro-workshop's Issues

Learning documents

Is there a presentation or book or blog posts that can be followed to learn the necessary knowledge required to accomplish the workshop ?

Help with Cargo Expand?

Hello,

Thank you for the wonderful workshop. It is helping me learn macros!
One thing is that I am using the cargo expand command... and I'm not getting clean expansion...

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let ast = match ::syn::parse_macro_input::parse::<DeriveInput>(input) {
        ::syn::__private::Ok(data) => data,
        ::syn::__private::Err(err) => {
            return ::syn::__private::TokenStream::from(err.to_compile_error());
        }
    };
    let name = &ast.ident;
    let bname = syn::Ident::new(
        &{
            let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
                &["", "Builder"],
                &[::core::fmt::ArgumentV1::new_display(&name)],
            ));
            res
        },
        name.span(),
    );
    let fields = if let syn::Data::Struct(syn::DataStruct {
        fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
        ..
    }) = ast.data
    {
        named
    } else {
        ::core::panicking::panic("not implemented")
    };
    let bname_struct_fields = fields.iter().map(|field| {
        let field_name = &field.ident;
        let field_type = &field.ty;
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_ident(&mut _s, "std");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&field_type, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            _s
        }
    });
    let builder = {
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "pub");
        ::quote::__private::push_ident(&mut _s, "struct");
        ::quote::ToTokens::to_tokens(&bname, &mut _s);
        ::quote::__private::push_group(&mut _s, ::quote::__private::Delimiter::Brace, {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let has_iter = ::quote::__private::ThereIsNoIteratorInRepetition;
                #[allow(unused_mut)]
                let (mut bname_struct_fields, i) = bname_struct_fields.quote_into_iter();
                let has_iter = has_iter | i;
                let _: ::quote::__private::HasIterator = has_iter;
                while true {
                    let bname_struct_fields = match bname_struct_fields.next() {
                        Some(_x) => ::quote::__private::RepInterp(_x),
                        None => break,
                    };
                    ::quote::ToTokens::to_tokens(&bname_struct_fields, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                }
            };
            _s
        });
        ::quote::__private::push_ident(&mut _s, "impl");
        ::quote::ToTokens::to_tokens(&name, &mut _s);
        ::quote::__private::push_group(&mut _s, ::quote::__private::Delimiter::Brace, {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "pub");
            ::quote::__private::push_ident(&mut _s, "fn");
            ::quote::__private::push_ident(&mut _s, "builder");
            ::quote::__private::push_group(
                &mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new(),
            );
            ::quote::__private::push_rarrow(&mut _s);
            ::quote::ToTokens::to_tokens(&bname, &mut _s);
            ::quote::__private::push_group(&mut _s, ::quote::__private::Delimiter::Brace, {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::__private::push_ident(&mut _s, "return");
                ::quote::ToTokens::to_tokens(&bname, &mut _s);
                ::quote::__private::push_group(&mut _s, ::quote::__private::Delimiter::Brace, {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "executable");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "None");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "args");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "None");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "env");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "None");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "current_dir");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "None");
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
                _s
            });
            _s
        });
        _s
    };
    return builder.into();
}
const _: () = {
    extern crate proc_macro;
    #[rustc_proc_macro_decls]
    #[allow(deprecated)]
    static _DECLS: &[proc_macro::bridge::client::ProcMacro] =
        &[proc_macro::bridge::client::ProcMacro::custom_derive(
            "Builder",
            &[],
            derive,
        )];
};

I've seen other output, per tutorial by Jon Gjengset](https://www.youtube.com/watch?v=geovSK3wMB8), and it looks clearer and helpful. While the current expand is filled with " ::quote::__private::push_comma"... Not sure if it is just a setting? Any direction or help would be really appreciated!

Thank you!

Thank you for creating this!

Thank you for creating this project; I'm moving through it very slowly, but I wanted to say thank you for putting it together. It's been a big help in learning to read macros in rust!

List comprehension macro

I'd like to see a basic implementation of a macro for Python-style list and map comprehensions, and decide whether it would be a better teaching example for function-like proc macros than the current seq! project.

Something like:

let squares_map = c![n => n*n for n in 0..100 if n % 5 != 0];

Suggested by @jonhoo.

Full path in compiler error for bitfield 04

I am trying to solve 04 right now. There is just a small thing left where I was wondering how this was done in the reference implementation.

My error output currently looks like this:

EXPECTED:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0277]: the trait bound `bitfield::checks::SevenMod8: bitfield::checks::TotalSizeIsMultipleOfEightBits` is not satisfied
  --> tests/04-multiple-of-8bits.rs:53:1
   |
53 | #[bitfield]
   | ^^^^^^^^^^^ the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is not implemented for `bitfield::checks::SevenMod8`
   |
   = help: the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is implemented for `bitfield::checks::ZeroMod8`
   = note: this error originates in the attribute macro `bitfield` (in Nightly builds, run with -Z macro-backtrace for more info)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0277]: the trait bound `SevenMod8: TotalSizeIsMultipleOfEightsBits` is not satisfied
   --> tests/04-multiple-of-8bits.rs:53:1
    |
53  | #[bitfield]
    | ^^^^^^^^^^^ the trait `TotalSizeIsMultipleOfEightsBits` is not implemented for `SevenMod8`
    |
    = help: the trait `TotalSizeIsMultipleOfEightsBits` is implemented for `ZeroMod8`
note: required by a bound in `width_check`
   --> src/lib.rs
    |
    |     pub fn width_check<T: TotalSizeIsMultipleOfEightsBits>() {}
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `width_check`
    = note: this error originates in the attribute macro `bitfield` (in Nightly builds, run with -Z macro-backtrace for more info)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

I am happy with this, but I was curious why my output does not have the full paths as the reference implementation had and what to consider to get those full paths.

Kind Regards
Robin

The build function in Builder derive macro should take ownership

In the build derive macro, 04-call-builder, the build function takes a &mut self, and it returns a Command with the ownership of the fields. So I have to clone the fields in the implementation, which is not correct I suppose.

The build function should take the ownership of Builder, and return a builded result.

like this:

fn build(self) -> Result<Command, dyn Error>{
    unimplemented!()
}

Getting the correct span for multi-segment Path

Hi,

I spent a long time tracing down a bug in my implementation of the sorted macro against the 06-pattern-path test. Namely, my output always looked like this:

error: Error::Fmt should sort before Error::Io
  --> main.rs:23:7
   |
23 |       Error::Fmt(e) => write!(f, "{}", e),
   |       ^^^^^

where the error span is just Error instead of the expected Error::Fmt. I checked my code to made sure in my code that it's indeed the path's span being stored and emitted instead of the ident's. It's even more confusing because cargo expand gives the expected output whereas the cargo test and cargo run gives different answer.

After more debugging and searching I finally stumbled upon this issue. So after switching my toolchain from stable to nightly, the test passes as expected. Now I see it's because the stable proc-macro doesn't allow joining of spans, thus only the first token of the syntax tree is returned upon calling the Spanned::span function.

I think it may be good to indicate this trick somewhere in note since it's probably not a trivial bug. Anyway, to the future learners who had their head scratched hard for this error, I hope this issue can be helpful :)

Builder test 2 - Option intended?

builder/tests/02-create-builder.rs says "Before moving on, have the macro also generate:" and it shows the CommandBuilder struct based on the input Command type, but with each field made Optional if it's not already.

I understand why they'd be optional -- the builder pattern requires adding individual fields before construction of the output type -- but is dynamically making fields optional an intended piece of test 2? It comes quite early in the workshop, but doing so seems rather complicated.

In particular, it seems to assume knowledge gained in test 6 (see #12) regarding detection of whether a field has Option (because one field is already Option) plus knowledge of modifying field types.

Simpler tasks, like building an ident, have a resource link, but there's no comment about type modification.

Sorry if I'm missing an obvious alternative for adding Option!

Seq05 would break multiple other tests.

Seq05 introduced new rule to only repeat tokens inside #( ... )*.
This new rule would cause other tests fall since they don't have their full body inside #( ... )*.
Broken tests are Seq02, Seq03, Seq04, Seq08 .

Why span in `compile_error!` error message include the ending `;` ?

I use rustc 1.54.0 (a178d0322 2021-07-26), in seq/tests/03-expand-four-errors.rs, span in the expected error message doesn't include the ending ; as shown below.

error: error number 0
  --> tests/03-expand-four-errors.rs:20:5
   |
20 |     compile_error!(concat!("error number ", stringify!(N)));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

However, my solution gives an almost equal version except that the span ^ includes the ending ';'.

error: error number 0
  --> tests/03-expand-four-errors.rs:20:5
   |
20 |     compile_error!(concat!("error number ", stringify!(N)));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I try to add compile_error! explicitly, and the error message also includes the ending ;.

error: error number 0
  --> main.rs:13:5
   |
13 |     compile_error!(concat!("error number ", stringify!(0)));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I wonder whether it's just an compiler version issue or it's intended to be so and I should figure out how to control the span in error meesage generated by compile_error!?

seq test case 03 fail

Hi,
my code failed when executing seq test case 03, the output is:

ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: error number 0
  --> tests/03-expand-four-errors.rs:19:1
   |
19 | / seq!(N in 0..4 {
20 | |     compile_error!(concat!("error number ", stringify!(N)));
21 | | });
   | |__^
   |
   = note: this error originates in the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: error number 1
  --> tests/03-expand-four-errors.rs:19:1
   |
19 | / seq!(N in 0..4 {
20 | |     compile_error!(concat!("error number ", stringify!(N)));
21 | | });
   | |__^
   |
   = note: this error originates in the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: error number 2
  --> tests/03-expand-four-errors.rs:19:1
   |
19 | / seq!(N in 0..4 {
20 | |     compile_error!(concat!("error number ", stringify!(N)));
21 | | });
   | |__^
   |
   = note: this error originates in the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: error number 3
  --> tests/03-expand-four-errors.rs:19:1
   |
19 | / seq!(N in 0..4 {
20 | |     compile_error!(concat!("error number ", stringify!(N)));
21 | | });
   | |__^
   |
   = note: this error originates in the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)

I even tried to print the result TokenStream before returning and it looks totally correct (begin with "compile_error" as ident).
Download someone else's code from github and test, same output...

rust version: 1.75.0

dependencies:

proc-macro2 = "1.0.81"
syn = { version = "2.0.59", features = ["full", "extra-traits"] }
quote = "1.0.36"
prettyplease = "0.2.1"

repository: https://github.com/apodemakeles/proc-macro-workshop/blob/seq/seq/src/lib.rs

Unused code warning in sorted 04

I received unused import warnings which lead to a compile error missmatch for an otherwise correct test 04.
I solved this issue by adding #![allow(unused_imports)] to the 04 code and adapting the line numbers in the *.stderr file.

[Help] How to parsing multi recursive proc macros ?

I want parsing recursive like this ”any“,”all” proc content. Is there a simpler tool or example?

(command is convert fn to trait struct, like rocket get/post macros)

// get name content
#[event(command("hello {name}"))]
async fn hello( name: Option<String>) -> anyhow::Result<bool> {
    if name.is_none() {
        return Ok(false);
    }
    Ok(true)
}

// all , any 
#[event(all(command("hello {name}", filter = "i_am_filter")))]
async fn hello( name: Option<String>) -> anyhow::Result<bool> {
    if name.is_none() && name.unwrap().start_with("123") {
        return Ok(false);
    }
    event.send_message_to_source(format!("hello {}", name.unwrap()).parse_message_chain()).await.unwrap();
    Ok(true)
}

async fn i_am_filter( name: Option<String>) -> anyhow::Result<bool> {
    if name.is_none() {
        return Ok(false);
    }
   // do some
    Ok(true)
}

#[event(regexp("^123$"))]

Feature request: examples using proc-macro2 and darling

Thank you for creating this project! It's what I've been looking for to learn how to write procedural macros in rust.

I noticed that you are the co-author of the proc-macro2 crate; could you add some examples of how to use that crate here? Also, could you give examples of how to use darling?

I know nothing about writing macros, but want to learn how to do so correctly so that end users actually want to use my macros, instead of feeling that they have to use them. So once again, thank you for making this project!

What to do?

Sorry if its really obvious. Where do I find the article, video, or whatever I am supposed to follow along?

Update projects and tests to 2021 edition

At least the seq! project will require some rework because ident concatenation Variant#N is no longer legal syntax lexically. There may be other incompatibilities in the other projects as well.

Help with `Seq` macro

Hi! I'm working on implementing the Seq macro and I'm getting a little stuck. It alludes to treating the body of the expression as a TokenStream and not as some type of syn AST structure.

I can parse out everything that's instructed in the 01-parse-header.rs file, but when I get to 02-parse-body.rs, I get stuck on how to treat the loop body of the macro as a TokenStream. Additionally, when I attempt to run cargo test, I get an error on unexpected tokens.

The error message:

Testing tests/01-parse-header.rs ... error
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: unexpected token
  --> $DIR/01-parse-header.rs:25:16
   |
25 |   seq!(N in 0..8 {
   |  ________________^
26 | |     // nothing
27 | | });
   | |_^
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

Link to the code I have so far: https://github.com/ELD/proc-macro-workshop/blob/seq-macro/seq/src/lib.rs

Any guidance would be appreciated!

Compile fail on newer version of syn and quote on derive_builder unrecognize attribute test

In the newer version of syn and quote this 08-unrecognized-attribute.rs builder test fails.

EXPECTED:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: expected `builder(each = "...")`
  --> $DIR/08-unrecognized-attribute.rs:22:7
   |
22 |     #[builder(eac = "arg")]
   |       ^^^^^^^^^^^^^^^^^^^^
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: expected `builder(each = "...")`
  --> $DIR/08-unrecognized-attribute.rs:22:15
   |
22 |     #[builder(eac = "arg")]
   |               ^^^^^^^^^^^
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

Rust version: rustc 1.57.0-nightly (c02371c44 2021-10-01)
syn: 1.0.78
quote: 1.0.9

Nothing is implemented

there's probably something i'm not getting here, but i checked out the builder and the bitfield code and they are empty. what's happening?

Sorted: Tests for sorted match expressions do not catch if you always compare to the first arm

In sorted::check I forgot to update the last arm and ended comparing all arms with the first arm but still all tests (1-8) passed.

I only noticed my error because I was wondering why test 8 did not fail when having an arm that started with a lower case char (lower chase char > "-") before actually impl. it

EDIT:
For test 8 it also would be a good idea to use match arms that start with a lower case letter (e.g. io::Result) because otherwise the order is correct anyways because all upper case chars are less than "_". At least if you use string comparison.

Add more explanations/links to Builder

Hi

Following up on the builder tutorial, I found the transition to 06-optional-field to the end is tough. It'd help a lot to explain a little in more details what to do or link to similar code about how to check whether Option exists or not in 06.

What is best way to print whole path

Hi

For a Path type for Error::Fmt, how to print it exactly same as the code?
When I convert it to tokenstream, then print it, it has space between path segments and punct.

EXPECTED:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: Error::Fmt should sort before Error::Io
  --> tests/06-pattern-path.rs:33:13
   |
33 |             Error::Fmt(e) => write!(f, "{}", e),
   |             ^^^^^^^^^^
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: Error :: Fmt should sort before Error :: Io
  --> tests/06-pattern-path.rs:33:13
   |
33 |             Error::Fmt(e) => write!(f, "{}", e),
   |             ^^^^^^^^^^

At present, I have to use quite a long expression to get the string

path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::");

Lesson 3

If would if if gods if would if soon if would if references if self if would if will if would if god if would if tests if would if fail?

use proc_macro::TokenStream;
use syn::{DeriveInput, __private::quote::quote, parse_macro_input};
#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;
    let bname = format!("{}Builder", name);
    let bident = syn::Ident::new(&bname, name.span());
    let expanded = quote! {
        pub struct #bident {
            executable: Option<String>,
            args: Option<Vec<String>>,
            env: Option<Vec<String>>,
            current_dir: Option<String>,
        }
        impl #bident {
            pub fn executable(&mut self, executable: String) -> &mut self {
                self.executable = Some(executable);
                self
            }
            pub fn args(&mut self, args: Vec<String>) -> &mut self {
                self.args = Some(args);
                self
            }
            pub fn env(&mut self, env: Vec<String>) -> &mut self {
                self.env = Some(env);
                self
            }
            pub fn current_dir(&mut self, current_dir: String) -> &mut self {
                self.current_dir = Some(current_dir);
                self
            }
        }
        impl #name {
            fn builder() -> #bident {
                #bident {
                    executable: None,
                    args: None,
                    env: None,
                    current_dir: None
                }
            }
        }
    }; 
    expanded.into()
}

if would if throws if gods if would if error?

expected type, found module `self`
  --> $DIR/02-create-builder.rs:47:10
   |
47 | #[derive(Builder)]
   |          ^^^^^^^ help: a self type with a similar name exists: `Self`
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

if would if could if would if post if would if answers if gods if would if solutions if to if would if lessons?

if would if is if gods https://www.youtube.com/watch?v=geovSK3wMB8&t=17s if minute if 42.22

builder/tests/07-repeated-field.rs needs a fix to pass

Hello. First things first, this is an excellent learning resource for Rust. If I was a recruiter, I would require passing this workshop before hiring. =) Now to the issue: In the builder project tests/07-repeated-field.rs , the test can't pass because the env field isn't optional but the test code doesn't set it before calling build(). A line like .env("FOO=1".to_owned()) just before the call to build (line 49) should fix this. It made it pass for me.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.