#macro #proc-macro #telety #path #module #information #alias

telety-impl

telety的通用代码。不建议公开使用。

2个不稳定版本

0.2.0 2024年4月11日
0.1.0 2024年3月22日

#1080过程宏

每月 21 次下载
用于 4 个crate(直接使用 2 个)

MIT/Apache

82KB
2K SLoC

telety

在您的proc宏中跨crate和模块访问类型信息

创建telety信息

只需将属性应用到支持的项上,并提供当前模块路径作为参数即可。

pub mod my_mod {
    #[telety(crate::my_mod)]
    pub struct MyStruct;
}

如果项具有其他属性,应在修改项定义的最后一个属性之后放置 #[telety]

使用telety信息

v* (例如 v0v1) 模块包含用于读取telety信息的TokenStreams生成对象。
您需要两个宏(或一个具有两个模式的宏),一个用于生成读取信息的代码,另一个用于您自己的目的使用信息。
这个过程有点繁琐,但工作原理如下

  1. 您的proc宏使用宏路径调用 Command::apply 并返回输出。
  2. 生成的令牌将调用用于类型(或回退)的 #[telety]-生成的宏。
  3. #[telety]-生成的宏将文本性地在请求的位置插入信息,通常作为第二个宏调用的参数。
  4. 您的第二个proc宏然后可以使用请求的信息。
    1. 如果该信息是项的定义,您可以创建一个 Telety 对象。
    2. 使用 Telety::alias_of,您可以访问项中引用的任何类型的别名。这些别名具有全局路径,因此可以在其他上下文中使用。
    3. 如果项目是通用的,可以使用 Telety::generics_visitor 将泛型参数替换到别名中。

示例

以下是如何编写 mix! 的示例,这是一个将两个结构体的字段组合成新结构的 proc macro。我们想要组合的两个来自不同包的类型

#[telety(crate)]
pub struct Water {
    pub water_liters: f32,
    pub source: water::Source,
}
#[telety(crate)]
pub struct Oil {
    pub oil_liters: f32,
    pub variety: oil::Variety,
}

我们定义第一个宏,它接受结构体的路径


/// mix!(path_to_struct0, path_to_struct1, new_struct_ident);
#[proc_macro]
pub fn mix(tokens: TokenStream) -> TokenStream {
    // Split `tokens` to `path_to_struct0`, `path_to_struct1`, & `new_struct_ident`
    // ...
    // Take the relative paths `path_to_struct0` and `path_to_struct1`
    // and use v1::TY::apply to call mix_impl! with the actual definition
    let item0: syn::Path = parse2(path_to_struct0)?;
    let item1: syn::Path = parse2(path_to_struct1)?;
    
    // telety works by find and replace - define a 'needle', and put it
    // where you want the type information inserted.
    let needle0: syn::Ident = parse_quote!(item0_goes_here);
    let needle1: syn::Ident = parse_quote!(item1_goes_here);
    // This macro generates the call to our actual implementation.
    // The `TY.apply` calls will replace the needles with the type definitions.
    let output = quote! {
        ::my_crate::mix_impl!(#needle0, #needle1, #new_struct_ident);
    };
    
    let output = telety::v1::TY.apply(
        item0,
        needle0,
        output,
    );
    let output = telety::v1::TY.apply(
        item1,
        needle1,
        output.into_token_stream(),
    );
    output
}

第一个宏将生成对第二个宏的调用,并包含两个结构体的定义。

/// mix_impl!(struct0_definition, struct1_definition, new_struct_ident);
#[proc_macro]
pub fn mix_impl(tokens: TokenStream) -> TokenStream {
    // Parse macro arguments
    // ...
    let item0: syn::Item = parse2(struct0_definition)?;
    let item1: syn::Item = parse2(struct1_definition)?;
    // Telety lets us reference remote types
    let telety0 = Telety::new(&item0);
    let telety1 = Telety::new(&item1);
    // Get the fields from the struct definitions
    // ...
    // Change the original type tokens to our aliases
    for field in fields0.iter_mut() {
        // We can get a location-independent alias for any type
        // used in the original item definition.
        let mut aliased_ty = telety0.alias_of(&field.ty).unwrap();
        // Switch to `crate::...` if in the same crate the alias was defined,
        // otherwise keep the path as `::my_crate::...`.
        telety::visitor::Crateify::new().visit_type_mut(&mut aliased_ty);
        field.ty = aliased_ty;
    }
    for field in fields1.iter_mut() {
        let mut aliased_ty = telety1.alias_of(&field.ty).unwrap();
        telety::visitor::Crateify::new().visit_type_mut(&mut aliased_ty);
        field.ty = aliased_ty;
    }

    // Create a new struct with all the fields from both mixed types
    quote::quote! {
        pub struct #new_struct_ident {
            #fields0
            #fields1
        }
    }
}

限制

  • Telety 在处理类型的所有功能方面还不够稳健。如果你的类型具有生命周期、const 泛型、关联类型、impl 类型或 dyn 类型,则可能会出现失败。
  • 项目目前不能包含比它更不公开的类型。例如:
    struct Private;
    #[telety(crate)]
    pub struct Public(Private);
    
    将无法编译。
  • 你无法在同一个模块中有一个与项目同名的外部宏,因为 Telety 需要定义自己的。
  • 类型别名(例如 type MyAlias = MyType)不会传播宏,因此无法通过别名访问任何 Telety 信息。(在未来,通过添加属性应该可以实现这一点。)
  • 使用 use my_mod::MyStruct::{self} 语法导入时,只导入类型,而不是宏或值。Telety 信息不会被导入。

工作原理

#[telety] 属性会扫描项目中的所有不同引用类型。然后它创建一个包含每个类型别名的隐藏模块。如果原始类型的同一路径中存在宏,则该宏也将被别名。
现在,如果你有类型的规范路径,你可以从任何地方定位别名模块。如果你也有原始项目定义,你可以将一个 syn::Type 映射到一个在任何地方都可以工作的别名。
创建与项目相同名称的宏。该宏包含有关关联类型的信息,包括规范路径和项目定义。
因为宏和项目具有相同的名称,但存在于不同的命名空间中,所以大多数情况下可以通过使用与类型相同的标记来定位宏。
例如

use my_crate::my_module;

// my_module::MyType is a type with the #[telety(...)] macro
struct NewType(my_module::MyType);
// The telety-created macro can also be used with the same path
my_module::MyType!(...);

Command::apply 生成利用此宏的代码。如果你不确定一个类型是否具有 #[telety] 属性,Apply::with_fallback 利用特殊的语言技术来实现回退行为,而不是导致编译错误(受一些限制,请务必查看文档)。


lib.rs:

包含 Telety 和 telety-macro 之间的通用代码。只有通过 Telety 重新导出的项目应被视为公共项目。

依赖关系

~0.3–0.8MB
~19K SLoC