Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hanna-kruppe opened this issue Nov 17, 2024 · 0 comments

Comments

@hanna-kruppe
Copy link

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) but it also occurs at a smaller scale in other derives such as darling or educe. 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 clap-rs/clap#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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant