use syn::{Ident, Token, parse::ParseStream};
pub(crate) struct TierDescriptor {
pub name: &'static str,
pub suffix: &'static str,
pub token_path: &'static str,
pub as_method: &'static str,
pub target_arch: Option<&'static str>,
pub cfg_feature: Option<&'static str>,
pub priority: u32,
}
pub(crate) const ALL_TIERS: &[TierDescriptor] = &[
TierDescriptor {
name: "v4x",
suffix: "v4x",
token_path: "archmage::X64V4xToken",
as_method: "as_x64v4x",
target_arch: Some("x86_64"),
cfg_feature: Some("avx512"),
priority: 50,
},
TierDescriptor {
name: "v4",
suffix: "v4",
token_path: "archmage::X64V4Token",
as_method: "as_x64v4",
target_arch: Some("x86_64"),
cfg_feature: Some("avx512"),
priority: 40,
},
TierDescriptor {
name: "v3_crypto",
suffix: "v3_crypto",
token_path: "archmage::X64V3CryptoToken",
as_method: "as_x64v3_crypto",
target_arch: Some("x86_64"),
cfg_feature: None,
priority: 35,
},
TierDescriptor {
name: "v3",
suffix: "v3",
token_path: "archmage::X64V3Token",
as_method: "as_x64v3",
target_arch: Some("x86_64"),
cfg_feature: None,
priority: 30,
},
TierDescriptor {
name: "x64_crypto",
suffix: "x64_crypto",
token_path: "archmage::X64CryptoToken",
as_method: "as_x64_crypto",
target_arch: Some("x86_64"),
cfg_feature: None,
priority: 25,
},
TierDescriptor {
name: "v2",
suffix: "v2",
token_path: "archmage::X64V2Token",
as_method: "as_x64v2",
target_arch: Some("x86_64"),
cfg_feature: None,
priority: 20,
},
TierDescriptor {
name: "v1",
suffix: "v1",
token_path: "archmage::X64V1Token",
as_method: "as_x64v1",
target_arch: Some("x86_64"),
cfg_feature: None,
priority: 10,
},
TierDescriptor {
name: "arm_v3",
suffix: "arm_v3",
token_path: "archmage::Arm64V3Token",
as_method: "as_arm_v3",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 50,
},
TierDescriptor {
name: "arm_v2",
suffix: "arm_v2",
token_path: "archmage::Arm64V2Token",
as_method: "as_arm_v2",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 40,
},
TierDescriptor {
name: "neon_aes",
suffix: "neon_aes",
token_path: "archmage::NeonAesToken",
as_method: "as_neon_aes",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 30,
},
TierDescriptor {
name: "neon_sha3",
suffix: "neon_sha3",
token_path: "archmage::NeonSha3Token",
as_method: "as_neon_sha3",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 30,
},
TierDescriptor {
name: "neon_crc",
suffix: "neon_crc",
token_path: "archmage::NeonCrcToken",
as_method: "as_neon_crc",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 30,
},
TierDescriptor {
name: "neon",
suffix: "neon",
token_path: "archmage::NeonToken",
as_method: "as_neon",
target_arch: Some("aarch64"),
cfg_feature: None,
priority: 20,
},
TierDescriptor {
name: "wasm128_relaxed",
suffix: "wasm128_relaxed",
token_path: "archmage::Wasm128RelaxedToken",
as_method: "as_wasm128_relaxed",
target_arch: Some("wasm32"),
cfg_feature: None,
priority: 21,
},
TierDescriptor {
name: "wasm128",
suffix: "wasm128",
token_path: "archmage::Wasm128Token",
as_method: "as_wasm128",
target_arch: Some("wasm32"),
cfg_feature: None,
priority: 20,
},
TierDescriptor {
name: "scalar",
suffix: "scalar",
token_path: "archmage::ScalarToken",
as_method: "as_scalar",
target_arch: None,
cfg_feature: None,
priority: 0,
},
TierDescriptor {
name: "default",
suffix: "default",
token_path: "", as_method: "", target_arch: None,
cfg_feature: None,
priority: 0,
},
];
pub(crate) const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
pub(crate) fn parse_tier_name_with_gate(ident: &Ident, input: ParseStream) -> syn::Result<String> {
if input.peek(syn::token::Paren) {
let paren_content;
syn::parenthesized!(paren_content in input);
let feat_name = if paren_content.peek(Ident) && paren_content.peek2(syn::token::Paren) {
let kw: Ident = paren_content.parse()?;
if kw != "cfg" {
return Err(syn::Error::new(
kw.span(),
format!("expected `cfg` in tier gate, got `{kw}`"),
));
}
let inner;
syn::parenthesized!(inner in paren_content);
let feat: Ident = inner.parse()?;
feat.to_string()
} else {
let feat: Ident = paren_content.parse()?;
feat.to_string()
};
Ok(format!("{}({})", ident, feat_name))
} else {
Ok(ident.to_string())
}
}
pub(crate) fn parse_one_tier(input: ParseStream) -> syn::Result<String> {
let prefix = if input.peek(Token![+]) {
let _: Token![+] = input.parse()?;
"+"
} else if input.peek(Token![-]) {
let _: Token![-] = input.parse()?;
"-"
} else {
""
};
let ident: Ident = input.parse()?;
let name = parse_tier_name_with_gate(&ident, input)?;
Ok(format!("{prefix}{name}"))
}
pub(crate) fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
let name = name.strip_prefix('_').unwrap_or(name);
ALL_TIERS.iter().find(|t| t.name == name)
}
#[derive(Clone)]
pub(crate) struct ResolvedTier {
pub tier: &'static TierDescriptor,
pub feature_gate: Option<String>,
pub allow_unexpected_cfg: bool,
}
impl core::ops::Deref for ResolvedTier {
type Target = TierDescriptor;
fn deref(&self) -> &TierDescriptor {
self.tier
}
}
pub(crate) fn resolve_tiers(
tier_names: &[String],
error_span: proc_macro2::Span,
default_feature_gates: bool,
) -> syn::Result<Vec<ResolvedTier>> {
let any_modifier = tier_names
.iter()
.any(|n| n.starts_with('+') || n.starts_with('-'));
let any_plain = tier_names
.iter()
.any(|n| !n.starts_with('+') && !n.starts_with('-'));
if any_modifier && any_plain {
return Err(syn::Error::new(
error_span,
"Cannot mix `+tier`/`-tier` (modify defaults) with plain `tier` (override).\n\
Use all `+`/`-` to modify defaults, or none to replace them entirely.",
));
}
let mut user_overrides: Vec<String> = Vec::new();
let effective_names: Vec<String> = if any_modifier {
let mut names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
for raw in tier_names {
let is_removal = raw.starts_with('-');
let stripped = raw
.strip_prefix('+')
.or_else(|| raw.strip_prefix('-'))
.unwrap_or(raw);
let base = stripped.split('(').next().unwrap_or(stripped);
let base = base.strip_prefix('_').unwrap_or(base);
let is_fallback = base == "default" || base == "scalar";
let pos = names.iter().position(|n| {
let n_base = n.split('(').next().unwrap_or(n);
if is_fallback {
n_base == "default" || n_base == "scalar"
} else {
n_base == base
}
});
if is_removal {
if let Some(pos) = pos {
names.remove(pos);
}
} else if let Some(pos) = pos {
names[pos] = stripped.to_string();
user_overrides.push(base.to_string());
} else {
names.push(stripped.to_string());
user_overrides.push(base.to_string());
}
}
names
} else {
tier_names.to_vec()
};
let mut tiers = Vec::new();
for raw_name in &effective_names {
let (name, explicit_gate) = if let Some(paren_pos) = raw_name.find('(') {
let tier_name = &raw_name[..paren_pos];
let feat = raw_name[paren_pos + 1..].trim_end_matches(')');
(tier_name, Some(feat.to_string()))
} else {
(raw_name.as_str(), None)
};
match find_tier(name) {
Some(tier) => {
let is_explicit = explicit_gate.is_some();
let is_user_override = user_overrides.iter().any(|o| o == tier.name);
let feature_gate = explicit_gate.or_else(|| {
if default_feature_gates && !is_user_override {
tier.cfg_feature.map(String::from)
} else {
None
}
});
tiers.push(ResolvedTier {
tier,
allow_unexpected_cfg: feature_gate.is_some() && !is_explicit,
feature_gate,
});
}
None => {
let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
return Err(syn::Error::new(
error_span,
format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
));
}
}
}
let has_scalar = tiers.iter().any(|rt| rt.tier.name == "scalar");
let has_default = tiers.iter().any(|rt| rt.tier.name == "default");
if has_scalar && has_default {
return Err(syn::Error::new(
error_span,
"`scalar` and `default` are mutually exclusive fallback tiers. \
Use `scalar` (takes ScalarToken) or `default` (tokenless).",
));
}
if !has_scalar && !has_default {
tiers.push(ResolvedTier {
tier: find_tier("scalar").unwrap(),
feature_gate: None,
allow_unexpected_cfg: false,
});
}
tiers.sort_by_key(|rt| core::cmp::Reverse(rt.tier.priority));
Ok(tiers)
}