1use std::collections::HashMap;
32use std::fmt;
33
34use super::ast::{AlterCategory, AlterDef, AlterSource, AlterState};
35use crate::ast::{Evidentiality, Ident, TypeExpr};
36use crate::span::Span;
37
38#[derive(Debug, Clone, PartialEq)]
44pub struct PluralType {
45 pub base: TypeExpr,
47 pub evidentiality: Option<Evidentiality>,
49 pub alter_source: Option<AlterSource>,
51 pub in_alter_context: bool,
53 pub span: Span,
55}
56
57impl PluralType {
58 pub fn from_type(base: TypeExpr, span: Span) -> Self {
60 Self {
61 base,
62 evidentiality: None,
63 alter_source: None,
64 in_alter_context: false,
65 span,
66 }
67 }
68
69 pub fn with_alter_source(mut self, source: AlterSource) -> Self {
71 self.alter_source = Some(source);
72 self
73 }
74
75 pub fn with_evidentiality(mut self, ev: Evidentiality) -> Self {
77 self.evidentiality = Some(ev);
78 self
79 }
80
81 pub fn is_fronting(&self) -> bool {
83 matches!(self.alter_source, Some(AlterSource::Fronting))
84 }
85
86 pub fn is_cocon(&self) -> bool {
88 matches!(self.alter_source, Some(AlterSource::CoConscious(_)))
89 }
90
91 pub fn is_dormant(&self) -> bool {
93 matches!(self.alter_source, Some(AlterSource::Dormant(_)))
94 }
95
96 pub fn is_blended(&self) -> bool {
98 matches!(self.alter_source, Some(AlterSource::Blended(_)))
99 }
100}
101
102#[derive(Debug, Clone)]
108pub struct AlterContext {
109 pub current_alter: Option<Ident>,
111 pub alter_stack: Vec<Ident>,
113 pub alter_defs: HashMap<String, AlterDef>,
115 pub current_category: Option<AlterCategory>,
117 pub current_state: Option<AlterState>,
119}
120
121impl Default for AlterContext {
122 fn default() -> Self {
123 Self {
124 current_alter: None,
125 alter_stack: Vec::new(),
126 alter_defs: HashMap::new(),
127 current_category: None,
128 current_state: None,
129 }
130 }
131}
132
133impl AlterContext {
134 pub fn new() -> Self {
136 Self::default()
137 }
138
139 pub fn register_alter(&mut self, def: AlterDef) {
141 self.alter_defs.insert(def.name.name.clone(), def);
142 }
143
144 pub fn enter_alter(&mut self, alter: Ident) {
146 if let Some(current) = &self.current_alter {
147 self.alter_stack.push(current.clone());
148 }
149 self.current_alter = Some(alter.clone());
150
151 if let Some(def) = self.alter_defs.get(&alter.name) {
153 self.current_category = Some(def.category);
154 }
155 }
156
157 pub fn exit_alter(&mut self) {
159 self.current_alter = self.alter_stack.pop();
160 self.current_category = self.current_alter.as_ref().and_then(|a| {
161 self.alter_defs
162 .get(&a.name)
163 .map(|def| def.category)
164 });
165 }
166
167 pub fn in_alter_block(&self) -> bool {
169 self.current_alter.is_some()
170 }
171
172 pub fn fronter_name(&self) -> Option<&str> {
174 self.current_alter.as_ref().map(|a| a.name.as_str())
175 }
176
177 pub fn can_front(&self, alter_name: &str) -> bool {
179 if let Some(def) = self.alter_defs.get(alter_name) {
180 matches!(def.category, AlterCategory::Council)
181 } else {
182 true }
184 }
185
186 pub fn validate_switch(&self, target: &str) -> SwitchValidation {
188 if let Some(def) = self.alter_defs.get(target) {
189 match def.category {
190 AlterCategory::Council => SwitchValidation::Allowed,
191 AlterCategory::Servant => SwitchValidation::Denied {
192 reason: "Servants cannot front".to_string(),
193 },
194 AlterCategory::Fragment => SwitchValidation::Warning {
195 reason: "Fragments are unstable and may not maintain fronting".to_string(),
196 },
197 AlterCategory::Hidden => SwitchValidation::Warning {
198 reason: "Hidden alters may not respond to switch requests".to_string(),
199 },
200 AlterCategory::Persecutor => SwitchValidation::Warning {
201 reason: "Switching to a Persecutor may be destabilizing".to_string(),
202 },
203 AlterCategory::Custom => SwitchValidation::Allowed,
204 }
205 } else {
206 SwitchValidation::Unknown {
207 alter: target.to_string(),
208 }
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq)]
215pub enum SwitchValidation {
216 Allowed,
217 Warning { reason: String },
218 Denied { reason: String },
219 Unknown { alter: String },
220}
221
222#[derive(Debug, Clone, Copy, PartialEq)]
228pub enum AlterSourceCompatibility {
229 Compatible,
231 CoercibleWith(AlterSourceCoercion),
233 Incompatible,
235 RequiresResolution,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq)]
241pub enum AlterSourceCoercion {
242 UpgradeCertainty,
244 DowngradeCertainty,
246 TransferAlter,
248 Blend,
250}
251
252pub fn check_compatibility(from: &PluralType, to: &PluralType) -> AlterSourceCompatibility {
254 if from.alter_source == to.alter_source {
256 return AlterSourceCompatibility::Compatible;
257 }
258
259 match (&from.alter_source, &to.alter_source) {
260 (Some(AlterSource::Fronting), _) => AlterSourceCompatibility::Compatible,
262
263 (Some(AlterSource::CoConscious(_)), Some(AlterSource::Fronting)) => {
265 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
266 }
267
268 (Some(AlterSource::CoConscious(_)), Some(AlterSource::CoConscious(_))) => {
270 AlterSourceCompatibility::Compatible
271 }
272
273 (Some(AlterSource::CoConscious(_)), Some(AlterSource::Dormant(_))) => {
275 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::DowngradeCertainty)
276 }
277
278 (Some(AlterSource::Dormant(_)), Some(AlterSource::Dormant(_))) => {
280 AlterSourceCompatibility::Compatible
281 }
282
283 (Some(AlterSource::Dormant(_)), _) => AlterSourceCompatibility::Incompatible,
285
286 (Some(AlterSource::Blended(_)), _) | (_, Some(AlterSource::Blended(_))) => {
288 AlterSourceCompatibility::RequiresResolution
289 }
290
291 (Some(AlterSource::Named(_)), Some(AlterSource::Fronting)) => {
293 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::TransferAlter)
294 }
295
296 (Some(AlterSource::Bound(_)), _) => {
298 AlterSourceCompatibility::Compatible
300 }
301
302 (None, _) | (_, None) => AlterSourceCompatibility::Compatible,
304
305 _ => AlterSourceCompatibility::Incompatible,
307 }
308}
309
310#[derive(Debug, Clone)]
316pub enum PluralityTypeError {
317 DormantAccessWithoutVerification {
319 alter: String,
320 data_type: String,
321 span: Span,
322 },
323
324 UnresolvedBlendedState {
326 alters: Vec<String>,
327 data_type: String,
328 span: Span,
329 },
330
331 ServantCannotFront {
333 alter: String,
334 span: Span,
335 },
336
337 AlterNotFound {
339 name: String,
340 span: Span,
341 },
342
343 SwitchDenied {
345 target: String,
346 reason: String,
347 span: Span,
348 },
349
350 AlterSourceMismatch {
352 expected: String,
353 found: String,
354 span: Span,
355 },
356
357 CoCOnNotActive {
359 alter: String,
360 span: Span,
361 },
362
363 RealityLayerMismatch {
365 expected: String,
366 found: String,
367 span: Span,
368 },
369
370 SplitSourceNotFound {
372 alter: String,
373 span: Span,
374 },
375
376 TriggerNotDefined {
378 trigger: String,
379 span: Span,
380 },
381}
382
383impl fmt::Display for PluralityTypeError {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 match self {
386 PluralityTypeError::DormantAccessWithoutVerification { alter, data_type, .. } => {
387 write!(
388 f,
389 "Cannot access dormant alter '{}' data of type '{}' without verification. \
390 Use `verify_access` or add `@?` uncertainty marker.",
391 alter, data_type
392 )
393 }
394 PluralityTypeError::UnresolvedBlendedState { alters, data_type, .. } => {
395 write!(
396 f,
397 "Blended state from alters [{}] for type '{}' must be resolved. \
398 Use `resolve_blend` or explicit alter selection.",
399 alters.join(", "),
400 data_type
401 )
402 }
403 PluralityTypeError::ServantCannotFront { alter, .. } => {
404 write!(
405 f,
406 "Alter '{}' is a Servant and cannot front. \
407 Servants maintain internal functions only.",
408 alter
409 )
410 }
411 PluralityTypeError::AlterNotFound { name, .. } => {
412 write!(f, "Alter '{}' not found in system definition.", name)
413 }
414 PluralityTypeError::SwitchDenied { target, reason, .. } => {
415 write!(f, "Switch to '{}' denied: {}", target, reason)
416 }
417 PluralityTypeError::AlterSourceMismatch { expected, found, .. } => {
418 write!(
419 f,
420 "Alter-source mismatch: expected '{}', found '{}'",
421 expected, found
422 )
423 }
424 PluralityTypeError::CoCOnNotActive { alter, .. } => {
425 write!(
426 f,
427 "Alter '{}' is not co-conscious and cannot participate in channel.",
428 alter
429 )
430 }
431 PluralityTypeError::RealityLayerMismatch { expected, found, .. } => {
432 write!(
433 f,
434 "Reality layer mismatch: expected '{}', found '{}'",
435 expected, found
436 )
437 }
438 PluralityTypeError::SplitSourceNotFound { alter, .. } => {
439 write!(f, "Cannot split from '{}': alter not found.", alter)
440 }
441 PluralityTypeError::TriggerNotDefined { trigger, .. } => {
442 write!(f, "Trigger '{}' is not defined in system.", trigger)
443 }
444 }
445 }
446}
447
448impl std::error::Error for PluralityTypeError {}
449
450#[derive(Debug)]
456pub struct PluralityTypeChecker {
457 context: AlterContext,
459 errors: Vec<PluralityTypeError>,
461 warnings: Vec<String>,
463}
464
465impl Default for PluralityTypeChecker {
466 fn default() -> Self {
467 Self::new()
468 }
469}
470
471impl PluralityTypeChecker {
472 pub fn new() -> Self {
474 Self {
475 context: AlterContext::new(),
476 errors: Vec::new(),
477 warnings: Vec::new(),
478 }
479 }
480
481 pub fn register_alter(&mut self, def: AlterDef) {
483 self.context.register_alter(def);
484 }
485
486 pub fn enter_alter_block(&mut self, alter: &Ident) {
488 self.context.enter_alter(alter.clone());
489 }
490
491 pub fn exit_alter_block(&mut self) {
493 self.context.exit_alter();
494 }
495
496 pub fn in_alter_block(&self) -> bool {
498 self.context.in_alter_block()
499 }
500
501 pub fn validate_switch(&mut self, target: &str, span: Span) -> bool {
503 match self.context.validate_switch(target) {
504 SwitchValidation::Allowed => true,
505 SwitchValidation::Warning { reason } => {
506 self.warnings.push(format!("Switch to '{}': {}", target, reason));
507 true
508 }
509 SwitchValidation::Denied { reason } => {
510 self.errors.push(PluralityTypeError::SwitchDenied {
511 target: target.to_string(),
512 reason,
513 span,
514 });
515 false
516 }
517 SwitchValidation::Unknown { alter } => {
518 self.warnings.push(format!(
519 "Switch to unknown alter '{}'. Consider defining it.",
520 alter
521 ));
522 true
523 }
524 }
525 }
526
527 pub fn check_assignment(
529 &mut self,
530 from: &PluralType,
531 to: &PluralType,
532 span: Span,
533 ) -> bool {
534 match check_compatibility(from, to) {
535 AlterSourceCompatibility::Compatible => true,
536 AlterSourceCompatibility::CoercibleWith(coercion) => {
537 match coercion {
538 AlterSourceCoercion::UpgradeCertainty => {
539 self.warnings.push(format!(
540 "Implicit certainty upgrade from co-conscious to fronting at {:?}",
541 span
542 ));
543 }
544 AlterSourceCoercion::DowngradeCertainty => {
545 self.warnings.push(format!(
546 "Certainty downgrade - data may be less reliable at {:?}",
547 span
548 ));
549 }
550 AlterSourceCoercion::TransferAlter => {
551 self.warnings.push(format!(
552 "Cross-alter data transfer at {:?}",
553 span
554 ));
555 }
556 AlterSourceCoercion::Blend => {
557 self.warnings.push(format!(
558 "Blending data from multiple alters at {:?}",
559 span
560 ));
561 }
562 }
563 true
564 }
565 AlterSourceCompatibility::Incompatible => {
566 let from_str = format!("{:?}", from.alter_source);
567 let to_str = format!("{:?}", to.alter_source);
568 self.errors.push(PluralityTypeError::AlterSourceMismatch {
569 expected: to_str,
570 found: from_str,
571 span,
572 });
573 false
574 }
575 AlterSourceCompatibility::RequiresResolution => {
576 if let Some(AlterSource::Blended(alters)) = &from.alter_source {
577 self.errors.push(PluralityTypeError::UnresolvedBlendedState {
578 alters: alters.iter().map(|a| a.name.clone()).collect(),
579 data_type: format!("{:?}", from.base),
580 span,
581 });
582 }
583 false
584 }
585 }
586 }
587
588 pub fn check_dormant_access(&mut self, alter: &str, data_type: &str, span: Span) -> bool {
590 if self.context.in_alter_block() {
592 if let Some(fronter) = self.context.fronter_name() {
593 if fronter != alter {
594 self.errors.push(PluralityTypeError::DormantAccessWithoutVerification {
595 alter: alter.to_string(),
596 data_type: data_type.to_string(),
597 span,
598 });
599 return false;
600 }
601 }
602 }
603 true
604 }
605
606 pub fn errors(&self) -> &[PluralityTypeError] {
608 &self.errors
609 }
610
611 pub fn warnings(&self) -> &[String] {
613 &self.warnings
614 }
615
616 pub fn has_errors(&self) -> bool {
618 !self.errors.is_empty()
619 }
620
621 pub fn clear(&mut self) {
623 self.errors.clear();
624 self.warnings.clear();
625 }
626}
627
628#[cfg(test)]
633mod tests {
634 use super::*;
635 use crate::ast::TypeExpr;
636
637 fn mock_alter_def(name: &str, category: AlterCategory) -> AlterDef {
638 AlterDef {
639 visibility: crate::ast::Visibility::Public,
640 attrs: Vec::new(),
641 name: Ident {
642 name: name.to_string(),
643 evidentiality: None,
644 affect: None,
645 span: Span::default(),
646 },
647 category,
648 generics: None,
649 where_clause: None,
650 body: super::super::ast::AlterBody {
651 archetype: None,
652 preferred_reality: None,
653 abilities: Vec::new(),
654 triggers: Vec::new(),
655 anima: None,
656 states: None,
657 special: Vec::new(),
658 methods: Vec::new(),
659 types: Vec::new(),
660 },
661 span: Span::default(),
662 }
663 }
664
665 #[test]
666 fn test_alter_context_registration() {
667 let mut ctx = AlterContext::new();
668 let def = mock_alter_def("Abaddon", AlterCategory::Council);
669 ctx.register_alter(def);
670
671 assert!(ctx.can_front("Abaddon"));
672 }
673
674 #[test]
675 fn test_servant_cannot_front() {
676 let mut ctx = AlterContext::new();
677 let def = mock_alter_def("Watcher", AlterCategory::Servant);
678 ctx.register_alter(def);
679
680 assert!(!ctx.can_front("Watcher"));
681 }
682
683 #[test]
684 fn test_switch_validation() {
685 let mut ctx = AlterContext::new();
686 ctx.register_alter(mock_alter_def("Abaddon", AlterCategory::Council));
687 ctx.register_alter(mock_alter_def("Watcher", AlterCategory::Servant));
688
689 assert_eq!(ctx.validate_switch("Abaddon"), SwitchValidation::Allowed);
690 assert!(matches!(
691 ctx.validate_switch("Watcher"),
692 SwitchValidation::Denied { .. }
693 ));
694 }
695
696 #[test]
697 fn test_alter_source_compatibility() {
698 let fronting_type = PluralType {
699 base: TypeExpr::Path(crate::ast::TypePath {
700 segments: vec![],
701 }),
702 evidentiality: None,
703 alter_source: Some(AlterSource::Fronting),
704 in_alter_context: true,
705 span: Span::default(),
706 };
707
708 let cocon_type = PluralType {
709 base: TypeExpr::Path(crate::ast::TypePath {
710 segments: vec![],
711 }),
712 evidentiality: None,
713 alter_source: Some(AlterSource::CoConscious(None)),
714 in_alter_context: true,
715 span: Span::default(),
716 };
717
718 assert_eq!(
720 check_compatibility(&fronting_type, &cocon_type),
721 AlterSourceCompatibility::Compatible
722 );
723
724 assert!(matches!(
726 check_compatibility(&cocon_type, &fronting_type),
727 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
728 ));
729 }
730
731 #[test]
732 fn test_blended_requires_resolution() {
733 let blended = PluralType {
734 base: TypeExpr::Path(crate::ast::TypePath {
735 segments: vec![],
736 }),
737 evidentiality: None,
738 alter_source: Some(AlterSource::Blended(Vec::new())),
739 in_alter_context: true,
740 span: Span::default(),
741 };
742
743 let fronting = PluralType {
744 base: TypeExpr::Path(crate::ast::TypePath {
745 segments: vec![],
746 }),
747 evidentiality: None,
748 alter_source: Some(AlterSource::Fronting),
749 in_alter_context: true,
750 span: Span::default(),
751 };
752
753 assert_eq!(
754 check_compatibility(&blended, &fronting),
755 AlterSourceCompatibility::RequiresResolution
756 );
757 }
758}