Skip to content

Commit 4360da3

Browse files
committed
Evolve: Code structure and crates
Signed-off-by: Mark Van de Vyver <[email protected]>
1 parent c136066 commit 4360da3

File tree

7 files changed

+245
-129
lines changed

7 files changed

+245
-129
lines changed

minitrace-macro/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ keywords = ["tracing", "span", "datadog", "jaeger", "opentracing"]
1515
proc-macro = true
1616

1717
[dependencies]
18+
bae = "0.1.7"
1819
# The macro `quote_spanned!` is added to syn in 1.0.84
1920
syn = { version = "1.0.84", features = ["full", "parsing", "extra-traits", "proc-macro", "visit-mut"] }
2021
quote = "1"

minitrace-macro/src/lib.rs

+36-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.
2-
3-
// Instrumenting the async fn is not as straight forward as expected because `async_trait` rewrites `async fn`
4-
// into a normal fn which returns `Box<impl Future>`, and this stops the macro from distinguishing `async fn` from `fn`.
5-
// The following code reused the `async_trait` probes from [tokio-tracing](https://github.com/tokio-rs/tracing/blob/6a61897a5e834988ad9ac709e28c93c4dbf29116/tracing-attributes/src/expand.rs).
6-
71
#![recursion_limit = "256"]
8-
2+
//! This Crate adopts the [Ferrous Systems](https://ferrous-systems.com/blog/testing-proc-macros/)
3+
//! proc-macro pipeline:
4+
//!
5+
#[cfg_attr(doc, aquamarine::aquamarine)]
6+
/// ```mermaid
7+
/// flowchart LR;
8+
/// subgraph M[proc-macro-attribute]
9+
/// direction LR
10+
/// P([Parse])--AST->A([Analyze])--Model->L([Lower])--IR->C([CodeGen])
11+
/// end
12+
/// Input-. Rust .->M-. Rust .->Output;
13+
/// ```
14+
// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.
15+
//
16+
// Instrumenting the async fn is not as straight forward as expected because
17+
// `async_trait` rewrites `async fn` into a normal fn which returns
18+
// `Box<impl Future>`, and this stops the macro from distinguishing
19+
// `async fn` from `fn`.
20+
// The following code is from the `async_trait` probes from
21+
// [tokio-tracing](https://github.com/tokio-rs/tracing/blob/6a61897a5e834988ad9ac709e28c93c4dbf29116/tracing-attributes/src/expand.rs).
922
extern crate proc_macro;
1023

1124
#[macro_use]
@@ -22,14 +35,24 @@ mod trace;
2235

