Skip to main content

ax_crate_interface/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::quote;
7use syn::{ItemImpl, ItemTrait, PathArguments, PathSegment, parse::Error, parse_macro_input};
8
9mod args;
10mod def_interface;
11mod errors;
12mod impl_interface;
13mod naming;
14mod validator;
15
16use args::{CallInterface, DefInterfaceArgs, ImplInterfaceArgs};
17use naming::{extern_fn_mod_name, extern_fn_name};
18
19fn compiler_error(err: Error) -> TokenStream {
20    err.to_compile_error().into()
21}
22
23/// Define a crate interface.
24///
25/// This attribute should be added above the definition of a trait. All traits
26/// that use the attribute cannot have the same name, unless they are assigned
27/// different namespaces with `namespace = "..."` option.
28///
29/// It is not necessary to define it in the same crate as the implementation,
30/// but it is required that these crates are linked together.
31///
32/// See the [crate-level documentation](crate) for more details.
33///
34/// ## Calling Helper Functions
35///
36/// It is also possible to generate calling helper functions for each interface
37/// function by enabling the `gen_caller` option.
38///
39/// ## Restrictions
40///
41/// ### No Receivers
42///
43/// Methods with receivers (`self`, `&self`, `&mut self`) are not
44/// allowed. Only associated functions (static methods) are supported:
45///
46/// ```rust,compile_fail
47/// # use ax_crate_interface::*;
48/// #[def_interface]
49/// trait MyIf {
50///     fn foo(&self); // error: methods with receiver (self) are not allowed
51/// }
52/// ```
53///
54/// ### No Generic Parameters
55///
56/// Generic parameters are not supported. Interface functions cannot
57/// have generic type parameters, lifetime parameters, or const generic
58/// parameters:
59///
60/// ```rust,compile_fail
61/// # use ax_crate_interface::*;
62/// #[def_interface]
63/// trait MyIf {
64///     fn foo<T>(x: T); // error: generic parameters are not allowed
65/// }
66/// ```
67#[proc_macro_attribute]
68pub fn def_interface(attr: TokenStream, item: TokenStream) -> TokenStream {
69    let macro_arg = syn::parse_macro_input!(attr as DefInterfaceArgs);
70    let ast = syn::parse_macro_input!(item as ItemTrait);
71
72    def_interface::def_interface(ast, macro_arg)
73        .map(Into::into)
74        .unwrap_or_else(compiler_error)
75}
76
77/// Implement the crate interface for a struct.
78///
79/// This attribute should be added above the implementation of a trait for a
80/// struct, and the trait must be defined with
81/// [`#[def_interface]`](macro@crate::def_interface).
82///
83/// It is not necessary to implement it in the same crate as the definition, but
84/// it is required that these crates are linked together.
85///
86/// See the [crate-level documentation](crate) for more details.
87///
88/// ## Restrictions
89///
90/// ### No Alias
91///
92/// The specified trait name must not be an alias to the originally defined
93/// name; otherwise, it will result in a compile error.
94///
95/// ```rust,compile_fail
96/// # use ax_crate_interface::*;
97/// #[def_interface]
98/// trait MyIf {
99///     fn foo();
100/// }
101///
102/// use MyIf as MyIf2;
103/// struct MyImpl;
104/// #[impl_interface]
105/// impl MyIf2 for MyImpl {
106///     fn foo() {}
107/// }
108/// ```
109///
110/// ### No Namespace Mismatch
111///
112/// It's also mandatory to match the namespace if one is specified when defining
113/// the interface. For example, the following will result in a compile error:
114///
115/// ```rust,compile_fail
116/// # use ax_crate_interface::*;
117/// #[def_interface(namespace = MyNs)]
118/// trait MyIf {
119///     fn foo();
120/// }
121///
122/// struct MyImpl;
123///
124/// #[impl_interface(namespace = OtherNs)] // error: namespace does not match
125/// impl MyIf for MyImpl {
126///     fn foo() {}
127/// }
128/// ```
129///
130/// ### No Receivers
131///
132/// Methods with receivers (`self`, `&self`, `&mut self`) are not
133/// allowed in the implementation either:
134///
135/// ```rust,compile_fail
136/// # use ax_crate_interface::*;
137/// trait MyIf {
138///     fn foo(&self);
139/// }
140///
141/// struct MyImpl;
142///
143/// #[impl_interface]
144/// impl MyIf for MyImpl {
145///     fn foo(&self) {} // error: methods with receiver (self) are not allowed
146/// }
147/// ```
148///
149/// ### No Generic Parameters
150///
151/// Generic parameters are not supported in the implementation either:
152///
153/// ```rust,compile_fail
154/// # use ax_crate_interface::*;
155/// trait MyIf {
156///     fn foo<T>(x: T);
157/// }
158///
159/// struct MyImpl;
160///
161/// #[impl_interface]
162/// impl MyIf for MyImpl {
163///     fn foo<T>(x: T) {} // error: generic parameters are not allowed
164/// }
165/// ```
166#[proc_macro_attribute]
167pub fn impl_interface(attr: TokenStream, item: TokenStream) -> TokenStream {
168    let arg = syn::parse_macro_input!(attr as ImplInterfaceArgs);
169    let ast = syn::parse_macro_input!(item as ItemImpl);
170
171    impl_interface::impl_interface(ast, arg)
172        .map(Into::into)
173        .unwrap_or_else(compiler_error)
174}
175
176/// Call a function in a crate interface.
177///
178/// It is not necessary to call it in the same crate as the implementation, but
179/// it is required that these crates are linked together.
180///
181/// See the [crate-level documentation](crate) for more details.
182#[proc_macro]
183pub fn call_interface(item: TokenStream) -> TokenStream {
184    let call = parse_macro_input!(item as CallInterface);
185    let args = call.args;
186    let mut path = call.path.segments;
187
188    if path.len() < 2 {
189        compiler_error(Error::new(Span::call_site(), "expect `Trait::func`"));
190    }
191    let fn_name = path.pop().unwrap();
192    let trait_name = path.pop().unwrap();
193    let extern_fn_name = extern_fn_name(
194        call.namespace.as_deref(),
195        &trait_name.value().ident,
196        &fn_name.value().ident,
197    );
198
199    path.push_value(PathSegment {
200        ident: extern_fn_mod_name(&trait_name.value().ident),
201        arguments: PathArguments::None,
202    });
203    quote! { unsafe { #path :: #extern_fn_name( #args ) } }.into()
204}