how to debug a custom proc macro? - rust

I am studying Rust macro, and i want to see the expand output of the macro.
I had tried by using command: rustc +nightly -Zunpretty=expanded xxx.rs follow the tutorial The Book of Rust Macros - Debugging.
but i got the error when i use the custom macro in the same crate:
can't use a procedural macro from the same crate
But when i moved the test code into other file, it still failed with another message:
unresolved import
And then when i changed the code followed the help message, it change the error message:
can't find crate
here are my demo:
use syn;
use quote::{quote, ToTokens, TokenStreamExt};
use proc_macro;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() -> String {
String::from(stringify!(#name))
}
}
};
gen.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_macro () {
trait HelloMacro {
fn hello_macro() -> String {
String::from("666")
}
}
#[derive(HelloMacro)]
struct A;
assert_eq!(A::hello_macro(), "A".to_string());
}
}
I had also tried using cargo-expand, but the output still incorrect.
error[E0514]: found crate `my_macro` compiled by an incompatible version of rustc
--> my_macro\tests\test_macro.rs:1:5
|
1 | use my_macro::{ HelloMacro };
| ^^^^^^^^
|
= note: the following crate versions were found:
crate `my_macro` compiled by rustc 1.66.1 (90743e729 2023-01-10): \\?\D:\vscode\rust-study\game\my_mine_sweeper\target\debug\deps\my_macro-b82452821ca4c556.dll
= help: please recompile that crate using this compiler (rustc 1.69.0-nightly (2773383a3 2023-02-10)) (consider running `cargo clean` first)
error: cannot determine resolution for the derive macro `HelloMacro`
--> my_macro\tests\test_macro.rs:11:14
|
11 | #[derive(HelloMacro)]
| ^^^^^^^^^^
|
= note: import resolution is stuck, try simplifying macro imports
For more information about this error, try `rustc --explain E0514`.
error: could not compile `my_macro` due to 2 previous errors
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use my_macro::HelloMacro;
extern crate test;
#[cfg(test)]
#[rustc_test_marker = "test_macro"]
pub const test_macro: test::TestDescAndFn = test::TestDescAndFn {
desc: test::TestDesc {
name: test::StaticTestName("test_macro"),
ignore: false,
ignore_message: ::core::option::Option::None,
compile_fail: false,
no_run: false,
should_panic: test::ShouldPanic::No,
test_type: test::TestType::IntegrationTest,
},
testfn: test::StaticTestFn(|| test::assert_test_result(test_macro())),
};
fn test_macro() {
trait HelloMacro {
fn hello_macro() -> String {
String::from("666")
}
}
struct A;
match (&A::hello_macro(), &"A".to_string()) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(
kind,
&*left_val,
&*right_val,
::core::option::Option::None,
);
}
}
};
}
#[rustc_main]
pub fn main() -> () {
extern crate test;
test::test_main_static(&[&test_macro])
}
I just want to know how to debug the procedural macro and see the expand output since it is hard to know whether the result is correct.

