Как процедурный макрос может проверить общий тип для параметра<Option> и свести его к одному параметру?

#rust #rust-macros #rust-proc-macros

#Ржавчина #rust-макросы #rust-proc-macros

Вопрос:

Я пишу производный процедурный макрос, в котором все значения преобразуются в Options . Проблема в том, что любые Option поля в структуре могут содержаться внутри этих Option типов. Сам по себе это не представляет большой проблемы, пока я не начну сериализовывать данные с помощью serde. Я хочу иметь возможность пропускать любые значения, где значение равно None , но бывают случаи, когда это будет что-то вроде Some(None) или Some(CustomOption::None) . Оба эти случая не более значимы, чем простой None , но я не могу просто писать #[serde(skip_serializing_if = "Option::is_none")] в производных полях. Конечно, они выводят null значение в формате JSON.

В принципе, я хочу иметь возможность использовать библиотеку syn, чтобы проверить, будет ли тип внутреннего значения производного поля Option и сгладить его в единственное число Option<T> в производной структуре вместо Option<Option<T>> типа. Я бы хотел, чтобы у Rust было сопоставление шаблонов на основе типов в общих файлах, но на самом деле это не так.

Я могу придумать два решения этой проблемы, но я не могу придумать, как их реализовать. Первым делом нужно было бы просмотреть все поля и найти Option s, затем развернуть эти параметры и повторно обернуть их так, чтобы они имели только один Option снаружи. Одна из потенциальных проблем с этим решением заключается в том, что мне, возможно, придется переписать их в другое Option после выполнения вычислений. Вторым решением было бы найти Option и соответствующим образом изменить сгенерированный код, чтобы, если внутренний параметр содержит None , все это стало None ; в основном просто иметь вспомогательную функцию, которая выводит логическое значение, если поле является Option . Есть идеи о том, как реализовать любой из них или лучшее решение?

Вот пример кода:

 #[derive(Macro)]
struct X {
    a: usize,
    b: SomeType<String>,
    c: Option<String>,
}
  
 struct GeneratedX {
    a: Option<usize>,
    b: Option<SomeType<String>>,
    c: Option<Option<String>>,
}
  

Используя подобную функцию, чтобы перенести все значения в параметры:

 pub fn wrap_typ_in_options(amp;self) -> TokenStream {
    // self is a struct with the type Type in it along with some other items.
    let typ: syn::Type = self.typ();

    // attribute to check if should ignore a field.
    if self.should_ignore() {
        quote! { Option<#typ> }
    } else {
        quote! { Option<<#typ as module::Trait>::Type> }
    }
}
  

Комментарии:

1. Основная проблема в том, что вы можете просматривать только токены. Тип, подобный MyFoo , на самом деле может быть псевдонимом типа для необязательного. У вас не было бы способа обнаружить это.

2. На самом деле немного неясно, о чем вы спрашиваете. Генерация полей структуры без вложенных параметров? Или иметь дело с преобразованиями значений?

3. Не беспокойтесь об псевдонимах, поскольку эта библиотека используется только для очень специфического проекта. И одно из моих решений использует первое, а другое — второе.

Ответ №1:

Я нашел решение этой проблемы, следуя второй идее, которая у меня была в исходном сообщении. Я использовал подобную функцию, чтобы определить, был ли токен Option :

 let idents_of_path = path
    .segments
    .iter()
    .fold(String::new(), |mut acc, v| {
        acc.push_str(amp;v.ident.to_string());
        acc.push(':');
        acc
    });
vec!["Option:", "std:option:Option:", "core:option:Option:"]
    .into_iter()
    .find(|s| idents_of_path == *s)
    .and_then(|_| path.segments.last())
  

Затем я добавил новый метод с именем, is_option который возвращает логическое значение, если Type::Path это был параметр.

 pub fn is_option(amp;self) -> bool {
    let typ = self.typ();

    let opt = match typ {
        Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()),
        _ => None,
    };

    if let Some(o) = opt {
        check_for_option(amp;o).is_some()
    } else {
        false
    }
}
  

Я модифицировал сгенерированный код на основе результатов этого вызова способом, аналогичным тому, как я обрабатываю свои различные атрибуты. Все это должно работать нормально для моего конкретного варианта использования, поскольку никакие псевдонимы Option никогда не будут введены в эту экосистему. Это немного запутанно, но на данный момент он выполняет свою работу.