use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
Attribute, Ident, Token,
parse::{Parse, ParseStream},
parse_quote,
};
use crate::common::*;
use crate::generated::{
canonical_token_to_tier_suffix, tier_to_canonical_token, token_to_arch, token_to_features,
token_to_magetypes_namespace,
};
use crate::token_discovery::*;
#[derive(Default)]
pub(crate) struct RiteArgs {
pub(crate) shared: SharedOptions,
tier_tokens: Vec<String>,
}
pub(crate) const DEFAULT_TIER_SENTINEL: &str = "";
impl Parse for RiteArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut args = RiteArgs::default();
let mut additions: Vec<String> = Vec::new();
let mut removals: Vec<String> = Vec::new();
let resolve_tier = |name: &str| -> Option<String> {
if name == "default" || name == "_default" {
Some(String::from(DEFAULT_TIER_SENTINEL))
} else {
tier_to_canonical_token(name).map(String::from)
}
};
while !input.is_empty() {
if input.peek(Token![+]) || input.peek(Token![-]) {
let is_removal = input.peek(Token![-]);
if is_removal {
let _: Token![-] = input.parse()?;
} else {
let _: Token![+] = input.parse()?;
}
let ident: Ident = input.parse()?;
let name = ident.to_string();
match resolve_tier(&name) {
Some(entry) => {
if is_removal {
removals.push(entry);
} else {
additions.push(entry);
}
}
None => {
return Err(syn::Error::new(
ident.span(),
format!(
"`{name}` after `{}` is not a tier name. \
`+`/`-` modifiers apply only to tiers \
(v1, v2, v3, v4, neon, arm_v2, wasm128, scalar, default, ...).",
if is_removal { "-" } else { "+" }
),
));
}
}
} else {
let ident: Ident = input.parse()?;
if parse_shared_option(&ident, input, &mut args.shared)? {
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
continue;
}
match ident.to_string().as_str() {
"in_trait" | "nested" => {
return Err(syn::Error::new(
ident.span(),
"`#[rite]` has no trait-method mode: it applies `#[target_feature]` \
directly, which rustc rejects on a safe trait method on x86-64 \
and AArch64 (wasm32's simd128 is the exception). Use \
`#[arcane(in_trait, _self = Type)]` on the trait method, or have \
it call a `#[rite]` free function.",
));
}
"in_impl" => {
return Err(syn::Error::new(
ident.span(),
"`in_impl` is an `#[arcane]` option. `#[rite]` has no wrapper, \
so an associated function in an inherent impl needs no flag.",
));
}
"default" | "_default" => {
additions.push(String::from(DEFAULT_TIER_SENTINEL));
}
other => {
if let Some(canonical) = tier_to_canonical_token(other) {
additions.push(String::from(canonical));
} else {
return Err(syn::Error::new(
ident.span(),
format!(
"unknown rite argument: `{}`. Supported: tier names \
(v1, v2, v3, v4, neon, arm_v2, wasm128, scalar, default, ...), \
optional `+`/`-` tier modifiers, \
`import_intrinsics`, `import_magetypes`, `cfg(feature)`.",
other
),
));
}
}
}
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
}
for entry in additions {
if removals.contains(&entry) {
continue;
}
if !args.tier_tokens.contains(&entry) {
args.tier_tokens.push(entry);
}
}
Ok(args)
}
}
pub(crate) fn rite_impl(input_fn: LightFn, args: RiteArgs) -> TokenStream {
if args.tier_tokens.len() > 1 {
return rite_multi_tier_impl(input_fn, &args);
}
rite_single_impl(input_fn, args)
}
struct RiteTier {
token: Option<String>,
suffix: Option<&'static str>,
features: std::borrow::Cow<'static, [&'static str]>,
target_arch: Option<&'static str>,
magetypes_namespace: Option<&'static str>,
token_ident: Option<Ident>,
tier_traits: Vec<String>,
}
impl RiteTier {
fn from_tier_token(tier_token: &str) -> Option<Self> {
if tier_token == DEFAULT_TIER_SENTINEL {
return Some(RiteTier {
token: None,
suffix: Some("default"),
features: std::borrow::Cow::Borrowed(&[]),
target_arch: None,
magetypes_namespace: None,
token_ident: None,
tier_traits: Vec::new(),
});
}
Some(RiteTier {
token: Some(tier_token.to_string()),
suffix: canonical_token_to_tier_suffix(tier_token),
features: std::borrow::Cow::Borrowed(token_to_features(tier_token)?),
target_arch: token_to_arch(tier_token),
magetypes_namespace: token_to_magetypes_namespace(tier_token),
token_ident: None,
tier_traits: Vec::new(),
})
}
fn from_token_param(info: TokenParamInfo) -> Self {
RiteTier {
suffix: info
.token_type_name
.as_deref()
.and_then(canonical_token_to_tier_suffix),
token: info.token_type_name,
features: info.features,
target_arch: info.target_arch,
magetypes_namespace: info.magetypes_namespace,
token_ident: Some(info.ident),
tier_traits: info.tier_traits,
}
}
}
pub(crate) fn rite_single_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
let tier = if let Some(tier_token) = args.tier_tokens.first() {
RiteTier::from_tier_token(tier_token)
.expect("tier_to_canonical_token returned invalid token name")
} else {
rename_wildcard_token(&mut input_fn.sig);
match find_token_param(&input_fn.sig) {
Some(info) => RiteTier::from_token_param(info),
None => {
return missing_token_error(
&input_fn.sig,
"rite",
" or a tier name. Supported forms:\n\
- Tier name: `#[rite(v3)]`, `#[rite(neon)]`\n\
- Multi-tier: `#[rite(v3, v4, neon)]` (generates suffixed variants)\n\
- Concrete: `token: X64V3Token`\n\
- impl Trait: `token: impl HasX64V2`\n\
- Generic: `fn foo<T: HasX64V2>(token: T, ...)`",
);
}
}
};
if tier.token_ident.is_some() {
let token_params = token_param_idents(&input_fn.sig);
if token_params.len() > 1 {
return multiple_tokens_error(&input_fn.sig, "rite", &token_params);
}
}
match emit_rite_variant(input_fn, &args, tier) {
Ok(tokens) => tokens,
Err(err) => err,
}
}
pub(crate) fn rite_multi_tier_impl(input_fn: LightFn, args: &RiteArgs) -> TokenStream {
let fn_name = &input_fn.sig.ident;
let mut variants = proc_macro2::TokenStream::new();
for tier_token in &args.tier_tokens {
let Some(tier) = RiteTier::from_tier_token(tier_token) else {
return syn::Error::new_spanned(
&input_fn.sig,
format!("unknown token `{tier_token}` in multi-tier #[rite]"),
)
.to_compile_error();
};
let suffix = tier.suffix.expect("every attribute tier has a suffix");
let mut variant_fn = input_fn.clone();
variant_fn.sig.ident = format_ident!("{}_{}", fn_name, suffix);
let tier = match crate::token_discovery::find_token_param(&variant_fn.sig) {
Some(info) => RiteTier {
token_ident: Some(info.ident),
..tier
},
None => tier,
};
match emit_rite_variant(variant_fn, args, tier) {
Ok(tokens) => variants.extend(tokens),
Err(err) => return err,
}
}
variants
}
fn emit_rite_variant(
mut variant_fn: LightFn,
args: &RiteArgs,
tier: RiteTier,
) -> Result<TokenStream, TokenStream> {
let token_desc = tier
.token
.clone()
.unwrap_or_else(|| "an AVX-512 token".to_string());
if let Some(err) = avx512_import_error(
&variant_fn.sig,
args.shared.import_intrinsics,
&tier.features,
&token_desc,
) {
return Err(err);
}
if let Some(tier_suffix) = tier.suffix
&& let Some(resolved) = crate::tiers::find_tier(tier_suffix)
{
let ctx = match &tier.token_ident {
Some(ident) => crate::rewrite::CallerContext {
tier_suffix: tier_suffix.to_string(),
target_arch: resolved.target_arch,
token_ident: ident.clone(),
has_token: true,
derive_token: false,
},
None => crate::rewrite::CallerContext {
tier_suffix: tier_suffix.to_string(),
target_arch: resolved.target_arch,
token_ident: quote::format_ident!("_"),
has_token: false,
derive_token: true,
},
};
variant_fn.body = crate::rewrite::rewrite_incant_in_body(variant_fn.body, &ctx);
}
let mut new_attrs: Vec<Attribute> = Vec::new();
if !tier.features.is_empty() {
let features_csv =
crate::token_discovery::features_csv(tier.token.as_deref(), &tier.features);
new_attrs.push(parse_quote!(#[target_feature(enable = #features_csv)]));
}
new_attrs.push(parse_quote!(#[inline]));
for attr in filter_inline_attrs(&variant_fn.attrs) {
new_attrs.push(attr.clone());
}
variant_fn.attrs = new_attrs;
let body_imports = generate_imports(
tier.target_arch,
tier.magetypes_namespace,
args.shared.import_intrinsics,
args.shared.import_magetypes,
);
let tier_trait_assertion = match &tier.token_ident {
Some(ident) => gen_tier_trait_assertion(&tier.tier_traits, ident),
None => quote! {},
};
prepend_to_body(
&mut variant_fn.body,
quote! { #body_imports #tier_trait_assertion },
);
let cfg_guard = gen_cfg_guard(tier.target_arch, args.shared.cfg_feature.as_deref());
drop_attrs_equal_to(&mut variant_fn.attrs, &cfg_guard);
let vis = &variant_fn.vis;
let sig = &variant_fn.sig;
let attrs = &variant_fn.attrs;
let body = &variant_fn.body;
Ok(quote! {
#cfg_guard
#(#attrs)*
#vis #sig {
#body
}
})
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(args: &str) -> RiteArgs {
syn::parse_str::<RiteArgs>(args).expect("RiteArgs should parse")
}
#[test]
fn single_tier() {
assert_eq!(parse("v3").tier_tokens, vec!["X64V3Token"]);
}
#[test]
fn multi_tier() {
assert_eq!(
parse("v3, v4").tier_tokens,
vec!["X64V3Token", "X64V4Token"]
);
}
#[test]
fn minus_scalar_is_noop_when_absent() {
assert_eq!(parse("v3, -scalar").tier_tokens, vec!["X64V3Token"]);
}
#[test]
fn minus_removes_listed_tier() {
assert_eq!(parse("v3, scalar, -scalar").tier_tokens, vec!["X64V3Token"]);
}
#[test]
fn plus_prefix_adds() {
assert_eq!(
parse("+v3, +neon").tier_tokens,
vec!["X64V3Token", "NeonToken"]
);
}
#[test]
fn plain_and_plus_mix() {
assert_eq!(
parse("v3, +v4, -scalar").tier_tokens,
vec!["X64V3Token", "X64V4Token"]
);
}
#[test]
fn minus_cancels_plain() {
assert!(parse("v3, -v3").tier_tokens.is_empty());
}
#[test]
fn dedup() {
assert_eq!(parse("v3, v3").tier_tokens, vec!["X64V3Token"]);
}
#[test]
fn default_sentinel() {
assert_eq!(parse("default").tier_tokens, vec![DEFAULT_TIER_SENTINEL]);
assert_eq!(parse("v3, -default").tier_tokens, vec!["X64V3Token"]);
}
#[test]
fn tiers_coexist_with_keywords() {
let a = parse("v3, import_intrinsics");
assert_eq!(a.tier_tokens, vec!["X64V3Token"]);
assert!(a.shared.import_intrinsics);
}
#[test]
fn minus_before_keyword_errors() {
assert!(syn::parse_str::<RiteArgs>("+import_intrinsics").is_err());
assert!(syn::parse_str::<RiteArgs>("-cfg").is_err());
}
#[test]
fn underscore_prefix_accepted() {
assert_eq!(parse("_v3, -_scalar").tier_tokens, vec!["X64V3Token"]);
}
}