#proc-macro #syntax-tree #syn #error #source #tree-node #token

已撤销 syn-error-experiment

Syn错误实验

使用旧Rust 2015

0.0.0 2018年8月24日

#70 in #tree-node

MIT/Apache

1.5MB
40K SLoC

Rust源代码解析器

github crates.io docs.rs build status

Syn是一个解析库,可以将Rust令牌流解析成Rust源代码的语法树。

目前这个库主要面向Rust过程宏的使用,但包含了一些可能更通用的API。

  • 数据结构 — Syn提供了一个完整的语法树,可以表示任何有效的Rust源代码。语法树的根是syn::File,它代表一个完整的源文件,但还有其他可能对过程宏有用的入口点,包括syn::Itemsyn::Exprsyn::Type

  • 派生 — 对于派生宏来说,特别有趣的是syn::DeriveInput,它是派生宏的三个合法输入项之一。以下示例展示了在一个可以派生用户定义特质实现的库中使用此类型。

  • 解析 — Syn中的解析围绕具有签名 fn(ParseStream) -> Result<T>解析函数构建。Syn定义的每个语法树节点都可以单独解析,可以用作自定义语法的构建块,或者你可以梦想出你自己的全新语法,而不涉及任何我们的语法树类型。

  • 位置信息 — Syn解析的每个令牌都与一个跟踪行和列信息的Span相关联,该信息可以追溯到该令牌的源。这些跨度允许过程宏显示指向用户代码中所有正确位置的详细错误消息。下面是一个示例。

  • 功能标志 — 功能被积极限制,因此您的过程宏只启用它们需要的功能,而不会在编译时间上为所有其他功能付费。

版本要求:Syn支持rustc 1.61及以上版本。

发布说明


资源

了解过程宏的最佳方式是通过编写一些。考虑通过这个过程宏研讨会熟悉不同类型的过程宏。在完成每个项目时,研讨会包含指向Syn文档的相关链接。


derive宏示例

使用Syn的规范derive宏看起来像这样。我们编写一个带有proc_macro_derive属性和要派生的特质名称的普通Rust函数。每当该derive出现在用户代码中时,Rust编译器就会将用户的数据结构作为令牌传递到我们的宏中。我们可以执行任意Rust代码来了解如何处理这些令牌,然后向编译器返回一些令牌以编译到用户的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)
}

heapsize示例目录展示了derive宏的完整工作实现。该示例派生了一个HeapSize特质,该特质计算一个值拥有的堆内存的估计值。

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

derive宏允许用户在其程序中的数据结构上编写#[derive(HeapSize)]

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

跨度与错误报告

基于令牌的过程宏API为在用户代码中显示编译器的错误消息提供了很好的控制。考虑如果用户的字段类型没有实现HeapSize时,用户看到的错误。

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

通过跟踪跨度信息,就像在heapsize示例中所展示的那样,基于令牌的宏在Syn中能够触发直接指向问题源的错误。

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`

解析自定义语法

lazy-static示例目录展示了使用Syn的解析API解析输入令牌的functionlike!(...)过程宏的实现。

该示例重新实现了来自crates.io的流行crate lazy_static,作为过程宏。

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

该实现展示了如何在宏输入上触发自定义警告和错误消息。

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

测试

在测试宏时,我们不仅关心宏能否成功使用,而且还关心当宏提供无效输入时,它产生的错误消息尽可能有帮助。考虑使用trybuild crate为宏发出的错误或Rust编译器在宏误用时检测到的错误编写测试。此类测试有助于避免由于后续重构而导致的回归,这些重构错误地使错误不再触发或不如以前有帮助。


调试

在开发过程宏时,查看生成的代码的样子可能会有所帮助。使用cargo rustc -- -Zunstable-options --pretty=expandedcargo expand子命令。

要显示使用您的过程宏的一些crate的展开代码,请在该crate中运行cargo expand。要显示您的测试用例之一的展开代码,请运行cargo expand --test the_test_case,其中最后一个参数是测试文件名(不包括.rs扩展名)。

Brandon W Maister的这篇文档详细讨论了调试:调试Rust的新自定义派生系统


可选功能

Syn将大量功能放在可选功能之后,以优化最常用情况的编译时间。以下功能可用。

  • derive (默认启用) — 表示派生宏可能输入的数据结构,包括结构体、枚举和类型。
  • full — 表示所有有效Rust源代码语法树的表示数据结构,包括项和表达式。
  • parsing (默认启用) — 将输入令牌解析为所选类型的语法树节点的能力。
  • printing (默认启用) — 将语法树节点打印为Rust源代码的令牌。
  • visit — 遍历语法树的特质。
  • visit-mut — 遍历和原地修改语法树的特质。
  • fold — 用于转换拥有语法树的特质。
  • clone-impls (默认启用) — 所有语法树类型的克隆实现。
  • extra-traits — 所有语法树类型的Debug、Eq、PartialEq、Hash实现。
  • proc-macro (默认启用) — 对rustc工具链中动态库libproc_macro的运行时依赖。

过程宏shim

Syn在proc-macro2 crate提供的令牌表示上操作,而不是直接使用编译器内置的过程宏crate。这使得使用Syn的代码可以在过程宏之外的环境中执行,例如在单元测试或build.rs中,并避免进程宏与非宏用例之间不兼容的生态系统。

一般来说,您应该针对proc-macro2编写所有代码,而不是proc-macro。唯一的例外是过程宏入口点的签名,语言要求使用proc_macro::TokenStream

当过程宏激活时,proc-macro2 crate将自动检测并使用编译器的数据结构。


许可协议

根据您的选择,在Apache License, Version 2.0MIT license下获得许可。
除非您明确声明,否则任何根据Apache-2.0许可定义的、有意提交以包含在此crate中的贡献,都将如上所述双重许可,而不附加任何额外的条款或条件。

依赖关系