compiler-plugins.md
commit 1430a3500076ad504a0b30be77fd2ad4468ea769
rustc
可以加載編譯器插件,它是由用戶提供的庫用來擴充編譯器的行為,例如新的語法擴展,lint檢查等。
一個插件是帶有設(shè)計好的用來在rustc
中注冊擴展的注冊(registrar)函數(shù)的一個動態(tài)庫包裝箱。其它包裝箱可以使用#![plugin(...)]
屬性來裝載這個擴展。查看rustc::plugin文檔來獲取更多關(guān)于定義和裝載插件的機制。
如果屬性存在的話,#![plugin(foo(... args ...))]
傳遞的參數(shù)并不由rustc
自身解釋。它們被傳遞給插件的Registry
args方法。
在絕大多數(shù)情況中,一個插件應(yīng)該只通過#![plugin]
而不通過extern crate
來使用。鏈接一個插件會將libsyntax
和librustc
加入到你的包裝箱的依賴中?;旧夏悴粫M绱顺悄阍跇?gòu)建另一個插件。plugin_as_library
lint會檢查這些原則。
通常的做法是將插件放到它們自己的包裝箱中,與任何那些會被庫的調(diào)用者使用的macro_rules!
宏或 Rust 代碼分開。
插件可以有多種方法來擴展 Rust 的語法。一種語法擴展是宏過程。它們與[普通宏](5.35.Macros 宏.md)的調(diào)用方法一樣,不過擴展是通過執(zhí)行任意Rust代碼在編譯時操作語法樹進(jìn)行的。
讓我們寫一個實現(xiàn)了羅馬數(shù)字的插件roman_numerals.rs。
#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]
extern crate syntax;
extern crate rustc;
extern crate rustc_plugin;
use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::TokenTree;
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder; // trait for expr_usize
use rustc_plugin::Registry;
fn expand_rn(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
-> Box<MacResult + 'static> {
static NUMERALS: &'static [(&'static str, usize)] = &[
("M", 1000), ("CM", 900), ("D", 500), ("CD", 400),
("C", 100), ("XC", 90), ("L", 50), ("XL", 40),
("X", 10), ("IX", 9), ("V", 5), ("IV", 4),
("I", 1)];
if args.len() != 1 {
cx.span_err(
sp,
&format!("argument should be a single identifier, but got {} arguments", args.len()));
return DummyResult::any(sp);
}
let text = match args[0] {
TokenTree::Token(_, token::Ident(s, _)) => s.to_string(),
_ => {
cx.span_err(sp, "argument should be a single identifier");
return DummyResult::any(sp);
}
};
let mut text = &*text;
let mut total = 0;
while !text.is_empty() {
match NUMERALS.iter().find(|&&(rn, _)| text.starts_with(rn)) {
Some(&(rn, val)) => {
total += val;
text = &text[rn.len()..];
}
None => {
cx.span_err(sp, "invalid Roman numeral");
return DummyResult::any(sp);
}
}
}
MacEager::expr(cx.expr_usize(sp, total))
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_macro("rn", expand_rn);
}
我們可以像其它宏那樣使用rn!()
:
#![feature(plugin)]
#![plugin(roman_numerals)]
fn main() {
assert_eq!(rn!(MMXV), 2015);
}
與一個簡單的fn(&str) -> u32
函數(shù)相比的優(yōu)勢有:
除了宏過程,你可以定義新的類derive屬性和其它類型的擴展。查看Registry::register_syntax_extension和SyntaxExtension enum。對于更復(fù)雜的宏例子,查看regex_macros。
這里提供一些[宏調(diào)試的提示](5.35.Macros 宏.md#debugging-macro-code)。
你可以使用syntax::parse來將記號樹轉(zhuǎn)換為像表達(dá)式這樣的更高級的語法元素:
fn expand_foo(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
-> Box<MacResult+'static> {
let mut parser = cx.new_parser_from_tts(args);
let expr: P<Expr> = parser.parse_expr();
看完libsyntax解析器代碼會給你一個解析基礎(chǔ)設(shè)施如何工作的感覺。
保留你解析所有的Span,以便更好的報告錯誤。你可以用Spanned包圍你的自定數(shù)據(jù)結(jié)構(gòu)。
調(diào)用ExtCtxt::span_fatal將會立即終止編譯。相反最好調(diào)用ExtCtxt::span_err并返回DummyResult,這樣編譯器可以繼續(xù)并找到更多錯誤。
為了打印用于調(diào)試的語法段,你可以同時使用span_note和syntax::print::pprust::*_to_string。
上面的例子使用AstBuilder::expr_usize產(chǎn)生了一個普通整數(shù)。作為一個AstBuilder
特性的額外選擇,libsyntax
提供了一個準(zhǔn)引用宏的集合。它們并沒有文檔并且非常邊緣化。然而,這些將會是實現(xiàn)一個作為一個普通插件庫的改進(jìn)準(zhǔn)引用的好的出發(fā)點。
插件可以擴展Rust Lint基礎(chǔ)設(shè)施來添加額外的代碼風(fēng)格,安全檢查等。你可以查看src/test/auxiliary/lint_plugin_test.rs來了解一個完整的例子,我們在這里重現(xiàn)它的核心部分:
#![feature(plugin_registrar)]
#![feature(box_syntax, rustc_private)]
extern crate syntax;
// Load rustc as a plugin to get macros
#[macro_use]
extern crate rustc;
extern crate rustc_plugin;
use rustc::lint::{EarlyContext, LintContext, LintPass, EarlyLintPass,
EarlyLintPassObject, LintArray};
use rustc_plugin::Registry;
use syntax::ast;
declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'");
struct Pass;
impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(TEST_LINT)
}
}
impl EarlyLintPass for Pass {
fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
if it.ident.name.as_str() == "lintme" {
cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
}
}
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_early_lint_pass(box Pass as EarlyLintPassObject);
}
那么像這樣的代碼:
#![plugin(lint_plugin_test)]
fn lintme() { }
將產(chǎn)生一個編譯警告:
foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
foo.rs:4 fn lintme() { }
^~~~~~~~~~~~~~~
Lint插件的組件有:
declare_lint!
調(diào)用,它定義了Lint結(jié)構(gòu)LintPass
可能會對多個不同的Lint
調(diào)用span_lint
,不過它們都需要用get_lints
方法進(jìn)行注冊。Lint過程是語法遍歷,不過它們運行在編譯的晚期,這時類型信息時可用的。rustc
的內(nèi)建lint與lint插件使用相同的基礎(chǔ)構(gòu)架,并提供了如何訪問類型信息的例子。
由插件定義的語法通常通過屬性和插件標(biāo)識控制,例如,[#[allow(test_lint)]]
,-A test-lint
。這些標(biāo)識符來自于declare_lint!
的第一個參數(shù),經(jīng)過合適的大小寫和標(biāo)點轉(zhuǎn)換。
你可以運行rustc -W help foo.rs
來見檢查lint列表是否為rustc
所知,包括由foo.rs
加載的插件。