Skip to content

Commit

Permalink
feat(labels): Add support for primary label in specifying line/col in…
Browse files Browse the repository at this point in the history
…formation (#291)
  • Loading branch information
willcrichton committed Sep 20, 2023
1 parent cc81382 commit db0b7e4
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 51 deletions.
79 changes: 61 additions & 18 deletions miette-derive/src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ struct Label {
label: Option<Display>,
ty: syn::Type,
span: syn::Member,
primary: bool,
}

struct LabelAttr {
label: Option<Display>,
primary: bool,
}

impl Parse for LabelAttr {
Expand All @@ -40,10 +42,22 @@ impl Parse for LabelAttr {
}
});
let la = input.lookahead1();
let label = if la.peek(syn::token::Paren) {
// #[label("{}", x)]
let (primary, label) = if la.peek(syn::token::Paren) {
// #[label(primary?, "{}", x)]
let content;
parenthesized!(content in input);

let primary = if content.peek(syn::Ident) {
let ident: syn::Ident = content.parse()?;
if ident != "primary" {
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
}
let _ = content.parse::<Token![,]>();
true
} else {
false
};

if content.peek(syn::LitStr) {
let fmt = content.parse()?;
let args = if content.is_empty() {
Expand All @@ -56,22 +70,27 @@ impl Parse for LabelAttr {
args,
has_bonus_display: false,
};
Some(display)
(primary, Some(display))
} else if !primary {
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
} else {
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
(primary, None)
}
} else if la.peek(Token![=]) {
// #[label = "blabla"]
input.parse::<Token![=]>()?;
Some(Display {
fmt: input.parse()?,
args: TokenStream::new(),
has_bonus_display: false,
})
(
false,
Some(Display {
fmt: input.parse()?,
args: TokenStream::new(),
has_bonus_display: false,
}),
)
} else {
None
(false, None)
};
Ok(LabelAttr { label })
Ok(LabelAttr { label, primary })
}
}

Expand Down Expand Up @@ -100,12 +119,21 @@ impl Labels {
})
};
use quote::ToTokens;
let LabelAttr { label } =
let LabelAttr { label, primary } =
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;

if primary && labels.iter().any(|l: &Label| l.primary) {
return Err(syn::Error::new(
field.span(),
"Cannot have more than one primary label.",
));
}

