1use proc_macro::TokenStream;
7use quote::{ToTokens, format_ident, quote};
8use syn::{
9 Attribute, FnArg, GenericParam, Ident, PatType, Signature, Token, Type, TypeParamBound,
10 parse::{Parse, ParseStream},
11 parse_macro_input, parse_quote, token,
12};
13
14#[derive(Clone)]
20struct LightFn {
21 attrs: Vec<Attribute>,
22 vis: syn::Visibility,
23 sig: Signature,
24 brace_token: token::Brace,
25 body: proc_macro2::TokenStream,
26}
27
28impl Parse for LightFn {
29 fn parse(input: ParseStream) -> syn::Result<Self> {
30 let attrs = input.call(Attribute::parse_outer)?;
31 let vis: syn::Visibility = input.parse()?;
32 let sig: Signature = input.parse()?;
33 let content;
34 let brace_token = syn::braced!(content in input);
35 let body: proc_macro2::TokenStream = content.parse()?;
36 Ok(LightFn {
37 attrs,
38 vis,
39 sig,
40 brace_token,
41 body,
42 })
43 }
44}
45
46impl ToTokens for LightFn {
47 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
48 for attr in &self.attrs {
49 attr.to_tokens(tokens);
50 }
51 self.vis.to_tokens(tokens);
52 self.sig.to_tokens(tokens);
53 self.brace_token.surround(tokens, |tokens| {
54 self.body.to_tokens(tokens);
55 });
56 }
57}
58
59fn filter_inline_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
64 attrs
65 .iter()
66 .filter(|attr| !attr.path().is_ident("inline"))
67 .collect()
68}
69
70fn build_turbofish(generics: &syn::Generics) -> proc_macro2::TokenStream {
79 let params: Vec<proc_macro2::TokenStream> = generics
80 .params
81 .iter()
82 .filter_map(|param| match param {
83 GenericParam::Type(tp) => {
84 let ident = &tp.ident;
85 Some(quote! { #ident })
86 }
87 GenericParam::Const(cp) => {
88 let ident = &cp.ident;
89 Some(quote! { #ident })
90 }
91 GenericParam::Lifetime(_) => None,
92 })
93 .collect();
94 if params.is_empty() {
95 quote! {}
96 } else {
97 quote! { ::<#(#params),*> }
98 }
99}
100
101fn replace_self_in_tokens(
106 tokens: proc_macro2::TokenStream,
107 replacement: &Type,
108) -> proc_macro2::TokenStream {
109 let mut result = proc_macro2::TokenStream::new();
110 for tt in tokens {
111 match tt {
112 proc_macro2::TokenTree::Ident(ref ident) if ident == "Self" => {
113 result.extend(replacement.to_token_stream());
114 }
115 proc_macro2::TokenTree::Group(group) => {
116 let new_stream = replace_self_in_tokens(group.stream(), replacement);
117 let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
118 new_group.set_span(group.span());
119 result.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
120 }
121 other => {
122 result.extend(std::iter::once(other));
123 }
124 }
125 }
126 result
127}
128
129#[derive(Default)]
131struct ArcaneArgs {
132 inline_always: bool,
135 self_type: Option<Type>,
139 stub: bool,
142 nested: bool,
146 import_intrinsics: bool,
148 import_magetypes: bool,
151}
152
153impl Parse for ArcaneArgs {
154 fn parse(input: ParseStream) -> syn::Result<Self> {
155 let mut args = ArcaneArgs::default();
156
157 while !input.is_empty() {
158 let ident: Ident = input.parse()?;
159 match ident.to_string().as_str() {
160 "inline_always" => args.inline_always = true,
161 "stub" => args.stub = true,
162 "nested" => args.nested = true,
163 "import_intrinsics" => args.import_intrinsics = true,
164 "import_magetypes" => args.import_magetypes = true,
165 "_self" => {
166 let _: Token![=] = input.parse()?;
167 args.self_type = Some(input.parse()?);
168 }
169 other => {
170 return Err(syn::Error::new(
171 ident.span(),
172 format!("unknown arcane argument: `{}`", other),
173 ));
174 }
175 }
176 if input.peek(Token![,]) {
178 let _: Token![,] = input.parse()?;
179 }
180 }
181
182 if args.self_type.is_some() {
184 args.nested = true;
185 }
186
187 Ok(args)
188 }
189}
190
191mod generated;
194use generated::{
195 canonical_token_to_tier_suffix, tier_to_canonical_token, token_to_arch, token_to_features,
196 token_to_magetypes_namespace, trait_to_arch, trait_to_features, trait_to_magetypes_namespace,
197};
198
199enum TokenTypeInfo {
201 Concrete(String),
203 ImplTrait(Vec<String>),
205 Generic(String),
207}
208
209fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
211 match ty {
212 Type::Path(type_path) => {
213 type_path.path.segments.last().map(|seg| {
215 let name = seg.ident.to_string();
216 if token_to_features(&name).is_some() {
218 TokenTypeInfo::Concrete(name)
219 } else {
220 TokenTypeInfo::Generic(name)
222 }
223 })
224 }
225 Type::Reference(type_ref) => {
226 extract_token_type_info(&type_ref.elem)
228 }
229 Type::ImplTrait(impl_trait) => {
230 let traits: Vec<String> = extract_trait_names_from_bounds(&impl_trait.bounds);
232 if traits.is_empty() {
233 None
234 } else {
235 Some(TokenTypeInfo::ImplTrait(traits))
236 }
237 }
238 _ => None,
239 }
240}
241
242fn extract_trait_names_from_bounds(
244 bounds: &syn::punctuated::Punctuated<TypeParamBound, Token![+]>,
245) -> Vec<String> {
246 bounds
247 .iter()
248 .filter_map(|bound| {
249 if let TypeParamBound::Trait(trait_bound) = bound {
250 trait_bound
251 .path
252 .segments
253 .last()
254 .map(|seg| seg.ident.to_string())
255 } else {
256 None
257 }
258 })
259 .collect()
260}
261
262fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
264 for param in &sig.generics.params {
266 if let GenericParam::Type(type_param) = param
267 && type_param.ident == type_name
268 {
269 let traits = extract_trait_names_from_bounds(&type_param.bounds);
270 if !traits.is_empty() {
271 return Some(traits);
272 }
273 }
274 }
275
276 if let Some(where_clause) = &sig.generics.where_clause {
278 for predicate in &where_clause.predicates {
279 if let syn::WherePredicate::Type(pred_type) = predicate
280 && let Type::Path(type_path) = &pred_type.bounded_ty
281 && let Some(seg) = type_path.path.segments.last()
282 && seg.ident == type_name
283 {
284 let traits = extract_trait_names_from_bounds(&pred_type.bounds);
285 if !traits.is_empty() {
286 return Some(traits);
287 }
288 }
289 }
290 }
291
292 None
293}
294
295fn traits_to_features(trait_names: &[String]) -> Option<Vec<&'static str>> {
297 let mut all_features = Vec::new();
298
299 for trait_name in trait_names {
300 if let Some(features) = trait_to_features(trait_name) {
301 for &feature in features {
302 if !all_features.contains(&feature) {
303 all_features.push(feature);
304 }
305 }
306 }
307 }
308
309 if all_features.is_empty() {
310 None
311 } else {
312 Some(all_features)
313 }
314}
315
316const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
320
321fn find_featureless_trait(trait_names: &[String]) -> Option<&'static str> {
324 for name in trait_names {
325 for &featureless in FEATURELESS_TRAIT_NAMES {
326 if name == featureless {
327 return Some(featureless);
328 }
329 }
330 }
331 None
332}
333
334fn diagnose_featureless_token(sig: &Signature) -> Option<&'static str> {
337 for arg in &sig.inputs {
338 if let FnArg::Typed(PatType { ty, .. }) = arg
339 && let Some(info) = extract_token_type_info(ty)
340 {
341 match &info {
342 TokenTypeInfo::ImplTrait(names) => {
343 if let Some(name) = find_featureless_trait(names) {
344 return Some(name);
345 }
346 }
347 TokenTypeInfo::Generic(type_name) => {
348 let as_vec = vec![type_name.clone()];
351 if let Some(name) = find_featureless_trait(&as_vec) {
352 return Some(name);
353 }
354 if let Some(bounds) = find_generic_bounds(sig, type_name)
356 && let Some(name) = find_featureless_trait(&bounds)
357 {
358 return Some(name);
359 }
360 }
361 TokenTypeInfo::Concrete(_) => {}
362 }
363 }
364 }
365 None
366}
367
368struct TokenParamInfo {
370 ident: Ident,
372 features: Vec<&'static str>,
374 target_arch: Option<&'static str>,
376 token_type_name: Option<String>,
378 magetypes_namespace: Option<&'static str>,
380}
381
382fn traits_to_magetypes_namespace(trait_names: &[String]) -> Option<&'static str> {
385 for name in trait_names {
386 if let Some(ns) = trait_to_magetypes_namespace(name) {
387 return Some(ns);
388 }
389 }
390 None
391}
392
393fn traits_to_arch(trait_names: &[String]) -> Option<&'static str> {
395 for name in trait_names {
396 if let Some(arch) = trait_to_arch(name) {
397 return Some(arch);
398 }
399 }
400 None
401}
402
403fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
405 for arg in &sig.inputs {
406 match arg {
407 FnArg::Receiver(_) => {
408 continue;
414 }
415 FnArg::Typed(PatType { pat, ty, .. }) => {
416 if let Some(info) = extract_token_type_info(ty) {
417 let (features, arch, token_name, mage_ns) = match info {
418 TokenTypeInfo::Concrete(ref name) => {
419 let features = token_to_features(name).map(|f| f.to_vec());
420 let arch = token_to_arch(name);
421 let ns = token_to_magetypes_namespace(name);
422 (features, arch, Some(name.clone()), ns)
423 }
424 TokenTypeInfo::ImplTrait(ref trait_names) => {
425 let ns = traits_to_magetypes_namespace(trait_names);
426 let arch = traits_to_arch(trait_names);
427 (traits_to_features(trait_names), arch, None, ns)
428 }
429 TokenTypeInfo::Generic(type_name) => {
430 let bounds = find_generic_bounds(sig, &type_name);
432 let features = bounds.as_ref().and_then(|t| traits_to_features(t));
433 let ns = bounds
434 .as_ref()
435 .and_then(|t| traits_to_magetypes_namespace(t));
436 let arch = bounds.as_ref().and_then(|t| traits_to_arch(t));
437 (features, arch, None, ns)
438 }
439 };
440
441 if let Some(features) = features {
442 let ident = match pat.as_ref() {
444 syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
445 syn::Pat::Wild(w) => {
446 Some(Ident::new("__archmage_token", w.underscore_token.span))
447 }
448 _ => None,
449 };
450 if let Some(ident) = ident {
451 return Some(TokenParamInfo {
452 ident,
453 features,
454 target_arch: arch,
455 token_type_name: token_name,
456 magetypes_namespace: mage_ns,
457 });
458 }
459 }
460 }
461 }
462 }
463 }
464 None
465}
466
467enum SelfReceiver {
469 Owned,
471 Ref,
473 RefMut,
475}
476
477fn generate_imports(
482 target_arch: Option<&str>,
483 magetypes_namespace: Option<&str>,
484 import_intrinsics: bool,
485 import_magetypes: bool,
486) -> proc_macro2::TokenStream {
487 let mut imports = proc_macro2::TokenStream::new();
488
489 if import_intrinsics && let Some(arch) = target_arch {
490 let arch_ident = format_ident!("{}", arch);
491 imports.extend(quote! {
492 #[allow(unused_imports)]
493 use archmage::intrinsics::#arch_ident::*;
494 });
495 }
497
498 if import_magetypes && let Some(ns) = magetypes_namespace {
499 let ns_ident = format_ident!("{}", ns);
500 imports.extend(quote! {
501 #[allow(unused_imports)]
502 use magetypes::simd::#ns_ident::*;
503 #[allow(unused_imports)]
504 use magetypes::simd::backends::*;
505 });
506 }
507
508 imports
509}
510
511fn arcane_impl(mut input_fn: LightFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
513 let has_self_receiver = input_fn
515 .sig
516 .inputs
517 .first()
518 .map(|arg| matches!(arg, FnArg::Receiver(_)))
519 .unwrap_or(false);
520
521 if has_self_receiver && args.nested && args.self_type.is_none() {
525 let msg = format!(
526 "{} with self receiver in nested mode requires `_self = Type` argument.\n\
527 Example: #[{}(nested, _self = MyType)]\n\
528 Use `_self` (not `self`) in the function body to refer to self.\n\
529 \n\
530 Alternatively, remove `nested` to use sibling expansion (default), \
531 which handles self/Self naturally.",
532 macro_name, macro_name
533 );
534 return syn::Error::new_spanned(&input_fn.sig, msg)
535 .to_compile_error()
536 .into();
537 }
538
539 let TokenParamInfo {
541 ident: _token_ident,
542 features,
543 target_arch,
544 token_type_name,
545 magetypes_namespace,
546 } = match find_token_param(&input_fn.sig) {
547 Some(result) => result,
548 None => {
549 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
551 let msg = format!(
552 "`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
553 because it doesn't specify any CPU features.\n\
554 \n\
555 #[{macro_name}] needs concrete features to generate #[target_feature]. \
556 Use a concrete token or a feature trait:\n\
557 \n\
558 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
559 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
560 );
561 return syn::Error::new_spanned(&input_fn.sig, msg)
562 .to_compile_error()
563 .into();
564 }
565 let msg = format!(
566 "{} requires a token parameter. Supported forms:\n\
567 - Concrete: `token: X64V3Token`\n\
568 - impl Trait: `token: impl HasX64V2`\n\
569 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
570 - With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
571 macro_name, macro_name
572 );
573 return syn::Error::new_spanned(&input_fn.sig, msg)
574 .to_compile_error()
575 .into();
576 }
577 };
578
579 let body_imports = generate_imports(
581 target_arch,
582 magetypes_namespace,
583 args.import_intrinsics,
584 args.import_magetypes,
585 );
586 if !body_imports.is_empty() {
587 let original_body = &input_fn.body;
588 input_fn.body = quote! {
589 #body_imports
590 #original_body
591 };
592 }
593
594 let target_feature_attrs: Vec<Attribute> = features
596 .iter()
597 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
598 .collect();
599
600 let mut wild_rename_counter = 0u32;
602 for arg in &mut input_fn.sig.inputs {
603 if let FnArg::Typed(pat_type) = arg
604 && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
605 {
606 let ident = format_ident!("__archmage_wild_{}", wild_rename_counter);
607 wild_rename_counter += 1;
608 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
609 attrs: vec![],
610 by_ref: None,
611 mutability: None,
612 ident,
613 subpat: None,
614 });
615 }
616 }
617
618 let inline_attr: Attribute = if args.inline_always {
620 parse_quote!(#[inline(always)])
621 } else {
622 parse_quote!(#[inline])
623 };
624
625 if target_arch == Some("wasm32") {
629 return arcane_impl_wasm_safe(
630 input_fn,
631 &args,
632 token_type_name,
633 target_feature_attrs,
634 inline_attr,
635 );
636 }
637
638 if args.nested {
639 arcane_impl_nested(
640 input_fn,
641 &args,
642 target_arch,
643 token_type_name,
644 target_feature_attrs,
645 inline_attr,
646 )
647 } else {
648 arcane_impl_sibling(
649 input_fn,
650 &args,
651 target_arch,
652 token_type_name,
653 target_feature_attrs,
654 inline_attr,
655 )
656 }
657}
658
659fn arcane_impl_wasm_safe(
668 input_fn: LightFn,
669 args: &ArcaneArgs,
670 token_type_name: Option<String>,
671 target_feature_attrs: Vec<Attribute>,
672 inline_attr: Attribute,
673) -> TokenStream {
674 let vis = &input_fn.vis;
675 let sig = &input_fn.sig;
676 let fn_name = &sig.ident;
677 let attrs = &input_fn.attrs;
678
679 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
680
681 let body = if args.self_type.is_some() {
685 let original_body = &input_fn.body;
686 quote! {
687 let _self = self;
688 #original_body
689 }
690 } else {
691 input_fn.body.clone()
692 };
693
694 let mut new_attrs = target_feature_attrs;
696 new_attrs.push(inline_attr);
697 for attr in filter_inline_attrs(attrs) {
698 new_attrs.push(attr.clone());
699 }
700
701 let stub = if args.stub {
702 let stub_args: Vec<proc_macro2::TokenStream> = sig
704 .inputs
705 .iter()
706 .filter_map(|arg| match arg {
707 FnArg::Typed(pat_type) => {
708 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
709 let ident = &pat_ident.ident;
710 Some(quote!(#ident))
711 } else {
712 None
713 }
714 }
715 FnArg::Receiver(_) => None,
716 })
717 .collect();
718
719 quote! {
720 #[cfg(not(target_arch = "wasm32"))]
721 #vis #sig {
722 let _ = (#(#stub_args),*);
723 unreachable!(
724 "BUG: {}() was called but requires {} (target_arch = \"wasm32\"). \
725 {}::summon() returns None on this architecture, so this function \
726 is unreachable in safe code. If you used forge_token_dangerously(), \
727 that is the bug.",
728 stringify!(#fn_name),
729 #token_type_str,
730 #token_type_str,
731 )
732 }
733 }
734 } else {
735 quote! {}
736 };
737
738 let expanded = quote! {
739 #[cfg(target_arch = "wasm32")]
740 #(#new_attrs)*
741 #vis #sig {
742 #body
743 }
744
745 #stub
746 };
747
748 expanded.into()
749}
750
751fn arcane_impl_sibling(
774 input_fn: LightFn,
775 args: &ArcaneArgs,
776 target_arch: Option<&str>,
777 token_type_name: Option<String>,
778 target_feature_attrs: Vec<Attribute>,
779 inline_attr: Attribute,
780) -> TokenStream {
781 let vis = &input_fn.vis;
782 let sig = &input_fn.sig;
783 let fn_name = &sig.ident;
784 let generics = &sig.generics;
785 let where_clause = &generics.where_clause;
786 let inputs = &sig.inputs;
787 let output = &sig.output;
788 let body = &input_fn.body;
789 let attrs = filter_inline_attrs(&input_fn.attrs);
792
793 let sibling_name = format_ident!("__arcane_{}", fn_name);
794
795 let has_self_receiver = inputs
797 .first()
798 .map(|arg| matches!(arg, FnArg::Receiver(_)))
799 .unwrap_or(false);
800
801 let sibling_sig_inputs = inputs;
805
806 let turbofish = build_turbofish(generics);
808
809 let sibling_call = if has_self_receiver {
811 let other_args: Vec<proc_macro2::TokenStream> = inputs
813 .iter()
814 .skip(1) .filter_map(|arg| {
816 if let FnArg::Typed(pat_type) = arg
817 && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
818 {
819 let ident = &pat_ident.ident;
820 Some(quote!(#ident))
821 } else {
822 None
823 }
824 })
825 .collect();
826 quote! { self.#sibling_name #turbofish(#(#other_args),*) }
827 } else {
828 let all_args: Vec<proc_macro2::TokenStream> = inputs
830 .iter()
831 .filter_map(|arg| {
832 if let FnArg::Typed(pat_type) = arg
833 && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
834 {
835 let ident = &pat_ident.ident;
836 Some(quote!(#ident))
837 } else {
838 None
839 }
840 })
841 .collect();
842 quote! { #sibling_name #turbofish(#(#all_args),*) }
843 };
844
845 let stub_args: Vec<proc_macro2::TokenStream> = inputs
847 .iter()
848 .filter_map(|arg| match arg {
849 FnArg::Typed(pat_type) => {
850 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
851 let ident = &pat_ident.ident;
852 Some(quote!(#ident))
853 } else {
854 None
855 }
856 }
857 FnArg::Receiver(_) => None, })
859 .collect();
860
861 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
862
863 let expanded = if let Some(arch) = target_arch {
864 let sibling_fn = quote! {
868 #[cfg(target_arch = #arch)]
869 #[doc(hidden)]
870 #(#target_feature_attrs)*
871 #inline_attr
872 fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
873 #body
874 }
875 };
876
877 let wrapper_fn = quote! {
881 #[cfg(target_arch = #arch)]
882 #(#attrs)*
883 #[inline(always)]
884 #vis #sig {
885 unsafe { #sibling_call }
890 }
891 };
892
893 let stub = if args.stub {
895 quote! {
896 #[cfg(not(target_arch = #arch))]
897 #(#attrs)*
898 #vis #sig {
899 let _ = (#(#stub_args),*);
900 unreachable!(
901 "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
902 {}::summon() returns None on this architecture, so this function \
903 is unreachable in safe code. If you used forge_token_dangerously(), \
904 that is the bug.",
905 stringify!(#fn_name),
906 #token_type_str,
907 #arch,
908 #token_type_str,
909 )
910 }
911 }
912 } else {
913 quote! {}
914 };
915
916 quote! {
917 #sibling_fn
918 #wrapper_fn
919 #stub
920 }
921 } else {
922 let sibling_fn = quote! {
925 #[doc(hidden)]
926 #(#target_feature_attrs)*
927 #inline_attr
928 fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
929 #body
930 }
931 };
932
933 let wrapper_fn = quote! {
934 #(#attrs)*
935 #[inline(always)]
936 #vis #sig {
937 unsafe { #sibling_call }
939 }
940 };
941
942 quote! {
943 #sibling_fn
944 #wrapper_fn
945 }
946 };
947
948 expanded.into()
949}
950
951fn arcane_impl_nested(
957 input_fn: LightFn,
958 args: &ArcaneArgs,
959 target_arch: Option<&str>,
960 token_type_name: Option<String>,
961 target_feature_attrs: Vec<Attribute>,
962 inline_attr: Attribute,
963) -> TokenStream {
964 let vis = &input_fn.vis;
965 let sig = &input_fn.sig;
966 let fn_name = &sig.ident;
967 let generics = &sig.generics;
968 let where_clause = &generics.where_clause;
969 let inputs = &sig.inputs;
970 let output = &sig.output;
971 let body = &input_fn.body;
972 let attrs = filter_inline_attrs(&input_fn.attrs);
974
975 let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
977 FnArg::Receiver(receiver) => {
978 if receiver.reference.is_none() {
979 Some(SelfReceiver::Owned)
980 } else if receiver.mutability.is_some() {
981 Some(SelfReceiver::RefMut)
982 } else {
983 Some(SelfReceiver::Ref)
984 }
985 }
986 _ => None,
987 });
988
989 let inner_params: Vec<proc_macro2::TokenStream> = inputs
993 .iter()
994 .map(|arg| match arg {
995 FnArg::Receiver(_) => {
996 let self_ty = args.self_type.as_ref().unwrap();
998 match self_receiver_kind.as_ref().unwrap() {
999 SelfReceiver::Owned => quote!(_self: #self_ty),
1000 SelfReceiver::Ref => quote!(_self: &#self_ty),
1001 SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
1002 }
1003 }
1004 FnArg::Typed(pat_type) => {
1005 if let Some(ref self_ty) = args.self_type {
1006 replace_self_in_tokens(quote!(#pat_type), self_ty)
1007 } else {
1008 quote!(#pat_type)
1009 }
1010 }
1011 })
1012 .collect();
1013
1014 let inner_args: Vec<proc_macro2::TokenStream> = inputs
1016 .iter()
1017 .filter_map(|arg| match arg {
1018 FnArg::Typed(pat_type) => {
1019 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
1020 let ident = &pat_ident.ident;
1021 Some(quote!(#ident))
1022 } else {
1023 None
1024 }
1025 }
1026 FnArg::Receiver(_) => Some(quote!(self)), })
1028 .collect();
1029
1030 let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
1031
1032 let turbofish = build_turbofish(generics);
1034
1035 let (inner_output, inner_body, inner_where_clause): (
1037 proc_macro2::TokenStream,
1038 proc_macro2::TokenStream,
1039 proc_macro2::TokenStream,
1040 ) = if let Some(ref self_ty) = args.self_type {
1041 let transformed_output = replace_self_in_tokens(output.to_token_stream(), self_ty);
1042 let transformed_body = replace_self_in_tokens(body.clone(), self_ty);
1043 let transformed_where = where_clause
1044 .as_ref()
1045 .map(|wc| replace_self_in_tokens(wc.to_token_stream(), self_ty))
1046 .unwrap_or_default();
1047 (transformed_output, transformed_body, transformed_where)
1048 } else {
1049 (
1050 output.to_token_stream(),
1051 body.clone(),
1052 where_clause
1053 .as_ref()
1054 .map(|wc| wc.to_token_stream())
1055 .unwrap_or_default(),
1056 )
1057 };
1058
1059 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
1060 let expanded = if let Some(arch) = target_arch {
1061 let stub = if args.stub {
1062 quote! {
1063 #[cfg(not(target_arch = #arch))]
1065 #(#attrs)*
1066 #vis #sig {
1067 let _ = (#(#inner_args),*);
1068 unreachable!(
1069 "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
1070 {}::summon() returns None on this architecture, so this function \
1071 is unreachable in safe code. If you used forge_token_dangerously(), \
1072 that is the bug.",
1073 stringify!(#fn_name),
1074 #token_type_str,
1075 #arch,
1076 #token_type_str,
1077 )
1078 }
1079 }
1080 } else {
1081 quote! {}
1082 };
1083
1084 quote! {
1085 #[cfg(target_arch = #arch)]
1087 #(#attrs)*
1088 #[inline(always)]
1089 #vis #sig {
1090 #(#target_feature_attrs)*
1091 #inline_attr
1092 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1093 #inner_body
1094 }
1095
1096 unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1098 }
1099
1100 #stub
1101 }
1102 } else {
1103 quote! {
1105 #(#attrs)*
1106 #[inline(always)]
1107 #vis #sig {
1108 #(#target_feature_attrs)*
1109 #inline_attr
1110 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1111 #inner_body
1112 }
1113
1114 unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1116 }
1117 }
1118 };
1119
1120 expanded.into()
1121}
1122
1123#[proc_macro_attribute]
1283pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
1284 let args = parse_macro_input!(attr as ArcaneArgs);
1285 let input_fn = parse_macro_input!(item as LightFn);
1286 arcane_impl(input_fn, "arcane", args)
1287}
1288
1289#[proc_macro_attribute]
1293#[doc(hidden)]
1294pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
1295 let args = parse_macro_input!(attr as ArcaneArgs);
1296 let input_fn = parse_macro_input!(item as LightFn);
1297 arcane_impl(input_fn, "simd_fn", args)
1298}
1299
1300#[proc_macro_attribute]
1313pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
1314 let args = parse_macro_input!(attr as ArcaneArgs);
1315 let input_fn = parse_macro_input!(item as LightFn);
1316 arcane_impl(input_fn, "token_target_features_boundary", args)
1317}
1318
1319#[proc_macro_attribute]
1398pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
1399 let args = parse_macro_input!(attr as RiteArgs);
1400 let input_fn = parse_macro_input!(item as LightFn);
1401 rite_impl(input_fn, args)
1402}
1403
1404#[proc_macro_attribute]
1415pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
1416 let args = parse_macro_input!(attr as RiteArgs);
1417 let input_fn = parse_macro_input!(item as LightFn);
1418 rite_impl(input_fn, args)
1419}
1420
1421#[derive(Default)]
1423struct RiteArgs {
1424 stub: bool,
1427 import_intrinsics: bool,
1429 import_magetypes: bool,
1432 tier_tokens: Vec<String>,
1437}
1438
1439impl Parse for RiteArgs {
1440 fn parse(input: ParseStream) -> syn::Result<Self> {
1441 let mut args = RiteArgs::default();
1442
1443 while !input.is_empty() {
1444 let ident: Ident = input.parse()?;
1445 match ident.to_string().as_str() {
1446 "stub" => args.stub = true,
1447 "import_intrinsics" => args.import_intrinsics = true,
1448 "import_magetypes" => args.import_magetypes = true,
1449 other => {
1450 if let Some(canonical) = tier_to_canonical_token(other) {
1451 args.tier_tokens.push(String::from(canonical));
1452 } else {
1453 return Err(syn::Error::new(
1454 ident.span(),
1455 format!(
1456 "unknown rite argument: `{}`. Supported: tier names \
1457 (v1, v2, v3, v4, neon, arm_v2, wasm128, ...), \
1458 `stub`, `import_intrinsics`, `import_magetypes`.",
1459 other
1460 ),
1461 ));
1462 }
1463 }
1464 }
1465 if input.peek(Token![,]) {
1466 let _: Token![,] = input.parse()?;
1467 }
1468 }
1469
1470 Ok(args)
1471 }
1472}
1473
1474fn rite_impl(input_fn: LightFn, args: RiteArgs) -> TokenStream {
1476 if args.tier_tokens.len() > 1 {
1478 return rite_multi_tier_impl(input_fn, &args);
1479 }
1480
1481 rite_single_impl(input_fn, args)
1483}
1484
1485fn rite_single_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
1487 let TokenParamInfo {
1489 features,
1490 target_arch,
1491 magetypes_namespace,
1492 ..
1493 } = if let Some(tier_token) = args.tier_tokens.first() {
1494 let features = token_to_features(tier_token)
1496 .expect("tier_to_canonical_token returned invalid token name")
1497 .to_vec();
1498 let target_arch = token_to_arch(tier_token);
1499 let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1500 TokenParamInfo {
1501 ident: Ident::new("_", proc_macro2::Span::call_site()),
1502 features,
1503 target_arch,
1504 token_type_name: Some(tier_token.clone()),
1505 magetypes_namespace,
1506 }
1507 } else {
1508 match find_token_param(&input_fn.sig) {
1509 Some(result) => result,
1510 None => {
1511 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
1513 let msg = format!(
1514 "`{trait_name}` cannot be used as a token bound in #[rite] \
1515 because it doesn't specify any CPU features.\n\
1516 \n\
1517 #[rite] needs concrete features to generate #[target_feature]. \
1518 Use a concrete token, a feature trait, or a tier name:\n\
1519 \n\
1520 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
1521 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ...\n\
1522 Tier names: #[rite(v3)], #[rite(neon)], #[rite(v4)], ..."
1523 );
1524 return syn::Error::new_spanned(&input_fn.sig, msg)
1525 .to_compile_error()
1526 .into();
1527 }
1528 let msg = "rite requires a token parameter or a tier name. Supported forms:\n\
1529 - Tier name: `#[rite(v3)]`, `#[rite(neon)]`\n\
1530 - Multi-tier: `#[rite(v3, v4, neon)]` (generates suffixed variants)\n\
1531 - Concrete: `token: X64V3Token`\n\
1532 - impl Trait: `token: impl HasX64V2`\n\
1533 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`";
1534 return syn::Error::new_spanned(&input_fn.sig, msg)
1535 .to_compile_error()
1536 .into();
1537 }
1538 }
1539 };
1540
1541 let target_feature_attrs: Vec<Attribute> = features
1543 .iter()
1544 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1545 .collect();
1546
1547 let inline_attr: Attribute = parse_quote!(#[inline]);
1549
1550 let mut new_attrs = target_feature_attrs;
1552 new_attrs.push(inline_attr);
1553 for attr in filter_inline_attrs(&input_fn.attrs) {
1554 new_attrs.push(attr.clone());
1555 }
1556 input_fn.attrs = new_attrs;
1557
1558 let body_imports = generate_imports(
1560 target_arch,
1561 magetypes_namespace,
1562 args.import_intrinsics,
1563 args.import_magetypes,
1564 );
1565 if !body_imports.is_empty() {
1566 let original_body = &input_fn.body;
1567 input_fn.body = quote! {
1568 #body_imports
1569 #original_body
1570 };
1571 }
1572
1573 if let Some(arch) = target_arch {
1575 let vis = &input_fn.vis;
1576 let sig = &input_fn.sig;
1577 let attrs = &input_fn.attrs;
1578 let body = &input_fn.body;
1579
1580 let stub = if args.stub {
1581 quote! {
1582 #[cfg(not(target_arch = #arch))]
1583 #vis #sig {
1584 unreachable!(concat!(
1585 "This function requires ",
1586 #arch,
1587 " architecture"
1588 ))
1589 }
1590 }
1591 } else {
1592 quote! {}
1593 };
1594
1595 quote! {
1596 #[cfg(target_arch = #arch)]
1597 #(#attrs)*
1598 #vis #sig {
1599 #body
1600 }
1601
1602 #stub
1603 }
1604 .into()
1605 } else {
1606 quote!(#input_fn).into()
1608 }
1609}
1610
1611fn rite_multi_tier_impl(input_fn: LightFn, args: &RiteArgs) -> TokenStream {
1620 let fn_name = &input_fn.sig.ident;
1621 let mut variants = proc_macro2::TokenStream::new();
1622
1623 for tier_token in &args.tier_tokens {
1624 let features = match token_to_features(tier_token) {
1625 Some(f) => f,
1626 None => {
1627 return syn::Error::new_spanned(
1628 &input_fn.sig,
1629 format!("unknown token `{tier_token}` in multi-tier #[rite]"),
1630 )
1631 .to_compile_error()
1632 .into();
1633 }
1634 };
1635 let target_arch = token_to_arch(tier_token);
1636 let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1637 let suffix = canonical_token_to_tier_suffix(tier_token)
1638 .expect("canonical token must have a tier suffix");
1639
1640 let suffixed_ident = format_ident!("{}_{}", fn_name, suffix);
1642
1643 let mut variant_fn = input_fn.clone();
1645 variant_fn.sig.ident = suffixed_ident;
1646
1647 let target_feature_attrs: Vec<Attribute> = features
1649 .iter()
1650 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1651 .collect();
1652 let inline_attr: Attribute = parse_quote!(#[inline]);
1653
1654 let mut new_attrs = target_feature_attrs;
1655 new_attrs.push(inline_attr);
1656 for attr in filter_inline_attrs(&variant_fn.attrs) {
1657 new_attrs.push(attr.clone());
1658 }
1659 variant_fn.attrs = new_attrs;
1660
1661 let body_imports = generate_imports(
1663 target_arch,
1664 magetypes_namespace,
1665 args.import_intrinsics,
1666 args.import_magetypes,
1667 );
1668 if !body_imports.is_empty() {
1669 let original_body = &variant_fn.body;
1670 variant_fn.body = quote! {
1671 #body_imports
1672 #original_body
1673 };
1674 }
1675
1676 if let Some(arch) = target_arch {
1678 let vis = &variant_fn.vis;
1679 let sig = &variant_fn.sig;
1680 let attrs = &variant_fn.attrs;
1681 let body = &variant_fn.body;
1682
1683 variants.extend(quote! {
1684 #[cfg(target_arch = #arch)]
1685 #(#attrs)*
1686 #vis #sig {
1687 #body
1688 }
1689 });
1690
1691 if args.stub {
1692 variants.extend(quote! {
1693 #[cfg(not(target_arch = #arch))]
1694 #vis #sig {
1695 unreachable!(concat!(
1696 "This function requires ",
1697 #arch,
1698 " architecture"
1699 ))
1700 }
1701 });
1702 }
1703 } else {
1704 variants.extend(quote!(#variant_fn));
1706 }
1707 }
1708
1709 variants.into()
1710}
1711
1712#[proc_macro_attribute]
1771pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1772 let input_fn = parse_macro_input!(item as LightFn);
1773
1774 let tier_names: Vec<String> = if attr.is_empty() {
1776 DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
1777 } else {
1778 let parser = |input: ParseStream| input.parse_terminated(Ident::parse, Token![,]);
1779 let idents = match syn::parse::Parser::parse(parser, attr) {
1780 Ok(p) => p,
1781 Err(e) => return e.to_compile_error().into(),
1782 };
1783 idents.iter().map(|i| i.to_string()).collect()
1784 };
1785
1786 let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
1787 Ok(t) => t,
1788 Err(e) => return e.to_compile_error().into(),
1789 };
1790
1791 magetypes_impl(input_fn, &tiers)
1792}
1793
1794fn magetypes_impl(mut input_fn: LightFn, tiers: &[&TierDescriptor]) -> TokenStream {
1795 input_fn
1798 .attrs
1799 .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
1800
1801 let fn_name = &input_fn.sig.ident;
1802 let fn_attrs = &input_fn.attrs;
1803
1804 let fn_str = input_fn.to_token_stream().to_string();
1806
1807 let mut variants = Vec::new();
1808
1809 for tier in tiers {
1810 let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1812
1813 let mut variant_str = fn_str.clone();
1815
1816 variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1818
1819 variant_str = variant_str.replace("Token", tier.token_path);
1821
1822 let variant_tokens: proc_macro2::TokenStream = match variant_str.parse() {
1824 Ok(t) => t,
1825 Err(e) => {
1826 return syn::Error::new_spanned(
1827 &input_fn,
1828 format!(
1829 "Failed to parse generated variant `{}`: {}",
1830 suffixed_name, e
1831 ),
1832 )
1833 .to_compile_error()
1834 .into();
1835 }
1836 };
1837
1838 let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
1840 (Some(arch), Some(feature)) => {
1841 quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
1842 }
1843 (Some(arch), None) => {
1844 quote! { #[cfg(target_arch = #arch)] }
1845 }
1846 (None, Some(feature)) => {
1847 quote! { #[cfg(feature = #feature)] }
1848 }
1849 (None, None) => {
1850 quote! {} }
1852 };
1853
1854 variants.push(if tier.name != "scalar" {
1855 quote! {
1857 #cfg_guard
1858 #[archmage::arcane]
1859 #variant_tokens
1860 }
1861 } else {
1862 quote! {
1863 #cfg_guard
1864 #variant_tokens
1865 }
1866 });
1867 }
1868
1869 let filtered_attrs: Vec<_> = fn_attrs
1871 .iter()
1872 .filter(|a| !a.path().is_ident("magetypes"))
1873 .collect();
1874
1875 let output = quote! {
1876 #(#filtered_attrs)*
1877 #(#variants)*
1878 };
1879
1880 output.into()
1881}
1882
1883struct TierDescriptor {
1893 name: &'static str,
1895 suffix: &'static str,
1897 token_path: &'static str,
1899 as_method: &'static str,
1901 target_arch: Option<&'static str>,
1903 cargo_feature: Option<&'static str>,
1905 priority: u32,
1907}
1908
1909const ALL_TIERS: &[TierDescriptor] = &[
1911 TierDescriptor {
1913 name: "v4x",
1914 suffix: "v4x",
1915 token_path: "archmage::X64V4xToken",
1916 as_method: "as_x64v4x",
1917 target_arch: Some("x86_64"),
1918 cargo_feature: Some("avx512"),
1919 priority: 50,
1920 },
1921 TierDescriptor {
1922 name: "v4",
1923 suffix: "v4",
1924 token_path: "archmage::X64V4Token",
1925 as_method: "as_x64v4",
1926 target_arch: Some("x86_64"),
1927 cargo_feature: Some("avx512"),
1928 priority: 40,
1929 },
1930 TierDescriptor {
1931 name: "v3_crypto",
1932 suffix: "v3_crypto",
1933 token_path: "archmage::X64V3CryptoToken",
1934 as_method: "as_x64v3_crypto",
1935 target_arch: Some("x86_64"),
1936 cargo_feature: None,
1937 priority: 35,
1938 },
1939 TierDescriptor {
1940 name: "v3",
1941 suffix: "v3",
1942 token_path: "archmage::X64V3Token",
1943 as_method: "as_x64v3",
1944 target_arch: Some("x86_64"),
1945 cargo_feature: None,
1946 priority: 30,
1947 },
1948 TierDescriptor {
1949 name: "x64_crypto",
1950 suffix: "x64_crypto",
1951 token_path: "archmage::X64CryptoToken",
1952 as_method: "as_x64_crypto",
1953 target_arch: Some("x86_64"),
1954 cargo_feature: None,
1955 priority: 25,
1956 },
1957 TierDescriptor {
1958 name: "v2",
1959 suffix: "v2",
1960 token_path: "archmage::X64V2Token",
1961 as_method: "as_x64v2",
1962 target_arch: Some("x86_64"),
1963 cargo_feature: None,
1964 priority: 20,
1965 },
1966 TierDescriptor {
1967 name: "v1",
1968 suffix: "v1",
1969 token_path: "archmage::X64V1Token",
1970 as_method: "as_x64v1",
1971 target_arch: Some("x86_64"),
1972 cargo_feature: None,
1973 priority: 10,
1974 },
1975 TierDescriptor {
1977 name: "arm_v3",
1978 suffix: "arm_v3",
1979 token_path: "archmage::Arm64V3Token",
1980 as_method: "as_arm_v3",
1981 target_arch: Some("aarch64"),
1982 cargo_feature: None,
1983 priority: 50,
1984 },
1985 TierDescriptor {
1986 name: "arm_v2",
1987 suffix: "arm_v2",
1988 token_path: "archmage::Arm64V2Token",
1989 as_method: "as_arm_v2",
1990 target_arch: Some("aarch64"),
1991 cargo_feature: None,
1992 priority: 40,
1993 },
1994 TierDescriptor {
1995 name: "neon_aes",
1996 suffix: "neon_aes",
1997 token_path: "archmage::NeonAesToken",
1998 as_method: "as_neon_aes",
1999 target_arch: Some("aarch64"),
2000 cargo_feature: None,
2001 priority: 30,
2002 },
2003 TierDescriptor {
2004 name: "neon_sha3",
2005 suffix: "neon_sha3",
2006 token_path: "archmage::NeonSha3Token",
2007 as_method: "as_neon_sha3",
2008 target_arch: Some("aarch64"),
2009 cargo_feature: None,
2010 priority: 30,
2011 },
2012 TierDescriptor {
2013 name: "neon_crc",
2014 suffix: "neon_crc",
2015 token_path: "archmage::NeonCrcToken",
2016 as_method: "as_neon_crc",
2017 target_arch: Some("aarch64"),
2018 cargo_feature: None,
2019 priority: 30,
2020 },
2021 TierDescriptor {
2022 name: "neon",
2023 suffix: "neon",
2024 token_path: "archmage::NeonToken",
2025 as_method: "as_neon",
2026 target_arch: Some("aarch64"),
2027 cargo_feature: None,
2028 priority: 20,
2029 },
2030 TierDescriptor {
2032 name: "wasm128_relaxed",
2033 suffix: "wasm128_relaxed",
2034 token_path: "archmage::Wasm128RelaxedToken",
2035 as_method: "as_wasm128_relaxed",
2036 target_arch: Some("wasm32"),
2037 cargo_feature: None,
2038 priority: 21,
2039 },
2040 TierDescriptor {
2041 name: "wasm128",
2042 suffix: "wasm128",
2043 token_path: "archmage::Wasm128Token",
2044 as_method: "as_wasm128",
2045 target_arch: Some("wasm32"),
2046 cargo_feature: None,
2047 priority: 20,
2048 },
2049 TierDescriptor {
2051 name: "scalar",
2052 suffix: "scalar",
2053 token_path: "archmage::ScalarToken",
2054 as_method: "as_scalar",
2055 target_arch: None,
2056 cargo_feature: None,
2057 priority: 0,
2058 },
2059];
2060
2061const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
2063
2064const REQUIRE_EXPLICIT_SCALAR: bool = false;
2067
2068fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
2070 ALL_TIERS.iter().find(|t| t.name == name)
2071}
2072
2073fn resolve_tiers(
2076 tier_names: &[String],
2077 error_span: proc_macro2::Span,
2078) -> syn::Result<Vec<&'static TierDescriptor>> {
2079 let mut tiers = Vec::new();
2080 for name in tier_names {
2081 match find_tier(name) {
2082 Some(tier) => tiers.push(tier),
2083 None => {
2084 let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
2085 return Err(syn::Error::new(
2086 error_span,
2087 format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
2088 ));
2089 }
2090 }
2091 }
2092
2093 if !tiers.iter().any(|t| t.name == "scalar") {
2095 tiers.push(find_tier("scalar").unwrap());
2096 }
2097
2098 tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
2100
2101 Ok(tiers)
2102}
2103
2104struct IncantInput {
2110 func_path: syn::Path,
2112 args: Vec<syn::Expr>,
2114 with_token: Option<syn::Expr>,
2116 tiers: Option<(Vec<String>, proc_macro2::Span)>,
2118}
2119
2120fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
2123 let mut suffixed = path.clone();
2124 if let Some(last) = suffixed.segments.last_mut() {
2125 last.ident = format_ident!("{}_{}", last.ident, suffix);
2126 }
2127 suffixed
2128}
2129
2130impl Parse for IncantInput {
2131 fn parse(input: ParseStream) -> syn::Result<Self> {
2132 let func_path: syn::Path = input.parse()?;
2134
2135 let content;
2137 syn::parenthesized!(content in input);
2138 let args = content
2139 .parse_terminated(syn::Expr::parse, Token![,])?
2140 .into_iter()
2141 .collect();
2142
2143 let with_token = if input.peek(Ident) {
2145 let kw: Ident = input.parse()?;
2146 if kw != "with" {
2147 return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
2148 }
2149 Some(input.parse()?)
2150 } else {
2151 None
2152 };
2153
2154 let tiers = if input.peek(Token![,]) {
2156 let _: Token![,] = input.parse()?;
2157 let bracket_content;
2158 let bracket = syn::bracketed!(bracket_content in input);
2159 let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
2160 let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
2161 Some((tier_names, bracket.span.join()))
2162 } else {
2163 None
2164 };
2165
2166 Ok(IncantInput {
2167 func_path,
2168 args,
2169 with_token,
2170 tiers,
2171 })
2172 }
2173}
2174
2175#[proc_macro]
2247pub fn incant(input: TokenStream) -> TokenStream {
2248 let input = parse_macro_input!(input as IncantInput);
2249 incant_impl(input)
2250}
2251
2252#[proc_macro]
2254pub fn simd_route(input: TokenStream) -> TokenStream {
2255 let input = parse_macro_input!(input as IncantInput);
2256 incant_impl(input)
2257}
2258
2259#[proc_macro]
2267pub fn dispatch_variant(input: TokenStream) -> TokenStream {
2268 let input = parse_macro_input!(input as IncantInput);
2269 incant_impl(input)
2270}
2271
2272fn incant_impl(input: IncantInput) -> TokenStream {
2273 let func_path = &input.func_path;
2274 let args = &input.args;
2275
2276 let tier_names: Vec<String> = match &input.tiers {
2278 Some((names, _)) => names.clone(),
2279 None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2280 };
2281 let last_segment_span = func_path
2282 .segments
2283 .last()
2284 .map(|s| s.ident.span())
2285 .unwrap_or_else(proc_macro2::Span::call_site);
2286 let error_span = input
2287 .tiers
2288 .as_ref()
2289 .map(|(_, span)| *span)
2290 .unwrap_or(last_segment_span);
2291
2292 if REQUIRE_EXPLICIT_SCALAR
2297 && let Some((names, span)) = &input.tiers
2298 && !names.iter().any(|n| n == "scalar")
2299 {
2300 return syn::Error::new(
2301 *span,
2302 "explicit tier list must include `scalar`. \
2303 incant! always dispatches to fn_scalar() as the final fallback, \
2304 so `scalar` must appear in the tier list to acknowledge this. \
2305 Example: [v3, neon, scalar]",
2306 )
2307 .to_compile_error()
2308 .into();
2309 }
2310
2311 let tiers = match resolve_tiers(&tier_names, error_span) {
2312 Ok(t) => t,
2313 Err(e) => return e.to_compile_error().into(),
2314 };
2315
2316 if let Some(token_expr) = &input.with_token {
2319 gen_incant_passthrough(func_path, args, token_expr, &tiers)
2320 } else {
2321 gen_incant_entry(func_path, args, &tiers)
2322 }
2323}
2324
2325fn gen_incant_passthrough(
2327 func_path: &syn::Path,
2328 args: &[syn::Expr],
2329 token_expr: &syn::Expr,
2330 tiers: &[&TierDescriptor],
2331) -> TokenStream {
2332 let mut dispatch_arms = Vec::new();
2333
2334 let mut arch_groups: Vec<(Option<&str>, Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2336 for tier in tiers {
2337 if tier.name == "scalar" {
2338 continue; }
2340 let key = (tier.target_arch, tier.cargo_feature);
2341 if let Some(group) = arch_groups.iter_mut().find(|(a, f, _)| (*a, *f) == key) {
2342 group.2.push(tier);
2343 } else {
2344 arch_groups.push((tier.target_arch, tier.cargo_feature, vec![tier]));
2345 }
2346 }
2347
2348 for (target_arch, cargo_feature, group_tiers) in &arch_groups {
2349 let mut tier_checks = Vec::new();
2350 for tier in group_tiers {
2351 let fn_suffixed = suffix_path(func_path, tier.suffix);
2352 let as_method = format_ident!("{}", tier.as_method);
2353 tier_checks.push(quote! {
2354 if let Some(__t) = __incant_token.#as_method() {
2355 break '__incant #fn_suffixed(__t, #(#args),*);
2356 }
2357 });
2358 }
2359
2360 let inner = quote! { #(#tier_checks)* };
2361
2362 let guarded = match (target_arch, cargo_feature) {
2363 (Some(arch), Some(feat)) => quote! {
2364 #[cfg(target_arch = #arch)]
2365 {
2366 #[cfg(feature = #feat)]
2367 { #inner }
2368 }
2369 },
2370 (Some(arch), None) => quote! {
2371 #[cfg(target_arch = #arch)]
2372 { #inner }
2373 },
2374 (None, Some(feat)) => quote! {
2375 #[cfg(feature = #feat)]
2376 { #inner }
2377 },
2378 (None, None) => inner,
2379 };
2380
2381 dispatch_arms.push(guarded);
2382 }
2383
2384 let fn_scalar = suffix_path(func_path, "scalar");
2386 let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
2387 quote! {
2388 if let Some(__t) = __incant_token.as_scalar() {
2389 break '__incant #fn_scalar(__t, #(#args),*);
2390 }
2391 unreachable!("Token did not match any known variant")
2392 }
2393 } else {
2394 quote! { unreachable!("Token did not match any known variant") }
2395 };
2396
2397 let expanded = quote! {
2398 '__incant: {
2399 use archmage::IntoConcreteToken;
2400 let __incant_token = #token_expr;
2401 #(#dispatch_arms)*
2402 #scalar_arm
2403 }
2404 };
2405 expanded.into()
2406}
2407
2408fn gen_incant_entry(
2410 func_path: &syn::Path,
2411 args: &[syn::Expr],
2412 tiers: &[&TierDescriptor],
2413) -> TokenStream {
2414 let mut dispatch_arms = Vec::new();
2415
2416 let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2419 for tier in tiers {
2420 if tier.name == "scalar" {
2421 continue;
2422 }
2423 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2424 group.1.push(tier);
2425 } else {
2426 arch_groups.push((tier.target_arch, vec![tier]));
2427 }
2428 }
2429
2430 for (target_arch, group_tiers) in &arch_groups {
2431 let mut tier_checks = Vec::new();
2432 for tier in group_tiers {
2433 let fn_suffixed = suffix_path(func_path, tier.suffix);
2434 let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2435
2436 let check = quote! {
2437 if let Some(__t) = #token_path::summon() {
2438 break '__incant #fn_suffixed(__t, #(#args),*);
2439 }
2440 };
2441
2442 if let Some(feat) = tier.cargo_feature {
2443 tier_checks.push(quote! {
2444 #[cfg(feature = #feat)]
2445 { #check }
2446 });
2447 } else {
2448 tier_checks.push(check);
2449 }
2450 }
2451
2452 let inner = quote! { #(#tier_checks)* };
2453
2454 if let Some(arch) = target_arch {
2455 dispatch_arms.push(quote! {
2456 #[cfg(target_arch = #arch)]
2457 { #inner }
2458 });
2459 } else {
2460 dispatch_arms.push(inner);
2461 }
2462 }
2463
2464 let fn_scalar = suffix_path(func_path, "scalar");
2466
2467 let expanded = quote! {
2468 '__incant: {
2469 use archmage::SimdToken;
2470 #(#dispatch_arms)*
2471 #fn_scalar(archmage::ScalarToken, #(#args),*)
2472 }
2473 };
2474 expanded.into()
2475}
2476
2477struct AutoversionArgs {
2483 self_type: Option<Type>,
2485 tiers: Option<Vec<String>>,
2487}
2488
2489impl Parse for AutoversionArgs {
2490 fn parse(input: ParseStream) -> syn::Result<Self> {
2491 let mut self_type = None;
2492 let mut tier_names = Vec::new();
2493
2494 while !input.is_empty() {
2495 let ident: Ident = input.parse()?;
2496 if ident == "_self" {
2497 let _: Token![=] = input.parse()?;
2498 self_type = Some(input.parse()?);
2499 } else {
2500 tier_names.push(ident.to_string());
2502 }
2503 if input.peek(Token![,]) {
2504 let _: Token![,] = input.parse()?;
2505 }
2506 }
2507
2508 Ok(AutoversionArgs {
2509 self_type,
2510 tiers: if tier_names.is_empty() {
2511 None
2512 } else {
2513 Some(tier_names)
2514 },
2515 })
2516 }
2517}
2518
2519struct SimdTokenParamInfo {
2521 index: usize,
2523 #[allow(dead_code)]
2525 ident: Ident,
2526}
2527
2528fn find_simd_token_param(sig: &Signature) -> Option<SimdTokenParamInfo> {
2533 for (i, arg) in sig.inputs.iter().enumerate() {
2534 if let FnArg::Typed(PatType { pat, ty, .. }) = arg
2535 && let Type::Path(type_path) = ty.as_ref()
2536 && let Some(seg) = type_path.path.segments.last()
2537 && seg.ident == "SimdToken"
2538 {
2539 let ident = match pat.as_ref() {
2540 syn::Pat::Ident(pi) => pi.ident.clone(),
2541 syn::Pat::Wild(w) => Ident::new("__autoversion_token", w.underscore_token.span),
2542 _ => continue,
2543 };
2544 return Some(SimdTokenParamInfo { index: i, ident });
2545 }
2546 }
2547 None
2548}
2549
2550fn autoversion_impl(mut input_fn: LightFn, args: AutoversionArgs) -> TokenStream {
2555 let has_self = input_fn
2557 .sig
2558 .inputs
2559 .first()
2560 .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));
2561
2562 let token_param = match find_simd_token_param(&input_fn.sig) {
2567 Some(p) => p,
2568 None => {
2569 return syn::Error::new_spanned(
2570 &input_fn.sig,
2571 "autoversion requires a `SimdToken` parameter.\n\
2572 Example: fn process(token: SimdToken, data: &[f32]) -> f32 { ... }\n\n\
2573 SimdToken is the dispatch placeholder — autoversion replaces it \
2574 with concrete token types and generates a runtime dispatcher.",
2575 )
2576 .to_compile_error()
2577 .into();
2578 }
2579 };
2580
2581 let tier_names: Vec<String> = match &args.tiers {
2583 Some(names) => names.clone(),
2584 None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2585 };
2586 let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
2587 Ok(t) => t,
2588 Err(e) => return e.to_compile_error().into(),
2589 };
2590
2591 input_fn
2593 .attrs
2594 .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
2595
2596 let fn_name = &input_fn.sig.ident;
2597 let vis = input_fn.vis.clone();
2598
2599 let fn_attrs: Vec<Attribute> = input_fn.attrs.drain(..).collect();
2601
2602 let mut variants = Vec::new();
2612
2613 for tier in &tiers {
2614 let mut variant_fn = input_fn.clone();
2615
2616 variant_fn.vis = syn::Visibility::Inherited;
2618
2619 variant_fn.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
2621
2622 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
2624 if let FnArg::Typed(pt) = &mut variant_fn.sig.inputs[token_param.index] {
2625 *pt.ty = concrete_type;
2626 }
2627
2628 if tier.name == "scalar" && has_self && args.self_type.is_some() {
2631 let original_body = variant_fn.body.clone();
2632 variant_fn.body = quote!(let _self = self; #original_body);
2633 }
2634
2635 let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
2637 (Some(arch), Some(feature)) => {
2638 quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
2639 }
2640 (Some(arch), None) => quote! { #[cfg(target_arch = #arch)] },
2641 (None, Some(feature)) => quote! { #[cfg(feature = #feature)] },
2642 (None, None) => quote! {},
2643 };
2644
2645 if tier.name != "scalar" {
2646 let arcane_attr = if let Some(ref self_type) = args.self_type {
2648 quote! { #[archmage::arcane(_self = #self_type)] }
2649 } else {
2650 quote! { #[archmage::arcane] }
2651 };
2652 variants.push(quote! {
2653 #cfg_guard
2654 #arcane_attr
2655 #variant_fn
2656 });
2657 } else {
2658 variants.push(quote! {
2659 #cfg_guard
2660 #variant_fn
2661 });
2662 }
2663 }
2664
2665 let mut dispatcher_inputs: Vec<FnArg> = input_fn.sig.inputs.iter().cloned().collect();
2671 dispatcher_inputs.remove(token_param.index);
2672
2673 let mut wild_counter = 0u32;
2675 for arg in &mut dispatcher_inputs {
2676 if let FnArg::Typed(pat_type) = arg
2677 && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
2678 {
2679 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
2680 wild_counter += 1;
2681 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2682 attrs: vec![],
2683 by_ref: None,
2684 mutability: None,
2685 ident,
2686 subpat: None,
2687 });
2688 }
2689 }
2690
2691 let dispatch_args: Vec<Ident> = dispatcher_inputs
2693 .iter()
2694 .filter_map(|arg| {
2695 if let FnArg::Typed(PatType { pat, .. }) = arg
2696 && let syn::Pat::Ident(pi) = pat.as_ref()
2697 {
2698 return Some(pi.ident.clone());
2699 }
2700 None
2701 })
2702 .collect();
2703
2704 let turbofish = build_turbofish(&input_fn.sig.generics);
2706
2707 let mut arch_groups: Vec<(Option<&str>, Vec<&&TierDescriptor>)> = Vec::new();
2709 for tier in &tiers {
2710 if tier.name == "scalar" {
2711 continue;
2712 }
2713 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2714 group.1.push(tier);
2715 } else {
2716 arch_groups.push((tier.target_arch, vec![tier]));
2717 }
2718 }
2719
2720 let mut dispatch_arms = Vec::new();
2721 for (target_arch, group_tiers) in &arch_groups {
2722 let mut tier_checks = Vec::new();
2723 for tier in group_tiers {
2724 let suffixed = format_ident!("{}_{}", fn_name, tier.suffix);
2725 let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2726
2727 let call = if has_self {
2728 quote! { self.#suffixed #turbofish(__t, #(#dispatch_args),*) }
2729 } else {
2730 quote! { #suffixed #turbofish(__t, #(#dispatch_args),*) }
2731 };
2732
2733 let check = quote! {
2734 if let Some(__t) = #token_path::summon() {
2735 break '__dispatch #call;
2736 }
2737 };
2738
2739 if let Some(feat) = tier.cargo_feature {
2740 tier_checks.push(quote! {
2741 #[cfg(feature = #feat)]
2742 { #check }
2743 });
2744 } else {
2745 tier_checks.push(check);
2746 }
2747 }
2748
2749 let inner = quote! { #(#tier_checks)* };
2750
2751 if let Some(arch) = target_arch {
2752 dispatch_arms.push(quote! {
2753 #[cfg(target_arch = #arch)]
2754 { #inner }
2755 });
2756 } else {
2757 dispatch_arms.push(inner);
2758 }
2759 }
2760
2761 let scalar_name = format_ident!("{}_scalar", fn_name);
2763 let scalar_call = if has_self {
2764 quote! { self.#scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2765 } else {
2766 quote! { #scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2767 };
2768
2769 let dispatcher_inputs_punct: syn::punctuated::Punctuated<FnArg, Token![,]> =
2771 dispatcher_inputs.into_iter().collect();
2772 let output = &input_fn.sig.output;
2773 let generics = &input_fn.sig.generics;
2774 let where_clause = &generics.where_clause;
2775
2776 let dispatcher = quote! {
2777 #(#fn_attrs)*
2778 #vis fn #fn_name #generics (#dispatcher_inputs_punct) #output #where_clause {
2779 '__dispatch: {
2780 use archmage::SimdToken;
2781 #(#dispatch_arms)*
2782 #scalar_call
2783 }
2784 }
2785 };
2786
2787 let expanded = quote! {
2788 #dispatcher
2789 #(#variants)*
2790 };
2791
2792 expanded.into()
2793}
2794
2795#[proc_macro_attribute]
3035pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
3036 let args = parse_macro_input!(attr as AutoversionArgs);
3037 let input_fn = parse_macro_input!(item as LightFn);
3038 autoversion_impl(input_fn, args)
3039}
3040
3041#[cfg(test)]
3046mod tests {
3047 use super::*;
3048
3049 use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
3050 use syn::{ItemFn, ReturnType};
3051
3052 #[test]
3053 fn every_concrete_token_is_in_token_to_features() {
3054 for &name in ALL_CONCRETE_TOKENS {
3055 assert!(
3056 token_to_features(name).is_some(),
3057 "Token `{}` exists in runtime crate but is NOT recognized by \
3058 token_to_features() in the proc macro. Add it!",
3059 name
3060 );
3061 }
3062 }
3063
3064 #[test]
3065 fn every_trait_is_in_trait_to_features() {
3066 for &name in ALL_TRAIT_NAMES {
3067 assert!(
3068 trait_to_features(name).is_some(),
3069 "Trait `{}` exists in runtime crate but is NOT recognized by \
3070 trait_to_features() in the proc macro. Add it!",
3071 name
3072 );
3073 }
3074 }
3075
3076 #[test]
3077 fn token_aliases_map_to_same_features() {
3078 assert_eq!(
3080 token_to_features("Desktop64"),
3081 token_to_features("X64V3Token"),
3082 "Desktop64 and X64V3Token should map to identical features"
3083 );
3084
3085 assert_eq!(
3087 token_to_features("Server64"),
3088 token_to_features("X64V4Token"),
3089 "Server64 and X64V4Token should map to identical features"
3090 );
3091 assert_eq!(
3092 token_to_features("X64V4Token"),
3093 token_to_features("Avx512Token"),
3094 "X64V4Token and Avx512Token should map to identical features"
3095 );
3096
3097 assert_eq!(
3099 token_to_features("Arm64"),
3100 token_to_features("NeonToken"),
3101 "Arm64 and NeonToken should map to identical features"
3102 );
3103 }
3104
3105 #[test]
3106 fn trait_to_features_includes_tokens_as_bounds() {
3107 let tier_tokens = [
3111 "X64V2Token",
3112 "X64CryptoToken",
3113 "X64V3Token",
3114 "Desktop64",
3115 "Avx2FmaToken",
3116 "X64V4Token",
3117 "Avx512Token",
3118 "Server64",
3119 "X64V4xToken",
3120 "Avx512Fp16Token",
3121 "NeonToken",
3122 "Arm64",
3123 "NeonAesToken",
3124 "NeonSha3Token",
3125 "NeonCrcToken",
3126 "Arm64V2Token",
3127 "Arm64V3Token",
3128 ];
3129
3130 for &name in &tier_tokens {
3131 assert!(
3132 trait_to_features(name).is_some(),
3133 "Tier token `{}` should also be recognized in trait_to_features() \
3134 for use as a generic bound. Add it!",
3135 name
3136 );
3137 }
3138 }
3139
3140 #[test]
3141 fn trait_features_are_cumulative() {
3142 let v2_features = trait_to_features("HasX64V2").unwrap();
3144 let v4_features = trait_to_features("HasX64V4").unwrap();
3145
3146 for &f in v2_features {
3147 assert!(
3148 v4_features.contains(&f),
3149 "HasX64V4 should include v2 feature `{}` but doesn't",
3150 f
3151 );
3152 }
3153
3154 assert!(
3156 v4_features.len() > v2_features.len(),
3157 "HasX64V4 should have more features than HasX64V2"
3158 );
3159 }
3160
3161 #[test]
3162 fn x64v3_trait_features_include_v2() {
3163 let v2 = trait_to_features("HasX64V2").unwrap();
3165 let v3 = trait_to_features("X64V3Token").unwrap();
3166
3167 for &f in v2 {
3168 assert!(
3169 v3.contains(&f),
3170 "X64V3Token trait features should include v2 feature `{}` but don't",
3171 f
3172 );
3173 }
3174 }
3175
3176 #[test]
3177 fn has_neon_aes_includes_neon() {
3178 let neon = trait_to_features("HasNeon").unwrap();
3179 let neon_aes = trait_to_features("HasNeonAes").unwrap();
3180
3181 for &f in neon {
3182 assert!(
3183 neon_aes.contains(&f),
3184 "HasNeonAes should include NEON feature `{}`",
3185 f
3186 );
3187 }
3188 }
3189
3190 #[test]
3191 fn no_removed_traits_are_recognized() {
3192 let removed = [
3194 "HasSse",
3195 "HasSse2",
3196 "HasSse41",
3197 "HasSse42",
3198 "HasAvx",
3199 "HasAvx2",
3200 "HasFma",
3201 "HasAvx512f",
3202 "HasAvx512bw",
3203 "HasAvx512vl",
3204 "HasAvx512vbmi2",
3205 "HasSve",
3206 "HasSve2",
3207 ];
3208
3209 for &name in &removed {
3210 assert!(
3211 trait_to_features(name).is_none(),
3212 "Removed trait `{}` should NOT be in trait_to_features(). \
3213 It was removed in 0.3.0 — users should migrate to tier traits.",
3214 name
3215 );
3216 }
3217 }
3218
3219 #[test]
3220 fn no_nonexistent_tokens_are_recognized() {
3221 let fake = [
3223 "SveToken",
3224 "Sve2Token",
3225 "Avx512VnniToken",
3226 "X64V4ModernToken",
3227 "NeonFp16Token",
3228 ];
3229
3230 for &name in &fake {
3231 assert!(
3232 token_to_features(name).is_none(),
3233 "Non-existent token `{}` should NOT be in token_to_features()",
3234 name
3235 );
3236 }
3237 }
3238
3239 #[test]
3240 fn featureless_traits_are_not_in_registries() {
3241 for &name in FEATURELESS_TRAIT_NAMES {
3244 assert!(
3245 token_to_features(name).is_none(),
3246 "`{}` should NOT be in token_to_features() — it has no CPU features",
3247 name
3248 );
3249 assert!(
3250 trait_to_features(name).is_none(),
3251 "`{}` should NOT be in trait_to_features() — it has no CPU features",
3252 name
3253 );
3254 }
3255 }
3256
3257 #[test]
3258 fn find_featureless_trait_detects_simdtoken() {
3259 let names = vec!["SimdToken".to_string()];
3260 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3261
3262 let names = vec!["IntoConcreteToken".to_string()];
3263 assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
3264
3265 let names = vec!["HasX64V2".to_string()];
3267 assert_eq!(find_featureless_trait(&names), None);
3268
3269 let names = vec!["HasNeon".to_string()];
3270 assert_eq!(find_featureless_trait(&names), None);
3271
3272 let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
3274 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3275 }
3276
3277 #[test]
3278 fn arm64_v2_v3_traits_are_cumulative() {
3279 let v2_features = trait_to_features("HasArm64V2").unwrap();
3280 let v3_features = trait_to_features("HasArm64V3").unwrap();
3281
3282 for &f in v2_features {
3283 assert!(
3284 v3_features.contains(&f),
3285 "HasArm64V3 should include v2 feature `{}` but doesn't",
3286 f
3287 );
3288 }
3289
3290 assert!(
3291 v3_features.len() > v2_features.len(),
3292 "HasArm64V3 should have more features than HasArm64V2"
3293 );
3294 }
3295
3296 #[test]
3301 fn autoversion_args_empty() {
3302 let args: AutoversionArgs = syn::parse_str("").unwrap();
3303 assert!(args.self_type.is_none());
3304 assert!(args.tiers.is_none());
3305 }
3306
3307 #[test]
3308 fn autoversion_args_single_tier() {
3309 let args: AutoversionArgs = syn::parse_str("v3").unwrap();
3310 assert!(args.self_type.is_none());
3311 assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
3312 }
3313
3314 #[test]
3315 fn autoversion_args_tiers_only() {
3316 let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
3317 assert!(args.self_type.is_none());
3318 let tiers = args.tiers.unwrap();
3319 assert_eq!(tiers, vec!["v3", "v4", "neon"]);
3320 }
3321
3322 #[test]
3323 fn autoversion_args_many_tiers() {
3324 let args: AutoversionArgs =
3325 syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
3326 assert_eq!(
3327 args.tiers.unwrap(),
3328 vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
3329 );
3330 }
3331
3332 #[test]
3333 fn autoversion_args_trailing_comma() {
3334 let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
3335 assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
3336 }
3337
3338 #[test]
3339 fn autoversion_args_self_only() {
3340 let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
3341 assert!(args.self_type.is_some());
3342 assert!(args.tiers.is_none());
3343 }
3344
3345 #[test]
3346 fn autoversion_args_self_and_tiers() {
3347 let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
3348 assert!(args.self_type.is_some());
3349 let tiers = args.tiers.unwrap();
3350 assert_eq!(tiers, vec!["v3", "neon"]);
3351 }
3352
3353 #[test]
3354 fn autoversion_args_tiers_then_self() {
3355 let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
3357 assert!(args.self_type.is_some());
3358 let tiers = args.tiers.unwrap();
3359 assert_eq!(tiers, vec!["v3", "neon"]);
3360 }
3361
3362 #[test]
3363 fn autoversion_args_self_with_path_type() {
3364 let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
3365 assert!(args.self_type.is_some());
3366 assert!(args.tiers.is_none());
3367 }
3368
3369 #[test]
3370 fn autoversion_args_self_with_generic_type() {
3371 let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
3372 assert!(args.self_type.is_some());
3373 let ty_str = args.self_type.unwrap().to_token_stream().to_string();
3374 assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
3375 }
3376
3377 #[test]
3378 fn autoversion_args_self_trailing_comma() {
3379 let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
3380 assert!(args.self_type.is_some());
3381 assert!(args.tiers.is_none());
3382 }
3383
3384 #[test]
3389 fn find_simd_token_param_first_position() {
3390 let f: ItemFn =
3391 syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3392 let param = find_simd_token_param(&f.sig).unwrap();
3393 assert_eq!(param.index, 0);
3394 assert_eq!(param.ident, "token");
3395 }
3396
3397 #[test]
3398 fn find_simd_token_param_second_position() {
3399 let f: ItemFn =
3400 syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
3401 let param = find_simd_token_param(&f.sig).unwrap();
3402 assert_eq!(param.index, 1);
3403 assert_eq!(param.ident, "token");
3404 }
3405
3406 #[test]
3407 fn find_simd_token_param_underscore_prefix() {
3408 let f: ItemFn =
3409 syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3410 let param = find_simd_token_param(&f.sig).unwrap();
3411 assert_eq!(param.index, 0);
3412 assert_eq!(param.ident, "_token");
3413 }
3414
3415 #[test]
3416 fn find_simd_token_param_wildcard() {
3417 let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3418 let param = find_simd_token_param(&f.sig).unwrap();
3419 assert_eq!(param.index, 0);
3420 assert_eq!(param.ident, "__autoversion_token");
3421 }
3422
3423 #[test]
3424 fn find_simd_token_param_not_found() {
3425 let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
3426 assert!(find_simd_token_param(&f.sig).is_none());
3427 }
3428
3429 #[test]
3430 fn find_simd_token_param_no_params() {
3431 let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
3432 assert!(find_simd_token_param(&f.sig).is_none());
3433 }
3434
3435 #[test]
3436 fn find_simd_token_param_concrete_token_not_matched() {
3437 let f: ItemFn =
3439 syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
3440 assert!(find_simd_token_param(&f.sig).is_none());
3441 }
3442
3443 #[test]
3444 fn find_simd_token_param_scalar_token_not_matched() {
3445 let f: ItemFn =
3446 syn::parse_str("fn process(token: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
3447 assert!(find_simd_token_param(&f.sig).is_none());
3448 }
3449
3450 #[test]
3451 fn find_simd_token_param_among_many() {
3452 let f: ItemFn = syn::parse_str(
3453 "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
3454 )
3455 .unwrap();
3456 let param = find_simd_token_param(&f.sig).unwrap();
3457 assert_eq!(param.index, 2);
3458 assert_eq!(param.ident, "token");
3459 }
3460
3461 #[test]
3462 fn find_simd_token_param_with_generics() {
3463 let f: ItemFn =
3464 syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
3465 let param = find_simd_token_param(&f.sig).unwrap();
3466 assert_eq!(param.index, 0);
3467 assert_eq!(param.ident, "token");
3468 }
3469
3470 #[test]
3471 fn find_simd_token_param_with_where_clause() {
3472 let f: ItemFn = syn::parse_str(
3473 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
3474 )
3475 .unwrap();
3476 let param = find_simd_token_param(&f.sig).unwrap();
3477 assert_eq!(param.index, 0);
3478 }
3479
3480 #[test]
3481 fn find_simd_token_param_with_lifetime() {
3482 let f: ItemFn =
3483 syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
3484 .unwrap();
3485 let param = find_simd_token_param(&f.sig).unwrap();
3486 assert_eq!(param.index, 0);
3487 }
3488
3489 #[test]
3494 fn autoversion_default_tiers_all_resolve() {
3495 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3496 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3497 assert!(!tiers.is_empty());
3498 assert!(tiers.iter().any(|t| t.name == "scalar"));
3500 }
3501
3502 #[test]
3503 fn autoversion_scalar_always_appended() {
3504 let names = vec!["v3".to_string(), "neon".to_string()];
3505 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3506 assert!(
3507 tiers.iter().any(|t| t.name == "scalar"),
3508 "scalar must be auto-appended"
3509 );
3510 }
3511
3512 #[test]
3513 fn autoversion_scalar_not_duplicated() {
3514 let names = vec!["v3".to_string(), "scalar".to_string()];
3515 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3516 let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
3517 assert_eq!(scalar_count, 1, "scalar must not be duplicated");
3518 }
3519
3520 #[test]
3521 fn autoversion_tiers_sorted_by_priority() {
3522 let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
3523 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3524 let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
3526 for window in priorities.windows(2) {
3527 assert!(
3528 window[0] >= window[1],
3529 "Tiers not sorted by priority: {:?}",
3530 priorities
3531 );
3532 }
3533 }
3534
3535 #[test]
3536 fn autoversion_unknown_tier_errors() {
3537 let names = vec!["v3".to_string(), "avx9000".to_string()];
3538 let result = resolve_tiers(&names, proc_macro2::Span::call_site());
3539 match result {
3540 Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
3541 Err(e) => {
3542 let err_msg = e.to_string();
3543 assert!(
3544 err_msg.contains("avx9000"),
3545 "Error should mention unknown tier: {}",
3546 err_msg
3547 );
3548 }
3549 }
3550 }
3551
3552 #[test]
3553 fn autoversion_all_known_tiers_resolve() {
3554 for tier in ALL_TIERS {
3556 assert!(
3557 find_tier(tier.name).is_some(),
3558 "Tier '{}' should be findable by name",
3559 tier.name
3560 );
3561 }
3562 }
3563
3564 #[test]
3565 fn autoversion_default_tier_list_is_sensible() {
3566 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3568 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3569
3570 let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
3571 let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
3572 let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
3573 let has_scalar = tiers.iter().any(|t| t.name == "scalar");
3574
3575 assert!(has_x86, "Default tiers should include an x86_64 tier");
3576 assert!(has_arm, "Default tiers should include an aarch64 tier");
3577 assert!(has_wasm, "Default tiers should include a wasm32 tier");
3578 assert!(has_scalar, "Default tiers should include scalar");
3579 }
3580
3581 fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
3589 let mut f: ItemFn = syn::parse_str(func).unwrap();
3590 let fn_name = f.sig.ident.to_string();
3591
3592 let tier = find_tier(tier_name).unwrap();
3593
3594 f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
3596
3597 let token_idx = find_simd_token_param(&f.sig)
3599 .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
3600 .index;
3601 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
3602 if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
3603 *pt.ty = concrete_type;
3604 }
3605
3606 if tier_name == "scalar" && has_self {
3608 let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
3609 f.block.stmts.insert(0, preamble);
3610 }
3611
3612 f
3613 }
3614
3615 #[test]
3616 fn variant_replacement_v3_renames_function() {
3617 let f = do_variant_replacement(
3618 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3619 "v3",
3620 false,
3621 );
3622 assert_eq!(f.sig.ident, "process_v3");
3623 }
3624
3625 #[test]
3626 fn variant_replacement_v3_replaces_token_type() {
3627 let f = do_variant_replacement(
3628 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3629 "v3",
3630 false,
3631 );
3632 let first_param_ty = match &f.sig.inputs[0] {
3633 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3634 _ => panic!("Expected typed param"),
3635 };
3636 assert!(
3637 first_param_ty.contains("X64V3Token"),
3638 "Expected X64V3Token, got: {}",
3639 first_param_ty
3640 );
3641 }
3642
3643 #[test]
3644 fn variant_replacement_neon_produces_valid_fn() {
3645 let f = do_variant_replacement(
3646 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3647 "neon",
3648 false,
3649 );
3650 assert_eq!(f.sig.ident, "compute_neon");
3651 let first_param_ty = match &f.sig.inputs[0] {
3652 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3653 _ => panic!("Expected typed param"),
3654 };
3655 assert!(
3656 first_param_ty.contains("NeonToken"),
3657 "Expected NeonToken, got: {}",
3658 first_param_ty
3659 );
3660 }
3661
3662 #[test]
3663 fn variant_replacement_wasm128_produces_valid_fn() {
3664 let f = do_variant_replacement(
3665 "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3666 "wasm128",
3667 false,
3668 );
3669 assert_eq!(f.sig.ident, "compute_wasm128");
3670 }
3671
3672 #[test]
3673 fn variant_replacement_scalar_produces_valid_fn() {
3674 let f = do_variant_replacement(
3675 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3676 "scalar",
3677 false,
3678 );
3679 assert_eq!(f.sig.ident, "compute_scalar");
3680 let first_param_ty = match &f.sig.inputs[0] {
3681 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3682 _ => panic!("Expected typed param"),
3683 };
3684 assert!(
3685 first_param_ty.contains("ScalarToken"),
3686 "Expected ScalarToken, got: {}",
3687 first_param_ty
3688 );
3689 }
3690
3691 #[test]
3692 fn variant_replacement_v4_produces_valid_fn() {
3693 let f = do_variant_replacement(
3694 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3695 "v4",
3696 false,
3697 );
3698 assert_eq!(f.sig.ident, "transform_v4");
3699 let first_param_ty = match &f.sig.inputs[0] {
3700 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3701 _ => panic!("Expected typed param"),
3702 };
3703 assert!(
3704 first_param_ty.contains("X64V4Token"),
3705 "Expected X64V4Token, got: {}",
3706 first_param_ty
3707 );
3708 }
3709
3710 #[test]
3711 fn variant_replacement_v4x_produces_valid_fn() {
3712 let f = do_variant_replacement(
3713 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3714 "v4x",
3715 false,
3716 );
3717 assert_eq!(f.sig.ident, "transform_v4x");
3718 }
3719
3720 #[test]
3721 fn variant_replacement_arm_v2_produces_valid_fn() {
3722 let f = do_variant_replacement(
3723 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3724 "arm_v2",
3725 false,
3726 );
3727 assert_eq!(f.sig.ident, "transform_arm_v2");
3728 }
3729
3730 #[test]
3731 fn variant_replacement_preserves_generics() {
3732 let f = do_variant_replacement(
3733 "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
3734 "v3",
3735 false,
3736 );
3737 assert_eq!(f.sig.ident, "process_v3");
3738 assert!(
3740 !f.sig.generics.params.is_empty(),
3741 "Generics should be preserved"
3742 );
3743 }
3744
3745 #[test]
3746 fn variant_replacement_preserves_where_clause() {
3747 let f = do_variant_replacement(
3748 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
3749 "v3",
3750 false,
3751 );
3752 assert!(
3753 f.sig.generics.where_clause.is_some(),
3754 "Where clause should be preserved"
3755 );
3756 }
3757
3758 #[test]
3759 fn variant_replacement_preserves_return_type() {
3760 let f = do_variant_replacement(
3761 "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
3762 "neon",
3763 false,
3764 );
3765 let ret = f.sig.output.to_token_stream().to_string();
3766 assert!(
3767 ret.contains("Vec"),
3768 "Return type should be preserved, got: {}",
3769 ret
3770 );
3771 }
3772
3773 #[test]
3774 fn variant_replacement_preserves_multiple_params() {
3775 let f = do_variant_replacement(
3776 "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
3777 "v3",
3778 false,
3779 );
3780 assert_eq!(f.sig.inputs.len(), 4);
3782 }
3783
3784 #[test]
3785 fn variant_replacement_preserves_no_return_type() {
3786 let f = do_variant_replacement(
3787 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3788 "v3",
3789 false,
3790 );
3791 assert!(
3792 matches!(f.sig.output, ReturnType::Default),
3793 "No return type should remain as Default"
3794 );
3795 }
3796
3797 #[test]
3798 fn variant_replacement_preserves_lifetime_params() {
3799 let f = do_variant_replacement(
3800 "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
3801 "v3",
3802 false,
3803 );
3804 assert!(!f.sig.generics.params.is_empty());
3805 }
3806
3807 #[test]
3808 fn variant_replacement_scalar_self_injects_preamble() {
3809 let f = do_variant_replacement(
3810 "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3811 "scalar",
3812 true, );
3814 assert_eq!(f.sig.ident, "method_scalar");
3815
3816 let body_str = f.block.to_token_stream().to_string();
3818 assert!(
3819 body_str.contains("let _self = self"),
3820 "Scalar+self variant should have _self preamble, got: {}",
3821 body_str
3822 );
3823 }
3824
3825 #[test]
3826 fn variant_replacement_all_default_tiers_produce_valid_fns() {
3827 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3828 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3829
3830 for tier in &tiers {
3831 let f = do_variant_replacement(
3832 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3833 tier.name,
3834 false,
3835 );
3836 let expected_name = format!("process_{}", tier.suffix);
3837 assert_eq!(
3838 f.sig.ident.to_string(),
3839 expected_name,
3840 "Tier '{}' should produce function '{}'",
3841 tier.name,
3842 expected_name
3843 );
3844 }
3845 }
3846
3847 #[test]
3848 fn variant_replacement_all_known_tiers_produce_valid_fns() {
3849 for tier in ALL_TIERS {
3850 let f = do_variant_replacement(
3851 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3852 tier.name,
3853 false,
3854 );
3855 let expected_name = format!("compute_{}", tier.suffix);
3856 assert_eq!(
3857 f.sig.ident.to_string(),
3858 expected_name,
3859 "Tier '{}' should produce function '{}'",
3860 tier.name,
3861 expected_name
3862 );
3863 }
3864 }
3865
3866 #[test]
3867 fn variant_replacement_no_simdtoken_remains() {
3868 for tier in ALL_TIERS {
3869 let f = do_variant_replacement(
3870 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3871 tier.name,
3872 false,
3873 );
3874 let full_str = f.to_token_stream().to_string();
3875 assert!(
3876 !full_str.contains("SimdToken"),
3877 "Tier '{}' variant still contains 'SimdToken': {}",
3878 tier.name,
3879 full_str
3880 );
3881 }
3882 }
3883
3884 #[test]
3889 fn tier_v3_targets_x86_64() {
3890 let tier = find_tier("v3").unwrap();
3891 assert_eq!(tier.target_arch, Some("x86_64"));
3892 assert_eq!(tier.cargo_feature, None);
3893 }
3894
3895 #[test]
3896 fn tier_v4_requires_avx512_feature() {
3897 let tier = find_tier("v4").unwrap();
3898 assert_eq!(tier.target_arch, Some("x86_64"));
3899 assert_eq!(tier.cargo_feature, Some("avx512"));
3900 }
3901
3902 #[test]
3903 fn tier_v4x_requires_avx512_feature() {
3904 let tier = find_tier("v4x").unwrap();
3905 assert_eq!(tier.cargo_feature, Some("avx512"));
3906 }
3907
3908 #[test]
3909 fn tier_neon_targets_aarch64() {
3910 let tier = find_tier("neon").unwrap();
3911 assert_eq!(tier.target_arch, Some("aarch64"));
3912 assert_eq!(tier.cargo_feature, None);
3913 }
3914
3915 #[test]
3916 fn tier_wasm128_targets_wasm32() {
3917 let tier = find_tier("wasm128").unwrap();
3918 assert_eq!(tier.target_arch, Some("wasm32"));
3919 assert_eq!(tier.cargo_feature, None);
3920 }
3921
3922 #[test]
3923 fn tier_scalar_has_no_guards() {
3924 let tier = find_tier("scalar").unwrap();
3925 assert_eq!(tier.target_arch, None);
3926 assert_eq!(tier.cargo_feature, None);
3927 assert_eq!(tier.priority, 0);
3928 }
3929
3930 #[test]
3931 fn tier_priorities_are_consistent() {
3932 let v2 = find_tier("v2").unwrap();
3934 let v3 = find_tier("v3").unwrap();
3935 let v4 = find_tier("v4").unwrap();
3936 assert!(v4.priority > v3.priority);
3937 assert!(v3.priority > v2.priority);
3938
3939 let neon = find_tier("neon").unwrap();
3940 let arm_v2 = find_tier("arm_v2").unwrap();
3941 let arm_v3 = find_tier("arm_v3").unwrap();
3942 assert!(arm_v3.priority > arm_v2.priority);
3943 assert!(arm_v2.priority > neon.priority);
3944
3945 let scalar = find_tier("scalar").unwrap();
3947 assert!(neon.priority > scalar.priority);
3948 assert!(v2.priority > scalar.priority);
3949 }
3950
3951 #[test]
3956 fn dispatcher_param_removal_free_fn() {
3957 let f: ItemFn =
3959 syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
3960 .unwrap();
3961
3962 let token_param = find_simd_token_param(&f.sig).unwrap();
3963 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3964 dispatcher_inputs.remove(token_param.index);
3965
3966 assert_eq!(dispatcher_inputs.len(), 2);
3968
3969 for arg in &dispatcher_inputs {
3971 if let FnArg::Typed(pt) = arg {
3972 let ty_str = pt.ty.to_token_stream().to_string();
3973 assert!(
3974 !ty_str.contains("SimdToken"),
3975 "SimdToken should be removed from dispatcher, found: {}",
3976 ty_str
3977 );
3978 }
3979 }
3980 }
3981
3982 #[test]
3983 fn dispatcher_param_removal_token_only() {
3984 let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
3985
3986 let token_param = find_simd_token_param(&f.sig).unwrap();
3987 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3988 dispatcher_inputs.remove(token_param.index);
3989
3990 assert_eq!(dispatcher_inputs.len(), 0);
3992 }
3993
3994 #[test]
3995 fn dispatcher_param_removal_token_last() {
3996 let f: ItemFn =
3997 syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
3998 .unwrap();
3999
4000 let token_param = find_simd_token_param(&f.sig).unwrap();
4001 assert_eq!(token_param.index, 2);
4002
4003 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4004 dispatcher_inputs.remove(token_param.index);
4005
4006 assert_eq!(dispatcher_inputs.len(), 2);
4007 }
4008
4009 #[test]
4010 fn dispatcher_dispatch_args_extraction() {
4011 let f: ItemFn =
4013 syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
4014
4015 let dispatch_args: Vec<String> = f
4016 .sig
4017 .inputs
4018 .iter()
4019 .filter_map(|arg| {
4020 if let FnArg::Typed(PatType { pat, .. }) = arg {
4021 if let syn::Pat::Ident(pi) = pat.as_ref() {
4022 return Some(pi.ident.to_string());
4023 }
4024 }
4025 None
4026 })
4027 .collect();
4028
4029 assert_eq!(dispatch_args, vec!["data", "scale"]);
4030 }
4031
4032 #[test]
4033 fn dispatcher_wildcard_params_get_renamed() {
4034 let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
4035
4036 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4037
4038 let mut wild_counter = 0u32;
4039 for arg in &mut dispatcher_inputs {
4040 if let FnArg::Typed(pat_type) = arg {
4041 if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
4042 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
4043 wild_counter += 1;
4044 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
4045 attrs: vec![],
4046 by_ref: None,
4047 mutability: None,
4048 ident,
4049 subpat: None,
4050 });
4051 }
4052 }
4053 }
4054
4055 assert_eq!(wild_counter, 2);
4057
4058 let names: Vec<String> = dispatcher_inputs
4059 .iter()
4060 .filter_map(|arg| {
4061 if let FnArg::Typed(PatType { pat, .. }) = arg {
4062 if let syn::Pat::Ident(pi) = pat.as_ref() {
4063 return Some(pi.ident.to_string());
4064 }
4065 }
4066 None
4067 })
4068 .collect();
4069
4070 assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
4071 }
4072
4073 #[test]
4078 fn suffix_path_simple() {
4079 let path: syn::Path = syn::parse_str("process").unwrap();
4080 let suffixed = suffix_path(&path, "v3");
4081 assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
4082 }
4083
4084 #[test]
4085 fn suffix_path_qualified() {
4086 let path: syn::Path = syn::parse_str("module::process").unwrap();
4087 let suffixed = suffix_path(&path, "neon");
4088 let s = suffixed.to_token_stream().to_string();
4089 assert!(
4090 s.contains("process_neon"),
4091 "Expected process_neon, got: {}",
4092 s
4093 );
4094 }
4095}