The invocation of a macro and the macro itself cannot be in the same crate.
That's why many projects are split into two crates:
<cratename>
<cratename>-macros
For example tokio and tokio-macros.
Usually, this is then organized into a workspace layout, with <cratename> and <cratename>-macros as subcrates. As in tokio. (Note that tokio has many more subcrates, but only tokio and tokio-macros are important for your problem)
You are probably aware of all of this already and only want to know how to test the macros in your <cratename>-macros crate.
You cannot test macros in the same file. You can, however, use the tests directory. It's more appropriate anyway, because your tests are integration tests. In-file tests are more meant for unit tests, as you have full access to struct internals. In the tests directory the visibility of your crate is more representative of how users would see your crate.
Like this:
src/lib.rs:
use proc_macro;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() -> String {
String::from(stringify!(#name))
}
}
};
gen.into()
}
tests/test_macros.rs:
use rust_playground::HelloMacro;
#[test]
fn test_macro() {
trait HelloMacro {
fn hello_macro() -> String {
String::from("666")
}
}
#[derive(HelloMacro)]
struct A;
assert_eq!(A::hello_macro(), "A".to_string());
}
$ cargo test
Compiling rust_playground v0.1.0 (/home/martin/work/rust_playground)
Finished test [unoptimized + debuginfo] target(s) in 0.45s
Running unittests src/lib.rs (target/debug/deps/rust_playground-4e67636c4d9901bb)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/test_macros.rs (target/debug/deps/test_macros-1278dc310f04b8db)
running 1 test
test test_macro ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rust_playground
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
More (potentially) helpful tips:
For quick and dirty feedback you can also use cargo-expand to show what your macro expands to.
To install it, run:
cargo install cargo-expand
rustup component add rustfmt
Then, let's for example expand the tests/test_macros.rs file:
$ cargo expand --test test_macros
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use rust_playground::HelloMacro;
extern crate test;
#[cfg(test)]
#[rustc_test_marker = "test_macro"]
pub const test_macro: test::TestDescAndFn = test::TestDescAndFn {
desc: test::TestDesc {
name: test::StaticTestName("test_macro"),
ignore: false,
ignore_message: ::core::option::Option::None,
compile_fail: false,
no_run: false,
should_panic: test::ShouldPanic::No,
test_type: test::TestType::IntegrationTest,
},
testfn: test::StaticTestFn(|| test::assert_test_result(test_macro())),
};
fn test_macro() {
trait HelloMacro {
fn hello_macro() -> String {
String::from("666")
}
}
struct A;
impl HelloMacro for A {
fn hello_macro() -> String {
String::from("A")
}
}
match (&A::hello_macro(), &"A".to_string()) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(
kind,
&*left_val,
&*right_val,
::core::option::Option::None,
);
}
}
};
}
#[rustc_main]
pub fn main() -> () {
extern crate test;
test::test_main_static(&[&test_macro])
}
It is a little bit noisy because it expands all macros, including #[test]. But you can find the expansion of your macro in there:
struct A;
impl HelloMacro for A {
fn hello_macro() -> String {
String::from("A")
}
}

Related

How to verify `extern "C"` definition signature matches implementation

Given the following files:
main.rs:
mod ffi;
mod impl_do_print;
fn main() {
unsafe {
ffi::do_print(42.0);
}
}
ffi.rs:
extern "C" {
pub fn do_print(x: f32);
}
impl_do_print.rs:
#[no_mangle]
pub extern "C" fn do_print(x: i32) {
println!("{}", x);
}
Obviously, the f32 of the definition and the i32 of the implementation don't match.
When I execute this, it prints:
1047505936
I understand that no_mangle is automatically considered unsafe, but is there any way I could ask the compiler to catch the mismatch, or would I have to write my own linter for this?
Usecase:
This question came up with generated FFIs. I am able to modify the implementation in any way possible, but I cannot edit the function definition, as it is generated via bindgen.
So far, two potentially viable solutions were proposed:
Use the ffi.rs to auto-generate an implementation for the method that simply forwards the call to my own implementation. That way, the compiler would catch mismatching arguments.
Use the ffi.rs to generate a type definition for the function. That way, you could write a compiler check to verify that the implementation matches the definition, like this: const _: fn_type = fn_impl.
Either way, it seems to require proc macros or an external generator.
After a little more experimenting, I managed to achieve a compile time check by utilizing a macro:
impl_do_print.rs:
#[no_mangle]
pub extern "C" fn do_print(x: i32) {
println!("{}", x);
}
macro_rules! check_implementation_type {
($t:ty, $name:ident) => {
const _: $t = $name;
const _: $t = crate::ffi::$name;
};
}
check_implementation_type!(unsafe extern "C" fn(i32), do_print);
This still requires me to write a check_implementation_type entry for every function I implement, but it gives me a reliable compile time error if either the ffi.rs or the implementation don't match:
error[E0308]: mismatched types
--> src/impl_do_print.rs:9:23
|
9 | const _: $t = crate::ffi::$name;
| ^^^^^^^^^^^^^^^^^ expected `i32`, found `f32`
...
13 | check_implementation_type!(unsafe extern "C" fn(i32), do_print);
| --------------------------------------------------------------- in this macro invocation
|
= note: expected fn pointer `unsafe extern "C" fn(i32)`
found fn item `unsafe extern "C" fn(f32) {ffi::do_print}`
= note: this error originates in the macro `check_implementation_type` (in Nightly builds, run with -Z macro-backtrace for more info)

