• Stars
    star
    2,574
  • Rank 17,098 (Top 0.4 %)
  • Language
    Rust
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Parser for Rust source code

Parser for Rust source code

github crates.io docs.rs build status

Syn is a parsing library for parsing a stream of Rust tokens into a syntax tree of Rust source code.

Currently this library is geared toward use in Rust procedural macros, but contains some APIs that may be useful more generally.

  • Data structures โ€” Syn provides a complete syntax tree that can represent any valid Rust source code. The syntax tree is rooted at syn::File which represents a full source file, but there are other entry points that may be useful to procedural macros including syn::Item, syn::Expr and syn::Type.

  • Derives โ€” Of particular interest to derive macros is syn::DeriveInput which is any of the three legal input items to a derive macro. An example below shows using this type in a library that can derive implementations of a user-defined trait.

  • Parsing โ€” Parsing in Syn is built around parser functions with the signature fn(ParseStream) -> Result<T>. Every syntax tree node defined by Syn is individually parsable and may be used as a building block for custom syntaxes, or you may dream up your own brand new syntax without involving any of our syntax tree types.

  • Location information โ€” Every token parsed by Syn is associated with a Span that tracks line and column information back to the source of that token. These spans allow a procedural macro to display detailed error messages pointing to all the right places in the user's code. There is an example of this below.

  • Feature flags โ€” Functionality is aggressively feature gated so your procedural macros enable only what they need, and do not pay in compile time for all the rest.

Version requirement: Syn supports rustc 1.60 and up.

Release notes


Resources

The best way to learn about procedural macros is by writing some. Consider working through this procedural macro workshop to get familiar with the different types of procedural macros. The workshop contains relevant links into the Syn documentation as you work through each project.


Example of a derive macro

The canonical derive macro using Syn looks like this. We write an ordinary Rust function tagged with a proc_macro_derive attribute and the name of the trait we are deriving. Any time that derive appears in the user's code, the Rust compiler passes their data structure as tokens into our macro. We get to execute arbitrary Rust code to figure out what to do with those tokens, then hand some tokens back to the compiler to compile into the user's crate.

[dependencies]
syn = "2.0"
quote = "1.0"

[lib]
proc-macro = true
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyMacro)]
pub fn my_macro(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree
    let input = parse_macro_input!(input as DeriveInput);

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
        // ...
    };

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}

The heapsize example directory shows a complete working implementation of a derive macro. The example derives a HeapSize trait which computes an estimate of the amount of heap memory owned by a value.

pub trait HeapSize {
    /// Total number of bytes of heap memory owned by `self`.
    fn heap_size_of_children(&self) -> usize;
}

The derive macro allows users to write #[derive(HeapSize)] on data structures in their program.

#[derive(HeapSize)]
struct Demo<'a, T: ?Sized> {
    a: Box<T>,
    b: u8,
    c: &'a str,
    d: String,
}

Spans and error reporting

The token-based procedural macro API provides great control over where the compiler's error messages are displayed in user code. Consider the error the user sees if one of their field types does not implement HeapSize.

#[derive(HeapSize)]
struct Broken {
    ok: String,
    bad: std::thread::Thread,
}

By tracking span information all the way through the expansion of a procedural macro as shown in the heapsize example, token-based macros in Syn are able to trigger errors that directly pinpoint the source of the problem.

error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied
 --> src/main.rs:7:5
  |
