TedDriggs / darling

A Rust proc-macro attribute parser

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How do I specify spans/use SpannedValue precisely?

detly opened this issue · comments

I'm trying to use Darling to write some derive proc macros under Rust stable 1.65, and I'd like to improve the spans I'm using for my errors.

The code extracts I show below are all from a sample project, if it helps. Basically I have:

#[derive(Debug, FromDeriveInput)]
struct DarlingTestDerive {
    ident: Ident,
    generics: Generics,
    data: darling::ast::Data<(), ()>,
}

(In real code the second () would be some other Darling-derived type eg. a FromField derived type.)

In my derive macro code, I'd like to be able to specify two different spans for two different error cases:

  1. The span for struct|enum ie. the "type" of Data.
  2. The span for the Data itself eg. all the fields of a struct.

Unfortunately not only can I not figure out how to do this, I seem to be able to only get a span covering a doc comment two lines before my data type.

For example, if I simply take the span for the input

let input = parse_macro_input!(input as syn::DeriveInput);
let span = input.span();
// ...
Err(syn::Error::new(span, "these fields are bad"))

...then for the using code:

/// Some doc comment.
#[derive(Clone, Debug, Default, DarlingTest, PartialEq)]
struct DarlingTestStruct(#[darling_test] bool, usize);

...it is the doc comment that is highlighted.

error: these fields are bad
 --> tests/test.rs:5:5
  |
5 |     /// Some doc comment.
  |     ^^^^^^^^^^^^^^^^^^^^^

I can't figure out (a) if this is a problem I should solve with tools in Darling or in Syn and (b) whichever it is, how to do it.

It's not in the Rustdocs, but the repository readme says:

Use darling::util::SpannedValue in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro.

The type's docs say:

You can use a SpannedValue<T> as a field in any struct that implements or derives any of darling’s core traits.

I couldn't figure out how to apply this though. I thought maybe wrapping the field of interest in it would do it, since it has map and deref implementations:

#[derive(Debug, FromDeriveInput)]
struct DarlingTestDerive {
    ident: Ident,
    generics: Generics,
    data: darling::util::SpannedValue<darling::ast::Data<(), ()>>,
}

Unfortunately not:

error[E0308]: `?` operator has incompatible types
  --> src/lib.rs:31:17
   |
31 | #[derive(Debug, FromDeriveInput)]
   |                 ^^^^^^^^^^^^^^^ expected struct `SpannedValue`, found enum `darling::ast::Data`
   |
   = note: `?` operator cannot convert from `darling::ast::Data<_, _>` to `SpannedValue<darling::ast::Data<(), ()>>`
   = note: expected struct `SpannedValue<darling::ast::Data<(), ()>>`
                found enum `darling::ast::Data<_, _>`
   = note: this error originates in the derive macro `FromDeriveInput` (in Nightly builds, run with -Z macro-backtrace for more info)

I thought to try it as a separate field, eg.

#[derive(Debug, FromDeriveInput)]
struct DarlingTestDerive {
    ident: Ident,
    generics: Generics,
    data: darling::ast::Data<(), ()>,
    span: darling::util::SpannedValue<??>
}

...but as the question marks suggest, I have no idea what T should be there.

I also can't figure out how to obtain a span from any of Darling's types directly eg. I can easily do:

let Some(data @ Fields { .. }) = darling_test_derive.data.take_struct() else { ... };

or

let Some(Fields { style, fields, .. }) = darling_test_derive.data.take_struct() else { ... };

...but none of those types seem to have any way to extract a span.

Anyway, this seems like a documentation issue as much as anything else, so if you can point me in the right direction I'll try to make some notes as I go and open a PR with additional documentation eventually.

I'm happy to answer the questions about SpannedValue, but before we go down that route - have you looked at using #[darling(supports(struct_any))]? That will take care of erroring on enums for you, and you can also use that to specify which kinds of struct you want to support, e.g. struct_named or struct_newtype.


I think what's happening here is that the span for the DeriveInput is being set to the start of the doc comment because that's the first thing that's part of the DeriveInput, but that span is then ending because the #[derive(...)] line is somehow considered not to be part of that input - maybe because it gets erased from the output? When dealing with disjoint spans right now, syn will choose the first one.

For this use-case, darling deliberately chooses to put the error message for the supports(...) at Span::call_site - the name of the macro - instead of on the struct/enum keyword, because darling doesn't have any way of knowing the name of the derived trait we're failing, and therefore an error that didn't point to the macro name would be very confusing.

The documentation is a bit nuanced here, but in essence you could write data: darling::ast::Data<SpannedValue<...>> because there's a trait impl impl<T: FromVariant> FromVariant for SpannedValue<T>, but "magic fields" don't rely on traits for their implementations so you can't put SpannedValue arbitrarily into those locations.

have you looked at using #[darling(supports(struct_any))]

Is this in the rustdocs, and if not, what source file is it in?

Looks like it isn’t documented. Check out this example, and the implementation here

Ah that's neat! Is the intended usage to still use a data: darling::ast::Data<Ignored, T> field and do take_struct().unwrap(), or is there another type to use for the field?

Ignored would be the way to go.

in essence you could write data: darling::ast::Data<SpannedValue<...>>

This was one of the points of confusion for me - Data takes two generics (enum variants and struct fields), should there be two SpannedValue types enclosing those types ie.

data: Data<SpannedValue<V>, SpannedValue<F>>

...or something else?

Doing that would mean you’d have a spanned value for each variant, and a spanned value for each field. Which is fine, if that’s your goal.

No, my goal is to have a span that covers all the fields ie. what syn and darling call the data.

I don’t think there’s an easy way to do that.

Ah nuts. It is possible to do by traversing the underlying syn types and getting the span for whatever kind of data you have in the type, but (a) you have to go pretty deep down the tree, and (b) you have to keep the input side by side with darling's structures. I was hoping for a neater way.

If I do manage to come up with something with a neater interface, would you be interested in doing that in darling somehow? I can't promise anything, but if I do it and it fits here, I can contribute it.

I’d certainly be open to it. I think the problem you’ll have is that there’s not really a place to put the thing.

Did #[darling(supports(…))] help with your original problem, or does that have an issue that means it can’t be used?

Did #[darling(supports(…))] help with your original problem, or does that have an issue that means it can’t be used?

It absolutely helped with a lot of the shape-related boilerplate, but there is also some subsequent "is the combination of attributes on these fields valid" logic, which is where I wanted to be more precise with the spans.

So basically, this issue resolves to 3 things:

  1. When dealing with disjoint spans right now, syn will choose the first one. - I see this is a known limitation of syn on stable, so there's nothing to do but wait until the improvements on nightly stabilise. I have not tested the behaviour on nightly myself. No issue.

  2. The span stuff must be done manually. If I see improvements I can contribute to Darling I'll open a PR. No issue.

  3. Darling could have better docs on #[darling(supports(…))] and the usage of SpannedValue. An issue, but I don't mind if you close this, and I'll try to open a PR for that myself... soonish.

I think it’s wise to close this and file an issue for documentation, since that’s pretty far removed from this issue’s original stated topic.

You could also check out WithOriginal as a way of keeping each field intact to fish the spans out - that won’t give you one span for the whole body, but it would give you the pieces needed for each field.

Oh that's a great pointer, I'll have a look.

One last thing (maybe):

You could also check out WithOriginal as a way of keeping each field intact

Presumably I would replace data: Data<(), MyField> with data: WithOriginal<Data<(), MyField>, O> - what is O expected to be? syn:::Data doesn't work, I get a similar error to the SpannedValue (mis)use above:

error[E0308]: `?` operator has incompatible types
  --> src/lib.rs:33:17
   |
33 | #[derive(Debug, FromDeriveInput)]
   |                 ^^^^^^^^^^^^^^^ expected struct `WithOriginal`, found enum `darling::ast::Data`
   |
   = note: `?` operator cannot convert from `darling::ast::Data<_, _>` to `WithOriginal<darling::ast::Data<Ignored, ()>, syn::Data>`
   = note: expected struct `WithOriginal<darling::ast::Data<Ignored, ()>, syn::Data>`
                found enum `darling::ast::Data<_, _>`
   = note: this error originates in the derive macro `FromDeriveInput` (in Nightly builds, run with -Z macro-backtrace for more info)

Never mind, I figured it out! It implements FromDeriveInput already with a blanket impl over T, so WithOriginal::<DarlingTestDerive, _>::from_derive_input(&input)?; works beautifully.

That works too! I was thinking you'd probably want to do something like this...

data: darling::ast::Data<Ignored, WithOriginal<MyField, syn::Field>>

That would give you access to the original fields with each of their span infos, so that when you have an error which impacts one or more of them you could add diagnostics to the right places instead of to the whole body. However, if you've found something that works for what you need, then party on!