2336
#[proc_macro_attribute]
2437
#[proc_macro_error]
25-
pub fn trace(
38+
pub fn trace2(
2639
args: proc_macro::TokenStream,
2740
item: proc_macro::TokenStream,
2841
) -> proc_macro::TokenStream {
29-
let ast = trace::parse(args.into(), item.into());
30-
let model = trace::analyze(ast);
42+
// Parse TokenStream in context of `#[proc_macro_attribute]`.
43+
// This context is required by `parse_macro_input!(...)`.
44+
let argsc = args.clone();
45+
let itemc = item.clone();
46+
let attr_args = parse_macro_input!(argsc as crate::trace::validate::TraceAttr);
47+
let itemfn = parse_macro_input!(itemc as ItemFn);
48+
let args2: proc_macro2::TokenStream = args.clone().into();
49+
50+
// Validate - Analyze - Lower - Generate - Rust (VALGR)
51+
52+
trace::validate(args2.clone(), item.into());
53+
let model = trace::analyze(attr_args, itemfn);
3154
let ir = trace::lower(model);
32-
let rust = trace::codegen(ir);
55+
let rust = trace::generate(ir);
3356
rust.into()
3457
}
3558

@@ -71,7 +94,7 @@ impl Args {
7194

7295
#[proc_macro_attribute]
7396
#[proc_macro_error]
74-
pub fn trace1(
97+
pub fn trace(
7598
args: proc_macro::TokenStream,
7699
item: proc_macro::TokenStream,
77100
) -> proc_macro::TokenStream {
@@ -476,6 +499,7 @@ struct AsyncTraitInfo<'a> {
476499
// proper function/future.
477500
// (this follows the approach suggested in
478501
// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673)
502+
//
479503
fn get_async_trait_info(block: &Block, block_is_async: bool) -> Option<AsyncTraitInfo<'_>> {
480504
// are we in an async context? If yes, this isn't a async_trait-like pattern
481505
if block_is_async {

minitrace-macro/src/trace.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
1+
//! # Argument Parsing/Validation
2+
//!
3+
//! Arguments passed to the attribute are parsed in two phases:
4+
//!
5+
//! 1. The `TokenStream` is parsed to `Punctuated<syn::Expr, _>` and validated in `parse(..)`
6+
//! 2. The `TokenStream` is parsed to `Vec<syn::NestedMeta>` (`Args.vars`) and
7+
//! mapped Model fields in `model(...)`:
8+
//!
9+
//! - `Model.event: syn::LitStr,` // String`
10+
//! - `Model.enter_on_poll: syn::LitBool,` // boolean
11+
//! - `Model.scope: syn::LitBool,` // boolean: see issue #1
12+
//! - `Model.black_box: syn::LitBool,` // boolean: see upstream issue #122
13+
//! - `Model.parent: syn::LitStr,` // String: see upstream issue #117
14+
//! - `Model.recorder: syn::Ident,` // see upstream issue #117
15+
//! - `Model.scope: syn::Ident,` // minitrace::LocalSpan or minitrace::Span
16+
//!
17+
//! We use `syn::Ident`, etc. to generate code with call sites, to aid
18+
//! resolving compile time errors:
19+
//!
20+
//! ```rust
21+
//! let ident = syn::Ident::new(input, syn::Span::call_site());
22+
//! // Create a variable binding whose name is this ident.
23+
//! let expanded = quote! { let #ident = 10; };
24+
//! ```
25+
//!
26+
//! In addition, we can guard against Rust keywords using:
27+
//! `input.call(Ident::parse)`.
28+
//!
129
mod analyze;
2-
mod codegen;
30+
mod generate;
331
mod lower;
4-
mod parse;
32+
pub mod validate;
533

634
// Re-export crate::trace::parse::parse(...) as crate::trace::parse(...)
735
pub use crate::trace::analyze::analyze;
8-
pub use crate::trace::codegen::codegen;
36+
pub use crate::trace::generate::generate;
937
pub use crate::trace::lower::lower;
10-
pub use crate::trace::parse::parse;
38+
pub use crate::trace::validate::validate;

minitrace-macro/src/trace/analyze.rs

+128-38
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
///
2+
/// Check for `async-trait`-like patterns in the block, and instrument the
3+
/// future instead of the wrapper.
4+
///
5+
/// Instrumenting the `async fn` is not as straight forward as expected because
6+
/// `async_trait` rewrites `async fn` into a normal `fn` which returns
7+
/// `Pin<Box<dyn Future + Send + 'async_trait>>`, and this stops the macro from
8+
/// distinguishing `async fn` from `fn`.
9+
///
10+
/// The following logic and code is from the `async-trait` probes from
11+
/// [tokio-tracing][tokio-logic].
12+
/// The Tokio logic is required for detecting the `async fn` that is already
13+
/// transformed to `fn -> Pin<Box<dyn Future + Send + 'async_trait>>` by
14+
/// `async-trait`.
15+
/// We have to distinguish this case from `fn -> impl Future` that is written
16+
/// by the user because for the latter, we instrument it like normal `fn`
17+
/// instead of `async fn`.
18+
///
19+
/// The reason why we elaborate `async fn` into `fn -> impl Future`:
20+
/// For an `async fn foo()`, we have to instrument the
21+
/// `Span::enter_with_local_parent()` in the first call to `foo()`, but not in
22+
/// the `poll()` or `.await`, because it's the only chance that
23+
/// local parent is present in that context.
24+
///
25+
/// []tokio-logic]: https://github.com/tokio-rs/tracing/blob/6a61897a5e834988ad9ac709e28c93c4dbf29116/tracing-attributes/src/expand.rs
26+
use bae::FromAttributes;
127
#[allow(unused_imports)]
228
use proc_macro_error::{abort, abort_call_site};
329
use syn::{
@@ -7,31 +33,94 @@ use syn::{
733
Expr, ItemFn,
834
};
935

10-
use crate::trace::parse::Ast;
11-
12-
pub fn analyze(ast: Ast) -> Model {
13-
let mut item = ast;
14-
let attrs = &mut item.attrs;
15-
for index in (0..attrs.len()).rev() {
16-
if let Some(ident) = attrs[index].path.get_ident() {
17-
if ident.to_string().as_str() == "precondition" {
18-
let attr = attrs.remove(index);
19-
let _span = attr.tokens.span();
20-
21-
// if let Ok(arg) = syn::parse2::<AttributeArgument>(attr.tokens) {
22-
// preconditions.push(arg.expr);
23-
// } else {
24-
// // ../tests/trace/ui/err/precondition-is-not-an-expression.rs
25-
// abort!(
26-
// span,
27-
// "expected an expression as argument";
28-
// help = "example syntax: `#[precondition(argument % 2 == 0)]`")
29-
// }
30-
}
31-
}
36+
use crate::trace::validate::Ast;
37+
38+
// - `Model.event: syn::LitStr,` // String`
39+
// - `Model.enter_on_poll: syn::LitBool,` // boolean
40+
// - `Model.black_box: syn::LitBool,` // boolean: see upstream issue #122
41+
// - `Model.parent: syn::LitStr,` // String: see upstream issue #117
42+
// - `Model.recorder: syn::Ident,` // see upstream issue #117
43+
// - `Model.scope: syn::Ident,` // minitrace::Local or minitrace::Threads
44+
//
45+
// impl Default for Model {
46+
//
47+
// fn default() -> Self {
48+
// Ok(Model {
49+
// event: todo!(),
50+
// enter_on_poll: todo!(),
51+
// black_box: todo!(),
52+
// parent: todo!(),
53+
// recorder: todo!(),
54+
// scope: todo!(),
55+
// vars: v,
56+
// })
57+
// }
58+
59+
#[derive(
60+
Debug,
61+
Eq,
62+
PartialEq,
63+
// This will add two functions:
64+
// ```
65+
// fn from_attributes(attrs: &[syn::Attribute]) -> Result<Trace, syn::Error>
66+
// fn try_from_attributes(attrs: &[syn::Attribute]) -> Result<Option<Trace>, syn::Error>
67+
// ```
68+
//
69+
// `try_from_attributes(...)` returns `Ok(None)` if the attribute is missing,
70+
// `Ok(Some(_))` if its there and is valid, `Err(_)` otherwise.
71+
FromAttributes,
72+
)]
73+
pub struct Trace {
74+
// Anything that implements `syn::parse::Parse` is supported.
75+
name: syn::LitStr,
76+
scope: syn::Type, // Local or Thread
77+
78+
// Fields wrapped in `Option` are optional and default to `None` if
79+
// not specified in the attribute.
80+
enter_on_poll: Option<syn::LitBool>,
81+
black_box: Option<syn::LitBool>,
82+
recorder: Option<syn::Ident>,
83+
parent: Option<syn::LitStr>,
84+
85+
// A "switch" is something that doesn't take arguments.
86+
// All fields with type `Option<()>` are considered switches.
87+
// They default to `None`.
88+
async_trait: Option<()>,
89+
}
90+
91+
pub fn analyze(meta: crate::trace::validate::TraceAttr, item: syn::ItemFn) -> Model {
92+
let mut itemfn = item;
93+
let attribute = Trace::from_attributes(&meta.attrs).unwrap();
94+
eprintln!("{:?}", attribute);
95+
// let attrs = &mut itemfn.attrs;
96+
97+
// attrs.iter().enumerate().for_each(|(i, a)| {
98+
// //
99+
// if let Some(ident) = a.path.get_ident() {
100+
// if ident.to_string().as_str() == "precondition" {
101+
// // Mark item as being subject to async_trait manipulation
102+
// }
103+
// }
104+
// });
105+
106+
// for index in (0..attrs.len()).rev() {
107+
// if let Some(ident) = attrs[index].path.get_ident() {
108+
// if ident.to_string().as_str() == "precondition" {
109+
// let attr = attrs.remove(index);
110+
// let _span = attr.tokens.span();
111+
// }
112+
// }
113+
// }
114+
115+
Model {
116+
attribute,
117+
item: itemfn,
32118
}
119+
}
33120

34-
Model { item }
121+
pub struct Model {
122+
pub attribute: Trace,
123+
pub item: syn::ItemFn,
35124
}
36125

37126
#[allow(dead_code)]
@@ -50,11 +139,6 @@ impl Parse for AttributeArgument {
50139
}
51140
}
52141

53-
pub struct Model {
54-
// pub preconditions: Vec<Expr>,
55-
pub item: ItemFn,
56-
}
57-
58142
#[cfg(test)]
59143
mod tests {
60144
use syn::{parse_quote, Attribute};
@@ -63,10 +147,13 @@ mod tests {
63147

64148
#[test]
65149
fn can_extract_precondition() {
66-
let model = analyze(parse_quote!(
67-
#[precondition(x)]
68-
fn f(x: bool) {}
69-
));
150+
let model = analyze(
151+
parse_quote!(),
152+
parse_quote!(
153+
#[precondition(x)]
154+
fn f(x: bool) {}
155+
),
156+
);
70157

71158
let _expected: &[Expr] = &[parse_quote!(x)];
72159
// assert_eq!(expected, model.preconditions);
@@ -76,12 +163,15 @@ mod tests {
76163

77164
#[test]
78165
fn non_dsl_attributes_are_preserved() {
79-
let model = analyze(parse_quote!(
80-
#[a]
81-
#[precondition(x)]
82-
#[b]
83-
fn f(x: bool) {}
84-
));
166+
let model = analyze(
167+
parse_quote!(),
168+
parse_quote!(
169+
#[a]
170+
#[precondition(x)]
171+
#[b]
172+
fn f(x: bool) {}
173+
),
174+
);
85175

86176
let expected: &[Attribute] = &[parse_quote!(#[a]), parse_quote!(#[b])];
87177
assert_eq!(expected, model.item.attrs);

minitrace-macro/src/trace/codegen.rs renamed to minitrace-macro/src/trace/generate.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::trace::lower::{Assertion, Ir};
66

77
pub type Rust = TokenStream;
88

9-
pub fn codegen(ir: Ir) -> Rust {
9+
pub fn generate(ir: Ir) -> Rust {
1010
let Ir { item } = ir;
1111

1212
let ItemFn {
@@ -48,7 +48,7 @@ mod tests {
4848
fn f() {}
4949
),
5050
};
51-
let rust = codegen(ir);
51+
let rust = generate(ir);
5252

5353
assert!(syn::parse2::<ItemFn>(rust).is_ok());
5454
}

minitrace-macro/src/trace/lower.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use syn::{Expr, ItemFn};
55
use crate::trace::analyze::Model;
66

77
pub fn lower(model: Model) -> Ir {
8-
let Model { item } = model;
8+
let Model { attribute, item } = model;
99

1010
// let assertions = preconditions
1111
// .into_iter()
@@ -33,10 +33,15 @@ mod tests {
3333
use syn::parse_quote;
3434

3535
use super::*;
36+
use crate::trace::analyze::Trace;
3637

3738
impl Model {
3839
fn stub() -> Self {
40+
let trace_meta =
41+
syn::parse_str::<crate::trace::validate::TraceAttr>("#[trace]").unwrap();
42+
let attribute = Trace::from_attributes(&trace_meta.attrs).unwrap();
3943
Self {
44+
attribute,
4045
// preconditions: vec![],
4146
item: parse_quote!(
4247
fn f() {}

0 commit comments

Comments
 (0)