dtolnay / dtolnay/syn

Feature request: recognize expressions from derive helper attributes w/o "full" parsing

Open
#1,782 0 comments 2 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
3.4k
Forks
374
Avg merge
1d 5h
Merged PRs (30d)
2

Description

Some custom derives have helper attributes that let the user pass through expressions into the generated code to customize behavior. This is usually done by parsing some tokens from the attribute as a single `syn::Expr` or as comma-separated sequence of them. By far the heaviest user of this is clap_derive ([example](https://github.com/clap-rs/clap/blob/2920fb082c987acb72ed1d1f47991c4d157e380d/examples/git-derive.rs#L32-L41)) but it also occurs at a smaller scale in other derives such as [darling](https://github.com/TedDriggs/darling/blob/b746a0c4a92b3c357338c430bdfd8ba9f3eafab6/examples/expr_with.rs#L9-L13) or [educe](https://docs.rs/educe/0.6.0/educe/#the-default-values-for-specific-fields). In many simple cases this works fine with only the "derive" feature, but some kinds of expressions can only be parsed with "full" enabled. When that distinction matters, there's no great option at the moment:

- If the derive itself enables syn/full, that carries a build-time cost for all users, at least in workspaces where nothing else enables the feature. Most of that cost is unnecessary because these derives only need to deal with expressions, rather than the full suite of items and statements gated under syn/full.
- If syn/full is not enabled by default, user experience is sub-par: some expressions will be rejected with the helpful error message `unsupported expression; enable syn's feature=["full"]` but others get a rather less helpful error message. Some derives try this anyway (e.g., num-derive has feature "complex-expressions" which forwards to syn/full) but in other cases the unsupported expressions are so commonly needed that it's not much better than enabling syn/full unconditionally.
- Wrapping the expression in a string (`num_args = "0..=1"` as opposed to `num_args = 0..=1` ) could be abused to skip expression parsing entirely, but is much less convenient for users, defers all validation to when rustc parses the derive's output, and (as far as I know) gives worse diagnostics for syntax errors in these strings because there's no way to turn the string contents into tokens with individualized, accurate spans. I believe this style of attribute mostly exist for historical reasons (limitations in earlier Rust versions) and not because anyone considers it a good idea today.

Moving more expression variants out of "full" and into "derive" would reduce the problem, but of course this adds a bunch of types and parsing code to *everyone's* syn builds, even when they're not needed. A simpler solution is to note that these expressions are usually passed through without being inspected, so parsing them as `Expr::Verbatim` is fine for at least some derives. Current syn versions already have some code that can do this (the expression scanner) included in non-full builds, but it's not exported and only used for parsing discriminants in enums (#1513). Parsing as `syn::Expr` doesn't hit that code path so it fails on ranges, arrays, tuples, closures, and other full-exclusive expression variants when "full" is not enabled.

I'm open to other solutions, but right now exposing the "parse any expression, as `Expr::Verbatim` if necessary" capability in some form seems like the best way to me. Per discussion in https://github.com/clap-rs/clap/issues/5657 this could allow the next major version of Clap to drop syn/full (no guarantees of course), which would slightly but measurably improve build times for many CLI programs. For me clap is the motivating use case but other derives may also benefit (educe seems plausible, but darling currently inspects the parsed `Expr` in some cases).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.