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
161 .current_alter
162 .as_ref()
163 .and_then(|a| self.alter_defs.get(&a.name).map(|def| def.category));
164 }
165
166 pub fn in_alter_block(&self) -> bool {
168 self.current_alter.is_some()
169 }
170
171 pub fn fronter_name(&self) -> Option<&str> {
173 self.current_alter.as_ref().map(|a| a.name.as_str())
174 }
175
176 pub fn can_front(&self, alter_name: &str) -> bool {
178 if let Some(def) = self.alter_defs.get(alter_name) {
179 matches!(def.category, AlterCategory::Council)
180 } else {
181 true }
183 }
184
185 pub fn validate_switch(&self, target: &str) -> SwitchValidation {
187 if let Some(def) = self.alter_defs.get(target) {
188 match def.category {
189 AlterCategory::Council => SwitchValidation::Allowed,
190 AlterCategory::Servant => SwitchValidation::Denied {
191 reason: "Servants cannot front".to_string(),
192 },
193 AlterCategory::Fragment => SwitchValidation::Warning {
194 reason: "Fragments are unstable and may not maintain fronting".to_string(),
195 },
196 AlterCategory::Hidden => SwitchValidation::Warning {
197 reason: "Hidden alters may not respond to switch requests".to_string(),
198 },
199 AlterCategory::Persecutor => SwitchValidation::Warning {
200 reason: "Switching to a Persecutor may be destabilizing".to_string(),
201 },
202 AlterCategory::Custom => SwitchValidation::Allowed,
203 }
204 } else {
205 SwitchValidation::Unknown {
206 alter: target.to_string(),
207 }
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
214pub enum SwitchValidation {
215 Allowed,
216 Warning { reason: String },
217 Denied { reason: String },
218 Unknown { alter: String },
219}
220
221#[derive(Debug, Clone, Copy, PartialEq)]
227pub enum AlterSourceCompatibility {
228 Compatible,
230 CoercibleWith(AlterSourceCoercion),
232 Incompatible,
234 RequiresResolution,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq)]
240pub enum AlterSourceCoercion {
241 UpgradeCertainty,
243 DowngradeCertainty,
245 TransferAlter,
247 Blend,
249}
250
251pub fn check_compatibility(from: &PluralType, to: &PluralType) -> AlterSourceCompatibility {
253 if from.alter_source == to.alter_source {
255 return AlterSourceCompatibility::Compatible;
256 }
257
258 match (&from.alter_source, &to.alter_source) {
259 (Some(AlterSource::Fronting), _) => AlterSourceCompatibility::Compatible,
261
262 (Some(AlterSource::CoConscious(_)), Some(AlterSource::Fronting)) => {
264 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
265 }
266
267 (Some(AlterSource::CoConscious(_)), Some(AlterSource::CoConscious(_))) => {
269 AlterSourceCompatibility::Compatible
270 }
271
272 (Some(AlterSource::CoConscious(_)), Some(AlterSource::Dormant(_))) => {
274 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::DowngradeCertainty)
275 }
276
277 (Some(AlterSource::Dormant(_)), Some(AlterSource::Dormant(_))) => {
279 AlterSourceCompatibility::Compatible
280 }
281
282 (Some(AlterSource::Dormant(_)), _) => AlterSourceCompatibility::Incompatible,
284
285 (Some(AlterSource::Blended(_)), _) | (_, Some(AlterSource::Blended(_))) => {
287 AlterSourceCompatibility::RequiresResolution
288 }
289
290 (Some(AlterSource::Named(_)), Some(AlterSource::Fronting)) => {
292 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::TransferAlter)
293 }
294
295 (Some(AlterSource::Bound(_)), _) => {
297 AlterSourceCompatibility::Compatible
299 }
300
301 (None, _) | (_, None) => AlterSourceCompatibility::Compatible,
303
304 _ => AlterSourceCompatibility::Incompatible,
306 }
307}
308
309#[derive(Debug, Clone)]
315pub enum PluralityTypeError {
316 DormantAccessWithoutVerification {
318 alter: String,
319 data_type: String,
320 span: Span,
321 },
322
323 UnresolvedBlendedState {
325 alters: Vec<String>,
326 data_type: String,
327 span: Span,
328 },
329
330 ServantCannotFront { alter: String, span: Span },
332
333 AlterNotFound { name: String, span: Span },
335
336 SwitchDenied {
338 target: String,
339 reason: String,
340 span: Span,
341 },
342
343 AlterSourceMismatch {
345 expected: String,
346 found: String,
347 span: Span,
348 },
349
350 CoCOnNotActive { alter: String, span: Span },
352
353 RealityLayerMismatch {
355 expected: String,
356 found: String,
357 span: Span,
358 },
359
360 SplitSourceNotFound { alter: String, span: Span },
362
363 TriggerNotDefined { trigger: String, span: Span },
365}
366
367impl fmt::Display for PluralityTypeError {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 match self {
370 PluralityTypeError::DormantAccessWithoutVerification {
371 alter, data_type, ..
372 } => {
373 write!(
374 f,
375 "Cannot access dormant alter '{}' data of type '{}' without verification. \
376 Use `verify_access` or add `@?` uncertainty marker.",
377 alter, data_type
378 )
379 }
380 PluralityTypeError::UnresolvedBlendedState {
381 alters, data_type, ..
382 } => {
383 write!(
384 f,
385 "Blended state from alters [{}] for type '{}' must be resolved. \
386 Use `resolve_blend` or explicit alter selection.",
387 alters.join(", "),
388 data_type
389 )
390 }
391 PluralityTypeError::ServantCannotFront { alter, .. } => {
392 write!(
393 f,
394 "Alter '{}' is a Servant and cannot front. \
395 Servants maintain internal functions only.",
396 alter
397 )
398 }
399 PluralityTypeError::AlterNotFound { name, .. } => {
400 write!(f, "Alter '{}' not found in system definition.", name)
401 }
402 PluralityTypeError::SwitchDenied { target, reason, .. } => {
403 write!(f, "Switch to '{}' denied: {}", target, reason)
404 }
405 PluralityTypeError::AlterSourceMismatch {
406 expected, found, ..
407 } => {
408 write!(
409 f,
410 "Alter-source mismatch: expected '{}', found '{}'",
411 expected, found
412 )
413 }
414 PluralityTypeError::CoCOnNotActive { alter, .. } => {
415 write!(
416 f,
417 "Alter '{}' is not co-conscious and cannot participate in channel.",
418 alter
419 )
420 }
421 PluralityTypeError::RealityLayerMismatch {
422 expected, found, ..
423 } => {
424 write!(
425 f,
426 "Reality layer mismatch: expected '{}', found '{}'",
427 expected, found
428 )
429 }
430 PluralityTypeError::SplitSourceNotFound { alter, .. } => {
431 write!(f, "Cannot split from '{}': alter not found.", alter)
432 }
433 PluralityTypeError::TriggerNotDefined { trigger, .. } => {
434 write!(f, "Trigger '{}' is not defined in system.", trigger)
435 }
436 }
437 }
438}
439
440impl std::error::Error for PluralityTypeError {}
441
442#[derive(Debug)]
448pub struct PluralityTypeChecker {
449 context: AlterContext,
451 errors: Vec<PluralityTypeError>,
453 warnings: Vec<String>,
455}
456
457impl Default for PluralityTypeChecker {
458 fn default() -> Self {
459 Self::new()
460 }
461}
462
463impl PluralityTypeChecker {
464 pub fn new() -> Self {
466 Self {
467 context: AlterContext::new(),
468 errors: Vec::new(),
469 warnings: Vec::new(),
470 }
471 }
472
473 pub fn register_alter(&mut self, def: AlterDef) {
475 self.context.register_alter(def);
476 }
477
478 pub fn enter_alter_block(&mut self, alter: &Ident) {
480 self.context.enter_alter(alter.clone());
481 }
482
483 pub fn exit_alter_block(&mut self) {
485 self.context.exit_alter();
486 }
487
488 pub fn in_alter_block(&self) -> bool {
490 self.context.in_alter_block()
491 }
492
493 pub fn validate_switch(&mut self, target: &str, span: Span) -> bool {
495 match self.context.validate_switch(target) {
496 SwitchValidation::Allowed => true,
497 SwitchValidation::Warning { reason } => {
498 self.warnings
499 .push(format!("Switch to '{}': {}", target, reason));
500 true
501 }
502 SwitchValidation::Denied { reason } => {
503 self.errors.push(PluralityTypeError::SwitchDenied {
504 target: target.to_string(),
505 reason,
506 span,
507 });
508 false
509 }
510 SwitchValidation::Unknown { alter } => {
511 self.warnings.push(format!(
512 "Switch to unknown alter '{}'. Consider defining it.",
513 alter
514 ));
515 true
516 }
517 }
518 }
519
520 pub fn check_assignment(&mut self, from: &PluralType, to: &PluralType, span: Span) -> bool {
522 match check_compatibility(from, to) {
523 AlterSourceCompatibility::Compatible => true,
524 AlterSourceCompatibility::CoercibleWith(coercion) => {
525 match coercion {
526 AlterSourceCoercion::UpgradeCertainty => {
527 self.warnings.push(format!(
528 "Implicit certainty upgrade from co-conscious to fronting at {:?}",
529 span
530 ));
531 }
532 AlterSourceCoercion::DowngradeCertainty => {
533 self.warnings.push(format!(
534 "Certainty downgrade - data may be less reliable at {:?}",
535 span
536 ));
537 }
538 AlterSourceCoercion::TransferAlter => {
539 self.warnings
540 .push(format!("Cross-alter data transfer at {:?}", span));
541 }
542 AlterSourceCoercion::Blend => {
543 self.warnings
544 .push(format!("Blending data from multiple alters at {:?}", span));
545 }
546 }
547 true
548 }
549 AlterSourceCompatibility::Incompatible => {
550 let from_str = format!("{:?}", from.alter_source);
551 let to_str = format!("{:?}", to.alter_source);
552 self.errors.push(PluralityTypeError::AlterSourceMismatch {
553 expected: to_str,
554 found: from_str,
555 span,
556 });
557 false
558 }
559 AlterSourceCompatibility::RequiresResolution => {
560 if let Some(AlterSource::Blended(alters)) = &from.alter_source {
561 self.errors
562 .push(PluralityTypeError::UnresolvedBlendedState {
563 alters: alters.iter().map(|a| a.name.clone()).collect(),
564 data_type: format!("{:?}", from.base),
565 span,
566 });
567 }
568 false
569 }
570 }
571 }
572
573 pub fn check_dormant_access(&mut self, alter: &str, data_type: &str, span: Span) -> bool {
575 if self.context.in_alter_block() {
577 if let Some(fronter) = self.context.fronter_name() {
578 if fronter != alter {
579 self.errors
580 .push(PluralityTypeError::DormantAccessWithoutVerification {
581 alter: alter.to_string(),
582 data_type: data_type.to_string(),
583 span,
584 });
585 return false;
586 }
587 }
588 }
589 true
590 }
591
592 pub fn errors(&self) -> &[PluralityTypeError] {
594 &self.errors
595 }
596
597 pub fn warnings(&self) -> &[String] {
599 &self.warnings
600 }
601
602 pub fn has_errors(&self) -> bool {
604 !self.errors.is_empty()
605 }
606
607 pub fn clear(&mut self) {
609 self.errors.clear();
610 self.warnings.clear();
611 }
612}
613
614#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::ast::TypeExpr;
622
623 fn mock_alter_def(name: &str, category: AlterCategory) -> AlterDef {
624 AlterDef {
625 visibility: crate::ast::Visibility::Public,
626 attrs: Vec::new(),
627 name: Ident {
628 name: name.to_string(),
629 evidentiality: None,
630 affect: None,
631 span: Span::default(),
632 },
633 category,
634 generics: None,
635 where_clause: None,
636 body: super::super::ast::AlterBody {
637 archetype: None,
638 preferred_reality: None,
639 abilities: Vec::new(),
640 triggers: Vec::new(),
641 anima: None,
642 states: None,
643 special: Vec::new(),
644 methods: Vec::new(),
645 types: Vec::new(),
646 },
647 span: Span::default(),
648 }
649 }
650
651 #[test]
652 fn test_alter_context_registration() {
653 let mut ctx = AlterContext::new();
654 let def = mock_alter_def("Abaddon", AlterCategory::Council);
655 ctx.register_alter(def);
656
657 assert!(ctx.can_front("Abaddon"));
658 }
659
660 #[test]
661 fn test_servant_cannot_front() {
662 let mut ctx = AlterContext::new();
663 let def = mock_alter_def("Watcher", AlterCategory::Servant);
664 ctx.register_alter(def);
665
666 assert!(!ctx.can_front("Watcher"));
667 }
668
669 #[test]
670 fn test_switch_validation() {
671 let mut ctx = AlterContext::new();
672 ctx.register_alter(mock_alter_def("Abaddon", AlterCategory::Council));
673 ctx.register_alter(mock_alter_def("Watcher", AlterCategory::Servant));
674
675 assert_eq!(ctx.validate_switch("Abaddon"), SwitchValidation::Allowed);
676 assert!(matches!(
677 ctx.validate_switch("Watcher"),
678 SwitchValidation::Denied { .. }
679 ));
680 }
681
682 #[test]
683 fn test_alter_source_compatibility() {
684 let fronting_type = PluralType {
685 base: TypeExpr::Path(crate::ast::TypePath { segments: vec![] }),
686 evidentiality: None,
687 alter_source: Some(AlterSource::Fronting),
688 in_alter_context: true,
689 span: Span::default(),
690 };
691
692 let cocon_type = PluralType {
693 base: TypeExpr::Path(crate::ast::TypePath { segments: vec![] }),
694 evidentiality: None,
695 alter_source: Some(AlterSource::CoConscious(None)),
696 in_alter_context: true,
697 span: Span::default(),
698 };
699
700 assert_eq!(
702 check_compatibility(&fronting_type, &cocon_type),
703 AlterSourceCompatibility::Compatible
704 );
705
706 assert!(matches!(
708 check_compatibility(&cocon_type, &fronting_type),
709 AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
710 ));
711 }
712
713 #[test]
714 fn test_blended_requires_resolution() {
715 let blended = PluralType {
716 base: TypeExpr::Path(crate::ast::TypePath { segments: vec![] }),
717 evidentiality: None,
718 alter_source: Some(AlterSource::Blended(Vec::new())),
719 in_alter_context: true,
720 span: Span::default(),
721 };
722
723 let fronting = PluralType {
724 base: TypeExpr::Path(crate::ast::TypePath { segments: vec![] }),
725 evidentiality: None,
726 alter_source: Some(AlterSource::Fronting),
727 in_alter_context: true,
728 span: Span::default(),
729 };
730
731 assert_eq!(
732 check_compatibility(&blended, &fronting),
733 AlterSourceCompatibility::RequiresResolution
734 );
735 }
736}