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

Upgrade syn to v2 #226

Merged
merged 22 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 66 additions & 66 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,76 +1,76 @@
name: CI

on:
push:
pull_request:
schedule: [cron: "40 1 * * *"]
push:
TedDriggs marked this conversation as resolved.
Show resolved Hide resolved
pull_request:
schedule: [cron: "40 1 * * *"]

env:
RUST_BACKTRACE: 1
RUST_BACKTRACE: 1

jobs:
test:
name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { rust: nightly, os: ubuntu-latest }
- { rust: nightly, os: macos-latest }
- { rust: nightly, os: windows-latest }
- { rust: stable, os: ubuntu-latest }
- { rust: stable, os: macos-latest }
- { rust: stable, os: windows-latest }
- { rust: 1.31.0, os: ubuntu-latest }
- { rust: 1.31.0, os: windows-latest }
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- uses: Swatinem/rust-cache@v2
- name: Check Cargo availability
run: cargo --version
- run: cargo test --verbose --all
- run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features
test:
name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { rust: nightly, os: ubuntu-latest }
- { rust: nightly, os: macos-latest }
- { rust: nightly, os: windows-latest }
- { rust: stable, os: ubuntu-latest }
- { rust: stable, os: macos-latest }
- { rust: stable, os: windows-latest }
- { rust: 1.56.0, os: ubuntu-latest }
- { rust: 1.56.0, os: windows-latest }
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- uses: Swatinem/rust-cache@v2
- name: Check Cargo availability
run: cargo --version
- run: cargo test --verbose --all
- run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features

# Diagnostics are remaining a nightly-only feature for the foreseeable future, but
# we don't want them to break without us realizing.
test_diagnostics:
name: Test nightly with diagnostics feature
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Check Cargo availability
run: cargo --version
- run: cargo test --verbose --workspace --features diagnostics
# Diagnostics are remaining a nightly-only feature for the foreseeable future, but
# we don't want them to break without us realizing.
test_diagnostics:
name: Test nightly with diagnostics feature
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Check Cargo availability
run: cargo --version
- run: cargo test --verbose --workspace --features diagnostics

clippy:
name: Lint with clippy
runs-on: ubuntu-latest
env:
RUSTFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run clippy --workspace --tests
run: cargo clippy --workspace --tests
clippy:
name: Lint with clippy
runs-on: ubuntu-latest
env:
RUSTFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run clippy --workspace --tests
run: cargo clippy --workspace --tests

rustfmt:
name: Verify code formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- uses: Swatinem/rust-cache@v2
- name: Run fmt --all -- --check
run: cargo fmt --all -- --check
rustfmt:
name: Verify code formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- uses: Swatinem/rust-cache@v2
- name: Run fmt --all -- --check
run: cargo fmt --all -- --check
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ darling_macro = { version = "=0.14.4", path = "macro" }
[dev-dependencies]
proc-macro2 = "1.0.37"
quote = "1.0.18"
syn = "1.0.91"
syn = "2.0.0"

[target.'cfg(compiletests)'.dev-dependencies]
rustversion = "1.0.9"
Expand Down
83 changes: 47 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
Darling
=======
# Darling

