Помимо паники, в настоящее время существует два способа сообщения об ошибках из proc-макроса: нестабильный Diagnostic
API и "трюк compile_error!
" .В настоящее время последний в основном используется, потому что он работает на стабильной.Давайте посмотрим, как они оба работают.
Трюк compile_error!
Начиная с Rust 1.20, макрос compile_error!
существует в стандартной библиотеке .Он принимает строку и приводит к ошибке во время компиляции.
compile_error!("oopsie woopsie");
Что приводит к ( Детская площадка ):
error: oopsie woopsie
--> src/lib.rs:1:1
|
1 | compile_error!("oopsie woopsie");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Этот макрос был добавлен для двух случаев: macro_rules!
макросов и #[cfg]
.В обоих случаях авторы библиотек могут добавить лучшие ошибки, если пользователь неправильно использует макрос или имеет неправильные значения cfg
.
Но у программистов proc-macro была интересная идея.Как вы, возможно, знаете, TokenStream
, который вы возвращаете из процедурного макроса, может быть создан по вашему усмотрению.Это включает в себя диапазоны этих токенов: вы можете прикрепить любые диапазоны, которые вам нравятся, к вашим выходным токенам.Итак, основная идея такова:
Излучить поток токенов, содержащий compile_error!("your error message");
, но установить диапазон этих токенов равным диапазону входного токена, вызвавшего ошибку. Существует даже макросв quote
, что облегчает это: quote_spanned!
.В вашем случае мы можем написать это:
let output = if ty.to_string() != "bool" {
quote_spanned! {
ty.span() =>
compile_error!("expected bool");
}
} else {
quote! {
const #name: #ty = false;
}
};
Для вашего неверного ввода компилятор теперь печатает это:
error: expected bool
--> examples/main.rs:4:7
|
4 | #[something_else]
| ^^^^^^^^^^^^^^
Почему именно это работает?Хорошо: ошибка для compile_error!
показывает фрагмент кода, содержащий вызов compile_error!
.Для этого используется диапазон вызова compile_error!
.Но так как мы установили span для неверного входного токена ty
, компилятор показывает фрагмент, подчеркивающий этот токен.
Этот прием также используется syn
для вывода хороших ошибок.Фактически, если вы все равно используете syn
, вы можете использовать его тип Error
и, в частности, метод Error::to_compile_error
, который возвращает именно тот поток токенов, который мы вручную создали с помощью quote_spanned!
:
syn::Error::new(ty.span(), "expected bool").to_compile_error()
API Diagnostic
Поскольку это все еще нестабильно, приведу только короткий пример.API диагностики является более мощным, чем описанный выше трюк, так как вы можете иметь несколько интервалов, предупреждений и примечаний.
Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();
После этой строки печатается ошибка, но вы все равно можете делать что-то в вашем proc-макросе,Обычно вы просто возвращаете пустой поток токенов.