How should I use `macro_export` for a custom module

I have the following structure:
Inside queues.rs I have a #[macro_export],
#[macro_export]
macro_rules! queue {...}
but I am not sure how to import it in lib.rs.
What I have looks like this:
use crate::utils::queues::*;
// use it here
let mut service_queue: Queue<isize> = queue![];
This throws an error
error: cannot find macro `queue` in this scope
--> src/broker.rs:54:51
|
54 | let mut service_queue: Queue<isize> = queue![];
| ^^^^^
|
= note: consider importing this macro:
crate::queue
= help: have you added the `#[macro_use]` on the module/import?
What is the correct way to type #[macro_use] here, and import my custom macros from utils?
You can use the queue! macro from lib.rs using the #[macro_use] attribute (playground):
#[macro_use]
mod utils {
#[macro_use]
mod queues {
macro_rules! queue {
() => { vec![] };
}
}
}
pub fn foo() {
let _service_queue: Vec<isize> = queue![];
}
In your existing code you are using #[macro_export] which means it is declared in the crate root scope and not in the utils::queues module. In your error message you can see the compile is suggesting to consider importing this macro: crate::queue.
To access it in this case (playground):
mod utils {
mod queues {
#[macro_export]
macro_rules! queue {
() => { vec![] };
}
}
}
pub fn foo() {
let _service_queue: Vec<isize> = queue![];
}
Note that in either case the use crate::utils::queues::*; statement does not help, in fact you'll get an unused_imports warning (unless you happen to have other items declared in that module).
There is also this trick (playground):
pub mod utils {
pub mod queues {
macro_rules! queue {
() => { vec![] };
}
pub(crate) use queue;
}
}
pub fn foo() {
let _service_queue: Vec<isize> = crate::utils::queues::queue![];
}
Aside: if you plan to use the macro in other crates and you wish to retain the module hierarchy (i.e. access from my_crate::utils::queues::queue! from my_other_crate) there is a (convoluted) way to do that.

Why is rust complaining about an unused function when it is only used from tests?

When a function is only called from tests rust complains that it is never used. Why does this happen and how to fix this?
Example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=52d8368dc5f30cf6e16184fcbdc372dc
fn greet() {
println!("Hello!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_greet() {
greet();
}
}
I get the following compiler warning:
Compiling playground v0.0.1 (/playground)
warning: function is never used: `greet`
--> src/lib.rs:1:4
|
1 | fn greet() {
| ^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: 1 warning emitted
In rust fn is private by default. greet() is not accessible outside your module. If greet() is not used inside it except in tests, then rust is correctly flagging it as dead code.
If greet() is supposed to be part of your public interface mark it as pub:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8a8c50b97fe3f1eb72a01a6252e9bfe6
pub fn greet() {
println!("Hello!")
}
If greet() is a helper intended to be only used in tests move it inside mod tests:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3dc51a36b4d5403ca655dec0210e4098
#[cfg(test)]
mod tests {
fn greet() {
println!("Hello!")
}
#[test]
fn test_greet() {
greet();
}
}

Using conditionally compiled module under `cfg` macro

I wonder how to use a conditionally compiled module under cfg! macro. I am trying this:
pub fn f() { ... }
#[cfg(feature = "x")]
pub mod xmodule {
pub fn f() { ... }
}
pub fn test() {
if cfg!(feature = "x") {
xmodule::f();
} else {
f();
};
}
It works fine when I compile it with cargo check --features x, but if I don't enable the feature it fails with the following error:
use of undeclared type or module `xmodule`
Am I doing something wrong or the compilation is not smart enough to understand that the module should not be used if the feature is not set?
While the #[cfg] attribute will conditionally compile code, cfg! is gives the equivalent boolean value (e.g. true if a feature is enabled, false otherwise). So your code essentially compiles into:
pub fn test() {
if false { // assuming "x" feature is not set
xmodule::f();
} else {
f();
};
}
Therefore both branches must still contain valid code, even if only one is ever run.
To get actual conditional compilation, you may do something like this:
pub fn test() {
#[cfg(feature = "x")]
fn inner() {
xmodule::f()
}
#[cfg(not(feature = "x"))]
fn inner() {
f()
}
inner();
}
Playground example
Or you can use a third-party macro like cfg-if:
use cfg_if::cfg_if;
pub fn test() {
cfg_if! {
if #[cfg(feature = "x")] {
xmodule::f();
} else {
f();
}
}
}
Playground example

