2个不稳定版本
0.2.0 | 2024年4月11日 |
---|---|
0.1.0 | 2024年3月22日 |
#1801 in 过程宏
在 3 个包中使用(通过 telety)
105KB
2.5K SLoC
telety
在进程宏中跨包和模块访问类型信息
创建telety信息
只需将属性应用于支持的项,并提供当前模块路径作为参数即可
pub mod my_mod {
#[telety(crate::my_mod)]
pub struct MyStruct;
}
如果项有其他属性,应在修改项定义的最后一个属性之后放置 #[telety]
。
使用telety信息
模块 v*(例如 v0
,v1
)包含用于读取telety信息的TokenStreams生成对象。
您需要两个宏(或一个具有两个模式的宏),一个用于生成读取信息的代码,另一个用于您自己的目的使用信息。
过程有些繁琐,但工作原理如下
- 您的进程宏调用
Command::apply
并返回宏的路径和输出。 - 生成的令牌将调用为类型(或回退)生成的
#[telety]
生成的宏。 #[telety]
生成的宏将在请求的位置文本插入信息,通常是作为您第二个宏调用的参数。- 然后您的第二个进程宏可以使用请求的信息。
- 如果此信息是项的定义,您可以创建一个
Telety
对象。 - 使用
Telety::alias_of
,您可以访问项中引用的任何类型的别名。这些别名具有全局路径,因此可以在其他上下文中使用。 - 如果项是泛型的,您可以使用
Telety::generics_visitor
将泛型参数替换为别名。
- 如果此信息是项的定义,您可以创建一个
示例
以下是我们可以如何编写 mix!
的示例,这是一个 proc macro,它将两个 struct 的字段组合成一个新的 struct。我们要组合来自不同 crate 的两种类型
#[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,
}
我们定义第一个宏,它接受 struct 的路径
/// 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
}
第一个宏将生成对第二个宏的调用,并包含两个 struct 的定义。
/// 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
使用特殊语言技术来实现回退行为,而不是导致编译错误(受一些限制的影响,请务必查看文档)。
依赖关系
~0.4–0.9MB
~20K SLoC