proc-macro-workshop:sorted-3

godme發表於2022-07-06
// At this point we have an enum and we need to check whether the variants
// appear in sorted order!
//
// When your implementation notices a variant that compares lexicographically
// less than one of the earlier variants, you'll want to construct a syn::Error
// that gets converted to TokenStream by the already existing code that handled
// this conversion during the previous test case.
//
// The "span" of your error, which determines where the compiler places the
// resulting error message, will be the span of whichever variant name that is
// not in the right place. Ideally the error message should also identify which
// other variant the user needs to move this one to be in front of.

use sorted::sorted;

#[sorted]
pub enum Error {
    ThatFailed,
    ThisFailed,
    SomethingFailed,
    WhoKnowsWhatFailed,
}

fn main() {}

這裡我們終於接觸到sorted的核心功能了,那就是排序檢查。
參考錯誤

error: SomethingFailed should sort before ThatFailed
  --> tests/03-out-of-order.rs:20:5
   |
20 |     SomethingFailed,
   |     ^^^^^^^^^^^^^^^

我們需要找出列舉欄位中的名稱,然後對比排序結果,不符合順序就要提示。

pub(crate) fn check_order(
    names: Vec<(String, &dyn quote::ToTokens)>,
) -> std::option::Option<syn::Error> {
    let origin_names = names;
    let mut sorted_names = origin_names.clone();
    // 排序
    sorted_names.sort_by(|a, b| a.0.cmp(&b.0));
    for (a, b) in origin_names.iter().zip(sorted_names.iter()) {
        // 順序不對就報錯
        if a.0 != b.0 {
            return std::option::Option::Some(syn::Error::new_spanned(
                b.1,
                format!("{} should sort before {}", b.0, a.0),
            ));
        }
    }
    std::option::Option::None
}

這裡我們直接使用的是ToTokens,雖然可以直接使用ident獲取span,但是後續的一些情況就得重寫,乾脆用頂級的ToTokens,相容兩者,避免重複勞動。

pub(crate) fn solution(item: &syn::ItemEnum) -> syn::Result<proc_macro2::TokenStream> {
    let mut names: Vec<(String, &dyn quote::ToTokens)> = Vec::new();
    for i in item.variants.iter() {
        names.push((i.ident.to_string(), &i.ident));
    }
    match crate::common::check_order(names) {
        Some(e) => syn::Result::Err(e),
        None => syn::Result::Ok(crate::common::to_token_stream(item)),
    }
}

獲取(string, ToTokens)之後,透過排序檢查即可。
因為上層會自動新增錯誤時候的程式碼解析,這裡直接報錯沒啥問題。

正常來說,涉及錯誤時候都應該使用Result,但是我們並沒有想得到任何結果。
因此這裡我使用的是Option<Error>,正規一點應該是Result<(), Error>

本作品採用《CC 協議》,轉載必須註明作者和本文連結