How to fix `definition of unknown language item 'panic_fmt'`? [duplicate]

This question already has an answer here:
What is a crate attribute and where do I add it?
(1 answer)
Closed 4 years ago.
For a no_std application there are a few language items defined in lang_items.rs, one of them being the panic_fmt language item (to specify the behavior of panic! in this no_std context) defined like:
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! { loop{} }
When compiling, I receive this error:
error[E0522]: definition of an unknown language item: `panic_fmt`
--> src/lang_items.rs:3:1
|
3 | #[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! { loop{} }
| ^^^^^^^^^^^^^^^^^^^^^ definition of unknown language item `panic_fmt`
error: `#[panic_implementation]` function required, but not found
After reading RFC 2070 I learned there was a recent breaking change for no_std/embedded programs. While it's recommended that I use the #[panic_implementation] attributes, a recently added feature, I still receive an error, doing so like:
#[panic_implementation] #[no_mangle] pub extern fn panic_fmt() -> ! { loop{} }
Gives the error:
error[E0658]: #[panic_implementation] is an unstable feature (see issue #44489)
--> src/lang_items.rs:4:1
|
4 | #[panic_implementation] #[no_mangle] pub extern fn panic_fmt() -> ! { loop{} }
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(panic_implementation)] to the crate attributes to enable
Following their suggestion of adding #![feature(panic_implementation)] to the top of the lang_items.rs file doesn't seem to do the trick, as I'm getting the same error. How do I enable this unstable feature properly so that I can compile this no_std application with behavior for panic! defined?
Okay, it was a simple mistake I was making. Here is a link to the playground containing my code. First of all, I needed to add the crate attribute to the top of my crate root (which, for me, was lib.rs). lang_items.rs was just a file that was used in lib.rs like so:
#![feature(compiler_builtins_lib, lang_items, asm, panic_implementation, core_intrinsics)]
#![no_builtins]
#![no_std]
pub mod lang_items;
const GPIO_BASE: usize = 0x3F000000 + 0x200000;
const GPIO_FSEL1: *mut u32 = (GPIO_BASE + 0x04) as *mut u32;
const GPIO_SET0: *mut u32 = (GPIO_BASE + 0x1C) as *mut u32;
const GPIO_CLR0: *mut u32 = (GPIO_BASE + 0x28) as *mut u32;
#[inline(never)]
fn spin_sleep_ms(ms: usize) {
for _ in 0..(ms * 600) {
unsafe { asm!("nop" :::: "volatile"); }
}
}
#[no_mangle]
pub unsafe extern "C" fn kmain() {
// STEP 1: Set GPIO Pin 16 as output.
GPIO_FSEL1.write_volatile(0x1 << 0x12);
// STEP 2: Continuously set and clear GPIO 16.
loop {
GPIO_SET0.write_volatile(0x1 << 0x10);
spin_sleep_ms(256);
GPIO_CLR0.write_volatile(0x1 << 0x10);
spin_sleep_ms(256);
}
}

Resources