1mod arcane;
6mod autoversion;
7mod common;
8mod generated;
9mod incant;
10mod magetypes;
11mod rite;
12mod tiers;
13mod token_discovery;
14
15use proc_macro::TokenStream;
16use syn::parse_macro_input;
17
18use arcane::*;
19use autoversion::*;
20use common::*;
21use incant::*;
22use magetypes::*;
23use rite::*;
24use tiers::*;
25
26#[cfg(test)]
28use generated::{token_to_features, trait_to_features};
29#[cfg(test)]
30use quote::{ToTokens, format_ident};
31#[cfg(test)]
32use syn::{FnArg, PatType, Type};
33#[cfg(test)]
34use token_discovery::*;
35
36#[proc_macro_attribute]
201pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
202 let args = parse_macro_input!(attr as ArcaneArgs);
203 let input_fn = parse_macro_input!(item as LightFn);
204 arcane_impl(input_fn, "arcane", args)
205}
206
207#[proc_macro_attribute]
211#[doc(hidden)]
212pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
213 let args = parse_macro_input!(attr as ArcaneArgs);
214 let input_fn = parse_macro_input!(item as LightFn);
215 arcane_impl(input_fn, "simd_fn", args)
216}
217
218#[proc_macro_attribute]
231pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
232 let args = parse_macro_input!(attr as ArcaneArgs);
233 let input_fn = parse_macro_input!(item as LightFn);
234 arcane_impl(input_fn, "token_target_features_boundary", args)
235}
236
237#[proc_macro_attribute]
316pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
317 let args = parse_macro_input!(attr as RiteArgs);
318 let input_fn = parse_macro_input!(item as LightFn);
319 rite_impl(input_fn, args)
320}
321
322#[proc_macro_attribute]
333pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
334 let args = parse_macro_input!(attr as RiteArgs);
335 let input_fn = parse_macro_input!(item as LightFn);
336 rite_impl(input_fn, args)
337}
338
339#[proc_macro_attribute]
400pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
401 let input_fn = parse_macro_input!(item as LightFn);
402
403 let tier_names: Vec<String> = if attr.is_empty() {
405 DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
406 } else {
407 match syn::parse::Parser::parse(parse_tier_names, attr) {
408 Ok(names) => names,
409 Err(e) => return e.to_compile_error().into(),
410 }
411 };
412
413 let tiers = match resolve_tiers(
415 &tier_names,
416 input_fn.sig.ident.span(),
417 true, ) {
419 Ok(t) => t,
420 Err(e) => return e.to_compile_error().into(),
421 };
422
423 magetypes_impl(input_fn, &tiers)
424}
425
426#[proc_macro]
504pub fn incant(input: TokenStream) -> TokenStream {
505 let input = parse_macro_input!(input as IncantInput);
506 incant_impl(input)
507}
508
509#[proc_macro]
511pub fn simd_route(input: TokenStream) -> TokenStream {
512 let input = parse_macro_input!(input as IncantInput);
513 incant_impl(input)
514}
515
516#[proc_macro]
524pub fn dispatch_variant(input: TokenStream) -> TokenStream {
525 let input = parse_macro_input!(input as IncantInput);
526 incant_impl(input)
527}
528
529#[proc_macro_attribute]
660pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
661 let args = parse_macro_input!(attr as AutoversionArgs);
662 let input_fn = parse_macro_input!(item as LightFn);
663 autoversion_impl(input_fn, args)
664}
665
666#[cfg(test)]
671mod tests {
672 use super::*;
673
674 use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
675 use syn::{ItemFn, ReturnType};
676
677 #[test]
678 fn every_concrete_token_is_in_token_to_features() {
679 for &name in ALL_CONCRETE_TOKENS {
680 assert!(
681 token_to_features(name).is_some(),
682 "Token `{}` exists in runtime crate but is NOT recognized by \
683 token_to_features() in the proc macro. Add it!",
684 name
685 );
686 }
687 }
688
689 #[test]
690 fn every_trait_is_in_trait_to_features() {
691 for &name in ALL_TRAIT_NAMES {
692 assert!(
693 trait_to_features(name).is_some(),
694 "Trait `{}` exists in runtime crate but is NOT recognized by \
695 trait_to_features() in the proc macro. Add it!",
696 name
697 );
698 }
699 }
700
701 #[test]
702 fn token_aliases_map_to_same_features() {
703 assert_eq!(
705 token_to_features("Desktop64"),
706 token_to_features("X64V3Token"),
707 "Desktop64 and X64V3Token should map to identical features"
708 );
709
710 assert_eq!(
712 token_to_features("Server64"),
713 token_to_features("X64V4Token"),
714 "Server64 and X64V4Token should map to identical features"
715 );
716 assert_eq!(
717 token_to_features("X64V4Token"),
718 token_to_features("Avx512Token"),
719 "X64V4Token and Avx512Token should map to identical features"
720 );
721
722 assert_eq!(
724 token_to_features("Arm64"),
725 token_to_features("NeonToken"),
726 "Arm64 and NeonToken should map to identical features"
727 );
728 }
729
730 #[test]
731 fn trait_to_features_includes_tokens_as_bounds() {
732 let tier_tokens = [
736 "X64V2Token",
737 "X64CryptoToken",
738 "X64V3Token",
739 "Desktop64",
740 "Avx2FmaToken",
741 "X64V4Token",
742 "Avx512Token",
743 "Server64",
744 "X64V4xToken",
745 "Avx512Fp16Token",
746 "NeonToken",
747 "Arm64",
748 "NeonAesToken",
749 "NeonSha3Token",
750 "NeonCrcToken",
751 "Arm64V2Token",
752 "Arm64V3Token",
753 ];
754
755 for &name in &tier_tokens {
756 assert!(
757 trait_to_features(name).is_some(),
758 "Tier token `{}` should also be recognized in trait_to_features() \
759 for use as a generic bound. Add it!",
760 name
761 );
762 }
763 }
764
765 #[test]
766 fn trait_features_are_cumulative() {
767 let v2_features = trait_to_features("HasX64V2").unwrap();
769 let v4_features = trait_to_features("HasX64V4").unwrap();
770
771 for &f in v2_features {
772 assert!(
773 v4_features.contains(&f),
774 "HasX64V4 should include v2 feature `{}` but doesn't",
775 f
776 );
777 }
778
779 assert!(
781 v4_features.len() > v2_features.len(),
782 "HasX64V4 should have more features than HasX64V2"
783 );
784 }
785
786 #[test]
787 fn x64v3_trait_features_include_v2() {
788 let v2 = trait_to_features("HasX64V2").unwrap();
790 let v3 = trait_to_features("X64V3Token").unwrap();
791
792 for &f in v2 {
793 assert!(
794 v3.contains(&f),
795 "X64V3Token trait features should include v2 feature `{}` but don't",
796 f
797 );
798 }
799 }
800
801 #[test]
802 fn has_neon_aes_includes_neon() {
803 let neon = trait_to_features("HasNeon").unwrap();
804 let neon_aes = trait_to_features("HasNeonAes").unwrap();
805
806 for &f in neon {
807 assert!(
808 neon_aes.contains(&f),
809 "HasNeonAes should include NEON feature `{}`",
810 f
811 );
812 }
813 }
814
815 #[test]
816 fn no_removed_traits_are_recognized() {
817 let removed = [
819 "HasSse",
820 "HasSse2",
821 "HasSse41",
822 "HasSse42",
823 "HasAvx",
824 "HasAvx2",
825 "HasFma",
826 "HasAvx512f",
827 "HasAvx512bw",
828 "HasAvx512vl",
829 "HasAvx512vbmi2",
830 "HasSve",
831 "HasSve2",
832 ];
833
834 for &name in &removed {
835 assert!(
836 trait_to_features(name).is_none(),
837 "Removed trait `{}` should NOT be in trait_to_features(). \
838 It was removed in 0.3.0 — users should migrate to tier traits.",
839 name
840 );
841 }
842 }
843
844 #[test]
845 fn no_nonexistent_tokens_are_recognized() {
846 let fake = [
848 "SveToken",
849 "Sve2Token",
850 "Avx512VnniToken",
851 "X64V4ModernToken",
852 "NeonFp16Token",
853 ];
854
855 for &name in &fake {
856 assert!(
857 token_to_features(name).is_none(),
858 "Non-existent token `{}` should NOT be in token_to_features()",
859 name
860 );
861 }
862 }
863
864 #[test]
865 fn featureless_traits_are_not_in_registries() {
866 for &name in FEATURELESS_TRAIT_NAMES {
869 assert!(
870 token_to_features(name).is_none(),
871 "`{}` should NOT be in token_to_features() — it has no CPU features",
872 name
873 );
874 assert!(
875 trait_to_features(name).is_none(),
876 "`{}` should NOT be in trait_to_features() — it has no CPU features",
877 name
878 );
879 }
880 }
881
882 #[test]
883 fn find_featureless_trait_detects_simdtoken() {
884 let names = vec!["SimdToken".to_string()];
885 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
886
887 let names = vec!["IntoConcreteToken".to_string()];
888 assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
889
890 let names = vec!["HasX64V2".to_string()];
892 assert_eq!(find_featureless_trait(&names), None);
893
894 let names = vec!["HasNeon".to_string()];
895 assert_eq!(find_featureless_trait(&names), None);
896
897 let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
899 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
900 }
901
902 #[test]
903 fn arm64_v2_v3_traits_are_cumulative() {
904 let v2_features = trait_to_features("HasArm64V2").unwrap();
905 let v3_features = trait_to_features("HasArm64V3").unwrap();
906
907 for &f in v2_features {
908 assert!(
909 v3_features.contains(&f),
910 "HasArm64V3 should include v2 feature `{}` but doesn't",
911 f
912 );
913 }
914
915 assert!(
916 v3_features.len() > v2_features.len(),
917 "HasArm64V3 should have more features than HasArm64V2"
918 );
919 }
920
921 fn resolve_tier_names(names: &[&str], default_gates: bool) -> Vec<String> {
926 let names: Vec<String> = names.iter().map(|s| s.to_string()).collect();
927 resolve_tiers(&names, proc_macro2::Span::call_site(), default_gates)
928 .unwrap()
929 .iter()
930 .map(|rt| {
931 if let Some(ref gate) = rt.feature_gate {
932 format!("{}({})", rt.name, gate)
933 } else {
934 rt.name.to_string()
935 }
936 })
937 .collect()
938 }
939
940 #[test]
941 fn resolve_defaults() {
942 let tiers = resolve_tier_names(&["v4", "v3", "neon", "wasm128", "scalar"], true);
943 assert!(tiers.contains(&"v3".to_string()));
944 assert!(tiers.contains(&"scalar".to_string()));
945 assert!(tiers.contains(&"v4(avx512)".to_string()));
947 }
948
949 #[test]
950 fn resolve_additive_appends() {
951 let tiers = resolve_tier_names(&["+v1"], true);
952 assert!(tiers.contains(&"v1".to_string()));
953 assert!(tiers.contains(&"v3".to_string())); assert!(tiers.contains(&"scalar".to_string())); }
956
957 #[test]
958 fn resolve_additive_v4_overrides_gate() {
959 let tiers = resolve_tier_names(&["+v4"], true);
961 assert!(tiers.contains(&"v4".to_string())); assert!(!tiers.iter().any(|t| t == "v4(avx512)")); }
964
965 #[test]
966 fn resolve_additive_default_replaces_scalar() {
967 let tiers = resolve_tier_names(&["+default"], true);
968 assert!(tiers.contains(&"default".to_string()));
969 assert!(!tiers.iter().any(|t| t == "scalar")); }
971
972 #[test]
973 fn resolve_subtractive_removes() {
974 let tiers = resolve_tier_names(&["-neon", "-wasm128"], true);
975 assert!(!tiers.iter().any(|t| t == "neon"));
976 assert!(!tiers.iter().any(|t| t == "wasm128"));
977 assert!(tiers.contains(&"v3".to_string())); }
979
980 #[test]
981 fn resolve_mixed_add_remove() {
982 let tiers = resolve_tier_names(&["-neon", "-wasm128", "+v1"], true);
983 assert!(tiers.contains(&"v1".to_string()));
984 assert!(!tiers.iter().any(|t| t == "neon"));
985 assert!(!tiers.iter().any(|t| t == "wasm128"));
986 assert!(tiers.contains(&"v3".to_string()));
987 assert!(tiers.contains(&"scalar".to_string()));
988 }
989
990 #[test]
991 fn resolve_additive_duplicate_is_noop() {
992 let tiers = resolve_tier_names(&["+v3"], true);
994 let v3_count = tiers.iter().filter(|t| t.as_str() == "v3").count();
995 assert_eq!(v3_count, 1);
996 }
997
998 #[test]
999 fn resolve_mixing_plus_and_plain_is_error() {
1000 let names: Vec<String> = vec!["+v1".into(), "v3".into()];
1001 let result = resolve_tiers(&names, proc_macro2::Span::call_site(), true);
1002 assert!(result.is_err());
1003 }
1004
1005 #[test]
1006 fn resolve_underscore_tier_name() {
1007 let tiers = resolve_tier_names(&["_v3", "_neon", "_scalar"], false);
1008 assert!(tiers.contains(&"v3".to_string()));
1009 assert!(tiers.contains(&"neon".to_string()));
1010 assert!(tiers.contains(&"scalar".to_string()));
1011 }
1012
1013 #[test]
1018 fn autoversion_args_empty() {
1019 let args: AutoversionArgs = syn::parse_str("").unwrap();
1020 assert!(args.self_type.is_none());
1021 assert!(args.tiers.is_none());
1022 }
1023
1024 #[test]
1025 fn autoversion_args_single_tier() {
1026 let args: AutoversionArgs = syn::parse_str("v3").unwrap();
1027 assert!(args.self_type.is_none());
1028 assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
1029 }
1030
1031 #[test]
1032 fn autoversion_args_tiers_only() {
1033 let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
1034 assert!(args.self_type.is_none());
1035 let tiers = args.tiers.unwrap();
1036 assert_eq!(tiers, vec!["v3", "v4", "neon"]);
1037 }
1038
1039 #[test]
1040 fn autoversion_args_many_tiers() {
1041 let args: AutoversionArgs =
1042 syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
1043 assert_eq!(
1044 args.tiers.unwrap(),
1045 vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
1046 );
1047 }
1048
1049 #[test]
1050 fn autoversion_args_trailing_comma() {
1051 let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
1052 assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
1053 }
1054
1055 #[test]
1056 fn autoversion_args_self_only() {
1057 let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
1058 assert!(args.self_type.is_some());
1059 assert!(args.tiers.is_none());
1060 }
1061
1062 #[test]
1063 fn autoversion_args_self_and_tiers() {
1064 let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
1065 assert!(args.self_type.is_some());
1066 let tiers = args.tiers.unwrap();
1067 assert_eq!(tiers, vec!["v3", "neon"]);
1068 }
1069
1070 #[test]
1071 fn autoversion_args_tiers_then_self() {
1072 let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
1074 assert!(args.self_type.is_some());
1075 let tiers = args.tiers.unwrap();
1076 assert_eq!(tiers, vec!["v3", "neon"]);
1077 }
1078
1079 #[test]
1080 fn autoversion_args_self_with_path_type() {
1081 let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
1082 assert!(args.self_type.is_some());
1083 assert!(args.tiers.is_none());
1084 }
1085
1086 #[test]
1087 fn autoversion_args_self_with_generic_type() {
1088 let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
1089 assert!(args.self_type.is_some());
1090 let ty_str = args.self_type.unwrap().to_token_stream().to_string();
1091 assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
1092 }
1093
1094 #[test]
1095 fn autoversion_args_self_trailing_comma() {
1096 let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
1097 assert!(args.self_type.is_some());
1098 assert!(args.tiers.is_none());
1099 }
1100
1101 #[test]
1106 fn find_autoversion_token_param_simdtoken_first() {
1107 let f: ItemFn =
1108 syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1109 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1110 assert_eq!(param.index, 0);
1111 assert_eq!(param.ident, "token");
1112 assert_eq!(param.kind, AutoversionTokenKind::SimdToken);
1113 }
1114
1115 #[test]
1116 fn find_autoversion_token_param_simdtoken_second() {
1117 let f: ItemFn =
1118 syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
1119 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1120 assert_eq!(param.index, 1);
1121 assert_eq!(param.kind, AutoversionTokenKind::SimdToken);
1122 }
1123
1124 #[test]
1125 fn find_autoversion_token_param_underscore_prefix() {
1126 let f: ItemFn =
1127 syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1128 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1129 assert_eq!(param.index, 0);
1130 assert_eq!(param.ident, "_token");
1131 }
1132
1133 #[test]
1134 fn find_autoversion_token_param_wildcard() {
1135 let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1136 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1137 assert_eq!(param.index, 0);
1138 assert_eq!(param.ident, "__autoversion_token");
1139 }
1140
1141 #[test]
1142 fn find_autoversion_token_param_scalar_token() {
1143 let f: ItemFn =
1144 syn::parse_str("fn process_scalar(_: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
1145 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1146 assert_eq!(param.index, 0);
1147 assert_eq!(param.kind, AutoversionTokenKind::ScalarToken);
1148 }
1149
1150 #[test]
1151 fn find_autoversion_token_param_not_found() {
1152 let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
1153 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1154 }
1155
1156 #[test]
1157 fn find_autoversion_token_param_no_params() {
1158 let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
1159 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1160 }
1161
1162 #[test]
1163 fn find_autoversion_token_param_concrete_token_errors() {
1164 let f: ItemFn =
1165 syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
1166 let err = find_autoversion_token_param(&f.sig).unwrap_err();
1167 let msg = err.to_string();
1168 assert!(
1169 msg.contains("concrete token"),
1170 "error should mention concrete token: {msg}"
1171 );
1172 assert!(
1173 msg.contains("#[arcane]"),
1174 "error should suggest #[arcane]: {msg}"
1175 );
1176 }
1177
1178 #[test]
1179 fn find_autoversion_token_param_neon_token_errors() {
1180 let f: ItemFn =
1181 syn::parse_str("fn process(token: NeonToken, data: &[f32]) -> f32 {}").unwrap();
1182 assert!(find_autoversion_token_param(&f.sig).is_err());
1183 }
1184
1185 #[test]
1186 fn find_autoversion_token_param_unknown_type_ignored() {
1187 let f: ItemFn = syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 {}").unwrap();
1189 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1190 }
1191
1192 #[test]
1193 fn find_autoversion_token_param_among_many() {
1194 let f: ItemFn = syn::parse_str(
1195 "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
1196 )
1197 .unwrap();
1198 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1199 assert_eq!(param.index, 2);
1200 assert_eq!(param.ident, "token");
1201 }
1202
1203 #[test]
1204 fn find_autoversion_token_param_with_generics() {
1205 let f: ItemFn =
1206 syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
1207 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1208 assert_eq!(param.index, 0);
1209 }
1210
1211 #[test]
1212 fn find_autoversion_token_param_with_where_clause() {
1213 let f: ItemFn = syn::parse_str(
1214 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
1215 )
1216 .unwrap();
1217 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1218 assert_eq!(param.index, 0);
1219 }
1220
1221 #[test]
1222 fn find_autoversion_token_param_with_lifetime() {
1223 let f: ItemFn =
1224 syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
1225 .unwrap();
1226 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1227 assert_eq!(param.index, 0);
1228 }
1229
1230 #[test]
1235 fn autoversion_default_tiers_all_resolve() {
1236 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1237 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1238 assert!(!tiers.is_empty());
1239 assert!(tiers.iter().any(|t| t.name == "scalar"));
1241 }
1242
1243 #[test]
1244 fn autoversion_scalar_always_appended() {
1245 let names = vec!["v3".to_string(), "neon".to_string()];
1246 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1247 assert!(
1248 tiers.iter().any(|t| t.name == "scalar"),
1249 "scalar must be auto-appended"
1250 );
1251 }
1252
1253 #[test]
1254 fn autoversion_scalar_not_duplicated() {
1255 let names = vec!["v3".to_string(), "scalar".to_string()];
1256 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1257 let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
1258 assert_eq!(scalar_count, 1, "scalar must not be duplicated");
1259 }
1260
1261 #[test]
1262 fn autoversion_tiers_sorted_by_priority() {
1263 let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
1264 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1265 let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
1267 for window in priorities.windows(2) {
1268 assert!(
1269 window[0] >= window[1],
1270 "Tiers not sorted by priority: {:?}",
1271 priorities
1272 );
1273 }
1274 }
1275
1276 #[test]
1277 fn autoversion_unknown_tier_errors() {
1278 let names = vec!["v3".to_string(), "avx9000".to_string()];
1279 let result = resolve_tiers(&names, proc_macro2::Span::call_site(), false);
1280 match result {
1281 Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
1282 Err(e) => {
1283 let err_msg = e.to_string();
1284 assert!(
1285 err_msg.contains("avx9000"),
1286 "Error should mention unknown tier: {}",
1287 err_msg
1288 );
1289 }
1290 }
1291 }
1292
1293 #[test]
1294 fn autoversion_all_known_tiers_resolve() {
1295 for tier in ALL_TIERS {
1297 assert!(
1298 find_tier(tier.name).is_some(),
1299 "Tier '{}' should be findable by name",
1300 tier.name
1301 );
1302 }
1303 }
1304
1305 #[test]
1306 fn autoversion_default_tier_list_is_sensible() {
1307 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1309 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1310
1311 let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
1312 let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
1313 let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
1314 let has_scalar = tiers.iter().any(|t| t.name == "scalar");
1315
1316 assert!(has_x86, "Default tiers should include an x86_64 tier");
1317 assert!(has_arm, "Default tiers should include an aarch64 tier");
1318 assert!(has_wasm, "Default tiers should include a wasm32 tier");
1319 assert!(has_scalar, "Default tiers should include scalar");
1320 }
1321
1322 fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
1330 let mut f: ItemFn = syn::parse_str(func).unwrap();
1331 let fn_name = f.sig.ident.to_string();
1332
1333 let tier = find_tier(tier_name).unwrap();
1334
1335 f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
1337
1338 let token_idx = find_autoversion_token_param(&f.sig)
1340 .expect("should not error on SimdToken")
1341 .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
1342 .index;
1343 if tier_name == "default" {
1344 let stmts = f.block.stmts.clone();
1346 let mut inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1347 inputs.remove(token_idx);
1348 f.sig.inputs = inputs.into_iter().collect();
1349 f.block.stmts = stmts;
1350 } else {
1351 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
1352 if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
1353 *pt.ty = concrete_type;
1354 }
1355 }
1356
1357 if (tier_name == "scalar" || tier_name == "default") && has_self {
1359 let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
1360 f.block.stmts.insert(0, preamble);
1361 }
1362
1363 f
1364 }
1365
1366 #[test]
1367 fn variant_replacement_v3_renames_function() {
1368 let f = do_variant_replacement(
1369 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1370 "v3",
1371 false,
1372 );
1373 assert_eq!(f.sig.ident, "process_v3");
1374 }
1375
1376 #[test]
1377 fn variant_replacement_v3_replaces_token_type() {
1378 let f = do_variant_replacement(
1379 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1380 "v3",
1381 false,
1382 );
1383 let first_param_ty = match &f.sig.inputs[0] {
1384 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1385 _ => panic!("Expected typed param"),
1386 };
1387 assert!(
1388 first_param_ty.contains("X64V3Token"),
1389 "Expected X64V3Token, got: {}",
1390 first_param_ty
1391 );
1392 }
1393
1394 #[test]
1395 fn variant_replacement_neon_produces_valid_fn() {
1396 let f = do_variant_replacement(
1397 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1398 "neon",
1399 false,
1400 );
1401 assert_eq!(f.sig.ident, "compute_neon");
1402 let first_param_ty = match &f.sig.inputs[0] {
1403 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1404 _ => panic!("Expected typed param"),
1405 };
1406 assert!(
1407 first_param_ty.contains("NeonToken"),
1408 "Expected NeonToken, got: {}",
1409 first_param_ty
1410 );
1411 }
1412
1413 #[test]
1414 fn variant_replacement_wasm128_produces_valid_fn() {
1415 let f = do_variant_replacement(
1416 "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1417 "wasm128",
1418 false,
1419 );
1420 assert_eq!(f.sig.ident, "compute_wasm128");
1421 }
1422
1423 #[test]
1424 fn variant_replacement_scalar_produces_valid_fn() {
1425 let f = do_variant_replacement(
1426 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1427 "scalar",
1428 false,
1429 );
1430 assert_eq!(f.sig.ident, "compute_scalar");
1431 let first_param_ty = match &f.sig.inputs[0] {
1432 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1433 _ => panic!("Expected typed param"),
1434 };
1435 assert!(
1436 first_param_ty.contains("ScalarToken"),
1437 "Expected ScalarToken, got: {}",
1438 first_param_ty
1439 );
1440 }
1441
1442 #[test]
1443 fn variant_replacement_v4_produces_valid_fn() {
1444 let f = do_variant_replacement(
1445 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1446 "v4",
1447 false,
1448 );
1449 assert_eq!(f.sig.ident, "transform_v4");
1450 let first_param_ty = match &f.sig.inputs[0] {
1451 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1452 _ => panic!("Expected typed param"),
1453 };
1454 assert!(
1455 first_param_ty.contains("X64V4Token"),
1456 "Expected X64V4Token, got: {}",
1457 first_param_ty
1458 );
1459 }
1460
1461 #[test]
1462 fn variant_replacement_v4x_produces_valid_fn() {
1463 let f = do_variant_replacement(
1464 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1465 "v4x",
1466 false,
1467 );
1468 assert_eq!(f.sig.ident, "transform_v4x");
1469 }
1470
1471 #[test]
1472 fn variant_replacement_arm_v2_produces_valid_fn() {
1473 let f = do_variant_replacement(
1474 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1475 "arm_v2",
1476 false,
1477 );
1478 assert_eq!(f.sig.ident, "transform_arm_v2");
1479 }
1480
1481 #[test]
1482 fn variant_replacement_preserves_generics() {
1483 let f = do_variant_replacement(
1484 "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
1485 "v3",
1486 false,
1487 );
1488 assert_eq!(f.sig.ident, "process_v3");
1489 assert!(
1491 !f.sig.generics.params.is_empty(),
1492 "Generics should be preserved"
1493 );
1494 }
1495
1496 #[test]
1497 fn variant_replacement_preserves_where_clause() {
1498 let f = do_variant_replacement(
1499 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
1500 "v3",
1501 false,
1502 );
1503 assert!(
1504 f.sig.generics.where_clause.is_some(),
1505 "Where clause should be preserved"
1506 );
1507 }
1508
1509 #[test]
1510 fn variant_replacement_preserves_return_type() {
1511 let f = do_variant_replacement(
1512 "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
1513 "neon",
1514 false,
1515 );
1516 let ret = f.sig.output.to_token_stream().to_string();
1517 assert!(
1518 ret.contains("Vec"),
1519 "Return type should be preserved, got: {}",
1520 ret
1521 );
1522 }
1523
1524 #[test]
1525 fn variant_replacement_preserves_multiple_params() {
1526 let f = do_variant_replacement(
1527 "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
1528 "v3",
1529 false,
1530 );
1531 assert_eq!(f.sig.inputs.len(), 4);
1533 }
1534
1535 #[test]
1536 fn variant_replacement_preserves_no_return_type() {
1537 let f = do_variant_replacement(
1538 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1539 "v3",
1540 false,
1541 );
1542 assert!(
1543 matches!(f.sig.output, ReturnType::Default),
1544 "No return type should remain as Default"
1545 );
1546 }
1547
1548 #[test]
1549 fn variant_replacement_preserves_lifetime_params() {
1550 let f = do_variant_replacement(
1551 "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
1552 "v3",
1553 false,
1554 );
1555 assert!(!f.sig.generics.params.is_empty());
1556 }
1557
1558 #[test]
1559 fn variant_replacement_scalar_self_injects_preamble() {
1560 let f = do_variant_replacement(
1561 "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1562 "scalar",
1563 true, );
1565 assert_eq!(f.sig.ident, "method_scalar");
1566
1567 let body_str = f.block.to_token_stream().to_string();
1569 assert!(
1570 body_str.contains("let _self = self"),
1571 "Scalar+self variant should have _self preamble, got: {}",
1572 body_str
1573 );
1574 }
1575
1576 #[test]
1577 fn variant_replacement_all_default_tiers_produce_valid_fns() {
1578 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1579 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1580
1581 for tier in &tiers {
1582 let f = do_variant_replacement(
1583 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1584 tier.name,
1585 false,
1586 );
1587 let expected_name = format!("process_{}", tier.suffix);
1588 assert_eq!(
1589 f.sig.ident.to_string(),
1590 expected_name,
1591 "Tier '{}' should produce function '{}'",
1592 tier.name,
1593 expected_name
1594 );
1595 }
1596 }
1597
1598 #[test]
1599 fn variant_replacement_all_known_tiers_produce_valid_fns() {
1600 for tier in ALL_TIERS {
1601 let f = do_variant_replacement(
1602 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1603 tier.name,
1604 false,
1605 );
1606 let expected_name = format!("compute_{}", tier.suffix);
1607 assert_eq!(
1608 f.sig.ident.to_string(),
1609 expected_name,
1610 "Tier '{}' should produce function '{}'",
1611 tier.name,
1612 expected_name
1613 );
1614 }
1615 }
1616
1617 #[test]
1618 fn variant_replacement_no_simdtoken_remains() {
1619 for tier in ALL_TIERS {
1620 let f = do_variant_replacement(
1621 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1622 tier.name,
1623 false,
1624 );
1625 let full_str = f.to_token_stream().to_string();
1626 assert!(
1627 !full_str.contains("SimdToken"),
1628 "Tier '{}' variant still contains 'SimdToken': {}",
1629 tier.name,
1630 full_str
1631 );
1632 }
1633 }
1634
1635 #[test]
1640 fn tier_v3_targets_x86_64() {
1641 let tier = find_tier("v3").unwrap();
1642 assert_eq!(tier.target_arch, Some("x86_64"));
1643 }
1644
1645 #[test]
1646 fn tier_v4_targets_x86_64() {
1647 let tier = find_tier("v4").unwrap();
1648 assert_eq!(tier.target_arch, Some("x86_64"));
1649 }
1650
1651 #[test]
1652 fn tier_v4x_targets_x86_64() {
1653 let tier = find_tier("v4x").unwrap();
1654 assert_eq!(tier.target_arch, Some("x86_64"));
1655 }
1656
1657 #[test]
1658 fn tier_neon_targets_aarch64() {
1659 let tier = find_tier("neon").unwrap();
1660 assert_eq!(tier.target_arch, Some("aarch64"));
1661 }
1662
1663 #[test]
1664 fn tier_wasm128_targets_wasm32() {
1665 let tier = find_tier("wasm128").unwrap();
1666 assert_eq!(tier.target_arch, Some("wasm32"));
1667 }
1668
1669 #[test]
1670 fn tier_scalar_has_no_guards() {
1671 let tier = find_tier("scalar").unwrap();
1672 assert_eq!(tier.target_arch, None);
1673 assert_eq!(tier.priority, 0);
1674 }
1675
1676 #[test]
1677 fn tier_priorities_are_consistent() {
1678 let v2 = find_tier("v2").unwrap();
1680 let v3 = find_tier("v3").unwrap();
1681 let v4 = find_tier("v4").unwrap();
1682 assert!(v4.priority > v3.priority);
1683 assert!(v3.priority > v2.priority);
1684
1685 let neon = find_tier("neon").unwrap();
1686 let arm_v2 = find_tier("arm_v2").unwrap();
1687 let arm_v3 = find_tier("arm_v3").unwrap();
1688 assert!(arm_v3.priority > arm_v2.priority);
1689 assert!(arm_v2.priority > neon.priority);
1690
1691 let scalar = find_tier("scalar").unwrap();
1693 assert!(neon.priority > scalar.priority);
1694 assert!(v2.priority > scalar.priority);
1695 }
1696
1697 #[test]
1702 fn dispatcher_param_removal_free_fn() {
1703 let f: ItemFn =
1705 syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
1706 .unwrap();
1707
1708 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1709 assert_eq!(token_param.kind, AutoversionTokenKind::SimdToken);
1711 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1712 dispatcher_inputs.remove(token_param.index);
1713 assert_eq!(dispatcher_inputs.len(), 2);
1714 }
1715
1716 #[test]
1717 fn dispatcher_param_removal_token_only() {
1718 let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
1719 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1720 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1721 dispatcher_inputs.remove(token_param.index);
1722 assert_eq!(dispatcher_inputs.len(), 0);
1723 }
1724
1725 #[test]
1726 fn dispatcher_param_removal_token_last() {
1727 let f: ItemFn =
1728 syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
1729 .unwrap();
1730 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1731 assert_eq!(token_param.index, 2);
1732 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1733 dispatcher_inputs.remove(token_param.index);
1734 assert_eq!(dispatcher_inputs.len(), 2);
1735 }
1736
1737 #[test]
1738 fn dispatcher_scalar_token_kept() {
1739 let f: ItemFn =
1741 syn::parse_str("fn process_scalar(_: ScalarToken, data: &[f32]) -> f32 { 0.0 }")
1742 .unwrap();
1743 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1744 assert_eq!(token_param.kind, AutoversionTokenKind::ScalarToken);
1745 assert_eq!(f.sig.inputs.len(), 2);
1747 }
1748
1749 #[test]
1750 fn dispatcher_dispatch_args_extraction() {
1751 let f: ItemFn =
1753 syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
1754
1755 let dispatch_args: Vec<String> = f
1756 .sig
1757 .inputs
1758 .iter()
1759 .filter_map(|arg| {
1760 if let FnArg::Typed(PatType { pat, .. }) = arg {
1761 if let syn::Pat::Ident(pi) = pat.as_ref() {
1762 return Some(pi.ident.to_string());
1763 }
1764 }
1765 None
1766 })
1767 .collect();
1768
1769 assert_eq!(dispatch_args, vec!["data", "scale"]);
1770 }
1771
1772 #[test]
1773 fn dispatcher_wildcard_params_get_renamed() {
1774 let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
1775
1776 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1777
1778 let mut wild_counter = 0u32;
1779 for arg in &mut dispatcher_inputs {
1780 if let FnArg::Typed(pat_type) = arg {
1781 if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
1782 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
1783 wild_counter += 1;
1784 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
1785 attrs: vec![],
1786 by_ref: None,
1787 mutability: None,
1788 ident,
1789 subpat: None,
1790 });
1791 }
1792 }
1793 }
1794
1795 assert_eq!(wild_counter, 2);
1797
1798 let names: Vec<String> = dispatcher_inputs
1799 .iter()
1800 .filter_map(|arg| {
1801 if let FnArg::Typed(PatType { pat, .. }) = arg {
1802 if let syn::Pat::Ident(pi) = pat.as_ref() {
1803 return Some(pi.ident.to_string());
1804 }
1805 }
1806 None
1807 })
1808 .collect();
1809
1810 assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
1811 }
1812
1813 #[test]
1818 fn suffix_path_simple() {
1819 let path: syn::Path = syn::parse_str("process").unwrap();
1820 let suffixed = suffix_path(&path, "v3");
1821 assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
1822 }
1823
1824 #[test]
1825 fn suffix_path_qualified() {
1826 let path: syn::Path = syn::parse_str("module::process").unwrap();
1827 let suffixed = suffix_path(&path, "neon");
1828 let s = suffixed.to_token_stream().to_string();
1829 assert!(
1830 s.contains("process_neon"),
1831 "Expected process_neon, got: {}",
1832 s
1833 );
1834 }
1835}