use std::collections::HashSet;
use std::sync::Arc;
use crate::path_mapping::PathMappingRule;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ExprRevision {
V2026_02,
}
impl ExprRevision {
pub const CURRENT: ExprRevision = ExprRevision::V2026_02;
}
impl Default for ExprRevision {
fn default() -> Self {
ExprRevision::CURRENT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ExprExtension {}
impl ExprExtension {
pub const ALL: &'static [ExprExtension] = &[];
}
#[derive(Debug, Clone, Default)]
pub enum HostContext {
#[default]
None,
Unresolved,
WithRules(Arc<Vec<PathMappingRule>>),
}
impl HostContext {
pub fn with_rules(rules: Vec<PathMappingRule>) -> Self {
HostContext::WithRules(Arc::new(rules))
}
pub fn is_enabled(&self) -> bool {
!matches!(self, HostContext::None)
}
pub fn is_unresolved(&self) -> bool {
matches!(self, HostContext::Unresolved)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub(crate) enum SyntaxFeature {
Walrus,
Lambda,
TupleLiteral,
DictLiteral,
SetLiteral,
DictComprehension,
SetComprehension,
GeneratorExpression,
FString,
Ellipsis,
Starred,
Await,
UnicodeStringPrefix,
BytesLiteral,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
BitwiseNot,
LeftShift,
RightShift,
MatMult,
IsOperator,
IsNotOperator,
KeywordArguments,
MultipleForClauses,
TupleUnpackingInComprehension,
MultipleIfClauses,
}
#[derive(Debug, Clone)]
pub struct ExprProfile {
revision: ExprRevision,
extensions: HashSet<ExprExtension>,
host_context: HostContext,
}
impl ExprProfile {
pub fn new(revision: ExprRevision) -> Self {
Self {
revision,
extensions: HashSet::new(),
host_context: HostContext::None,
}
}
pub fn current() -> Self {
Self::new(ExprRevision::CURRENT)
}
pub fn latest() -> Self {
Self {
revision: ExprRevision::CURRENT,
extensions: ExprExtension::ALL.iter().copied().collect(),
host_context: HostContext::None,
}
}
#[must_use]
pub fn with_extensions(mut self, extensions: HashSet<ExprExtension>) -> Self {
self.extensions = extensions;
self
}
#[must_use]
pub fn with_host_context(mut self, host_context: HostContext) -> Self {
self.host_context = host_context;
self
}
pub fn revision(&self) -> ExprRevision {
self.revision
}
pub fn extensions(&self) -> &HashSet<ExprExtension> {
&self.extensions
}
pub fn host_context(&self) -> &HostContext {
&self.host_context
}
pub fn has_extension(&self, ext: ExprExtension) -> bool {
self.extensions.contains(&ext)
}
pub(crate) fn allows_syntax(&self, feature: SyntaxFeature) -> bool {
let baseline_allows = match self.revision {
ExprRevision::V2026_02 => Self::baseline_syntax_v2026_02(feature),
};
if baseline_allows {
return true;
}
match self.revision {
ExprRevision::V2026_02 => self.extension_syntax_v2026_02(feature),
}
}
fn baseline_syntax_v2026_02(feature: SyntaxFeature) -> bool {
match feature {
SyntaxFeature::Walrus
| SyntaxFeature::Lambda
| SyntaxFeature::TupleLiteral
| SyntaxFeature::DictLiteral
| SyntaxFeature::SetLiteral
| SyntaxFeature::DictComprehension
| SyntaxFeature::SetComprehension
| SyntaxFeature::GeneratorExpression
| SyntaxFeature::FString
| SyntaxFeature::Ellipsis
| SyntaxFeature::Starred
| SyntaxFeature::Await
| SyntaxFeature::UnicodeStringPrefix
| SyntaxFeature::BytesLiteral
| SyntaxFeature::BitwiseAnd
| SyntaxFeature::BitwiseOr
| SyntaxFeature::BitwiseXor
| SyntaxFeature::BitwiseNot
| SyntaxFeature::LeftShift
| SyntaxFeature::RightShift
| SyntaxFeature::MatMult
| SyntaxFeature::IsOperator
| SyntaxFeature::IsNotOperator
| SyntaxFeature::KeywordArguments
| SyntaxFeature::MultipleForClauses
| SyntaxFeature::TupleUnpackingInComprehension
| SyntaxFeature::MultipleIfClauses => false,
}
}
#[allow(clippy::unused_self)] #[allow(clippy::never_loop)] fn extension_syntax_v2026_02(&self, feature: SyntaxFeature) -> bool {
for ext in &self.extensions {
match *ext {
}
}
let _ = feature; false
}
pub(crate) fn cache_key(&self) -> ProfileKey {
ProfileKey {
revision: self.revision,
extensions: {
let mut v: Vec<ExprExtension> = self.extensions.iter().copied().collect();
v.sort_by_key(|e| {
format!("{:?}", e)
});
v
},
host_kind: HostKind::from(&self.host_context),
}
}
}
impl Default for ExprProfile {
fn default() -> Self {
Self::current()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct ProfileKey {
pub(crate) revision: ExprRevision,
pub(crate) extensions: Vec<ExprExtension>,
pub(crate) host_kind: HostKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum HostKind {
None,
Unresolved,
WithRules,
}
impl From<&HostContext> for HostKind {
fn from(h: &HostContext) -> Self {
match h {
HostContext::None => HostKind::None,
HostContext::Unresolved => HostKind::Unresolved,
HostContext::WithRules(_) => HostKind::WithRules,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_profile_is_current() {
let p = ExprProfile::default();
assert_eq!(p.revision(), ExprRevision::CURRENT);
assert!(p.extensions().is_empty());
assert!(matches!(p.host_context(), HostContext::None));
}
#[test]
fn current_matches_v2026_02() {
assert_eq!(ExprRevision::CURRENT, ExprRevision::V2026_02);
}
#[test]
fn with_host_context_unresolved() {
let p = ExprProfile::current().with_host_context(HostContext::Unresolved);
assert!(p.host_context().is_enabled());
assert!(p.host_context().is_unresolved());
}
#[test]
fn with_host_context_rules() {
let rules = vec![];
let p = ExprProfile::current().with_host_context(HostContext::with_rules(rules));
assert!(p.host_context().is_enabled());
assert!(!p.host_context().is_unresolved());
}
#[test]
fn cache_key_ignores_rules_content() {
use crate::path_mapping::{PathFormat, PathMappingRule};
let r1 = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/a".into(),
destination_path: "/b".into(),
};
let r2 = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/c".into(),
destination_path: "/d".into(),
};
let p1 = ExprProfile::current().with_host_context(HostContext::with_rules(vec![r1]));
let p2 = ExprProfile::current().with_host_context(HostContext::with_rules(vec![r2]));
assert_eq!(p1.cache_key(), p2.cache_key());
}
#[test]
fn cache_key_distinguishes_host_kinds() {
let a = ExprProfile::current().cache_key(); let b = ExprProfile::current()
.with_host_context(HostContext::Unresolved)
.cache_key();
let c = ExprProfile::current()
.with_host_context(HostContext::with_rules(vec![]))
.cache_key();
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
}
#[test]
fn latest_enables_all_extensions() {
let p = ExprProfile::latest();
assert_eq!(p.revision(), ExprRevision::CURRENT);
for ext in ExprExtension::ALL {
assert!(
p.has_extension(*ext),
"ExprProfile::latest() must enable every extension in ExprExtension::ALL; missing {ext:?}"
);
}
assert_eq!(p.extensions().len(), ExprExtension::ALL.len());
assert!(matches!(p.host_context(), HostContext::None));
}
#[test]
fn v2026_02_rejects_every_syntax_feature() {
let p = ExprProfile::new(ExprRevision::V2026_02);
let all_features = [
SyntaxFeature::Walrus,
SyntaxFeature::Lambda,
SyntaxFeature::TupleLiteral,
SyntaxFeature::DictLiteral,
SyntaxFeature::SetLiteral,
SyntaxFeature::DictComprehension,
SyntaxFeature::SetComprehension,
SyntaxFeature::GeneratorExpression,
SyntaxFeature::FString,
SyntaxFeature::Ellipsis,
SyntaxFeature::Starred,
SyntaxFeature::Await,
SyntaxFeature::UnicodeStringPrefix,
SyntaxFeature::BytesLiteral,
SyntaxFeature::BitwiseAnd,
SyntaxFeature::BitwiseOr,
SyntaxFeature::BitwiseXor,
SyntaxFeature::BitwiseNot,
SyntaxFeature::LeftShift,
SyntaxFeature::RightShift,
SyntaxFeature::MatMult,
SyntaxFeature::IsOperator,
SyntaxFeature::IsNotOperator,
SyntaxFeature::KeywordArguments,
SyntaxFeature::MultipleForClauses,
SyntaxFeature::TupleUnpackingInComprehension,
SyntaxFeature::MultipleIfClauses,
];
for f in all_features {
assert!(
!p.allows_syntax(f),
"Under V2026_02, SyntaxFeature::{f:?} must be rejected"
);
}
}
#[test]
fn latest_rejects_same_features_as_current_for_v2026_02() {
let cur = ExprProfile::current();
let lat = ExprProfile::latest();
assert!(!cur.allows_syntax(SyntaxFeature::Lambda));
assert!(!lat.allows_syntax(SyntaxFeature::Lambda));
}
#[test]
fn extension_layer_does_not_reject_baseline_allowed_features() {
let p_no_ext = ExprProfile::current();
let p_all_ext = ExprProfile::latest();
for f in [
SyntaxFeature::Walrus,
SyntaxFeature::Lambda,
SyntaxFeature::DictLiteral,
SyntaxFeature::SetLiteral,
SyntaxFeature::FString,
SyntaxFeature::KeywordArguments,
] {
assert_eq!(p_no_ext.allows_syntax(f), p_all_ext.allows_syntax(f));
}
}
}