diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs index 27eb34eac..ca937f104 100644 --- a/crates/typst-library/src/foundations/func.rs +++ b/crates/typst-library/src/foundations/func.rs @@ -298,7 +298,12 @@ impl Func { } /// Non-generic implementation of `call`. - #[typst_macros::time(name = "func call", span = self.span())] + #[typst_macros::time( + name = "func call", + span = self.span(), + callsite = args.span, + func = self.name().unwrap_or("anonymous") + )] fn call_impl( &self, engine: &mut Engine, diff --git a/crates/typst-macros/src/time.rs b/crates/typst-macros/src/time.rs index 94fa4e5be..8bb24ccc5 100644 --- a/crates/typst-macros/src/time.rs +++ b/crates/typst-macros/src/time.rs @@ -8,13 +8,15 @@ use crate::util::{kw, parse_key_value, parse_string}; /// Expand the `#[time(..)]` macro. pub fn time(stream: TokenStream, item: syn::ItemFn) -> Result { let meta: Meta = syn::parse2(stream)?; - Ok(create(meta, item)) + create(meta, item) } /// The `..` in `#[time(..)]`. pub struct Meta { pub span: Option, + pub callsite: Option, pub name: Option, + pub func: Option, } impl Parse for Meta { @@ -22,15 +24,37 @@ impl Parse for Meta { Ok(Self { name: parse_string::(input)?, span: parse_key_value::(input)?, + callsite: parse_key_value::(input)?, + func: parse_key_value::(input)?, }) } } -fn create(meta: Meta, mut item: syn::ItemFn) -> TokenStream { +fn create(meta: Meta, mut item: syn::ItemFn) -> Result { let name = meta.name.unwrap_or_else(|| item.sig.ident.to_string()); - let construct = match meta.span.as_ref() { - Some(span) => quote! { with_span(#name, Some(#span.into_raw())) }, - None => quote! { new(#name) }, + + let func = match meta.func { + Some(func) => { + if meta.callsite.is_none() { + bail!(func, "the `func` argument can only be used with a callsite") + } + + quote! { Some(#func.into()) } + } + None => quote! { None }, + }; + + let construct = match (meta.span.as_ref(), meta.callsite.as_ref()) { + (Some(span), Some(callsite)) => quote! { + with_callsite(#name, Some(#span.into_raw()), Some(#callsite.into_raw()), #func) + }, + (Some(span), None) => quote! { + with_span(#name, Some(#span.into_raw())) + }, + (None, Some(expr)) => { + bail!(expr, "cannot have a callsite span without a main span") + } + (None, None) => quote! { new(#name) }, }; item.block.stmts.insert( @@ -40,5 +64,5 @@ fn create(meta: Meta, mut item: syn::ItemFn) -> TokenStream { }, ); - item.into_token_stream() + Ok(item.into_token_stream()) } diff --git a/crates/typst-macros/src/util.rs b/crates/typst-macros/src/util.rs index e8c0910b6..6c611dffc 100644 --- a/crates/typst-macros/src/util.rs +++ b/crates/typst-macros/src/util.rs @@ -254,6 +254,8 @@ impl Parse for BareType { pub mod kw { syn::custom_keyword!(name); syn::custom_keyword!(span); + syn::custom_keyword!(callsite); + syn::custom_keyword!(func); syn::custom_keyword!(title); syn::custom_keyword!(scope); syn::custom_keyword!(contextual); diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index 151ae76ba..21c4e5c2f 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -34,7 +34,7 @@ use typst_syntax::Span; use typst_utils::{SliceExt, SmallBitSet}; /// Realize content into a flat list of well-known, styled items. -#[typst_macros::time(name = "realize")] +#[typst_macros::time(name = "realize", span = content.span())] pub fn realize<'a>( kind: RealizationKind, engine: &mut Engine, diff --git a/crates/typst-timing/src/lib.rs b/crates/typst-timing/src/lib.rs index 6da2cdf02..0c307696d 100644 --- a/crates/typst-timing/src/lib.rs +++ b/crates/typst-timing/src/lib.rs @@ -93,20 +93,26 @@ pub fn export_json( mut source: impl FnMut(NonZeroU64) -> (String, u32), ) -> Result<(), String> { #[derive(Serialize)] - struct Entry { + struct Entry<'a> { name: &'static str, cat: &'static str, ph: &'static str, ts: f64, pid: u64, tid: u64, - args: Option, + args: Option>, } #[derive(Serialize)] - struct Args { + struct Args<'a> { file: String, line: u32, + #[serde(skip_serializing_if = "Option::is_none")] + function_name: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + callsite_file: Option, + #[serde(skip_serializing_if = "Option::is_none")] + callsite_line: Option, } let lock = EVENTS.lock(); @@ -128,7 +134,21 @@ pub fn export_json( ts: event.timestamp.micros_since(events[0].timestamp), pid: 1, tid: event.thread_id, - args: event.span.map(&mut source).map(|(file, line)| Args { file, line }), + args: event.span.map(&mut source).map(|(file, line)| { + let (callsite_file, callsite_line) = match event.callsite.map(&mut source) + { + Some((a, b)) => (Some(a), Some(b)), + None => (None, None), + }; + + Args { + file, + line, + callsite_file, + callsite_line, + function_name: event.func.as_deref(), + } + }), }) .map_err(|e| format!("failed to serialize event: {e}"))?; } @@ -142,6 +162,8 @@ pub fn export_json( pub struct TimingScope { name: &'static str, span: Option, + callsite: Option, + func: Option, thread_id: u64, } @@ -159,14 +181,34 @@ impl TimingScope { /// `typst-timing`). #[inline] pub fn with_span(name: &'static str, span: Option) -> Option { + Self::with_callsite(name, span, None, None) + } + + /// Create a new scope with a span if timing is enabled. + /// + /// The span is a raw number because `typst-timing` can't depend on + /// `typst-syntax` (or else `typst-syntax` couldn't depend on + /// `typst-timing`). + #[inline] + pub fn with_callsite( + name: &'static str, + span: Option, + callsite: Option, + func: Option, + ) -> Option { if is_enabled() { - return Some(Self::new_impl(name, span)); + return Some(Self::new_impl(name, span, callsite, func)); } None } /// Create a new scope without checking if timing is enabled. - fn new_impl(name: &'static str, span: Option) -> Self { + fn new_impl( + name: &'static str, + span: Option, + callsite: Option, + func: Option, + ) -> Self { let (thread_id, timestamp) = THREAD_DATA.with(|data| (data.id, Timestamp::now_with(data))); EVENTS.lock().push(Event { @@ -174,9 +216,11 @@ impl TimingScope { timestamp, name, span, + callsite, + func: func.clone(), thread_id, }); - Self { name, span, thread_id } + Self { name, span, callsite: None, thread_id, func } } } @@ -188,7 +232,9 @@ impl Drop for TimingScope { timestamp, name: self.name, span: self.span, + callsite: self.callsite, thread_id: self.thread_id, + func: std::mem::take(&mut self.func), }); } } @@ -203,6 +249,10 @@ struct Event { name: &'static str, /// The raw value of the span of code that this event was recorded in. span: Option, + /// The raw value of the callsite span of the code that this event was recorded in. + callsite: Option, + /// The function being called (if any). + func: Option, /// The thread ID of this event. thread_id: u64, }