labels.push(Label {
label,
span,
ty: field.ty.clone(),
primary,
});
}
}
Expand All @@ -120,21 +148,31 @@ impl Labels {
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
let (display_pat, display_members) = display_pat_members(fields);
let labels = self.0.iter().map(|highlight| {
let Label { span, label, ty } = highlight;
let Label {
span,
label,
ty,
primary,
} = highlight;
let var = quote! { __miette_internal_var };
let ctor = if *primary {
quote! { miette::LabeledSpan::new_primary_with_span }
} else {
quote! { miette::LabeledSpan::new_with_span }
};
if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! {
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
.map(|#var| miette::LabeledSpan::new_with_span(
.map(|#var| #ctor(
std::option::Option::Some(format!(#fmt #args)),
#var.clone(),
))
}
} else {
quote! {
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
.map(|#var| miette::LabeledSpan::new_with_span(
.map(|#var| #ctor(
std::option::Option::None,
#var.clone(),
))
Expand All @@ -161,27 +199,32 @@ impl Labels {
let (display_pat, display_members) = display_pat_members(fields);
labels.as_ref().and_then(|labels| {
let variant_labels = labels.0.iter().map(|label| {
let Label { span, label, ty } = label;
let Label { span, label, ty, primary } = label;
let field = match &span {
syn::Member::Named(ident) => ident.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
let var = quote! { __miette_internal_var };
let ctor = if *primary {
quote! { miette::LabeledSpan::new_primary_with_span }
} else {
quote! { miette::LabeledSpan::new_with_span }
};
if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! {
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
.map(|#var| miette::LabeledSpan::new_with_span(
.map(|#var| #ctor(
std::option::Option::Some(format!(#fmt #args)),
#var.clone(),
))
}
} else {
quote! {
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
.map(|#var| miette::LabeledSpan::new_with_span(
.map(|#var| #ctor(
std::option::Option::None,
#var.clone(),
))
Expand Down
27 changes: 23 additions & 4 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ impl GraphicalReportHandler {
) -> fmt::Result {
let (contents, lines) = self.get_lines(source, context.inner())?;

let primary_label = labels
.iter()
.find(|label| label.primary())
.or_else(|| labels.first());

// sorting is your friend
let labels = labels
.iter()
Expand Down Expand Up @@ -431,19 +436,33 @@ impl GraphicalReportHandler {
self.theme.characters.hbar,
)?;

if let Some(source_name) = contents.name() {
// If there is a primary label, then use its span
// as the reference point for line/column information.
let primary_contents = match primary_label {
Some(label) => source
.read_span(label.inner(), 0, 0)
.map_err(|_| fmt::Error)?,
None => contents,
};

if let Some(source_name) = primary_contents.name() {
let source_name = source_name.style(self.theme.styles.link);
writeln!(
f,
"[{}:{}:{}]",
source_name,
contents.line() + 1,
contents.column() + 1
primary_contents.line() + 1,
primary_contents.column() + 1
)?;
} else if lines.len() <= 1 {
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
} else {
writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
writeln!(
f,
"[{}:{}]",
primary_contents.line() + 1,
primary_contents.column() + 1
)?;
}

// Now it's time for the fun part--actually rendering everything!
Expand Down
12 changes: 8 additions & 4 deletions src/miette_diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,16 @@ fn test_serialize_miette_diagnostic() {
"offset": 0,
"length": 0
},
"label": "label1"
"label": "label1",
"primary": false
},
{
"span": {
"offset": 1,
"length": 2
},
"label": "label2"
"label": "label2",
"primary": false
}
]
});
Expand Down Expand Up @@ -350,14 +352,16 @@ fn test_deserialize_miette_diagnostic() {
"offset": 0,
"length": 0
},
"label": "label1"
"label": "label1",
"primary": false
},
{
"span": {
"offset": 1,
"length": 2
},
"label": "label2"
"label": "label2",
"primary": false
}
]
});
Expand Down
32 changes: 27 additions & 5 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ pub struct LabeledSpan {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
label: Option<String>,
span: SourceSpan,
primary: bool,
}

impl LabeledSpan {
Expand All @@ -257,6 +258,7 @@ impl LabeledSpan {
Self {
label,
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
primary: false,
}
}

Expand All @@ -265,6 +267,16 @@ impl LabeledSpan {
Self {
label,
span: span.into(),
primary: false,
}
}

/// Makes a new labeled primary span using an existing span.
pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
Self {
label,
span: span.into(),
primary: true,
}
}

Expand Down Expand Up @@ -340,6 +352,11 @@ impl LabeledSpan {
pub const fn is_empty(&self) -> bool {
self.span.is_empty()
}

/// True if this `LabeledSpan` is a primary span.
pub const fn primary(&self) -> bool {
self.primary
}
}

#[cfg(feature = "serde")]
Expand All @@ -350,15 +367,17 @@ fn test_serialize_labeled_span() {
assert_eq!(
json!(LabeledSpan::new(None, 0, 0)),
json!({
"span": { "offset": 0, "length": 0 }
"span": { "offset": 0, "length": 0, },
"primary": false,
})
);

assert_eq!(
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
json!({
"label": "label",
"span": { "offset": 0, "length": 0 }
"span": { "offset": 0, "length": 0, },
"primary": false,
})
)
}
Expand All @@ -370,20 +389,23 @@ fn test_deserialize_labeled_span() {

let span: LabeledSpan = serde_json::from_value(json!({
"label": null,
"span": { "offset": 0, "length": 0 }
"span": { "offset": 0, "length": 0, },
"primary": false,
}))
.unwrap();
assert_eq!(span, LabeledSpan::new(None, 0, 0));

let span: LabeledSpan = serde_json::from_value(json!({
"span": { "offset": 0, "length": 0 }
"span": { "offset": 0, "length": 0, },
"primary": false
}))
.unwrap();
assert_eq!(span, LabeledSpan::new(None, 0, 0));

let span: LabeledSpan = serde_json::from_value(json!({
"label": "label",
"span": { "offset": 0, "length": 0 }
"span": { "offset": 0, "length": 0, },
"primary": false
}))
.unwrap();
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
Expand Down
Loading

0 comments on commit db0b7e4

Please sign in to comment.