7 |     bad: std::thread::Thread,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HeapSize` is not implemented for `std::thread::Thread`

Parsing a custom syntax

The lazy-static example directory shows the implementation of a functionlike!(...) procedural macro in which the input tokens are parsed using Syn's parsing API.

The example reimplements the popular lazy_static crate from crates.io as a procedural macro.

lazy_static! {
    static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap();
}

The implementation shows how to trigger custom warnings and error messages on the macro input.

warning: come on, pick a more creative name
  --> src/main.rs:10:16
   |
10 |     static ref FOO: String = "lazy_static".to_owned();
   |                ^^^

Testing

When testing macros, we often care not just that the macro can be used successfully but also that when the macro is provided with invalid input it produces maximally helpful error messages. Consider using the trybuild crate to write tests for errors that are emitted by your macro or errors detected by the Rust compiler in the expanded code following misuse of the macro. Such tests help avoid regressions from later refactors that mistakenly make an error no longer trigger or be less helpful than it used to be.


Debugging

When developing a procedural macro it can be helpful to look at what the generated code looks like. Use cargo rustc -- -Zunstable-options --pretty=expanded or the cargo expand subcommand.

To show the expanded code for some crate that uses your procedural macro, run cargo expand from that crate. To show the expanded code for one of your own test cases, run cargo expand --test the_test_case where the last argument is the name of the test file without the .rs extension.

This write-up by Brandon W Maister discusses debugging in more detail: Debugging Rust's new Custom Derive system.


Optional features

Syn puts a lot of functionality behind optional features in order to optimize compile time for the most common use cases. The following features are available.

  • derive (enabled by default) โ€” Data structures for representing the possible input to a derive macro, including structs and enums and types.
  • full โ€” Data structures for representing the syntax tree of all valid Rust source code, including items and expressions.
  • parsing (enabled by default) โ€” Ability to parse input tokens into a syntax tree node of a chosen type.
  • printing (enabled by default) โ€” Ability to print a syntax tree node as tokens of Rust source code.
  • visit โ€” Trait for traversing a syntax tree.
  • visit-mut โ€” Trait for traversing and mutating in place a syntax tree.
  • fold โ€” Trait for transforming an owned syntax tree.
  • clone-impls (enabled by default) โ€” Clone impls for all syntax tree types.
  • extra-traits โ€” Debug, Eq, PartialEq, Hash impls for all syntax tree types.
  • proc-macro (enabled by default) โ€” Runtime dependency on the dynamic library libproc_macro from rustc toolchain.

Proc macro shim

Syn operates on the token representation provided by the proc-macro2 crate from crates.io rather than using the compiler's built in proc-macro crate directly. This enables code using Syn to execute outside of the context of a procedural macro, such as in unit tests or build.rs, and we avoid needing incompatible ecosystems for proc macros vs non-macro use cases.

In general all of your code should be written against proc-macro2 rather than proc-macro. The one exception is in the signatures of procedural macro entry points, which are required by the language to use proc_macro::TokenStream.

The proc-macro2 crate will automatically detect and use the compiler's data structures when a procedural macro is active.


License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

More Repositories

1

cxx

Safe interop between Rust and C++
Rust
5,106
star
2

anyhow

Flexible concrete Error type built on std::error::Error
Rust
4,193
star
3

thiserror

derive(Error) for struct and enum error types
Rust
3,352
star
4

proc-macro-workshop

Learn to write Rust procedural macrosโ€ƒโ€ƒ[Rust Latam conference, Montevideo Uruguay, March 2019]
Rust
2,988
star
5

cargo-expand

Subcommand to show result of macro expansion
Rust
2,433
star
6

async-trait

Type erasure for async trait methods
Rust
1,495
star
7

case-studies

Analysis of various tricky Rust code
Rust
1,340
star
8

rust-quiz

Medium to hard Rust questions with explanations
Rust
1,318
star
9

quote

Rust quasi-quoting
Rust
1,173
star
10

watt

Runtime for executing procedural macros as WebAssembly
Rust
1,062
star
11

typetag

Serde serializable and deserializable trait objects
Rust
888
star
12

paste

Macros for all your token pasting needs
Rust
852
star
13

serde-yaml

Strongly typed YAML library for Rust
Rust
804
star
14

no-panic

Attribute macro to require that the compiler prove a function can't ever panic
Rust
758
star
15

inventory

Typed distributed plugin registration
Rust
714
star
16

rust-toolchain

Concise GitHub Action for installing a Rust toolchain
Shell
621
star
17

trybuild

Test harness for ui tests of compiler diagnostics
Rust
615
star
18

miniserde

Data structure serialization library with several opposite design goals from Serde
Rust
612
star
19

reflect

Compile-time reflection API for developing robust procedural macros (proof of concept)
Rust
602
star
20

request-for-implementation

Crates that don't exist, but should
597
star
21

indoc

Indented document literals for Rust
Rust
537
star
22

prettyplease

A minimal `syn` syntax tree pretty-printer
Rust
517
star
23

erased-serde

Type-erased Serialize, Serializer and Deserializer traits
Rust
503
star
24

semver

Parser and evaluator for Cargo's flavor of Semantic Versioning
Rust
500
star
25

dyn-clone

Clone trait that is object-safe
Rust
486
star
26

ryu

Fast floating point to string conversion
Rust
471
star
27

linkme

Safe cross-platform linker shenanigans
Rust
399
star
28

semver-trick

How to avoid complicated coordinated upgrades
Rust
383
star
29

cargo-llvm-lines

Count lines of LLVM IR per generic function
Rust
368
star
30

efg

Conditional compilation using boolean expression syntax, rather than any(), all(), not()
Rust
297
star
31

rust-faq

Frequently Asked Questions ยท The Rust Programming Language
262
star
32

rustversion

Conditional compilation according to rustc compiler version
Rust
256
star
33

itoa

Fast function for printing integer primitives to a decimal string
Rust
248
star
34

path-to-error

Find out path at which a deserialization error occurred
Rust
241
star
35

cargo-tally

Graph the number of crates that depend on your crate over time
Rust
212
star
36

proc-macro-hack

Procedural macros in expression position
Rust
203
star
37

monostate

Type that deserializes only from one specific value
Rust
194
star
38

colorous

Color schemes for charts and maps
Rust
193
star
39

readonly

Struct fields that are made read-only accessible to other modules
Rust
187
star
40

dissimilar

Diff library with semantic cleanup, based on Google's diff-match-patch
Rust
175
star
41

star-history

Graph history of GitHub stars of a user or repo over time
Rust
156
star
42

ref-cast

Safely cast &T to &U where the struct U contains a single field of type T.
Rust
154
star
43

automod

Pull in every source file in a directory as a module
Rust
129
star
44

inherent

Make trait methods callable without the trait in scope
Rust
128
star
45

ghost

Define your own PhantomData
Rust
115
star
46

faketty

Wrapper to exec a command in a pty, even if redirecting the output
Rust
113
star
47

dtoa

Fast functions for printing floating-point primitives to a decimal string
Rust
110
star
48

clang-ast

Rust
108
star
49

seq-macro

Macro to repeat sequentially indexed copies of a fragment of code
Rust
102
star
50

remain

Compile-time checks that an enum or match is written in sorted order
Rust
99
star
51

mashup

Concatenate identifiers in a macro invocation
Rust
96
star
52

noisy-clippy

Rust
84
star
53

tt-call

Token tree calling convention
Rust
77
star
54

basic-toml

Minimal TOML library with few dependencies
Rust
76
star
55

squatternaut

A snapshot of name squatting on crates.io
Rust
73
star
56

serde-ignored

Find out about keys that are ignored when deserializing data
Rust
68
star
57

enumn

Convert number to enum
Rust
66
star
58

bootstrap

Bootstrapping rustc from source
Shell
62
star
59

essay

docs.rs as a publishing platform?
Rust
62
star
60

db-dump

Library for scripting analyses against crates.io's database dumps
Rust
60
star
61

scratch

Compile-time temporary directory shared by multiple crates and erased by `cargo clean`
Rust
59
star
62

gflags

Command line flags library that does not require a central list of all the flags
Rust
55
star
63

install

Fast `cargo install` action using a GitHub-based binary cache
Shell
55
star
64

oqueue

Non-interleaving multithreaded output queue
Rust
53
star
65

serde-starlark

Serde serializer for generating Starlark build targets
Rust
53
star
66

build-alert

Rust
51
star
67

unicode-ident

Determine whether characters have the XID_Start or XID_Continue properties
Rust
51
star
68

lalrproc

Proof of concept of procedural macro input parsed by LALRPOP
Rust
50
star
69

dragonbox

Rust
50
star
70

sha1dir

Checksum of a directory tree
Rust
38
star
71

hackfn

Fake implementation of `std::ops::Fn` for user-defined data types
Rust
38
star
72

reduce

iter.reduce(fn) in Rust
Rust
37
star
73

link-cplusplus

Link libstdc++ or libc++ automatically or manually
Rust
36
star
74

argv

Non-allocating iterator over command line arguments
Rust
33
star
75

get-all-crates

Download .crate files of all versions of all crates from crates.io
Rust
31
star
76

threadbound

Make any value Sync but only available on its original thread
Rust
31
star
77

dircnt

Count directory entriesโ€”`ls | wc -l` but faster
Rust
27
star
78

unsafe-libyaml

libyaml transpiled to rust by c2rust
Rust
27
star
79

serde-stacker

Serializer and Deserializer adapters that avoid stack overflows by dynamically growing the stack
Rust
27
star
80

cargo-unlock

Remove Cargo.lock lockfile
Rust
25
star
81

respan

Macros to erase scope information from tokens
Rust
24
star
82

isatty

libc::isatty that also works on Windows
Rust
21
star
83

iota

Related constants in Rust: 1 << iota
Rust
20
star
84

foreach

18
star
85

bufsize

bytes::BufMut implementation to count buffer size
Rust
18
star
86

hire

How to hire dtolnay
18
star
87

precise

Full precision decimal representation of f64
Rust
17
star
88

dashboard

15
star
89

rustflags

Parser for CARGO_ENCODED_RUSTFLAGS
Rust
13
star
90

libfyaml-rs

Rust binding for libfyaml
Rust
11
star
91

install-buck2

Install precompiled Buck2 build system
6
star
92

mailingset

Set-algebraic operations on mailing lists
Python
5
star
93

.github

5
star
94

jq-gdb

gdb pretty-printer for jv objects
Python
1
star