sigil_parser/plurality/
typeck.rs

1//! # Plurality Type Checking
2//!
3//! Type system extensions for plurality constructs. Extends Sigil's evidentiality
4//! type system to track alter-source information.
5//!
6//! ## Core Type Extensions
7//!
8//! Sigil's evidentiality markers (`!`, `~`, `?`, `‽`) are extended with alter-source:
9//!
10//! | Marker | Base Meaning | Alter-Source Extension |
11//! |--------|--------------|------------------------|
12//! | `@!`   | Certain      | Fronting alter's authoritative view |
13//! | `@~`   | Reported     | Co-conscious alter's shared perception |
14//! | `@?`   | Uncertain    | Dormant alter's cached memory |
15//! | `@‽`   | Paradox      | Blended state from multiple alters |
16//!
17//! ## Type Compatibility Rules
18//!
19//! 1. `T@!` (fronting) is always compatible with `T`
20//! 2. `T@~` (co-con) can be assigned to `T~` or `T?`
21//! 3. `T@?` (dormant) can only be assigned to `T?`
22//! 4. `T@‽` (blended) requires explicit resolution before use
23//!
24//! ## Alter-Polymorphism
25//!
26//! Functions can be polymorphic over fronting alter:
27//! ```sigil
28//! fn perceive<A: Alter>(entity: &Entity) -> Perception@A
29//! ```
30
31use 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// ============================================================================
39// PLURALITY TYPE SYSTEM
40// ============================================================================
41
42/// Extended type information for plurality
43#[derive(Debug, Clone, PartialEq)]
44pub struct PluralType {
45    /// The base type expression
46    pub base: TypeExpr,
47    /// Base evidentiality (!~?‽)
48    pub evidentiality: Option<Evidentiality>,
49    /// Alter-source information
50    pub alter_source: Option<AlterSource>,
51    /// Is this type inside an alter block?
52    pub in_alter_context: bool,
53    /// The span for error reporting
54    pub span: Span,
55}
56
57impl PluralType {
58    /// Create a new plural type from a base type
59    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    /// Add alter-source information
70    pub fn with_alter_source(mut self, source: AlterSource) -> Self {
71        self.alter_source = Some(source);
72        self
73    }
74
75    /// Add evidentiality marker
76    pub fn with_evidentiality(mut self, ev: Evidentiality) -> Self {
77        self.evidentiality = Some(ev);
78        self
79    }
80
81    /// Check if this type is in a fronting context
82    pub fn is_fronting(&self) -> bool {
83        matches!(self.alter_source, Some(AlterSource::Fronting))
84    }
85
86    /// Check if this type is from co-conscious context
87    pub fn is_cocon(&self) -> bool {
88        matches!(self.alter_source, Some(AlterSource::CoConscious(_)))
89    }
90
91    /// Check if this type is from dormant context
92    pub fn is_dormant(&self) -> bool {
93        matches!(self.alter_source, Some(AlterSource::Dormant(_)))
94    }
95
96    /// Check if this type is in blended state
97    pub fn is_blended(&self) -> bool {
98        matches!(self.alter_source, Some(AlterSource::Blended(_)))
99    }
100}
101
102// ============================================================================
103// ALTER TYPE CONTEXT
104// ============================================================================
105
106/// Context for type checking within alter blocks
107#[derive(Debug, Clone)]
108pub struct AlterContext {
109    /// Currently fronting alter (if in alter block)
110    pub current_alter: Option<Ident>,
111    /// Stack of alter blocks (for nested contexts)
112    pub alter_stack: Vec<Ident>,
113    /// Known alter definitions
114    pub alter_defs: HashMap<String, AlterDef>,
115    /// Current alter's category
116    pub current_category: Option<AlterCategory>,
117    /// Current alter's state
118    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    /// Create a new alter context
135    pub fn new() -> Self {
136        Self::default()
137    }
138
139    /// Register an alter definition
140    pub fn register_alter(&mut self, def: AlterDef) {
141        self.alter_defs.insert(def.name.name.clone(), def);
142    }
143
144    /// Enter an alter block context
145    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        // Update category from definition if known
152        if let Some(def) = self.alter_defs.get(&alter.name) {
153            self.current_category = Some(def.category);
154        }
155    }
156
157    /// Exit current alter block
158    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    /// Check if we're currently in an alter block
167    pub fn in_alter_block(&self) -> bool {
168        self.current_alter.is_some()
169    }
170
171    /// Get the current fronting alter's name
172    pub fn fronter_name(&self) -> Option<&str> {
173        self.current_alter.as_ref().map(|a| a.name.as_str())
174    }
175
176    /// Check if an alter can front based on category
177    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 // Unknown alters assumed to be able to front
182        }
183    }
184
185    /// Check if a switch to an alter is valid
186    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/// Result of validating a switch
213#[derive(Debug, Clone, PartialEq)]
214pub enum SwitchValidation {
215    Allowed,
216    Warning { reason: String },
217    Denied { reason: String },
218    Unknown { alter: String },
219}
220
221// ============================================================================
222// TYPE COMPATIBILITY
223// ============================================================================
224
225/// Rules for alter-source type compatibility
226#[derive(Debug, Clone, Copy, PartialEq)]
227pub enum AlterSourceCompatibility {
228    /// Types are compatible
229    Compatible,
230    /// Types are compatible with implicit coercion
231    CoercibleWith(AlterSourceCoercion),
232    /// Types are incompatible
233    Incompatible,
234    /// Requires explicit resolution
235    RequiresResolution,
236}
237
238/// Coercion type for alter-source
239#[derive(Debug, Clone, Copy, PartialEq)]
240pub enum AlterSourceCoercion {
241    /// Upgrade certainty (e.g., @~ -> @!)
242    UpgradeCertainty,
243    /// Downgrade certainty (e.g., @! -> @~)
244    DowngradeCertainty,
245    /// Transfer between alters
246    TransferAlter,
247    /// Blend multiple sources
248    Blend,
249}
250
251/// Check compatibility between two plural types
252pub fn check_compatibility(from: &PluralType, to: &PluralType) -> AlterSourceCompatibility {
253    // Same source is always compatible
254    if from.alter_source == to.alter_source {
255        return AlterSourceCompatibility::Compatible;
256    }
257
258    match (&from.alter_source, &to.alter_source) {
259        // Fronting is compatible with anything (authoritative source)
260        (Some(AlterSource::Fronting), _) => AlterSourceCompatibility::Compatible,
261
262        // Co-con to fronting requires upgrade
263        (Some(AlterSource::CoConscious(_)), Some(AlterSource::Fronting)) => {
264            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
265        }
266
267        // Co-con to co-con is compatible
268        (Some(AlterSource::CoConscious(_)), Some(AlterSource::CoConscious(_))) => {
269            AlterSourceCompatibility::Compatible
270        }
271
272        // Co-con to dormant is a downgrade
273        (Some(AlterSource::CoConscious(_)), Some(AlterSource::Dormant(_))) => {
274            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::DowngradeCertainty)
275        }
276
277        // Dormant can only be assigned to dormant or weaker
278        (Some(AlterSource::Dormant(_)), Some(AlterSource::Dormant(_))) => {
279            AlterSourceCompatibility::Compatible
280        }
281
282        // Dormant to anything else is not allowed without explicit verification
283        (Some(AlterSource::Dormant(_)), _) => AlterSourceCompatibility::Incompatible,
284
285        // Blended state requires explicit resolution
286        (Some(AlterSource::Blended(_)), _) | (_, Some(AlterSource::Blended(_))) => {
287            AlterSourceCompatibility::RequiresResolution
288        }
289
290        // Named alter to fronting needs verification
291        (Some(AlterSource::Named(_)), Some(AlterSource::Fronting)) => {
292            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::TransferAlter)
293        }
294
295        // Bound alters need trait checking
296        (Some(AlterSource::Bound(_)), _) => {
297            // This would involve trait resolution
298            AlterSourceCompatibility::Compatible
299        }
300
301        // No alter source means default fronting context
302        (None, _) | (_, None) => AlterSourceCompatibility::Compatible,
303
304        // Default case
305        _ => AlterSourceCompatibility::Incompatible,
306    }
307}
308
309// ============================================================================
310// TYPE ERRORS
311// ============================================================================
312
313/// Plurality-specific type errors
314#[derive(Debug, Clone)]
315pub enum PluralityTypeError {
316    /// Attempted to access dormant data without verification
317    DormantAccessWithoutVerification {
318        alter: String,
319        data_type: String,
320        span: Span,
321    },
322
323    /// Blended state requires resolution
324    UnresolvedBlendedState {
325        alters: Vec<String>,
326        data_type: String,
327        span: Span,
328    },
329
330    /// Servant alter cannot front
331    ServantCannotFront { alter: String, span: Span },
332
333    /// Alter not found in system
334    AlterNotFound { name: String, span: Span },
335
336    /// Switch to alter not allowed
337    SwitchDenied {
338        target: String,
339        reason: String,
340        span: Span,
341    },
342
343    /// Alter-source mismatch
344    AlterSourceMismatch {
345        expected: String,
346        found: String,
347        span: Span,
348    },
349
350    /// Co-con channel participants not co-conscious
351    CoCOnNotActive { alter: String, span: Span },
352
353    /// Reality layer mismatch
354    RealityLayerMismatch {
355        expected: String,
356        found: String,
357        span: Span,
358    },
359
360    /// Cannot split from non-existent alter
361    SplitSourceNotFound { alter: String, span: Span },
362
363    /// Trigger handler without matching trigger
364    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// ============================================================================
443// TYPE CHECKER EXTENSION
444// ============================================================================
445
446/// Plurality type checker
447#[derive(Debug)]
448pub struct PluralityTypeChecker {
449    /// Alter context
450    context: AlterContext,
451    /// Collected errors
452    errors: Vec<PluralityTypeError>,
453    /// Collected warnings
454    warnings: Vec<String>,
455}
456
457impl Default for PluralityTypeChecker {
458    fn default() -> Self {
459        Self::new()
460    }
461}
462
463impl PluralityTypeChecker {
464    /// Create a new type checker
465    pub fn new() -> Self {
466        Self {
467            context: AlterContext::new(),
468            errors: Vec::new(),
469            warnings: Vec::new(),
470        }
471    }
472
473    /// Register an alter definition
474    pub fn register_alter(&mut self, def: AlterDef) {
475        self.context.register_alter(def);
476    }
477
478    /// Enter an alter block
479    pub fn enter_alter_block(&mut self, alter: &Ident) {
480        self.context.enter_alter(alter.clone());
481    }
482
483    /// Exit alter block
484    pub fn exit_alter_block(&mut self) {
485        self.context.exit_alter();
486    }
487
488    /// Check if we're in an alter block
489    pub fn in_alter_block(&self) -> bool {
490        self.context.in_alter_block()
491    }
492
493    /// Validate a switch expression
494    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    /// Check type compatibility
521    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    /// Check dormant access
574    pub fn check_dormant_access(&mut self, alter: &str, data_type: &str, span: Span) -> bool {
575        // In a fronting context, dormant access requires explicit verification
576        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    /// Get collected errors
593    pub fn errors(&self) -> &[PluralityTypeError] {
594        &self.errors
595    }
596
597    /// Get collected warnings
598    pub fn warnings(&self) -> &[String] {
599        &self.warnings
600    }
601
602    /// Check if there are any errors
603    pub fn has_errors(&self) -> bool {
604        !self.errors.is_empty()
605    }
606
607    /// Clear errors and warnings
608    pub fn clear(&mut self) {
609        self.errors.clear();
610        self.warnings.clear();
611    }
612}
613
614// ============================================================================
615// TESTS
616// ============================================================================
617
618#[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        // Fronting to anything is compatible
701        assert_eq!(
702            check_compatibility(&fronting_type, &cocon_type),
703            AlterSourceCompatibility::Compatible
704        );
705
706        // Co-con to fronting needs upgrade
707        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}