[![Build Status](https://github.com/TedDriggs/darling/workflows/CI/badge.svg)](https://github.com/TedDriggs/darling/actions)
[![Latest Version](https://img.shields.io/crates/v/darling.svg)](https://crates.io/crates/darling)
[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html)
[![Rustc Version 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)]

`darling` is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by `serde` both in its internals and in its API.

# Benefits
* Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment.
* Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields.

- Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment.
- Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields.

# Usage

`darling` provides a set of traits which can be derived or manually implemented.

1. `FromMeta` is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like `FromStr` or `serde::Deserialize`. Trait implementations are provided for primitives, some std types, and some `syn` types.
Expand All @@ -21,9 +22,10 @@ Darling
5. `FromAttributes` is a lower-level version of the more-specific `FromDeriveInput`, `FromField`, and `FromVariant` traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros.

## Additional Modules
* `darling::ast` provides generic types for representing the AST.
* `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum.
* `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`.

- `darling::ast` provides generic types for representing the AST.
- `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum.
- `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`.

# Example

Expand Down Expand Up @@ -59,14 +61,17 @@ pub struct ConsumingType;
```

# Attribute Macros

Non-derive attribute macros are supported.
To parse arguments for attribute macros, derive `FromMeta` on the argument receiver type, then pass `&syn::AttributeArgs` to the `from_list` method.
This will produce a normal `darling::Result<T>` that can be used the same as a result from parsing a `DeriveInput`.

## Macro Code

```rust,ignore
use darling::FromMeta;
use syn::{AttributeArgs, ItemFn};
use darling::{Error, FromMeta};
use darling::ast::NestedMeta;
use syn::ItemFn;
use proc_macro::TokenStream;

#[derive(Debug, FromMeta)]
Expand All @@ -76,10 +81,13 @@ pub struct MacroArgs {
path: String,
}

#[proc_macro_attribute]
// #[proc_macro_attribute]
fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(args as AttributeArgs);
let _input = parse_macro_input!(input as ItemFn);
let attr_args = match NestedMeta::parse_meta_list(args) {
Ok(v) => v,
Err(e) => { return TokenStream::from(Error::from(e).write_errors()); }
};
let _input = syn::parse_macro_input!(input as ItemFn);

let _args = match MacroArgs::from_list(&attr_args) {
Ok(v) => v,
Expand All @@ -92,6 +100,7 @@ fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream {
```

## Consuming Code

```rust,ignore
use your_crate::your_attr;

Expand All @@ -102,37 +111,39 @@ fn do_stuff() {
```

# Features

TedDriggs marked this conversation as resolved.
Show resolved Hide resolved
Darling's features are built to work well for real-world projects.

* **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`.
Additionally, `Option<T>` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those.
* **Field Renaming**: Fields can have different names in usage vs. the backing code.
* **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`.
* **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct.
* **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items.
* **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`.
* **Span access**: 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. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases.
* **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields.
- **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`.
Additionally, `Option<T>` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those.
- **Field Renaming**: Fields can have different names in usage vs. the backing code.
- **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`.
- **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct.
- **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items.
- **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`.
- **Span access**: 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. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases.
- **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields.

## Shape Validation

Some proc-macros only work on structs, while others need enums whose variants are either unit or newtype variants.
Darling makes this sort of validation extremely simple.
On the receiver that derives `FromDeriveInput`, add `#[darling(supports(...))]` and then list the shapes that your macro should accept.

|Name|Description|
|---|---|
|`any`|Accept anything|
|`struct_any`|Accept any struct|
|`struct_named`|Accept structs with named fields, e.g. `struct Example { field: String }`|
|`struct_newtype`|Accept newtype structs, e.g. `struct Example(String)`|
|`struct_tuple`|Accept tuple structs, e.g. `struct Example(String, String)`|
|`struct_unit`|Accept unit structs, e.g. `struct Example;`|
|`enum_any`|Accept any enum|
|`enum_named`|Accept enum variants with named fields|
|`enum_newtype`|Accept newtype enum variants|
|`enum_tuple`|Accept tuple enum variants|
|`enum_unit`|Accept unit enum variants|
| Name | Description |
| ---------------- | ------------------------------------------------------------------------- |
| `any` | Accept anything |
| `struct_any` | Accept any struct |
| `struct_named` | Accept structs with named fields, e.g. `struct Example { field: String }` |
| `struct_newtype` | Accept newtype structs, e.g. `struct Example(String)` |
| `struct_tuple` | Accept tuple structs, e.g. `struct Example(String, String)` |
| `struct_unit` | Accept unit structs, e.g. `struct Example;` |
| `enum_any` | Accept any enum |
| `enum_named` | Accept enum variants with named fields |
| `enum_newtype` | Accept newtype enum variants |
| `enum_tuple` | Accept tuple enum variants |
| `enum_unit` | Accept unit enum variants |

Each one is additive, so listing `#[darling(supports(struct_any, enum_newtype))]` would accept all structs and any enum where every variant is a newtype variant.

This can also be used when deriving `FromVariant`, without the `enum_` prefix.
This can also be used when deriving `FromVariant`, without the `enum_` prefix.
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
msrv = "1.31.0"
msrv = "1.56.0"
disallowed-names = [] # we want to be able to use placeholder names in tests
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ suggestions = ["strsim"]
ident_case = "1.0.1"
proc-macro2 = "1.0.37"
quote = "1.0.18"
syn = { version = "1.0.91", features = ["full", "extra-traits"] }
syn = { version = "2.0.0", features = ["full", "extra-traits"] }
fnv = "1.0.7"
strsim = { version = "0.10.0", optional = true }
40 changes: 40 additions & 0 deletions core/src/ast/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::{slice, vec};

use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::ext::IdentExt;
use syn::parse::Parser;
use syn::spanned::Spanned;
use syn::Token;

use crate::usage::{
self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams,
Expand Down Expand Up @@ -410,6 +413,43 @@ impl<'a> From<&'a syn::Fields> for Style {
}
}

#[derive(Debug)]
pub enum NestedMeta {
Meta(syn::Meta),
Lit(syn::Lit),
}
TedDriggs marked this conversation as resolved.
Show resolved Hide resolved

impl NestedMeta {
pub fn parse_meta_list(tokens: TokenStream) -> syn::Result<Vec<Self>> {
syn::punctuated::Punctuated::<NestedMeta, Token![,]>::parse_terminated
.parse2(tokens)
.map(|punctuated| punctuated.into_iter().collect())
}
}

impl syn::parse::Parse for NestedMeta {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(Token![=])) {
input.parse().map(NestedMeta::Lit)
} else if input.peek(syn::Ident::peek_any)
|| input.peek(Token![::]) && input.peek3(syn::Ident::peek_any)
{
input.parse().map(NestedMeta::Meta)
} else {
Err(input.error("expected identifier or literal"))
}
}
}

impl ToTokens for NestedMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
NestedMeta::Meta(meta) => meta.to_tokens(tokens),
NestedMeta::Lit(lit) => lit.to_tokens(tokens),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading