Skip to main content

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.current_alter.as_ref().and_then(|a| {
161            self.alter_defs
162                .get(&a.name)
163                .map(|def| def.category)
164        });
165    }
166
167    /// Check if we're currently in an alter block
168    pub fn in_alter_block(&self) -> bool {
169        self.current_alter.is_some()
170    }
171
172    /// Get the current fronting alter's name
173    pub fn fronter_name(&self) -> Option<&str> {
174        self.current_alter.as_ref().map(|a| a.name.as_str())
175    }
176
177    /// Check if an alter can front based on category
178    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 // Unknown alters assumed to be able to front
183        }
184    }
185
186    /// Check if a switch to an alter is valid
187    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/// Result of validating a switch
214#[derive(Debug, Clone, PartialEq)]
215pub enum SwitchValidation {
216    Allowed,
217    Warning { reason: String },
218    Denied { reason: String },
219    Unknown { alter: String },
220}
221
222// ============================================================================
223// TYPE COMPATIBILITY
224// ============================================================================
225
226/// Rules for alter-source type compatibility
227#[derive(Debug, Clone, Copy, PartialEq)]
228pub enum AlterSourceCompatibility {
229    /// Types are compatible
230    Compatible,
231    /// Types are compatible with implicit coercion
232    CoercibleWith(AlterSourceCoercion),
233    /// Types are incompatible
234    Incompatible,
235    /// Requires explicit resolution
236    RequiresResolution,
237}
238
239/// Coercion type for alter-source
240#[derive(Debug, Clone, Copy, PartialEq)]
241pub enum AlterSourceCoercion {
242    /// Upgrade certainty (e.g., @~ -> @!)
243    UpgradeCertainty,
244    /// Downgrade certainty (e.g., @! -> @~)
245    DowngradeCertainty,
246    /// Transfer between alters
247    TransferAlter,
248    /// Blend multiple sources
249    Blend,
250}
251
252/// Check compatibility between two plural types
253pub fn check_compatibility(from: &PluralType, to: &PluralType) -> AlterSourceCompatibility {
254    // Same source is always compatible
255    if from.alter_source == to.alter_source {
256        return AlterSourceCompatibility::Compatible;
257    }
258
259    match (&from.alter_source, &to.alter_source) {
260        // Fronting is compatible with anything (authoritative source)
261        (Some(AlterSource::Fronting), _) => AlterSourceCompatibility::Compatible,
262
263        // Co-con to fronting requires upgrade
264        (Some(AlterSource::CoConscious(_)), Some(AlterSource::Fronting)) => {
265            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::UpgradeCertainty)
266        }
267
268        // Co-con to co-con is compatible
269        (Some(AlterSource::CoConscious(_)), Some(AlterSource::CoConscious(_))) => {
270            AlterSourceCompatibility::Compatible
271        }
272
273        // Co-con to dormant is a downgrade
274        (Some(AlterSource::CoConscious(_)), Some(AlterSource::Dormant(_))) => {
275            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::DowngradeCertainty)
276        }
277
278        // Dormant can only be assigned to dormant or weaker
279        (Some(AlterSource::Dormant(_)), Some(AlterSource::Dormant(_))) => {
280            AlterSourceCompatibility::Compatible
281        }
282
283        // Dormant to anything else is not allowed without explicit verification
284        (Some(AlterSource::Dormant(_)), _) => AlterSourceCompatibility::Incompatible,
285
286        // Blended state requires explicit resolution
287        (Some(AlterSource::Blended(_)), _) | (_, Some(AlterSource::Blended(_))) => {
288            AlterSourceCompatibility::RequiresResolution
289        }
290
291        // Named alter to fronting needs verification
292        (Some(AlterSource::Named(_)), Some(AlterSource::Fronting)) => {
293            AlterSourceCompatibility::CoercibleWith(AlterSourceCoercion::TransferAlter)
294        }
295
296        // Bound alters need trait checking
297        (Some(AlterSource::Bound(_)), _) => {
298            // This would involve trait resolution
299            AlterSourceCompatibility::Compatible
300        }
301
302        // No alter source means default fronting context
303        (None, _) | (_, None) => AlterSourceCompatibility::Compatible,
304
305        // Default case
306        _ => AlterSourceCompatibility::Incompatible,
307    }
308}
309
310// ============================================================================
311// TYPE ERRORS
312// ============================================================================
313
314/// Plurality-specific type errors
315#[derive(Debug, Clone)]
316pub enum PluralityTypeError {
317    /// Attempted to access dormant data without verification
318    DormantAccessWithoutVerification {
319        alter: String,
320        data_type: String,
321        span: Span,
322    },
323
324    /// Blended state requires resolution
325    UnresolvedBlendedState {
326        alters: Vec<String>,
327        data_type: String,
328        span: Span,
329    },
330
331    /// Servant alter cannot front
332    ServantCannotFront {
333        alter: String,
334        span: Span,
335    },
336
337    /// Alter not found in system
338    AlterNotFound {
339        name: String,
340        span: Span,
341    },
342
343    /// Switch to alter not allowed
344    SwitchDenied {
345        target: String,
346        reason: String,
347        span: Span,
348    },
349
350    /// Alter-source mismatch
351    AlterSourceMismatch {
352        expected: String,
353        found: String,
354        span: Span,
355    },
356
357    /// Co-con channel participants not co-conscious
358    CoCOnNotActive {
359        alter: String,
360        span: Span,
361    },
362
363    /// Reality layer mismatch
364    RealityLayerMismatch {
365        expected: String,
366        found: String,
367        span: Span,
368    },
369
370    /// Cannot split from non-existent alter
371    SplitSourceNotFound {
372        alter: String,
373        span: Span,
374    },
375
376    /// Trigger handler without matching trigger
377    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// ============================================================================
451// TYPE CHECKER EXTENSION
452// ============================================================================
453
454/// Plurality type checker
455#[derive(Debug)]
456pub struct PluralityTypeChecker {
457    /// Alter context
458    context: AlterContext,
459    /// Collected errors
460    errors: Vec<PluralityTypeError>,
461    /// Collected warnings
462    warnings: Vec<String>,
463}
464
465impl Default for PluralityTypeChecker {
466    fn default() -> Self {
467        Self::new()
468    }
469}
470
471impl PluralityTypeChecker {
472    /// Create a new type checker
473    pub fn new() -> Self {
474        Self {
475            context: AlterContext::new(),
476            errors: Vec::new(),
477            warnings: Vec::new(),
478        }
479    }
480
481    /// Register an alter definition
482    pub fn register_alter(&mut self, def: AlterDef) {
483        self.context.register_alter(def);
484    }
485
486    /// Enter an alter block
487    pub fn enter_alter_block(&mut self, alter: &Ident) {
488        self.context.enter_alter(alter.clone());
489    }
490
491    /// Exit alter block
492    pub fn exit_alter_block(&mut self) {
493        self.context.exit_alter();
494    }
495
496    /// Check if we're in an alter block
497    pub fn in_alter_block(&self) -> bool {
498        self.context.in_alter_block()
499    }
500
501    /// Validate a switch expression
502    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    /// Check type compatibility
528    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    /// Check dormant access
589    pub fn check_dormant_access(&mut self, alter: &str, data_type: &str, span: Span) -> bool {
590        // In a fronting context, dormant access requires explicit verification
591        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    /// Get collected errors
607    pub fn errors(&self) -> &[PluralityTypeError] {
608        &self.errors
609    }
610
611    /// Get collected warnings
612    pub fn warnings(&self) -> &[String] {
613        &self.warnings
614    }
615
616    /// Check if there are any errors
617    pub fn has_errors(&self) -> bool {
618        !self.errors.is_empty()
619    }
620
621    /// Clear errors and warnings
622    pub fn clear(&mut self) {
623        self.errors.clear();
624        self.warnings.clear();
625    }
626}
627
628// ============================================================================
629// TESTS
630// ============================================================================
631
632#[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        // Fronting to anything is compatible
719        assert_eq!(
720            check_compatibility(&fronting_type, &cocon_type),
721            AlterSourceCompatibility::Compatible
722        );
723
724        // Co-con to fronting needs upgrade
725        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}