Open TedDriggs opened 2 years ago
Would adding a struct like the one below solve this?
pub struct WithDiagnostics<T> {
pub value: T,
pub diagnostics: Vec<Error>,
}
impl<T: FromMeta> FromMeta for WithDiagnostics<T> {
fn from_meta(meta: &Meta) -> Result<Self> {
Ok(Self {
value: FromMeta::from_meta(meta)?,
diagnostics: vec![],
})
}
}
impl<T: ToTokens> ToTokens for WithDiagnostics<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.value.to_tokens(tokens);
for diagnostic in &self.diagnostics {
// TODO add errors to token stream
}
}
}
#[darling(with = "...")]
attribute lets someone write a function that isolates the field-level checks, while post-validation can be done by pushing diagnostics into the vec after the struct has been built (easily done with #[darling(map = "...")]
.darling
projects have the receiver struct and the "codegen" struct be different; to avoid losing diagnostics this would require supporting several combinators. That also raises the question of whether this needs to be generic over the type of Error
so that &Error
can be used in its stead when creating a WithDiagnostics
that borrows from value
.FromMeta
and have struct-internal non-fatal validations. Those would likely need to do something more involved where they add a field #[darling(skip)] diagnostics: Vec<Error>
to themselves, then populate that using #[darling(map = "...")]
.darling::Error
for the diagnostics; using proc_macro::Diagnostic
wouldn't work well because right now darling_core
doesn't have a hard dependency on proc_macro
, and introducing a darling::Diagnostic
type feels like it could quickly get confusing. However, I'm hesitant to call this struct WithErrors
if it can contain things that aren't errors.@AlisCode I'm curious to get your input on this one; does your company have situations where you want to add warnings/notes/etc. without adding an error? Would a new util type like WithDiagnostics
be a convenient way for you to do that?
In the case of the macro I've been refactoring, we don't exactly separate the concepts of "receiver structs" and "meaningful/stronger-typed structs". We have our "meaningful" structs implement darling
traits, namely FromVariant
.
Any validation problem on any of the variants of the enum is fatal, so I assume that no, optional diagnostics arent really a thing we need. Not for that specific macro, anyway :eyes: .
darling
has a simple pass-fail approach for turning the AST into receiver structs. All of thedarling
traits returnResult<Self, darling::Error>
. This is great for approachability, but it does mean there's not a good place to put non-fatal diagnostics.Some concrete examples:
#[darling(deprecated = "...")]
, enabling someone to deprecate a field in their macro as easily as they would a regular method.derive_builder
the conflicting visibility declarations don't need to block code generation. Ideally there would be one error for "A field cannot be both public and private" without a second compiler error "LoremBuilder does not exist"It's possible that these scenarios are best handled outside the existing system - since they don't block parsing, they could be implemented as functions that take the
Receiver
and return aVec<Diagnostic>
. That "feels" wrong though:darling
for derivation shouldn't need to remember to also call some other method to avoid losing non-fatal diagnostics.FromMeta
should "just work", including emitting non-fatal diagnostics when appropriate. If the non-fatal diagnostics are gathered in a separate pass, the author will need to remember to visit every field when gathering diagnostics or they'll miss things.darling
suggests this rarely happens, withString
andbool
being very common receiver field types. As a result, non-fatal diagnostics would not have access to good spans.There seem to be two ways this "could" work:
darling
passes around a context of some sort, andFromMeta
impls can add diagnostics into that context.darling
traits is changed to be something like:DarlingResult<T> { value: Option<T>, diagnostics: Vec<Diagnostic> }
. A fatal error would causevalue
to beNone
.Both of these have problems.
Approach 1 would likely require introduction of parallel traits:
FromMetaContext
,FromDeriveInputContext
, etc. Those wouldn't compose, since existing derivations ofFromMeta
wouldn't pass the context to their children when they callFromMeta
.Approach 2 would mean an enormous breaking change to the entire
darling
ecosystem, and despite being a 0.x crate, I'm extremely reluctant - bordering on outright unwilling - to do that.That leaves the after-the-fact traversal. Maybe there's a way with drop bombs to make that workable: Make
#[darling::post_validation]
be an attribute macro that implements a trait and injects a drop-bomb field to ensure the trait implementation is called?