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

Keywords in meta path positions cause parsing errors #1458

Closed
TedDriggs opened this issue May 25, 2023 · 7 comments
Closed

Keywords in meta path positions cause parsing errors #1458

TedDriggs opened this issue May 25, 2023 · 7 comments

Comments

@TedDriggs
Copy link
Contributor

On syn 2.0.16, the following code produces an error:

let _m: syn::Meta = parse_quote!(type = "hi");

This has caused issues for darling users (TedDriggs/darling#238) who are consuming meta items with names such as type or pub.

I think the issue is here where the impl Parse for Meta, impl Parse for MetaList and impl Parse for MetaNameValue all call out to Path::parse_mod_style. Could there be a similar function that for parsing paths in this Meta context uses Ident::peek_any so that keywords are allowed?

@dtolnay
Copy link
Owner

dtolnay commented May 25, 2023

I think this is behaving correctly. syn::Meta corresponds to Rust's "meta item" syntax (MetaItem in the reference) which does not allow keyword identifiers.

macro_rules! meta {
    ($m:meta) => {};
}

meta!(type = "hi");
error: expected identifier, found keyword `type`
 --> src/lib.rs:5:7
  |
5 | meta!(type = "hi");
  |       ^^^^ expected identifier, found keyword

@dtolnay dtolnay closed this as completed May 25, 2023
@TedDriggs
Copy link
Contributor Author

The inconsistency noted here, where the meta parses fine if it's meta!(hello(type = "world")) is confusing; would you be open to Meta exposing a parse_nested function that allowed keywords in the path position?

github-merge-queue bot pushed a commit to apollographql/subgraph-template-rust-async-graphql that referenced this issue Aug 29, 2023
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [async-graphql](https://togithub.com/async-graphql/async-graphql) |
dependencies | major | `5.0.10` -> `6.0.4` |
| [async-graphql-axum](https://togithub.com/async-graphql/async-graphql)
| dependencies | major | `5.0.10` -> `6.0.4` |

---

### Release Notes

<details>
<summary>async-graphql/async-graphql (async-graphql)</summary>

###
[`v6.0.4`](https://togithub.com/async-graphql/async-graphql/blob/HEAD/CHANGELOG.md#604-2023-08-18)

- Parse "repeatable" in directive definitions.
[#&#8203;1336](https://togithub.com/async-graphql/async-graphql/pull/1336)
- add support `multipart/mixed` request.
[#&#8203;1348](https://togithub.com/async-graphql/async-graphql/issues/1348)
-   async-graphql-actix-web: add `GraphQL` handler.
-   async-graphql-axum: add `GraphQL` service.

###
[`v6.0.3`](https://togithub.com/async-graphql/async-graphql/blob/HEAD/CHANGELOG.md#603-2023-08-15)

- dynamic: fix the error that some methods of `XXXAccessor` return
reference lifetimes that are smaller than expected.
- dynamic: no longer throws an error if the Query object does not
contain any fields but the schema contains entities.
- chore: make accessors public and reexport indexmap
[#&#8203;1329](https://togithub.com/async-graphql/async-graphql/pull/1329)
- feat: added `OutputType` implementation for `std::sync::Weak`
[#&#8203;1334](https://togithub.com/async-graphql/async-graphql/pull/1334)

###
[`v6.0.1`](https://togithub.com/async-graphql/async-graphql/blob/HEAD/CHANGELOG.md#601-2023-08-02)

-   dynamic: remove `TypeRefInnner`
-   update MSRV to `1.67.0`

###
[`v6.0.0`](https://togithub.com/async-graphql/async-graphql/blob/HEAD/CHANGELOG.md#600-2023-07-29)

-   Bump `syn` from `1.0` to `2.0`
-   Bump `darling` from `0.14` to `0.20`
-   Bump `indexmap` from `1.6.2` to `2`
- Attributes `guard`, `process_with`, `complexity` support expression or
string as value
[#&#8203;1295](https://togithub.com/async-graphql/async-graphql/issues/1295)
- Schema (type) level directive support with optional support of
federation composeDirective
[#&#8203;1308](https://togithub.com/async-graphql/async-graphql/pull/1308)
- Add support for generic structs derriving InputObject and SimpleObject
[#&#8203;1313](https://togithub.com/async-graphql/async-graphql/pull/1313)
- chore: trim up some unnecessary code
[#&#8203;1324](https://togithub.com/async-graphql/async-graphql/pull/1324)
- Adds `Dataloader::get_cached_values` method to the dataloader cache so
that callers can access the contents of the cache without knowing the
keys.
[#&#8203;1326](https://togithub.com/async-graphql/async-graphql/pull/1326)

#### Breaking Changes

- Since `syn 2.0` no longer supports keywords as meta path, rename the
parameter used to specify interface field types from `type` to `ty`.


[dtolnay/syn#1458

[TedDriggs/darling#238

```rust

#[derive(Interface)]
#[graphql(field(name = "id", ty = "&i32"))] // rename from type to ty
enum Node {
    MyObj(MyObj),
}
```

- Change the parameter `location` of the macro `Directive` to
*PascalCase*

```rust
// #[Directive(location = "field")]

#[Directive(location = "Field")]
pub fn lowercase() -> impl CustomDirective {
    LowercaseDirective
}
```

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/apollographql/subgraph-template-rust-async-graphql).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi42OC4xIiwidXBkYXRlZEluVmVyIjoiMzYuNjguMSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
DDtKey added a commit to DDtKey/rocket-grants that referenced this issue Nov 24, 2023
`syn2` doesn't allow to use keywords as part of `MetaItem` dtolnay/syn#1458

This is reason of renaming `type` to `ty`, which is breaking change
@dtolnay
Copy link
Owner

dtolnay commented Feb 23, 2024

would you be open to Meta exposing a parse_nested function that allowed keywords in the path position?

I would prefer not to add this. In macro_rules, the only intended use for $:meta is as #[$m:meta]. syn::Meta serves the same role. In general syn::Meta isn't the right data structure for representing anything different from that intended use case, which is top-level attribute content, same as $:meta.

Whether keys can be a keyword is not the only consideration. There is also the fact that Meta::NameValue has a rhs that is required to be an expression. In Rust syntax, #[foo(t = Vec<u8>)] is a syntactically valid attribute but #[t = Vec<u8>] is not. I've found using Meta to represent the content in the parenthesized part of a Meta::List is an antipattern: it leads to things that are not expressions needing to be handled as string literals instead, which is problematic when one needs macro-generated attributes such as #[foo(t = Vec<$T>)], and also problematic for IDE functionality like go-to-definition as each token parsed from the string literal would end up carrying the Span of the entire string literal.

@TedDriggs
Copy link
Contributor Author

Thanks for the explanation on this; it now makes more sense why NestedMeta got removed in syn v2 - and why attempting to recreate it to maintain compatibility in darling felt awkward.

For the darling::FromMeta use-case, I want that to take an input type that represents the items of a Meta::List. To my knowledge, there are three variants that make sense there:

  1. Path, e.g. the First in #[example(derive(First))].
  2. NameValue, e.g. the t = Vec<T> in #[example(t = Vec<T>)]
  3. List, e.g. the thing(...) in #[example(thing(t = Vec<T>, derive(First))]

Aside: In the paragraph that follows, I'm going to use darling::Meta to refer to a thing that you used to call a NestedMeta. I apologize for any confusion, but since darling's main trait is called FromMeta, I think I'm stuck using the term to mean the nested thing in the context of the darling library.

It'd be a pretty rough breaking change for darling, but if you're willing to lend a hand on writing the Parse impl for it, I'd be willing to introduce darling::Meta to specifically represent the contents inside the parentheses so that I'm not misusing the syn::Meta type across the darling API and all the impls it generates. That would allow using keywords as idents of these nested items, and - if I'm reading your message correctly - would be your recommended long-term supportable approach?

I think it'd be really interesting to make this generic over what's allowed in the RHS position of NameValue, e.g. darling::Meta<RHS = syn::Expr> - that would make it possible to use the type to limit the items that were valid in the value position, while the default value of the type parameter would make for easier compatibility with syn::Meta.

@dtolnay
Copy link
Owner

dtolnay commented Feb 25, 2024

I think it is not a matter of writing a smarter Parse impl — parsing an attribute's nested contents into any data structure without regard for what the keys mean is fundamentally problematic. For example, consider what you might do to parse #[foo(return_type = T<K, V>, precondition = A < K, note = "...")]. The meaning of the comma after K is determined by what key is being parsed.

@TedDriggs
Copy link
Contributor Author

That's a good example.

I'm passionate about preserving the ease-of-use of darling - the first time I added #[derive(Deserialize)] to a struct remains one of my most joyful moments with any programming language, and I want to deliver that experience for proc-macro authors.

Would your recommendation be that darling generates something like syn::Parse impls, taking a ParseStream or a ParseNestedMeta in and returning Result<Self>?

I think I can imagine a lot of darling's current generated FromMeta impls working with this approach - some, like enums, might require lookahead, but that doesn't seem like the end of the world? The breaking change would be quite severe, and the amount of work within the library to support this would be rough, but if that's the long-term right way to do this then it's worth having the discussion about how to execute on it.

@dtolnay
Copy link
Owner

dtolnay commented Feb 26, 2024

Deriving a function that operates on ParseNestedMeta would be the winning approach in the long term, I think.

I don't have a complete design with all of the signatures worked out, but following the handwritten ones in colin-kiegel/rust-derive-builder#310 would be a good guide.

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

2 participants