Skip to main content

presolve_compiler/
semantic_type.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::path::PathBuf;
4
5use crate::{
6    BindingTable, CapabilityOperationId, CapabilityOperationKind, CapabilityParameters,
7    CapabilityValueContract, ComponentNode, ComputedValue, ConsumerEntity, ContextEntity,
8    DeclaredStateType, Effect, EffectStatement, EffectStatementKind, ExpressionGraph,
9    ExpressionNodeKind, ImportBindingTarget, ProviderEntity, SemanticId, SerializableValue,
10    SlotEntity, SourceProvenance, SymbolKind, EFFECT_CAPABILITY_REGISTRY,
11};
12use crate::{
13    SemanticReference, SemanticReferenceKind, TemplateSemanticEntity, TemplateSemanticKind,
14};
15use presolve_parser::{ParsedFile, ParsedTypeAlias};
16
17/// A compiler-owned operator category used by semantic expression typing.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum SemanticOperator {
20    Arithmetic(crate::ArithmeticOperator),
21    Comparison(crate::ComparisonOperator),
22    Logical(crate::LogicalOperator),
23    NullishCoalescing,
24    Unary(crate::component_graph::UnaryOperator),
25}
26
27/// Whether a supported DOM binding writes an HTML attribute or a DOM property.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum DomBindingKind {
30    Attribute,
31    Property,
32}
33
34/// Compiler-owned type contract for one supported DOM binding name.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct DomBindingContract {
37    pub name: &'static str,
38    pub kind: DomBindingKind,
39    pub semantic_type: SemanticType,
40}
41
42/// Returns the contract for the currently supported dynamic DOM bindings.
43#[must_use]
44pub fn dom_binding_contract(name: &str) -> Option<DomBindingContract> {
45    match name {
46        "disabled" => Some(DomBindingContract {
47            name: "disabled",
48            kind: DomBindingKind::Property,
49            semantic_type: SemanticType::Boolean,
50        }),
51        "href" => Some(DomBindingContract {
52            name: "href",
53            kind: DomBindingKind::Attribute,
54            semantic_type: SemanticType::String,
55        }),
56        "class" | "for" => Some(DomBindingContract {
57            name: if name == "class" { "class" } else { "for" },
58            kind: DomBindingKind::Attribute,
59            semantic_type: SemanticType::String,
60        }),
61        "role" | "aria-label" | "aria-describedby" | "aria-errormessage" | "aria-controls"
62        | "aria-current" | "aria-live" => Some(DomBindingContract {
63            name: match name {
64                "role" => "role",
65                "aria-label" => "aria-label",
66                "aria-describedby" => "aria-describedby",
67                "aria-errormessage" => "aria-errormessage",
68                "aria-controls" => "aria-controls",
69                "aria-current" => "aria-current",
70                _ => "aria-live",
71            },
72            kind: DomBindingKind::Attribute,
73            semantic_type: SemanticType::String,
74        }),
75        "aria-invalid" | "aria-busy" | "aria-expanded" | "aria-pressed" | "aria-hidden" => {
76            Some(DomBindingContract {
77                name: match name {
78                    "aria-invalid" => "aria-invalid",
79                    "aria-busy" => "aria-busy",
80                    "aria-expanded" => "aria-expanded",
81                    "aria-hidden" => "aria-hidden",
82                    _ => "aria-pressed",
83                },
84                kind: DomBindingKind::Attribute,
85                semantic_type: SemanticType::Boolean,
86            })
87        }
88        "value" => Some(DomBindingContract {
89            name: "value",
90            kind: DomBindingKind::Property,
91            semantic_type: SemanticType::Union(vec![SemanticType::String, SemanticType::Number]),
92        }),
93        _ => None,
94    }
95}
96
97/// Compiler-owned semantic type algebra independent of TypeScript spelling.
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum SemanticType {
100    Unknown,
101    Never,
102    Null,
103    Boolean,
104    Number,
105    String,
106    /// Browser platform file value. It is admitted only through
107    /// TypeScript-authoritative V2 Form Field evidence.
108    File,
109    /// Nominal compile-time marker accepted only by canonical Form declarations.
110    Form,
111    /// Nominal built-in type accepted only by canonical Slot declarations.
112    SlotContent,
113    BooleanLiteral(bool),
114    NumberLiteral(String),
115    StringLiteral(String),
116    Array(Box<SemanticType>),
117    Tuple(Vec<SemanticType>),
118    Object(ObjectType),
119    Union(Vec<SemanticType>),
120    Resource(ResourceType),
121}
122
123/// Renders a canonical semantic type using the stable inspection spelling.
124#[must_use]
125pub fn semantic_type_text(semantic_type: &SemanticType) -> String {
126    match semantic_type {
127        SemanticType::Unknown => "unknown".to_string(),
128        SemanticType::Never => "never".to_string(),
129        SemanticType::Null => "null".to_string(),
130        SemanticType::Boolean => "boolean".to_string(),
131        SemanticType::Number => "number".to_string(),
132        SemanticType::String => "string".to_string(),
133        SemanticType::File => "File".to_string(),
134        SemanticType::Form => "Form".to_string(),
135        SemanticType::SlotContent => "SlotContent".to_string(),
136        SemanticType::BooleanLiteral(value) => value.to_string(),
137        SemanticType::NumberLiteral(value) => value.clone(),
138        SemanticType::StringLiteral(value) => format!("{value:?}"),
139        SemanticType::Array(element) => format!("{}[]", semantic_type_text(element)),
140        SemanticType::Tuple(items) => format!(
141            "[{}]",
142            items
143                .iter()
144                .map(semantic_type_text)
145                .collect::<Vec<_>>()
146                .join(", ")
147        ),
148        SemanticType::Object(object) => format!(
149            "{{ {} }}",
150            object
151                .properties
152                .iter()
153                .map(|(name, semantic_type)| format!(
154                    "{name}: {}",
155                    semantic_type_text(semantic_type)
156                ))
157                .collect::<Vec<_>>()
158                .join("; ")
159        ),
160        SemanticType::Union(members) => members
161            .iter()
162            .map(semantic_type_text)
163            .collect::<Vec<_>>()
164            .join(" | "),
165        SemanticType::Resource(resource) => format!(
166            "Resource<{}, {}>",
167            semantic_type_text(&resource.data),
168            semantic_type_text(&resource.error)
169        ),
170    }
171}
172
173/// Module-scoped authority for compiler-owned built-in marker types.
174///
175/// The parser supplies binding facts; this authority decides whether an exact
176/// authored marker spelling resolves to the compiler built-in rather than a
177/// module-local declaration or import. Downstream stages consume the resulting
178/// canonical entity and never repeat source-text recognition.
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub struct BuiltinTypeAuthority {
181    form_marker_is_unshadowed: bool,
182}
183
184impl BuiltinTypeAuthority {
185    #[must_use]
186    pub fn for_file(parsed: &ParsedFile) -> Self {
187        let has_local_binding = parsed
188            .local_type_bindings
189            .iter()
190            .any(|binding| binding == "Form");
191        let has_non_presolve_import_binding = parsed.imports.iter().any(|import| {
192            import.specifiers.iter().any(|specifier| {
193                specifier.local == "Form"
194                    && !(import.source == "presolve" && specifier.imported == "Form")
195            })
196        });
197        Self {
198            form_marker_is_unshadowed: !has_local_binding && !has_non_presolve_import_binding,
199        }
200    }
201
202    #[must_use]
203    pub fn recognizes_form_marker(self, type_text: &str) -> bool {
204        self.form_marker_is_unshadowed && type_text == "Form"
205    }
206}
207
208/// Compiler-owned resource contract with explicit data, error, and boundary semantics.
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct ResourceType {
211    pub data: Box<SemanticType>,
212    pub error: Box<SemanticType>,
213    pub pending: bool,
214    pub serializable: bool,
215    pub execution_boundary: ResourceExecutionBoundary,
216}
217
218/// Execution boundary declared by a canonical resource contract.
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum ResourceExecutionBoundary {
221    Client,
222    Server,
223    Shared,
224}
225
226/// Whether a canonical semantic type can safely cross a compiler/runtime boundary.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum SerializationCompatibility {
229    Serializable,
230    NotSerializable,
231}
232
233/// Execution side participating in a compiler/runtime boundary crossing.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum ExecutionBoundary {
236    Client,
237    Server,
238}
239
240/// Whether a semantic type may cross one execution boundary to another.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum BoundaryCompatibility {
243    Compatible,
244    Incompatible,
245}
246
247/// Determines whether a type can cross from one execution boundary to another.
248#[must_use]
249pub fn boundary_compatibility(
250    semantic_type: &SemanticType,
251    source: ExecutionBoundary,
252    target: ExecutionBoundary,
253) -> BoundaryCompatibility {
254    if source == target {
255        return BoundaryCompatibility::Compatible;
256    }
257    let compatible = match semantic_type {
258        SemanticType::Resource(resource) => {
259            resource.execution_boundary == ResourceExecutionBoundary::Shared
260                && serialization_compatibility(semantic_type)
261                    == SerializationCompatibility::Serializable
262        }
263        _ => serialization_compatibility(semantic_type) == SerializationCompatibility::Serializable,
264    };
265    if compatible {
266        BoundaryCompatibility::Compatible
267    } else {
268        BoundaryCompatibility::Incompatible
269    }
270}
271
272/// Canonicalizes equivalent semantic type forms for stable equality and output.
273#[must_use]
274pub fn normalize_semantic_type(semantic_type: SemanticType) -> SemanticType {
275    match semantic_type {
276        SemanticType::Array(element) => {
277            SemanticType::Array(Box::new(normalize_semantic_type(*element)))
278        }
279        SemanticType::Tuple(items) => {
280            SemanticType::Tuple(items.into_iter().map(normalize_semantic_type).collect())
281        }
282        SemanticType::Object(object) => SemanticType::Object(ObjectType {
283            properties: object
284                .properties
285                .into_iter()
286                .map(|(name, semantic_type)| (name, normalize_semantic_type(semantic_type)))
287                .collect(),
288        }),
289        SemanticType::Union(members) => normalize_union(members),
290        SemanticType::Resource(resource) => SemanticType::Resource(ResourceType {
291            data: Box::new(normalize_semantic_type(*resource.data)),
292            error: Box::new(normalize_semantic_type(*resource.error)),
293            pending: resource.pending,
294            serializable: resource.serializable,
295            execution_boundary: resource.execution_boundary,
296        }),
297        semantic_type => semantic_type,
298    }
299}
300
301fn normalize_union(members: Vec<SemanticType>) -> SemanticType {
302    let mut flattened = Vec::new();
303    for member in members {
304        match normalize_semantic_type(member) {
305            SemanticType::Union(items) => flattened.extend(items),
306            SemanticType::Never => {}
307            member => flattened.push(member),
308        }
309    }
310    flattened.sort_by_key(semantic_type_sort_key);
311    flattened.dedup();
312    match flattened.as_slice() {
313        [] => SemanticType::Never,
314        [member] => member.clone(),
315        _ => SemanticType::Union(flattened),
316    }
317}
318
319fn semantic_type_sort_key(semantic_type: &SemanticType) -> String {
320    format!("{semantic_type:?}")
321}
322
323/// Determines structural serialization compatibility for a canonical type.
324#[must_use]
325pub fn serialization_compatibility(semantic_type: &SemanticType) -> SerializationCompatibility {
326    let serializable = match semantic_type {
327        SemanticType::Unknown
328        | SemanticType::Never
329        | SemanticType::Form
330        | SemanticType::SlotContent
331        | SemanticType::File => false,
332        SemanticType::Null
333        | SemanticType::Boolean
334        | SemanticType::Number
335        | SemanticType::String
336        | SemanticType::BooleanLiteral(_)
337        | SemanticType::NumberLiteral(_)
338        | SemanticType::StringLiteral(_) => true,
339        SemanticType::Array(element) => {
340            serialization_compatibility(element) == SerializationCompatibility::Serializable
341        }
342        SemanticType::Tuple(items) | SemanticType::Union(items) => items.iter().all(|item| {
343            serialization_compatibility(item) == SerializationCompatibility::Serializable
344        }),
345        SemanticType::Object(object) => object.properties.values().all(|property| {
346            serialization_compatibility(property) == SerializationCompatibility::Serializable
347        }),
348        SemanticType::Resource(resource) => {
349            resource.serializable
350                && serialization_compatibility(&resource.data)
351                    == SerializationCompatibility::Serializable
352                && serialization_compatibility(&resource.error)
353                    == SerializationCompatibility::Serializable
354        }
355    };
356    if serializable {
357        SerializationCompatibility::Serializable
358    } else {
359        SerializationCompatibility::NotSerializable
360    }
361}
362
363/// Structural object shape in the canonical semantic type algebra.
364///
365/// C1 establishes only the representation. Object declaration lowering,
366/// property provenance, and member resolution are later Phase C slices.
367#[derive(Debug, Clone, PartialEq, Eq, Default)]
368pub struct ObjectType {
369    pub properties: BTreeMap<String, SemanticType>,
370}
371
372/// Canonical type assignments owned by the application semantic model.
373///
374/// C1 provides the stable container but deliberately populates no assignments.
375/// State inference, expression propagation, and typed declarations are added by
376/// later Phase C slices.
377#[derive(Debug, Clone, PartialEq, Eq, Default)]
378pub struct SemanticTypeModel {
379    pub assignments: BTreeMap<SemanticId, SemanticTypeAssignment>,
380    pub aliases: BTreeMap<SemanticId, SemanticTypeAlias>,
381    pub list_scopes: BTreeMap<SemanticId, ListTemplateScopeType>,
382    pub member_accesses: BTreeMap<SemanticId, MemberAccessType>,
383    pub computed_values: BTreeMap<SemanticId, ComputedValueType>,
384    pub action_signatures: BTreeMap<SemanticId, ActionSignatureType>,
385    pub effect_statements: BTreeMap<SemanticId, EffectStatementTypeRecord>,
386}
387
388/// Compatibility facts recorded by effect typing without issuing F5 diagnostics.
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub enum EffectCompatibility {
391    Compatible,
392    Incompatible,
393    Unknown,
394}
395
396/// The compiler-owned classification of one authored effect statement.
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
398pub enum EffectOperationClassification {
399    RecognizedCapability,
400    UnknownExternalCapability,
401    ReactiveStateAssignment,
402    ComponentActionCall,
403    ComponentEffectCall,
404    ComponentMethodCall,
405    UnresolvedComponentCall,
406    UnresolvedComponentAssignment,
407    BareReturn,
408    ValueReturn,
409    Empty,
410    UnsupportedStatement,
411}
412
413/// Immutable F4 typing and compatibility facts keyed by effect statement identity.
414#[derive(Debug, Clone, PartialEq, Eq)]
415pub struct EffectStatementTypeRecord {
416    pub statement: SemanticId,
417    pub operation_classification: EffectOperationClassification,
418    pub operand_types: Vec<SemanticType>,
419    pub target_type: Option<SemanticType>,
420    pub signature_compatibility: EffectCompatibility,
421    pub boundary_compatibility: EffectCompatibility,
422    pub serialization_compatibility: EffectCompatibility,
423    pub capability_operation: Option<CapabilityOperationId>,
424    pub provenance: SourceProvenance,
425}
426
427/// Canonical item and index type information for one template list scope.
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct ListTemplateScopeType {
430    pub list: SemanticId,
431    pub item_name: String,
432    pub item_type: SemanticType,
433    pub index_name: Option<String>,
434    pub index_type: Option<SemanticType>,
435}
436
437/// Canonical result of one supported template list-item member access.
438#[derive(Debug, Clone, PartialEq, Eq)]
439pub struct MemberAccessType {
440    pub entity: SemanticId,
441    pub expression: String,
442    pub semantic_type: Option<SemanticType>,
443}
444
445/// Canonical semantic type contract for one computed getter.
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub struct ComputedValueType {
448    pub computed: SemanticId,
449    pub method: SemanticId,
450    pub semantic_type: SemanticType,
451    pub status: SemanticTypeStatus,
452    pub declared_return_type: Option<SemanticType>,
453    pub declared_return_compatible: Option<bool>,
454    pub serialization: SerializationCompatibility,
455    pub execution_boundary: ExecutionBoundary,
456    pub boundary_compatibility: BoundaryCompatibility,
457    pub provenance: SourceProvenance,
458}
459
460/// Canonical input/output type contract for one decorator-marked action method.
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct ActionSignatureType {
463    pub method: SemanticId,
464    pub input: Vec<(SemanticId, SemanticType)>,
465    pub output: Option<SemanticType>,
466    pub is_async: bool,
467}
468
469/// Stable identity for one compiler-owned type assignment.
470#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
471pub struct SemanticTypeId(SemanticId);
472
473impl SemanticTypeId {
474    #[must_use]
475    pub fn for_subject(subject: &SemanticId) -> Self {
476        Self(subject.semantic_type())
477    }
478
479    #[must_use]
480    pub fn as_semantic_id(&self) -> &SemanticId {
481        &self.0
482    }
483}
484
485impl fmt::Display for SemanticTypeId {
486    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
487        self.0.fmt(formatter)
488    }
489}
490
491/// Whether a semantic type came from an authored declaration or compiler inference.
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
493pub enum SemanticTypeStatus {
494    Declared,
495    Inferred,
496}
497
498/// Stable families for diagnostics produced from canonical semantic types.
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum TypeDiagnosticFamily {
501    UnknownType,
502    InvalidOperator,
503    IncompatibleAssignment,
504    MissingMember,
505    InvalidCondition,
506    NonIterableList,
507    NonRenderableValue,
508    InvalidBinding,
509    NonSerializableState,
510}
511
512/// Stable codes for diagnostics produced from canonical semantic types.
513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
514pub enum TypeDiagnosticCode {
515    IncompatibleStateInitializer,
516    IncompatibleAssignment,
517    InvalidToggleTarget,
518    InvalidNumericMutationTarget,
519    InvalidCompoundMutationTarget,
520    InvalidCompoundMutationOperand,
521    InvalidArithmeticOperator,
522    InvalidComparisonOperator,
523    InvalidLogicalOperator,
524    InvalidNullishOperator,
525    InvalidUnaryOperator,
526    NonRenderableValue,
527    InvalidBinding,
528    InvalidCondition,
529    NonIterableList,
530    MissingMember,
531    UnknownType,
532    NonSerializableState,
533}
534
535impl TypeDiagnosticCode {
536    #[must_use]
537    pub const fn as_str(self) -> &'static str {
538        match self {
539            Self::IncompatibleStateInitializer => "PSC1016",
540            Self::IncompatibleAssignment => "PSC1017",
541            Self::InvalidToggleTarget => "PSC1018",
542            Self::InvalidNumericMutationTarget => "PSC1019",
543            Self::InvalidCompoundMutationTarget => "PSC1020",
544            Self::InvalidCompoundMutationOperand => "PSC1021",
545            Self::InvalidArithmeticOperator => "PSC1022",
546            Self::InvalidComparisonOperator => "PSC1023",
547            Self::InvalidLogicalOperator => "PSC1024",
548            Self::InvalidNullishOperator => "PSC1025",
549            Self::InvalidUnaryOperator => "PSC1026",
550            Self::NonRenderableValue => "PSC1027",
551            Self::InvalidBinding => "PSC1028",
552            Self::InvalidCondition => "PSC1029",
553            Self::NonIterableList => "PSC1030",
554            Self::MissingMember => "PSC1031",
555            Self::UnknownType => "PSC1032",
556            Self::NonSerializableState => "PSC1033",
557        }
558    }
559
560    #[must_use]
561    pub const fn family(self) -> TypeDiagnosticFamily {
562        match self {
563            Self::UnknownType => TypeDiagnosticFamily::UnknownType,
564            Self::InvalidArithmeticOperator
565            | Self::InvalidComparisonOperator
566            | Self::InvalidLogicalOperator
567            | Self::InvalidNullishOperator
568            | Self::InvalidUnaryOperator => TypeDiagnosticFamily::InvalidOperator,
569            Self::IncompatibleStateInitializer
570            | Self::IncompatibleAssignment
571            | Self::InvalidToggleTarget
572            | Self::InvalidNumericMutationTarget
573            | Self::InvalidCompoundMutationTarget
574            | Self::InvalidCompoundMutationOperand => TypeDiagnosticFamily::IncompatibleAssignment,
575            Self::MissingMember => TypeDiagnosticFamily::MissingMember,
576            Self::InvalidCondition => TypeDiagnosticFamily::InvalidCondition,
577            Self::NonIterableList => TypeDiagnosticFamily::NonIterableList,
578            Self::NonRenderableValue => TypeDiagnosticFamily::NonRenderableValue,
579            Self::InvalidBinding => TypeDiagnosticFamily::InvalidBinding,
580            Self::NonSerializableState => TypeDiagnosticFamily::NonSerializableState,
581        }
582    }
583}
584
585/// A type assignment with canonical identity, semantic origin, and authored location.
586#[derive(Debug, Clone, PartialEq, Eq)]
587pub struct SemanticTypeAssignment {
588    pub id: SemanticTypeId,
589    pub subject: SemanticId,
590    pub semantic_type: SemanticType,
591    pub origin: SemanticId,
592    pub status: SemanticTypeStatus,
593    pub provenance: SourceProvenance,
594}
595
596/// A named authored type alias resolved to canonical semantic type meaning.
597#[derive(Debug, Clone, PartialEq, Eq)]
598pub struct SemanticTypeAlias {
599    pub id: SemanticId,
600    pub name: String,
601    pub semantic_type: SemanticType,
602    pub provenance: SourceProvenance,
603}
604
605/// Canonical result of resolving one authored type annotation through the
606/// existing type and binding authorities.
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub struct ResolvedDeclaredSemanticType {
609    pub semantic_type: SemanticType,
610    pub alias_origin: Option<SemanticId>,
611}
612
613impl SemanticTypeModel {
614    #[must_use]
615    pub fn resolve_declared_type(
616        &self,
617        declared_type: &DeclaredStateType,
618        bindings: Option<&BindingTable>,
619    ) -> Option<ResolvedDeclaredSemanticType> {
620        let local_alias = self.aliases.values().find(|alias| {
621            alias.provenance.path == declared_type.provenance.path
622                && alias.name == declared_type.text
623        });
624        let imported_alias = bindings
625            .and_then(|bindings| {
626                bindings.resolve_import(&declared_type.provenance.path, &declared_type.text)
627            })
628            .and_then(|binding| match &binding.target {
629                ImportBindingTarget::Symbol(symbol) if symbol.kind == SymbolKind::TypeAlias => {
630                    self.aliases.get(&symbol.id)
631                }
632                _ => None,
633            });
634        let alias = local_alias.or(imported_alias);
635        let semantic_type = alias
636            .map(|alias| alias.semantic_type.clone())
637            .or_else(|| semantic_type_from_annotation(&declared_type.text))?;
638        Some(ResolvedDeclaredSemanticType {
639            semantic_type: normalize_semantic_type(semantic_type),
640            alias_origin: alias.map(|alias| alias.id.clone()),
641        })
642    }
643
644    #[must_use]
645    pub fn from_components(
646        components: &[ComponentNode],
647        provenance: &BTreeMap<SemanticId, SourceProvenance>,
648    ) -> Self {
649        Self::from_components_with_aliases_and_bindings(components, provenance, &[], None)
650    }
651
652    #[must_use]
653    pub fn from_components_with_aliases(
654        components: &[ComponentNode],
655        provenance: &BTreeMap<SemanticId, SourceProvenance>,
656        parsed_aliases: &[(PathBuf, ParsedTypeAlias)],
657    ) -> Self {
658        Self::from_components_with_aliases_and_bindings(
659            components,
660            provenance,
661            parsed_aliases,
662            None,
663        )
664    }
665
666    #[must_use]
667    pub fn from_components_with_aliases_and_bindings(
668        components: &[ComponentNode],
669        provenance: &BTreeMap<SemanticId, SourceProvenance>,
670        parsed_aliases: &[(PathBuf, ParsedTypeAlias)],
671        bindings: Option<&BindingTable>,
672    ) -> Self {
673        let aliases = parsed_aliases
674            .iter()
675            .filter_map(|(path, alias)| {
676                semantic_type_from_annotation(&alias.type_text).map(|semantic_type| {
677                    let id = SemanticId::type_alias_in_module(path, &alias.name);
678                    (
679                        id.clone(),
680                        SemanticTypeAlias {
681                            id,
682                            name: alias.name.clone(),
683                            semantic_type,
684                            provenance: SourceProvenance::new(path, alias.type_span),
685                        },
686                    )
687                })
688            })
689            .collect::<BTreeMap<_, _>>();
690        let aliases_by_path_and_name = aliases
691            .values()
692            .map(|alias| ((alias.provenance.path.clone(), alias.name.clone()), alias))
693            .collect::<BTreeMap<_, _>>();
694        let mut assignments = BTreeMap::new();
695
696        for field in components
697            .iter()
698            .flat_map(|component| &component.state_fields)
699        {
700            let (semantic_type, origin, status, assignment_provenance) =
701                if let Some(declared_type) = &field.declared_type {
702                    let alias = aliases_by_path_and_name
703                        .get(&(
704                            declared_type.provenance.path.clone(),
705                            declared_type.text.clone(),
706                        ))
707                        .copied();
708                    let imported_alias = bindings
709                        .and_then(|bindings| {
710                            bindings
711                                .resolve_import(&declared_type.provenance.path, &declared_type.text)
712                        })
713                        .and_then(|binding| match &binding.target {
714                            ImportBindingTarget::Symbol(symbol)
715                                if symbol.kind == SymbolKind::TypeAlias =>
716                            {
717                                aliases.get(&symbol.id)
718                            }
719                            _ => None,
720                        });
721                    let alias = alias.or(imported_alias);
722                    let semantic_type = alias
723                        .map(|alias| alias.semantic_type.clone())
724                        .or_else(|| semantic_type_from_annotation(&declared_type.text));
725                    let Some(semantic_type) = semantic_type else {
726                        continue;
727                    };
728                    (
729                        semantic_type,
730                        alias.map_or_else(|| field.id.clone(), |alias| alias.id.clone()),
731                        SemanticTypeStatus::Declared,
732                        declared_type.provenance.clone(),
733                    )
734                } else {
735                    let Some(value) = &field.initial_value else {
736                        continue;
737                    };
738                    let Some(field_provenance) = provenance.get(&field.id) else {
739                        continue;
740                    };
741                    (
742                        infer_serializable_value_type(value),
743                        field.id.clone(),
744                        SemanticTypeStatus::Inferred,
745                        field_provenance.clone(),
746                    )
747                };
748            assignments.insert(
749                field.id.clone(),
750                SemanticTypeAssignment {
751                    id: SemanticTypeId::for_subject(&field.id),
752                    subject: field.id.clone(),
753                    semantic_type,
754                    origin,
755                    status,
756                    provenance: assignment_provenance,
757                },
758            );
759        }
760
761        extend_with_local_type_assignments(&mut assignments, components, provenance);
762        extend_with_parameter_type_assignments(&mut assignments, components, &aliases, bindings);
763        extend_with_method_return_type_assignments(
764            &mut assignments,
765            components,
766            provenance,
767            &aliases,
768            bindings,
769        );
770
771        Self::from_assignments_and_aliases(assignments, aliases)
772            .with_action_signature_types(components)
773    }
774
775    /// Attaches explicit G1 Context declaration contracts without performing
776    /// provider/consumer compatibility analysis.
777    #[must_use]
778    pub fn with_context_types(
779        mut self,
780        contexts: &BTreeMap<crate::ContextId, ContextEntity>,
781    ) -> Self {
782        for context in contexts.values() {
783            let semantic_type = self
784                .aliases
785                .values()
786                .find(|alias| {
787                    alias.provenance.path == context.declared_type.provenance.path
788                        && alias.name == context.declared_type.text
789                })
790                .map_or_else(
791                    || {
792                        semantic_type_from_annotation(&context.declared_type.text)
793                            .unwrap_or(SemanticType::Unknown)
794                    },
795                    |alias| alias.semantic_type.clone(),
796                );
797            let subject = context.id.as_semantic_id().clone();
798            self.assignments.insert(
799                subject.clone(),
800                SemanticTypeAssignment {
801                    id: context.declared_type_id.clone(),
802                    subject: subject.clone(),
803                    semantic_type,
804                    origin: subject,
805                    status: SemanticTypeStatus::Declared,
806                    provenance: context.provenance.clone(),
807                },
808            );
809        }
810        self
811    }
812
813    /// Attaches compiler-issued Resource declaration types after endpoint
814    /// resolution has established their exact data, error, and boundary facts.
815    #[must_use]
816    pub fn with_resource_types(
817        mut self,
818        resources: &BTreeMap<crate::ResourceId, crate::ResourceDeclaration>,
819    ) -> Self {
820        for resource in resources.values() {
821            let subject = resource.id.as_semantic_id().clone();
822            self.assignments.insert(
823                subject.clone(),
824                SemanticTypeAssignment {
825                    id: SemanticTypeId::for_subject(&subject),
826                    subject: subject.clone(),
827                    semantic_type: SemanticType::Resource(ResourceType {
828                        data: Box::new(resource.data_type.clone()),
829                        error: Box::new(resource.error_type.clone()),
830                        pending: true,
831                        serializable: true,
832                        execution_boundary: resource.execution_boundary,
833                    }),
834                    origin: subject,
835                    status: SemanticTypeStatus::Declared,
836                    provenance: resource.provenance.clone(),
837                },
838            );
839        }
840        self
841    }
842
843    /// Attaches the exact nominal H1 `SlotContent` type to canonical Slot
844    /// entities. Alias and structural compatibility are intentionally absent.
845    #[must_use]
846    pub fn with_slot_types(mut self, slots: &BTreeMap<crate::SlotId, SlotEntity>) -> Self {
847        for slot in slots.values() {
848            let subject = slot.id.as_semantic_id().clone();
849            self.assignments.insert(
850                subject.clone(),
851                SemanticTypeAssignment {
852                    id: SemanticTypeId::for_subject(&subject),
853                    subject: subject.clone(),
854                    semantic_type: SemanticType::SlotContent,
855                    origin: subject,
856                    status: SemanticTypeStatus::Declared,
857                    provenance: slot.provenance.clone(),
858                },
859            );
860        }
861        self
862    }
863
864    /// Attaches the exact nominal I2 `Form` marker to canonical Form entities.
865    /// Aliases, unions, generics, and structural compatibility never enter
866    /// this authority because invalid declaration candidates do not lower.
867    #[must_use]
868    pub fn with_form_types(mut self, forms: &BTreeMap<crate::FormId, crate::FormEntity>) -> Self {
869        for form in forms.values() {
870            let subject = form.id.as_semantic_id().clone();
871            self.assignments.insert(
872                subject.clone(),
873                SemanticTypeAssignment {
874                    id: SemanticTypeId::for_subject(&subject),
875                    subject: subject.clone(),
876                    semantic_type: SemanticType::Form,
877                    origin: subject,
878                    status: SemanticTypeStatus::Declared,
879                    provenance: form.provenance.clone(),
880                },
881            );
882        }
883        self
884    }
885
886    /// Attaches I3 Form Field assignments produced by the canonical field
887    /// resolver. No annotation parsing, inference, or assignability is repeated.
888    #[must_use]
889    pub fn with_form_field_types(
890        mut self,
891        fields: &BTreeMap<crate::FieldId, crate::FormFieldEntity>,
892    ) -> Self {
893        self.assignments.extend(fields.values().map(|field| {
894            (
895                field.id.as_semantic_id().clone(),
896                field.type_assignment.clone(),
897            )
898        }));
899        self
900    }
901
902    /// Attaches explicit G2 Provider declaration contracts without deciding
903    /// provider-value or Context compatibility.
904    #[must_use]
905    pub fn with_provider_types(
906        mut self,
907        providers: &BTreeMap<crate::ProviderId, ProviderEntity>,
908    ) -> Self {
909        for provider in providers.values() {
910            let semantic_type = self
911                .aliases
912                .values()
913                .find(|alias| {
914                    alias.provenance.path == provider.declared_type.provenance.path
915                        && alias.name == provider.declared_type.text
916                })
917                .map_or_else(
918                    || {
919                        semantic_type_from_annotation(&provider.declared_type.text)
920                            .unwrap_or(SemanticType::Unknown)
921                    },
922                    |alias| alias.semantic_type.clone(),
923                );
924            let subject = provider.id.as_semantic_id().clone();
925            self.assignments.insert(
926                subject.clone(),
927                SemanticTypeAssignment {
928                    id: provider.declared_type_id.clone(),
929                    subject: subject.clone(),
930                    semantic_type,
931                    origin: subject,
932                    status: SemanticTypeStatus::Declared,
933                    provenance: provider.provenance.clone(),
934                },
935            );
936        }
937        self
938    }
939
940    /// Attaches explicit G3 Consumer requested-type contracts without deciding
941    /// Context, Provider, or Consumer compatibility.
942    #[must_use]
943    pub fn with_consumer_types(
944        mut self,
945        consumers: &BTreeMap<crate::ConsumerId, ConsumerEntity>,
946    ) -> Self {
947        for consumer in consumers.values() {
948            let semantic_type = self
949                .aliases
950                .values()
951                .find(|alias| {
952                    alias.provenance.path == consumer.requested_type.provenance.path
953                        && alias.name == consumer.requested_type.text
954                })
955                .map_or_else(
956                    || {
957                        semantic_type_from_annotation(&consumer.requested_type.text)
958                            .unwrap_or(SemanticType::Unknown)
959                    },
960                    |alias| alias.semantic_type.clone(),
961                );
962            let subject = consumer.id.as_semantic_id().clone();
963            self.assignments.insert(
964                subject.clone(),
965                SemanticTypeAssignment {
966                    id: consumer.requested_type_id.clone(),
967                    subject: subject.clone(),
968                    semantic_type,
969                    origin: subject,
970                    status: SemanticTypeStatus::Declared,
971                    provenance: consumer.provenance.clone(),
972                },
973            );
974        }
975        self
976    }
977
978    fn from_assignments_and_aliases(
979        assignments: BTreeMap<SemanticId, SemanticTypeAssignment>,
980        aliases: BTreeMap<SemanticId, SemanticTypeAlias>,
981    ) -> Self {
982        Self {
983            assignments,
984            aliases,
985            list_scopes: BTreeMap::new(),
986            member_accesses: BTreeMap::new(),
987            computed_values: BTreeMap::new(),
988            action_signatures: BTreeMap::new(),
989            effect_statements: BTreeMap::new(),
990        }
991    }
992
993    /// Attaches canonical inferred and declared contracts to computed entities.
994    ///
995    /// # Panics
996    ///
997    /// Panics when a computed expression ID does not resolve in `graph`.
998    #[allow(clippy::too_many_lines)]
999    #[must_use]
1000    pub fn with_computed_value_types(
1001        mut self,
1002        components: &[ComponentNode],
1003        computed_values: &BTreeMap<SemanticId, ComputedValue>,
1004        graph: &ExpressionGraph,
1005        references: &[SemanticReference],
1006    ) -> Self {
1007        let computed_components = components
1008            .iter()
1009            .flat_map(|component| {
1010                component
1011                    .methods
1012                    .iter()
1013                    .filter(|method| method.is_computed())
1014                    .map(move |method| (component.id.computed(&method.name), component))
1015            })
1016            .collect::<BTreeMap<_, _>>();
1017        let mut computed_types = BTreeMap::new();
1018        let mut expression_types = BTreeMap::new();
1019
1020        for computed in computed_values.values() {
1021            let mut visiting = BTreeSet::new();
1022            infer_computed_type(
1023                &computed.id,
1024                graph,
1025                &self.assignments,
1026                &computed_components,
1027                computed_values,
1028                references,
1029                &mut computed_types,
1030                &mut expression_types,
1031                &mut visiting,
1032            );
1033        }
1034
1035        for (id, semantic_type) in expression_types {
1036            let node = graph.node(&id).expect("computed expression graph node");
1037            self.assignments.insert(
1038                id.clone(),
1039                SemanticTypeAssignment {
1040                    id: SemanticTypeId::for_subject(&id),
1041                    subject: id,
1042                    semantic_type,
1043                    origin: node.owner.clone(),
1044                    status: SemanticTypeStatus::Inferred,
1045                    provenance: node.provenance.clone(),
1046                },
1047            );
1048        }
1049
1050        for computed in computed_values.values() {
1051            let has_expression = graph.root_for(&computed.id).is_some();
1052            let inferred = computed_types
1053                .get(&computed.id)
1054                .cloned()
1055                .unwrap_or(SemanticType::Unknown);
1056            let method_assignment = self.assignments.get(&computed.method);
1057            let declared_return_type = method_assignment
1058                .filter(|assignment| assignment.status == SemanticTypeStatus::Declared)
1059                .map(|assignment| assignment.semantic_type.clone());
1060            let declared_return_compatible = declared_return_type
1061                .as_ref()
1062                .filter(|_| has_expression)
1063                .map(|declared| is_assignable(&inferred, declared));
1064            let (semantic_type, status, origin) = if has_expression {
1065                (
1066                    inferred,
1067                    SemanticTypeStatus::Inferred,
1068                    computed.method.clone(),
1069                )
1070            } else if let Some(assignment) = method_assignment {
1071                (
1072                    assignment.semantic_type.clone(),
1073                    assignment.status,
1074                    assignment.origin.clone(),
1075                )
1076            } else {
1077                (
1078                    SemanticType::Unknown,
1079                    SemanticTypeStatus::Inferred,
1080                    computed.method.clone(),
1081                )
1082            };
1083            let serialization = serialization_compatibility(&semantic_type);
1084            let boundary_compatibility = boundary_compatibility(
1085                &semantic_type,
1086                computed.execution_boundary,
1087                ExecutionBoundary::Client,
1088            );
1089
1090            self.assignments.insert(
1091                computed.id.clone(),
1092                SemanticTypeAssignment {
1093                    id: SemanticTypeId::for_subject(&computed.id),
1094                    subject: computed.id.clone(),
1095                    semantic_type: semantic_type.clone(),
1096                    origin,
1097                    status,
1098                    provenance: computed.provenance.clone(),
1099                },
1100            );
1101            self.computed_values.insert(
1102                computed.id.clone(),
1103                ComputedValueType {
1104                    computed: computed.id.clone(),
1105                    method: computed.method.clone(),
1106                    semantic_type,
1107                    status,
1108                    declared_return_type,
1109                    declared_return_compatible,
1110                    serialization,
1111                    execution_boundary: computed.execution_boundary,
1112                    boundary_compatibility,
1113                    provenance: computed.provenance.clone(),
1114                },
1115            );
1116        }
1117
1118        self
1119    }
1120
1121    /// Attaches inferred types to canonical Provider and Context-default
1122    /// expressions using their already-resolved component ownership and the
1123    /// existing State/Computed type products. This creates no Context
1124    /// compatibility result; G5 consumes these facts separately.
1125    #[must_use]
1126    pub fn with_context_source_expression_types(
1127        mut self,
1128        components: &[ComponentNode],
1129        contexts: &BTreeMap<crate::ContextId, ContextEntity>,
1130        providers: &BTreeMap<crate::ProviderId, ProviderEntity>,
1131        graph: &ExpressionGraph,
1132    ) -> Self {
1133        let components_by_id = components
1134            .iter()
1135            .map(|component| (component.id.clone(), component))
1136            .collect::<BTreeMap<_, _>>();
1137        let owners = contexts
1138            .values()
1139            .filter_map(|context| {
1140                context
1141                    .owner
1142                    .entity_id()
1143                    .map(|owner| (context.id.as_semantic_id().clone(), owner.clone()))
1144            })
1145            .chain(providers.values().filter_map(|provider| {
1146                provider
1147                    .owner
1148                    .entity_id()
1149                    .map(|owner| (provider.id.as_semantic_id().clone(), owner.clone()))
1150            }))
1151            .collect::<BTreeMap<_, _>>();
1152        let expression_ids = owners
1153            .keys()
1154            .flat_map(|owner| graph.nodes_for(owner))
1155            .map(|node| node.id.clone())
1156            .collect::<Vec<_>>();
1157        let mut inferred = BTreeMap::new();
1158        for id in expression_ids {
1159            infer_context_source_expression_type(
1160                &id,
1161                graph,
1162                &self.assignments,
1163                &owners,
1164                &components_by_id,
1165                &mut inferred,
1166            );
1167        }
1168        for (id, semantic_type) in inferred {
1169            let Some(node) = graph.node(&id) else {
1170                continue;
1171            };
1172            self.assignments.insert(
1173                id.clone(),
1174                SemanticTypeAssignment {
1175                    id: SemanticTypeId::for_subject(&id),
1176                    subject: id,
1177                    semantic_type,
1178                    origin: node.owner.clone(),
1179                    status: SemanticTypeStatus::Inferred,
1180                    provenance: node.provenance.clone(),
1181                },
1182            );
1183        }
1184        self
1185    }
1186
1187    fn with_action_signature_types(mut self, components: &[ComponentNode]) -> Self {
1188        for method in components
1189            .iter()
1190            .flat_map(|component| &component.methods)
1191            .filter(|method| method.is_action())
1192        {
1193            let input = method
1194                .parameters
1195                .iter()
1196                .filter_map(|parameter| {
1197                    self.assignments
1198                        .get(&parameter.id)
1199                        .map(|assignment| (parameter.id.clone(), assignment.semantic_type.clone()))
1200                })
1201                .collect();
1202            let output = self
1203                .assignments
1204                .get(&method.id)
1205                .map(|assignment| assignment.semantic_type.clone());
1206            self.action_signatures.insert(
1207                method.id.clone(),
1208                ActionSignatureType {
1209                    method: method.id.clone(),
1210                    input,
1211                    output,
1212                    is_async: method.is_async,
1213                },
1214            );
1215        }
1216        self
1217    }
1218
1219    #[must_use]
1220    pub fn normalized(mut self) -> Self {
1221        for assignment in self.assignments.values_mut() {
1222            assignment.semantic_type = normalize_semantic_type(assignment.semantic_type.clone());
1223        }
1224        for alias in self.aliases.values_mut() {
1225            alias.semantic_type = normalize_semantic_type(alias.semantic_type.clone());
1226        }
1227        for scope in self.list_scopes.values_mut() {
1228            scope.item_type = normalize_semantic_type(scope.item_type.clone());
1229            if let Some(index_type) = &mut scope.index_type {
1230                *index_type = normalize_semantic_type(index_type.clone());
1231            }
1232        }
1233        for access in self.member_accesses.values_mut() {
1234            if let Some(semantic_type) = &mut access.semantic_type {
1235                *semantic_type = normalize_semantic_type(semantic_type.clone());
1236            }
1237        }
1238        for computed in self.computed_values.values_mut() {
1239            computed.semantic_type = normalize_semantic_type(computed.semantic_type.clone());
1240        }
1241        for action in self.action_signatures.values_mut() {
1242            for (_, input) in &mut action.input {
1243                *input = normalize_semantic_type(input.clone());
1244            }
1245            if let Some(output) = &mut action.output {
1246                *output = normalize_semantic_type(output.clone());
1247            }
1248        }
1249        for statement in self.effect_statements.values_mut() {
1250            statement.operand_types = statement
1251                .operand_types
1252                .iter()
1253                .cloned()
1254                .map(normalize_semantic_type)
1255                .collect();
1256            if let Some(target_type) = &mut statement.target_type {
1257                *target_type = normalize_semantic_type(target_type.clone());
1258            }
1259        }
1260        self
1261    }
1262
1263    /// Attaches inferred types to canonical declaration expression nodes.
1264    ///
1265    /// # Panics
1266    ///
1267    /// Panics when the graph's node index contains an ID that does not resolve
1268    /// to an expression node.
1269    #[must_use]
1270    pub fn with_expression_types(
1271        mut self,
1272        graph: &ExpressionGraph,
1273        components: &[ComponentNode],
1274        contexts: &BTreeMap<crate::ContextId, ContextEntity>,
1275        providers: &BTreeMap<crate::ProviderId, ProviderEntity>,
1276    ) -> Self {
1277        let declaration_owners = components
1278            .iter()
1279            .flat_map(|component| component.state_fields.iter().map(|field| field.id.clone()))
1280            .chain(
1281                contexts
1282                    .keys()
1283                    .map(|context| context.as_semantic_id().clone()),
1284            )
1285            .chain(
1286                providers
1287                    .keys()
1288                    .map(|provider| provider.as_semantic_id().clone()),
1289            )
1290            .collect::<BTreeSet<_>>();
1291        let nodes = graph
1292            .nodes
1293            .values()
1294            .filter(|node| declaration_owners.contains(&node.owner))
1295            .map(|node| node.id.clone())
1296            .collect::<Vec<_>>();
1297        for id in nodes {
1298            let semantic_type = expression_semantic_type(&id, graph, &self.assignments);
1299            let node = graph.node(&id).expect("expression graph node");
1300            self.assignments.insert(
1301                id.clone(),
1302                SemanticTypeAssignment {
1303                    id: SemanticTypeId::for_subject(&id),
1304                    subject: id,
1305                    semantic_type,
1306                    origin: node.owner.clone(),
1307                    status: SemanticTypeStatus::Inferred,
1308                    provenance: node.provenance.clone(),
1309                },
1310            );
1311        }
1312        self
1313    }
1314
1315    /// Attaches effect operand types and statement-level capability facts.
1316    ///
1317    /// This consumes only the immutable registry and existing component/ASM
1318    /// products. It intentionally records incompatibilities without emitting
1319    /// semantic diagnostics; F5 owns language-rule rejection.
1320    ///
1321    /// # Panics
1322    ///
1323    /// Panics when the graph's effect expression index contains an ID that
1324    /// does not resolve to an expression node.
1325    #[must_use]
1326    pub fn with_effect_statement_types(
1327        mut self,
1328        components: &[ComponentNode],
1329        effects: &BTreeMap<SemanticId, Effect>,
1330        statements: &BTreeMap<SemanticId, EffectStatement>,
1331        graph: &ExpressionGraph,
1332    ) -> Self {
1333        let components_by_id = components
1334            .iter()
1335            .map(|component| (component.id.clone(), component))
1336            .collect::<BTreeMap<_, _>>();
1337        let effect_nodes = effects
1338            .values()
1339            .flat_map(|effect| graph.nodes_for(&effect.id))
1340            .map(|node| node.id.clone())
1341            .collect::<Vec<_>>();
1342        let mut expression_types = BTreeMap::new();
1343        for id in effect_nodes {
1344            infer_effect_expression_type(
1345                &id,
1346                graph,
1347                &self.assignments,
1348                &components_by_id,
1349                effects,
1350                &mut expression_types,
1351            );
1352        }
1353        for (id, semantic_type) in expression_types {
1354            let node = graph.node(&id).expect("effect expression graph node");
1355            self.assignments.insert(
1356                id.clone(),
1357                SemanticTypeAssignment {
1358                    id: SemanticTypeId::for_subject(&id),
1359                    subject: id,
1360                    semantic_type,
1361                    origin: node.owner.clone(),
1362                    status: SemanticTypeStatus::Inferred,
1363                    provenance: node.provenance.clone(),
1364                },
1365            );
1366        }
1367        for statement in statements.values() {
1368            let Some(effect) = effects.get(&statement.owner) else {
1369                continue;
1370            };
1371            let Some(component_id) = effect.owner.entity_id() else {
1372                continue;
1373            };
1374            let Some(component) = components_by_id.get(component_id) else {
1375                continue;
1376            };
1377            let record = effect_statement_type_record(
1378                statement,
1379                effect,
1380                component,
1381                graph,
1382                &self.assignments,
1383            );
1384            self.effect_statements.insert(statement.id.clone(), record);
1385        }
1386        self
1387    }
1388
1389    /// Attaches inferred types to direct text-binding entities with resolved
1390    /// state or local targets.
1391    #[must_use]
1392    pub fn with_template_binding_types(
1393        mut self,
1394        entities: &[TemplateSemanticEntity],
1395        references: &[SemanticReference],
1396    ) -> Self {
1397        for entity in entities.iter().filter(|entity| {
1398            matches!(
1399                entity.kind,
1400                TemplateSemanticKind::Binding
1401                    | TemplateSemanticKind::AttributeBinding
1402                    | TemplateSemanticKind::Conditional
1403            )
1404        }) {
1405            let Some(reference) = references.iter().find(|reference| {
1406                reference.source == entity.id
1407                    && matches!(
1408                        reference.kind,
1409                        SemanticReferenceKind::TemplateState
1410                            | SemanticReferenceKind::TemplateComputed
1411                            | SemanticReferenceKind::TemplateLocal
1412                    )
1413            }) else {
1414                continue;
1415            };
1416            let Some(target) = self.assignments.get(&reference.target) else {
1417                continue;
1418            };
1419            self.assignments.insert(
1420                entity.id.clone(),
1421                SemanticTypeAssignment {
1422                    id: SemanticTypeId::for_subject(&entity.id),
1423                    subject: entity.id.clone(),
1424                    semantic_type: target.semantic_type.clone(),
1425                    origin: target.origin.clone(),
1426                    status: SemanticTypeStatus::Inferred,
1427                    provenance: entity.provenance.clone(),
1428                },
1429            );
1430        }
1431        self.with_list_scope_types(entities, references)
1432            .with_list_member_types(entities)
1433    }
1434
1435    fn with_list_scope_types(
1436        mut self,
1437        entities: &[TemplateSemanticEntity],
1438        references: &[SemanticReference],
1439    ) -> Self {
1440        for entity in entities
1441            .iter()
1442            .filter(|entity| entity.kind == TemplateSemanticKind::List)
1443        {
1444            let Some(reference) = references.iter().find(|reference| {
1445                reference.source == entity.id
1446                    && matches!(
1447                        reference.kind,
1448                        SemanticReferenceKind::TemplateState
1449                            | SemanticReferenceKind::TemplateComputed
1450                    )
1451            }) else {
1452                continue;
1453            };
1454            let Some(iterable) = self.assignments.get(&reference.target) else {
1455                continue;
1456            };
1457            let iterable_type = iterable.semantic_type.clone();
1458            let iterable_origin = iterable.origin.clone();
1459            self.assignments.insert(
1460                entity.id.clone(),
1461                SemanticTypeAssignment {
1462                    id: SemanticTypeId::for_subject(&entity.id),
1463                    subject: entity.id.clone(),
1464                    semantic_type: iterable_type.clone(),
1465                    origin: iterable_origin,
1466                    status: SemanticTypeStatus::Inferred,
1467                    provenance: entity.provenance.clone(),
1468                },
1469            );
1470            let Some((item_name, item_type)) = entity
1471                .list_item_variable
1472                .as_ref()
1473                .zip(iterable_element_type(&iterable_type))
1474            else {
1475                continue;
1476            };
1477            self.list_scopes.insert(
1478                entity.id.clone(),
1479                ListTemplateScopeType {
1480                    list: entity.id.clone(),
1481                    item_name: item_name.clone(),
1482                    item_type,
1483                    index_name: entity.list_index_variable.clone(),
1484                    index_type: entity
1485                        .list_index_variable
1486                        .as_ref()
1487                        .map(|_| SemanticType::Number),
1488                },
1489            );
1490        }
1491        self
1492    }
1493
1494    fn with_list_member_types(mut self, entities: &[TemplateSemanticEntity]) -> Self {
1495        for entity in entities.iter().filter(|entity| {
1496            matches!(
1497                entity.kind,
1498                TemplateSemanticKind::Binding | TemplateSemanticKind::AttributeBinding
1499            ) && entity.scope == crate::TemplateSemanticScope::ListItem
1500        }) {
1501            let Some(expression) = entity.expression.as_deref() else {
1502                continue;
1503            };
1504            let Some((root, path)) = expression.split_once('.') else {
1505                continue;
1506            };
1507            let scopes = self
1508                .list_scopes
1509                .values()
1510                .filter(|scope| scope.item_name == root)
1511                .collect::<Vec<_>>();
1512            if scopes.len() != 1 {
1513                continue;
1514            }
1515            let semantic_type = member_access_type(&scopes[0].item_type, path);
1516            self.member_accesses.insert(
1517                entity.id.clone(),
1518                MemberAccessType {
1519                    entity: entity.id.clone(),
1520                    expression: expression.to_string(),
1521                    semantic_type: semantic_type.clone(),
1522                },
1523            );
1524            if let Some(semantic_type) = semantic_type {
1525                self.assignments.insert(
1526                    entity.id.clone(),
1527                    SemanticTypeAssignment {
1528                        id: SemanticTypeId::for_subject(&entity.id),
1529                        subject: entity.id.clone(),
1530                        semantic_type,
1531                        origin: scopes[0].list.clone(),
1532                        status: SemanticTypeStatus::Inferred,
1533                        provenance: entity.provenance.clone(),
1534                    },
1535                );
1536            }
1537        }
1538        self
1539    }
1540}
1541
1542fn member_access_type(semantic_type: &SemanticType, path: &str) -> Option<SemanticType> {
1543    let mut current = semantic_type;
1544    for member in path.split('.') {
1545        if matches!(current, SemanticType::Unknown) {
1546            return Some(SemanticType::Unknown);
1547        }
1548        let SemanticType::Object(object) = current else {
1549            return None;
1550        };
1551        current = object.properties.get(member)?;
1552    }
1553    Some(current.clone())
1554}
1555
1556fn iterable_element_type(semantic_type: &SemanticType) -> Option<SemanticType> {
1557    match semantic_type {
1558        SemanticType::Array(element) => Some((**element).clone()),
1559        SemanticType::Tuple(items) if items.is_empty() => Some(SemanticType::Unknown),
1560        SemanticType::Tuple(items) if items.windows(2).all(|pair| pair[0] == pair[1]) => {
1561            items.first().cloned()
1562        }
1563        SemanticType::Tuple(items) => Some(SemanticType::Union(items.clone())),
1564        SemanticType::Unknown => Some(SemanticType::Unknown),
1565        _ => None,
1566    }
1567}
1568
1569fn extend_with_local_type_assignments(
1570    assignments: &mut BTreeMap<SemanticId, SemanticTypeAssignment>,
1571    components: &[ComponentNode],
1572    provenance: &BTreeMap<SemanticId, SourceProvenance>,
1573) {
1574    for local in components
1575        .iter()
1576        .flat_map(|component| &component.methods)
1577        .flat_map(|method| &method.local_variables)
1578    {
1579        let Some(local_provenance) = provenance.get(&local.id) else {
1580            continue;
1581        };
1582        assignments.insert(
1583            local.id.clone(),
1584            SemanticTypeAssignment {
1585                id: SemanticTypeId::for_subject(&local.id),
1586                subject: local.id.clone(),
1587                semantic_type: infer_serializable_value_type(&local.value),
1588                origin: local.id.clone(),
1589                status: SemanticTypeStatus::Inferred,
1590                provenance: local_provenance.clone(),
1591            },
1592        );
1593    }
1594}
1595
1596fn extend_with_parameter_type_assignments(
1597    assignments: &mut BTreeMap<SemanticId, SemanticTypeAssignment>,
1598    components: &[ComponentNode],
1599    aliases: &BTreeMap<SemanticId, SemanticTypeAlias>,
1600    bindings: Option<&BindingTable>,
1601) {
1602    let aliases_by_path_and_name = aliases
1603        .values()
1604        .map(|alias| ((alias.provenance.path.clone(), alias.name.clone()), alias))
1605        .collect::<BTreeMap<_, _>>();
1606
1607    for parameter in components
1608        .iter()
1609        .flat_map(|component| &component.methods)
1610        .flat_map(|method| &method.parameters)
1611    {
1612        let Some(declared_type) = &parameter.declared_type else {
1613            continue;
1614        };
1615        let alias = aliases_by_path_and_name
1616            .get(&(
1617                declared_type.provenance.path.clone(),
1618                declared_type.text.clone(),
1619            ))
1620            .copied();
1621        let imported_alias = bindings
1622            .and_then(|bindings| {
1623                bindings.resolve_import(&declared_type.provenance.path, &declared_type.text)
1624            })
1625            .and_then(|binding| match &binding.target {
1626                ImportBindingTarget::Symbol(symbol) if symbol.kind == SymbolKind::TypeAlias => {
1627                    aliases.get(&symbol.id)
1628                }
1629                _ => None,
1630            });
1631        let alias = alias.or(imported_alias);
1632        let semantic_type = alias
1633            .map(|alias| alias.semantic_type.clone())
1634            .or_else(|| semantic_type_from_annotation(&declared_type.text));
1635        let Some(semantic_type) = semantic_type else {
1636            continue;
1637        };
1638        assignments.insert(
1639            parameter.id.clone(),
1640            SemanticTypeAssignment {
1641                id: SemanticTypeId::for_subject(&parameter.id),
1642                subject: parameter.id.clone(),
1643                semantic_type,
1644                origin: alias.map_or_else(|| parameter.id.clone(), |alias| alias.id.clone()),
1645                status: SemanticTypeStatus::Declared,
1646                provenance: declared_type.provenance.clone(),
1647            },
1648        );
1649    }
1650}
1651
1652fn extend_with_method_return_type_assignments(
1653    assignments: &mut BTreeMap<SemanticId, SemanticTypeAssignment>,
1654    components: &[ComponentNode],
1655    provenance: &BTreeMap<SemanticId, SourceProvenance>,
1656    aliases: &BTreeMap<SemanticId, SemanticTypeAlias>,
1657    bindings: Option<&BindingTable>,
1658) {
1659    let aliases_by_path_and_name = aliases
1660        .values()
1661        .map(|alias| ((alias.provenance.path.clone(), alias.name.clone()), alias))
1662        .collect::<BTreeMap<_, _>>();
1663
1664    for method in components.iter().flat_map(|component| &component.methods) {
1665        let declared = method.declared_return_type.as_ref();
1666        let (semantic_type, origin, status, assignment_provenance) =
1667            if let Some(declared) = declared {
1668                let alias = aliases_by_path_and_name
1669                    .get(&(declared.provenance.path.clone(), declared.text.clone()))
1670                    .copied()
1671                    .or_else(|| {
1672                        bindings
1673                            .and_then(|bindings| {
1674                                bindings.resolve_import(&declared.provenance.path, &declared.text)
1675                            })
1676                            .and_then(|binding| match &binding.target {
1677                                ImportBindingTarget::Symbol(symbol)
1678                                    if symbol.kind == SymbolKind::TypeAlias =>
1679                                {
1680                                    aliases.get(&symbol.id)
1681                                }
1682                                _ => None,
1683                            })
1684                    });
1685                let Some(semantic_type) = alias
1686                    .map(|alias| alias.semantic_type.clone())
1687                    .or_else(|| semantic_type_from_annotation(&declared.text))
1688                else {
1689                    continue;
1690                };
1691                (
1692                    semantic_type,
1693                    alias.map_or_else(|| method.id.clone(), |alias| alias.id.clone()),
1694                    SemanticTypeStatus::Declared,
1695                    declared.provenance.clone(),
1696                )
1697            } else {
1698                let Some(return_type) = inferred_return_type(&method.return_values) else {
1699                    continue;
1700                };
1701                let Some(method_provenance) = provenance.get(&method.id) else {
1702                    continue;
1703                };
1704                (
1705                    return_type,
1706                    method.id.clone(),
1707                    SemanticTypeStatus::Inferred,
1708                    method_provenance.clone(),
1709                )
1710            };
1711        assignments.insert(
1712            method.id.clone(),
1713            SemanticTypeAssignment {
1714                id: SemanticTypeId::for_subject(&method.id),
1715                subject: method.id.clone(),
1716                semantic_type,
1717                origin,
1718                status,
1719                provenance: assignment_provenance,
1720            },
1721        );
1722    }
1723}
1724
1725fn inferred_return_type(values: &[SerializableValue]) -> Option<SemanticType> {
1726    let mut types = values
1727        .iter()
1728        .map(infer_serializable_value_type)
1729        .collect::<Vec<_>>();
1730    match types.as_slice() {
1731        [] => None,
1732        [semantic_type] => Some(semantic_type.clone()),
1733        _ if types.windows(2).all(|window| window[0] == window[1]) => types.pop(),
1734        _ => Some(SemanticType::Union(types)),
1735    }
1736}
1737
1738#[allow(clippy::too_many_arguments)]
1739fn infer_effect_expression_type(
1740    id: &SemanticId,
1741    graph: &ExpressionGraph,
1742    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
1743    components: &BTreeMap<SemanticId, &ComponentNode>,
1744    effects: &BTreeMap<SemanticId, Effect>,
1745    expression_types: &mut BTreeMap<SemanticId, SemanticType>,
1746) -> SemanticType {
1747    if let Some(semantic_type) = expression_types.get(id) {
1748        return semantic_type.clone();
1749    }
1750    let node = graph.node(id).expect("effect expression graph node");
1751    let child_type =
1752        |child: &SemanticId, expression_types: &mut BTreeMap<SemanticId, SemanticType>| {
1753            infer_effect_expression_type(
1754                child,
1755                graph,
1756                assignments,
1757                components,
1758                effects,
1759                expression_types,
1760            )
1761        };
1762    let semantic_type = match &node.kind {
1763        ExpressionNodeKind::Literal(value) => state_initializer_value_type(value),
1764        ExpressionNodeKind::Boolean(value) => SemanticType::BooleanLiteral(*value),
1765        ExpressionNodeKind::Template { .. } => SemanticType::String,
1766        ExpressionNodeKind::Identifier(_) | ExpressionNodeKind::Call { .. } => {
1767            SemanticType::Unknown
1768        }
1769        ExpressionNodeKind::SemanticPackagePureCall {
1770            operation: crate::semantic_package::SemanticPackagePureOperation::Identity,
1771            arguments,
1772            ..
1773        } => arguments.first().map_or(SemanticType::Unknown, |argument| {
1774            child_type(argument, expression_types)
1775        }),
1776        ExpressionNodeKind::BuiltinPureCall {
1777            operation:
1778                crate::component_graph::BuiltinPureOperation::MathAbs
1779                | crate::component_graph::BuiltinPureOperation::MathFloor
1780                | crate::component_graph::BuiltinPureOperation::MathCeil
1781                | crate::component_graph::BuiltinPureOperation::MathRound
1782                | crate::component_graph::BuiltinPureOperation::MathMin
1783                | crate::component_graph::BuiltinPureOperation::MathMax,
1784            ..
1785        } => SemanticType::Number,
1786        ExpressionNodeKind::ThisMember { name } => effects
1787            .get(&node.owner)
1788            .and_then(|effect| effect.owner.entity_id())
1789            .and_then(|component_id| components.get(component_id))
1790            .and_then(|component| {
1791                component
1792                    .state_fields
1793                    .iter()
1794                    .find(|field| field.name == *name)
1795                    .map(|field| field.id.clone())
1796                    .or_else(|| {
1797                        let computed = component.id.computed(name);
1798                        assignments.contains_key(&computed).then_some(computed)
1799                    })
1800            })
1801            .and_then(|target| assignments.get(&target))
1802            .map_or(SemanticType::Unknown, |assignment| {
1803                assignment.semantic_type.clone()
1804            }),
1805        ExpressionNodeKind::MemberAccess {
1806            object, property, ..
1807        } => computed_member_access_type(&child_type(object, expression_types), property),
1808        ExpressionNodeKind::IndexAccess { object, index } => computed_index_access_type(
1809            &child_type(object, expression_types),
1810            &child_type(index, expression_types),
1811        ),
1812        ExpressionNodeKind::Conditional {
1813            when_true,
1814            when_false,
1815            ..
1816        } => conditional_result_type(
1817            child_type(when_true, expression_types),
1818            child_type(when_false, expression_types),
1819        ),
1820        ExpressionNodeKind::Arithmetic {
1821            left,
1822            right,
1823            operator,
1824        } => operator_result_type(
1825            SemanticOperator::Arithmetic(*operator),
1826            &[
1827                child_type(left, expression_types),
1828                child_type(right, expression_types),
1829            ],
1830        )
1831        .unwrap_or(SemanticType::Unknown),
1832        ExpressionNodeKind::Comparison {
1833            left,
1834            right,
1835            operator,
1836        } => operator_result_type(
1837            SemanticOperator::Comparison(*operator),
1838            &[
1839                child_type(left, expression_types),
1840                child_type(right, expression_types),
1841            ],
1842        )
1843        .unwrap_or(SemanticType::Unknown),
1844        ExpressionNodeKind::Logical {
1845            left,
1846            right,
1847            operator,
1848        } => operator_result_type(
1849            SemanticOperator::Logical(*operator),
1850            &[
1851                child_type(left, expression_types),
1852                child_type(right, expression_types),
1853            ],
1854        )
1855        .unwrap_or(SemanticType::Unknown),
1856        ExpressionNodeKind::NullishCoalescing { left, right } => operator_result_type(
1857            SemanticOperator::NullishCoalescing,
1858            &[
1859                child_type(left, expression_types),
1860                child_type(right, expression_types),
1861            ],
1862        )
1863        .unwrap_or(SemanticType::Unknown),
1864        ExpressionNodeKind::Unary { operand, operator } => operator_result_type(
1865            SemanticOperator::Unary(*operator),
1866            &[child_type(operand, expression_types)],
1867        )
1868        .unwrap_or(SemanticType::Unknown),
1869    };
1870    expression_types.insert(id.clone(), semantic_type.clone());
1871    semantic_type
1872}
1873
1874#[allow(clippy::too_many_lines)]
1875fn effect_statement_type_record(
1876    statement: &EffectStatement,
1877    effect: &Effect,
1878    component: &ComponentNode,
1879    graph: &ExpressionGraph,
1880    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
1881) -> EffectStatementTypeRecord {
1882    let expression_type = |id: &SemanticId| {
1883        assignments
1884            .get(id)
1885            .map_or(SemanticType::Unknown, |assignment| {
1886                assignment.semantic_type.clone()
1887            })
1888    };
1889    let record = |operation_classification,
1890                  operand_types,
1891                  target_type,
1892                  signature_compatibility,
1893                  boundary_compatibility,
1894                  serialization_compatibility,
1895                  capability_operation| EffectStatementTypeRecord {
1896        statement: statement.id.clone(),
1897        operation_classification,
1898        operand_types,
1899        target_type,
1900        signature_compatibility,
1901        boundary_compatibility,
1902        serialization_compatibility,
1903        capability_operation,
1904        provenance: statement.provenance.clone(),
1905    };
1906    match &statement.kind {
1907        EffectStatementKind::ExternalMemberAssignment { target, value } => {
1908            let value_type = expression_type(value);
1909            if let Some(name) = this_member_name(target, graph) {
1910                if let Some(field) = component
1911                    .state_fields
1912                    .iter()
1913                    .find(|field| field.name == name)
1914                {
1915                    let target_type = assignments
1916                        .get(&field.id)
1917                        .map(|assignment| assignment.semantic_type.clone());
1918                    let signature = target_type
1919                        .as_ref()
1920                        .map_or(EffectCompatibility::Unknown, |target| {
1921                            effect_assignability(&value_type, target)
1922                        });
1923                    return record(
1924                        EffectOperationClassification::ReactiveStateAssignment,
1925                        vec![value_type],
1926                        target_type,
1927                        signature,
1928                        EffectCompatibility::Compatible,
1929                        EffectCompatibility::Compatible,
1930                        None,
1931                    );
1932                }
1933                return record(
1934                    EffectOperationClassification::UnresolvedComponentAssignment,
1935                    vec![value_type],
1936                    None,
1937                    EffectCompatibility::Unknown,
1938                    EffectCompatibility::Unknown,
1939                    EffectCompatibility::Unknown,
1940                    None,
1941                );
1942            }
1943            let path = static_capability_path(target, graph);
1944            let operation = path.as_deref().and_then(|path| {
1945                EFFECT_CAPABILITY_REGISTRY
1946                    .operation_at(path, CapabilityOperationKind::MemberAssignment)
1947            });
1948            let Some(operation) = operation else {
1949                return record(
1950                    EffectOperationClassification::UnknownExternalCapability,
1951                    vec![value_type],
1952                    None,
1953                    EffectCompatibility::Unknown,
1954                    EffectCompatibility::Unknown,
1955                    EffectCompatibility::Unknown,
1956                    None,
1957                );
1958            };
1959            let target_type = operation_value_type(operation.signature.parameters, 0);
1960            let signature = target_type
1961                .as_ref()
1962                .map_or(EffectCompatibility::Unknown, |target| {
1963                    effect_assignability(&value_type, target)
1964                });
1965            record(
1966                EffectOperationClassification::RecognizedCapability,
1967                vec![value_type],
1968                target_type,
1969                signature,
1970                effect_boundary_compatibility(effect, operation.boundary),
1971                EffectCompatibility::Compatible,
1972                Some(operation.id),
1973            )
1974        }
1975        EffectStatementKind::CapabilityCall { callee, arguments } => {
1976            let argument_types = arguments.iter().map(expression_type).collect::<Vec<_>>();
1977            if let Some(name) = this_member_name(callee, graph) {
1978                let classification = component
1979                    .methods
1980                    .iter()
1981                    .find(|method| method.name == name)
1982                    .map_or(
1983                        EffectOperationClassification::UnresolvedComponentCall,
1984                        |method| {
1985                            if method.is_action() {
1986                                EffectOperationClassification::ComponentActionCall
1987                            } else if method.is_effect() {
1988                                EffectOperationClassification::ComponentEffectCall
1989                            } else {
1990                                EffectOperationClassification::ComponentMethodCall
1991                            }
1992                        },
1993                    );
1994                return record(
1995                    classification,
1996                    argument_types,
1997                    None,
1998                    EffectCompatibility::Unknown,
1999                    EffectCompatibility::Compatible,
2000                    EffectCompatibility::Compatible,
2001                    None,
2002                );
2003            }
2004            let path = static_capability_path(callee, graph);
2005            let operation = path.as_deref().and_then(|path| {
2006                EFFECT_CAPABILITY_REGISTRY.operation_at(path, CapabilityOperationKind::MethodCall)
2007            });
2008            let Some(operation) = operation else {
2009                return record(
2010                    EffectOperationClassification::UnknownExternalCapability,
2011                    argument_types,
2012                    None,
2013                    EffectCompatibility::Incompatible,
2014                    EffectCompatibility::Unknown,
2015                    EffectCompatibility::Unknown,
2016                    None,
2017                );
2018            };
2019            let signature =
2020                operation_signature_compatibility(operation.signature.parameters, &argument_types);
2021            let serialization = match operation.argument_serialization {
2022                crate::ArgumentSerializationPolicy::None => EffectCompatibility::Compatible,
2023                crate::ArgumentSerializationPolicy::Structural => argument_types.iter().fold(
2024                    EffectCompatibility::Compatible,
2025                    |compatibility, semantic_type| {
2026                        combine_effect_compatibility(
2027                            compatibility,
2028                            if serialization_compatibility(semantic_type)
2029                                == SerializationCompatibility::Serializable
2030                            {
2031                                EffectCompatibility::Compatible
2032                            } else {
2033                                EffectCompatibility::Incompatible
2034                            },
2035                        )
2036                    },
2037                ),
2038            };
2039            record(
2040                EffectOperationClassification::RecognizedCapability,
2041                argument_types,
2042                None,
2043                signature,
2044                effect_boundary_compatibility(effect, operation.boundary),
2045                serialization,
2046                Some(operation.id),
2047            )
2048        }
2049        EffectStatementKind::EffectReturn { value: None } => record(
2050            EffectOperationClassification::BareReturn,
2051            Vec::new(),
2052            None,
2053            EffectCompatibility::Compatible,
2054            EffectCompatibility::Compatible,
2055            EffectCompatibility::Compatible,
2056            None,
2057        ),
2058        EffectStatementKind::EffectReturn { value: Some(value) } => record(
2059            EffectOperationClassification::ValueReturn,
2060            vec![expression_type(value)],
2061            None,
2062            EffectCompatibility::Incompatible,
2063            EffectCompatibility::Compatible,
2064            EffectCompatibility::Compatible,
2065            None,
2066        ),
2067        EffectStatementKind::Empty => record(
2068            EffectOperationClassification::Empty,
2069            Vec::new(),
2070            None,
2071            EffectCompatibility::Compatible,
2072            EffectCompatibility::Compatible,
2073            EffectCompatibility::Compatible,
2074            None,
2075        ),
2076        EffectStatementKind::Unsupported(_) => record(
2077            EffectOperationClassification::UnsupportedStatement,
2078            Vec::new(),
2079            None,
2080            EffectCompatibility::Incompatible,
2081            EffectCompatibility::Unknown,
2082            EffectCompatibility::Unknown,
2083            None,
2084        ),
2085    }
2086}
2087
2088fn this_member_name<'a>(id: &SemanticId, graph: &'a ExpressionGraph) -> Option<&'a str> {
2089    match &graph.node(id)?.kind {
2090        ExpressionNodeKind::ThisMember { name } => Some(name),
2091        _ => None,
2092    }
2093}
2094
2095fn static_capability_path(id: &SemanticId, graph: &ExpressionGraph) -> Option<String> {
2096    match &graph.node(id)?.kind {
2097        ExpressionNodeKind::Identifier(name) if name != "this" => Some(name.clone()),
2098        ExpressionNodeKind::MemberAccess {
2099            object, property, ..
2100        } => Some(format!(
2101            "{}.{}",
2102            static_capability_path(object, graph)?,
2103            property
2104        )),
2105        _ => None,
2106    }
2107}
2108
2109fn operation_value_type(parameters: CapabilityParameters, index: usize) -> Option<SemanticType> {
2110    match parameters {
2111        CapabilityParameters::Fixed(parameters) => parameters
2112            .get(index)
2113            .and_then(|parameter| parameter.semantic_type()),
2114        CapabilityParameters::Variadic(parameter) => parameter.semantic_type(),
2115    }
2116}
2117
2118fn operation_signature_compatibility(
2119    parameters: CapabilityParameters,
2120    arguments: &[SemanticType],
2121) -> EffectCompatibility {
2122    match parameters {
2123        CapabilityParameters::Fixed(parameters) if parameters.len() != arguments.len() => {
2124            EffectCompatibility::Incompatible
2125        }
2126        CapabilityParameters::Fixed(parameters) => parameters.iter().zip(arguments).fold(
2127            EffectCompatibility::Compatible,
2128            |compatibility, (parameter, argument)| {
2129                combine_effect_compatibility(
2130                    compatibility,
2131                    capability_value_compatibility(*parameter, argument),
2132                )
2133            },
2134        ),
2135        CapabilityParameters::Variadic(parameter) => arguments.iter().fold(
2136            EffectCompatibility::Compatible,
2137            |compatibility, argument| {
2138                combine_effect_compatibility(
2139                    compatibility,
2140                    capability_value_compatibility(parameter, argument),
2141                )
2142            },
2143        ),
2144    }
2145}
2146
2147fn capability_value_compatibility(
2148    contract: CapabilityValueContract,
2149    value: &SemanticType,
2150) -> EffectCompatibility {
2151    match contract {
2152        CapabilityValueContract::String => effect_assignability(
2153            value,
2154            &contract
2155                .semantic_type()
2156                .expect("string capability contract type"),
2157        ),
2158        CapabilityValueContract::SerializableDiagnosticValue => {
2159            if serialization_compatibility(value) == SerializationCompatibility::Serializable {
2160                EffectCompatibility::Compatible
2161            } else {
2162                EffectCompatibility::Incompatible
2163            }
2164        }
2165    }
2166}
2167
2168fn effect_assignability(source: &SemanticType, target: &SemanticType) -> EffectCompatibility {
2169    if source == &SemanticType::Unknown || source == &SemanticType::Never {
2170        EffectCompatibility::Unknown
2171    } else if is_assignable(source, target) {
2172        EffectCompatibility::Compatible
2173    } else {
2174        EffectCompatibility::Incompatible
2175    }
2176}
2177
2178fn effect_boundary_compatibility(
2179    effect: &Effect,
2180    operation_boundary: ExecutionBoundary,
2181) -> EffectCompatibility {
2182    if effect.execution_boundary == operation_boundary {
2183        EffectCompatibility::Compatible
2184    } else {
2185        EffectCompatibility::Incompatible
2186    }
2187}
2188
2189fn combine_effect_compatibility(
2190    left: EffectCompatibility,
2191    right: EffectCompatibility,
2192) -> EffectCompatibility {
2193    match (left, right) {
2194        (EffectCompatibility::Incompatible, _) | (_, EffectCompatibility::Incompatible) => {
2195            EffectCompatibility::Incompatible
2196        }
2197        (EffectCompatibility::Unknown, _) | (_, EffectCompatibility::Unknown) => {
2198            EffectCompatibility::Unknown
2199        }
2200        _ => EffectCompatibility::Compatible,
2201    }
2202}
2203
2204fn expression_semantic_type(
2205    id: &SemanticId,
2206    graph: &ExpressionGraph,
2207    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
2208) -> SemanticType {
2209    let node = graph.node(id).expect("expression graph node");
2210    let child_type = |id: &SemanticId| {
2211        assignments.get(id).map_or_else(
2212            || expression_semantic_type(id, graph, assignments),
2213            |assignment| assignment.semantic_type.clone(),
2214        )
2215    };
2216
2217    match &node.kind {
2218        ExpressionNodeKind::Literal(value) => state_initializer_value_type(value),
2219        ExpressionNodeKind::Boolean(value) => SemanticType::BooleanLiteral(*value),
2220        ExpressionNodeKind::Template { .. } => SemanticType::String,
2221        ExpressionNodeKind::Identifier(_)
2222        | ExpressionNodeKind::Call { .. }
2223        | ExpressionNodeKind::SemanticPackagePureCall { .. }
2224        | ExpressionNodeKind::BuiltinPureCall { .. }
2225        | ExpressionNodeKind::ThisMember { .. }
2226        | ExpressionNodeKind::MemberAccess { .. }
2227        | ExpressionNodeKind::IndexAccess { .. }
2228        | ExpressionNodeKind::Conditional { .. } => SemanticType::Unknown,
2229        ExpressionNodeKind::Arithmetic {
2230            left,
2231            right,
2232            operator,
2233        } => operator_result_type(
2234            SemanticOperator::Arithmetic(*operator),
2235            &[child_type(left), child_type(right)],
2236        )
2237        .unwrap_or(SemanticType::Unknown),
2238        ExpressionNodeKind::Comparison {
2239            left,
2240            right,
2241            operator,
2242        } => operator_result_type(
2243            SemanticOperator::Comparison(*operator),
2244            &[child_type(left), child_type(right)],
2245        )
2246        .unwrap_or(SemanticType::Unknown),
2247        ExpressionNodeKind::Logical {
2248            left,
2249            right,
2250            operator,
2251        } => operator_result_type(
2252            SemanticOperator::Logical(*operator),
2253            &[child_type(left), child_type(right)],
2254        )
2255        .unwrap_or(SemanticType::Unknown),
2256        ExpressionNodeKind::NullishCoalescing { left, right } => operator_result_type(
2257            SemanticOperator::NullishCoalescing,
2258            &[child_type(left), child_type(right)],
2259        )
2260        .unwrap_or(SemanticType::Unknown),
2261        ExpressionNodeKind::Unary { operand, operator } => {
2262            operator_result_type(SemanticOperator::Unary(*operator), &[child_type(operand)])
2263                .unwrap_or(SemanticType::Unknown)
2264        }
2265    }
2266}
2267
2268#[allow(clippy::too_many_arguments)]
2269fn infer_computed_type(
2270    computed: &SemanticId,
2271    graph: &ExpressionGraph,
2272    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
2273    computed_components: &BTreeMap<SemanticId, &ComponentNode>,
2274    computed_values: &BTreeMap<SemanticId, ComputedValue>,
2275    references: &[SemanticReference],
2276    computed_types: &mut BTreeMap<SemanticId, SemanticType>,
2277    expression_types: &mut BTreeMap<SemanticId, SemanticType>,
2278    visiting: &mut BTreeSet<SemanticId>,
2279) -> SemanticType {
2280    if let Some(semantic_type) = computed_types.get(computed) {
2281        return semantic_type.clone();
2282    }
2283    if !visiting.insert(computed.clone()) {
2284        return SemanticType::Unknown;
2285    }
2286
2287    let semantic_type = graph
2288        .root_for(computed)
2289        .map_or(SemanticType::Unknown, |root| {
2290            infer_computed_expression_type(
2291                root,
2292                graph,
2293                assignments,
2294                computed_components,
2295                computed_values,
2296                references,
2297                computed_types,
2298                expression_types,
2299                visiting,
2300            )
2301        });
2302    visiting.remove(computed);
2303    computed_types.insert(computed.clone(), semantic_type.clone());
2304    semantic_type
2305}
2306
2307fn infer_context_source_expression_type(
2308    id: &SemanticId,
2309    graph: &ExpressionGraph,
2310    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
2311    owners: &BTreeMap<SemanticId, SemanticId>,
2312    components: &BTreeMap<SemanticId, &ComponentNode>,
2313    inferred: &mut BTreeMap<SemanticId, SemanticType>,
2314) -> SemanticType {
2315    if let Some(semantic_type) = inferred.get(id) {
2316        return semantic_type.clone();
2317    }
2318    let Some(node) = graph.node(id) else {
2319        return SemanticType::Unknown;
2320    };
2321    let child = |child: &SemanticId, inferred: &mut BTreeMap<SemanticId, SemanticType>| {
2322        infer_context_source_expression_type(
2323            child,
2324            graph,
2325            assignments,
2326            owners,
2327            components,
2328            inferred,
2329        )
2330    };
2331    let semantic_type = match &node.kind {
2332        ExpressionNodeKind::Literal(value) => state_initializer_value_type(value),
2333        ExpressionNodeKind::Boolean(value) => SemanticType::BooleanLiteral(*value),
2334        ExpressionNodeKind::Template { .. } => SemanticType::String,
2335        ExpressionNodeKind::Identifier(_) | ExpressionNodeKind::Call { .. } => {
2336            SemanticType::Unknown
2337        }
2338        ExpressionNodeKind::SemanticPackagePureCall {
2339            operation: crate::semantic_package::SemanticPackagePureOperation::Identity,
2340            arguments,
2341            ..
2342        } => arguments
2343            .first()
2344            .map_or(SemanticType::Unknown, |argument| child(argument, inferred)),
2345        ExpressionNodeKind::BuiltinPureCall {
2346            operation:
2347                crate::component_graph::BuiltinPureOperation::MathAbs
2348                | crate::component_graph::BuiltinPureOperation::MathFloor
2349                | crate::component_graph::BuiltinPureOperation::MathCeil
2350                | crate::component_graph::BuiltinPureOperation::MathRound
2351                | crate::component_graph::BuiltinPureOperation::MathMin
2352                | crate::component_graph::BuiltinPureOperation::MathMax,
2353            ..
2354        } => SemanticType::Number,
2355        ExpressionNodeKind::ThisMember { name } => owners
2356            .get(&node.owner)
2357            .and_then(|owner| components.get(owner))
2358            .and_then(|component| {
2359                component
2360                    .state_fields
2361                    .iter()
2362                    .find(|field| field.name == *name)
2363                    .map(|field| field.id.clone())
2364                    .or_else(|| {
2365                        let computed = component.id.computed(name);
2366                        assignments.contains_key(&computed).then_some(computed)
2367                    })
2368            })
2369            .and_then(|target| assignments.get(&target))
2370            .map_or(SemanticType::Unknown, |assignment| {
2371                assignment.semantic_type.clone()
2372            }),
2373        ExpressionNodeKind::MemberAccess {
2374            object, property, ..
2375        } => computed_member_access_type(&child(object, inferred), property),
2376        ExpressionNodeKind::IndexAccess { object, index } => {
2377            computed_index_access_type(&child(object, inferred), &child(index, inferred))
2378        }
2379        ExpressionNodeKind::Conditional {
2380            when_true,
2381            when_false,
2382            ..
2383        } => conditional_result_type(child(when_true, inferred), child(when_false, inferred)),
2384        ExpressionNodeKind::Arithmetic {
2385            left,
2386            right,
2387            operator,
2388        } => operator_result_type(
2389            SemanticOperator::Arithmetic(*operator),
2390            &[child(left, inferred), child(right, inferred)],
2391        )
2392        .unwrap_or(SemanticType::Unknown),
2393        ExpressionNodeKind::Comparison {
2394            left,
2395            right,
2396            operator,
2397        } => operator_result_type(
2398            SemanticOperator::Comparison(*operator),
2399            &[child(left, inferred), child(right, inferred)],
2400        )
2401        .unwrap_or(SemanticType::Unknown),
2402        ExpressionNodeKind::Logical {
2403            left,
2404            right,
2405            operator,
2406        } => operator_result_type(
2407            SemanticOperator::Logical(*operator),
2408            &[child(left, inferred), child(right, inferred)],
2409        )
2410        .unwrap_or(SemanticType::Unknown),
2411        ExpressionNodeKind::NullishCoalescing { left, right } => operator_result_type(
2412            SemanticOperator::NullishCoalescing,
2413            &[child(left, inferred), child(right, inferred)],
2414        )
2415        .unwrap_or(SemanticType::Unknown),
2416        ExpressionNodeKind::Unary { operand, operator } => operator_result_type(
2417            SemanticOperator::Unary(*operator),
2418            &[child(operand, inferred)],
2419        )
2420        .unwrap_or(SemanticType::Unknown),
2421    };
2422    inferred.insert(id.clone(), semantic_type.clone());
2423    semantic_type
2424}
2425
2426#[allow(clippy::too_many_arguments)]
2427fn infer_computed_expression_type(
2428    id: &SemanticId,
2429    graph: &ExpressionGraph,
2430    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
2431    computed_components: &BTreeMap<SemanticId, &ComponentNode>,
2432    computed_values: &BTreeMap<SemanticId, ComputedValue>,
2433    references: &[SemanticReference],
2434    computed_types: &mut BTreeMap<SemanticId, SemanticType>,
2435    expression_types: &mut BTreeMap<SemanticId, SemanticType>,
2436    visiting: &mut BTreeSet<SemanticId>,
2437) -> SemanticType {
2438    if let Some(semantic_type) = expression_types.get(id) {
2439        return semantic_type.clone();
2440    }
2441
2442    let node = graph.node(id).expect("computed expression graph node");
2443    let child_type = |child: &SemanticId,
2444                      expression_types: &mut BTreeMap<SemanticId, SemanticType>,
2445                      computed_types: &mut BTreeMap<SemanticId, SemanticType>,
2446                      visiting: &mut BTreeSet<SemanticId>| {
2447        infer_computed_expression_type(
2448            child,
2449            graph,
2450            assignments,
2451            computed_components,
2452            computed_values,
2453            references,
2454            computed_types,
2455            expression_types,
2456            visiting,
2457        )
2458    };
2459    let semantic_type = match &node.kind {
2460        ExpressionNodeKind::Literal(value) => state_initializer_value_type(value),
2461        ExpressionNodeKind::Boolean(value) => SemanticType::BooleanLiteral(*value),
2462        ExpressionNodeKind::Template { .. } => SemanticType::String,
2463        ExpressionNodeKind::Identifier(_) | ExpressionNodeKind::Call { .. } => {
2464            SemanticType::Unknown
2465        }
2466        ExpressionNodeKind::SemanticPackagePureCall {
2467            operation: crate::semantic_package::SemanticPackagePureOperation::Identity,
2468            arguments,
2469            ..
2470        } => arguments.first().map_or(SemanticType::Unknown, |argument| {
2471            child_type(argument, expression_types, computed_types, visiting)
2472        }),
2473        ExpressionNodeKind::BuiltinPureCall {
2474            operation:
2475                crate::component_graph::BuiltinPureOperation::MathAbs
2476                | crate::component_graph::BuiltinPureOperation::MathFloor
2477                | crate::component_graph::BuiltinPureOperation::MathCeil
2478                | crate::component_graph::BuiltinPureOperation::MathRound
2479                | crate::component_graph::BuiltinPureOperation::MathMin
2480                | crate::component_graph::BuiltinPureOperation::MathMax,
2481            ..
2482        } => SemanticType::Number,
2483        ExpressionNodeKind::ThisMember { name } => infer_computed_read_type(
2484            &node.owner,
2485            name,
2486            assignments,
2487            computed_components,
2488            computed_values,
2489            references,
2490            graph,
2491            computed_types,
2492            expression_types,
2493            visiting,
2494        ),
2495        ExpressionNodeKind::MemberAccess {
2496            object, property, ..
2497        } => {
2498            let object = child_type(object, expression_types, computed_types, visiting);
2499            computed_member_access_type(&object, property)
2500        }
2501        ExpressionNodeKind::IndexAccess { object, index } => {
2502            let object = child_type(object, expression_types, computed_types, visiting);
2503            let index = child_type(index, expression_types, computed_types, visiting);
2504            computed_index_access_type(&object, &index)
2505        }
2506        ExpressionNodeKind::Conditional {
2507            when_true,
2508            when_false,
2509            ..
2510        } => conditional_result_type(
2511            child_type(when_true, expression_types, computed_types, visiting),
2512            child_type(when_false, expression_types, computed_types, visiting),
2513        ),
2514        ExpressionNodeKind::Arithmetic {
2515            left,
2516            right,
2517            operator,
2518        } => operator_result_type(
2519            SemanticOperator::Arithmetic(*operator),
2520            &[
2521                child_type(left, expression_types, computed_types, visiting),
2522                child_type(right, expression_types, computed_types, visiting),
2523            ],
2524        )
2525        .unwrap_or(SemanticType::Unknown),
2526        ExpressionNodeKind::Comparison {
2527            left,
2528            right,
2529            operator,
2530        } => operator_result_type(
2531            SemanticOperator::Comparison(*operator),
2532            &[
2533                child_type(left, expression_types, computed_types, visiting),
2534                child_type(right, expression_types, computed_types, visiting),
2535            ],
2536        )
2537        .unwrap_or(SemanticType::Unknown),
2538        ExpressionNodeKind::Logical {
2539            left,
2540            right,
2541            operator,
2542        } => operator_result_type(
2543            SemanticOperator::Logical(*operator),
2544            &[
2545                child_type(left, expression_types, computed_types, visiting),
2546                child_type(right, expression_types, computed_types, visiting),
2547            ],
2548        )
2549        .unwrap_or(SemanticType::Unknown),
2550        ExpressionNodeKind::NullishCoalescing { left, right } => operator_result_type(
2551            SemanticOperator::NullishCoalescing,
2552            &[
2553                child_type(left, expression_types, computed_types, visiting),
2554                child_type(right, expression_types, computed_types, visiting),
2555            ],
2556        )
2557        .unwrap_or(SemanticType::Unknown),
2558        ExpressionNodeKind::Unary { operand, operator } => operator_result_type(
2559            SemanticOperator::Unary(*operator),
2560            &[child_type(
2561                operand,
2562                expression_types,
2563                computed_types,
2564                visiting,
2565            )],
2566        )
2567        .unwrap_or(SemanticType::Unknown),
2568    };
2569    expression_types.insert(id.clone(), semantic_type.clone());
2570    semantic_type
2571}
2572
2573#[allow(clippy::too_many_arguments)]
2574fn infer_computed_read_type(
2575    computed: &SemanticId,
2576    name: &str,
2577    assignments: &BTreeMap<SemanticId, SemanticTypeAssignment>,
2578    computed_components: &BTreeMap<SemanticId, &ComponentNode>,
2579    computed_values: &BTreeMap<SemanticId, ComputedValue>,
2580    references: &[SemanticReference],
2581    graph: &ExpressionGraph,
2582    computed_types: &mut BTreeMap<SemanticId, SemanticType>,
2583    expression_types: &mut BTreeMap<SemanticId, SemanticType>,
2584    visiting: &mut BTreeSet<SemanticId>,
2585) -> SemanticType {
2586    let Some(component) = computed_components.get(computed) else {
2587        return SemanticType::Unknown;
2588    };
2589    let target = component
2590        .state_fields
2591        .iter()
2592        .find(|field| field.name == name)
2593        .map(|field| field.id.clone())
2594        .or_else(|| {
2595            let target = component.id.computed(name);
2596            computed_values.contains_key(&target).then_some(target)
2597        })
2598        .or_else(|| {
2599            let target = component.id.resource(name);
2600            assignments.contains_key(&target).then_some(target)
2601        });
2602    let Some(target) = target else {
2603        return SemanticType::Unknown;
2604    };
2605    if !references
2606        .iter()
2607        .any(|reference| reference.source == *computed && reference.target == target)
2608    {
2609        return SemanticType::Unknown;
2610    }
2611    if computed_values.contains_key(&target) {
2612        return infer_computed_type(
2613            &target,
2614            graph,
2615            assignments,
2616            computed_components,
2617            computed_values,
2618            references,
2619            computed_types,
2620            expression_types,
2621            visiting,
2622        );
2623    }
2624    assignments
2625        .get(&target)
2626        .map_or(SemanticType::Unknown, |assignment| {
2627            assignment.semantic_type.clone()
2628        })
2629}
2630
2631fn computed_member_access_type(semantic_type: &SemanticType, property: &str) -> SemanticType {
2632    match semantic_type {
2633        SemanticType::Object(object) => object
2634            .properties
2635            .get(property)
2636            .cloned()
2637            .unwrap_or(SemanticType::Unknown),
2638        SemanticType::Resource(resource) => match property {
2639            "data" => normalize_semantic_type(SemanticType::Union(vec![
2640                resource.data.as_ref().clone(),
2641                SemanticType::Null,
2642            ])),
2643            "error" => normalize_semantic_type(SemanticType::Union(vec![
2644                resource.error.as_ref().clone(),
2645                SemanticType::Null,
2646            ])),
2647            "state" => SemanticType::String,
2648            _ => SemanticType::Unknown,
2649        },
2650        _ => SemanticType::Unknown,
2651    }
2652}
2653
2654fn computed_index_access_type(object: &SemanticType, index: &SemanticType) -> SemanticType {
2655    match (object, index) {
2656        (SemanticType::Tuple(items), SemanticType::NumberLiteral(index)) => index
2657            .parse::<usize>()
2658            .ok()
2659            .and_then(|index| items.get(index))
2660            .cloned()
2661            .unwrap_or(SemanticType::Unknown),
2662        (SemanticType::Array(element), SemanticType::Number | SemanticType::NumberLiteral(_)) => {
2663            element.as_ref().clone()
2664        }
2665        (SemanticType::Object(object), SemanticType::StringLiteral(property)) => object
2666            .properties
2667            .get(property)
2668            .cloned()
2669            .unwrap_or(SemanticType::Unknown),
2670        _ => SemanticType::Unknown,
2671    }
2672}
2673
2674fn conditional_result_type(when_true: SemanticType, when_false: SemanticType) -> SemanticType {
2675    if when_true == when_false {
2676        when_true
2677    } else {
2678        normalize_semantic_type(SemanticType::Union(vec![when_true, when_false]))
2679    }
2680}
2681
2682/// Returns the result type for one valid semantic operator application.
2683///
2684/// An absent result means the supplied operand types are not valid for that
2685/// operator. Unknown operands deliberately propagate `unknown` rather than
2686/// being treated as a valid concrete operation.
2687#[must_use]
2688pub fn operator_result_type(
2689    operator: SemanticOperator,
2690    operands: &[SemanticType],
2691) -> Option<SemanticType> {
2692    match operator {
2693        SemanticOperator::Arithmetic(_) | SemanticOperator::Comparison(_) => {
2694            let [left, right] = operands else {
2695                return None;
2696            };
2697            if left == &SemanticType::Unknown || right == &SemanticType::Unknown {
2698                Some(SemanticType::Unknown)
2699            } else if is_number_like(left) && is_number_like(right) {
2700                Some(match operator {
2701                    SemanticOperator::Arithmetic(_) => SemanticType::Number,
2702                    SemanticOperator::Comparison(_) => SemanticType::Boolean,
2703                    _ => unreachable!("operator category was matched above"),
2704                })
2705            } else {
2706                None
2707            }
2708        }
2709        SemanticOperator::Logical(_) => {
2710            let [left, right] = operands else {
2711                return None;
2712            };
2713            if left == &SemanticType::Unknown || right == &SemanticType::Unknown {
2714                Some(SemanticType::Unknown)
2715            } else if is_boolean_like(left) && is_boolean_like(right) {
2716                Some(SemanticType::Boolean)
2717            } else {
2718                None
2719            }
2720        }
2721        SemanticOperator::NullishCoalescing => {
2722            let [left, right] = operands else {
2723                return None;
2724            };
2725            if left == &SemanticType::Unknown || right == &SemanticType::Unknown {
2726                return Some(SemanticType::Unknown);
2727            }
2728            let left = remove_null(left);
2729            if left == SemanticType::Never {
2730                Some(right.clone())
2731            } else if left == *right {
2732                Some(left)
2733            } else {
2734                Some(SemanticType::Union(vec![left, right.clone()]))
2735            }
2736        }
2737        SemanticOperator::Unary(crate::component_graph::UnaryOperator::Not) => {
2738            let [operand] = operands else {
2739                return None;
2740            };
2741            if operand == &SemanticType::Unknown {
2742                Some(SemanticType::Unknown)
2743            } else if is_boolean_like(operand) {
2744                Some(SemanticType::Boolean)
2745            } else {
2746                None
2747            }
2748        }
2749        SemanticOperator::Unary(
2750            crate::component_graph::UnaryOperator::Plus
2751            | crate::component_graph::UnaryOperator::Minus,
2752        ) => {
2753            let [operand] = operands else {
2754                return None;
2755            };
2756            if operand == &SemanticType::Unknown {
2757                Some(SemanticType::Unknown)
2758            } else if is_number_like(operand) {
2759                Some(SemanticType::Number)
2760            } else {
2761                None
2762            }
2763        }
2764    }
2765}
2766
2767fn is_number_like(semantic_type: &SemanticType) -> bool {
2768    matches!(
2769        semantic_type,
2770        SemanticType::Number | SemanticType::NumberLiteral(_)
2771    )
2772}
2773
2774fn is_boolean_like(semantic_type: &SemanticType) -> bool {
2775    matches!(
2776        semantic_type,
2777        SemanticType::Boolean | SemanticType::BooleanLiteral(_)
2778    )
2779}
2780
2781fn remove_null(semantic_type: &SemanticType) -> SemanticType {
2782    match semantic_type {
2783        SemanticType::Null => SemanticType::Never,
2784        SemanticType::Union(members) => {
2785            let members = members
2786                .iter()
2787                .map(remove_null)
2788                .filter(|member| member != &SemanticType::Never)
2789                .collect::<Vec<_>>();
2790            match members.as_slice() {
2791                [] => SemanticType::Never,
2792                [member] => member.clone(),
2793                _ => SemanticType::Union(members),
2794            }
2795        }
2796        _ => semantic_type.clone(),
2797    }
2798}
2799
2800#[must_use]
2801pub fn infer_serializable_value_type(value: &SerializableValue) -> SemanticType {
2802    match value {
2803        SerializableValue::Null => SemanticType::Null,
2804        SerializableValue::Number(_) => SemanticType::Number,
2805        SerializableValue::String(_) => SemanticType::String,
2806        SerializableValue::Boolean(_) => SemanticType::Boolean,
2807        SerializableValue::Array(values) if values.is_empty() => {
2808            SemanticType::Array(Box::new(SemanticType::Unknown))
2809        }
2810        SerializableValue::Array(values) => {
2811            let mut types = values
2812                .iter()
2813                .map(infer_serializable_value_type)
2814                .collect::<Vec<_>>();
2815            let element = if types.windows(2).all(|window| window[0] == window[1]) {
2816                types.pop().unwrap_or(SemanticType::Unknown)
2817            } else {
2818                SemanticType::Union(types)
2819            };
2820            SemanticType::Array(Box::new(element))
2821        }
2822        SerializableValue::Object(values) => SemanticType::Object(ObjectType {
2823            properties: values
2824                .iter()
2825                .map(|(name, value)| (name.clone(), infer_serializable_value_type(value)))
2826                .collect(),
2827        }),
2828    }
2829}
2830
2831fn semantic_type_from_annotation(text: &str) -> Option<SemanticType> {
2832    let text = text.trim();
2833    let union_members = split_top_level(text, '|');
2834    if union_members.len() > 1 {
2835        return Some(SemanticType::Union(
2836            union_members
2837                .into_iter()
2838                .map(|member| {
2839                    semantic_type_from_annotation(member).unwrap_or(SemanticType::Unknown)
2840                })
2841                .collect(),
2842        ));
2843    }
2844    if let Some(element) = text.strip_suffix("[]") {
2845        return Some(SemanticType::Array(Box::new(
2846            semantic_type_from_annotation(element).unwrap_or(SemanticType::Unknown),
2847        )));
2848    }
2849    if text.starts_with('[') && text.ends_with(']') {
2850        let items = &text[1..text.len() - 1];
2851        return Some(SemanticType::Tuple(
2852            split_top_level(items, ',')
2853                .into_iter()
2854                .filter(|item| !item.trim().is_empty())
2855                .map(|item| semantic_type_from_annotation(item).unwrap_or(SemanticType::Unknown))
2856                .collect(),
2857        ));
2858    }
2859    if text.starts_with('{') && text.ends_with('}') {
2860        return object_type(text);
2861    }
2862    if let Some(arguments) = text
2863        .strip_prefix("Resource<")
2864        .and_then(|value| value.strip_suffix('>'))
2865    {
2866        let arguments = split_top_level(arguments, ',');
2867        if arguments.len() == 2 {
2868            return Some(SemanticType::Resource(ResourceType {
2869                data: Box::new(semantic_type_from_annotation(arguments[0])?),
2870                error: Box::new(semantic_type_from_annotation(arguments[1])?),
2871                pending: true,
2872                serializable: true,
2873                // The selected semantic-package endpoint is the authority for
2874                // the actual execution boundary during Resource lowering.
2875                execution_boundary: ResourceExecutionBoundary::Shared,
2876            }));
2877        }
2878    }
2879
2880    match text {
2881        "string" => Some(SemanticType::String),
2882        "number" => Some(SemanticType::Number),
2883        "boolean" => Some(SemanticType::Boolean),
2884        "null" => Some(SemanticType::Null),
2885        "SlotContent" => Some(SemanticType::SlotContent),
2886        "true" => Some(SemanticType::BooleanLiteral(true)),
2887        "false" => Some(SemanticType::BooleanLiteral(false)),
2888        _ => string_literal_type(text).or_else(|| numeric_literal_type(text)),
2889    }
2890}
2891
2892fn object_type(text: &str) -> Option<SemanticType> {
2893    let mut properties = BTreeMap::new();
2894    let fields = &text[1..text.len() - 1];
2895
2896    for field in split_top_level(fields, ';')
2897        .into_iter()
2898        .filter(|field| !field.trim().is_empty())
2899    {
2900        let (name, type_text) = field.split_once(':')?;
2901        let name = name.trim();
2902        if name.is_empty() {
2903            return None;
2904        }
2905        properties.insert(
2906            name.to_string(),
2907            semantic_type_from_annotation(type_text).unwrap_or(SemanticType::Unknown),
2908        );
2909    }
2910
2911    Some(SemanticType::Object(ObjectType { properties }))
2912}
2913
2914fn split_top_level(text: &str, delimiter: char) -> Vec<&str> {
2915    let mut depth = 0usize;
2916    let mut start = 0usize;
2917    let mut parts = Vec::new();
2918
2919    for (index, character) in text.char_indices() {
2920        match character {
2921            '{' | '[' | '(' => depth += 1,
2922            '}' | ']' | ')' => depth = depth.saturating_sub(1),
2923            _ => {}
2924        }
2925        if character == delimiter && depth == 0 {
2926            parts.push(&text[start..index]);
2927            start = index + character.len_utf8();
2928        }
2929    }
2930    parts.push(&text[start..]);
2931    parts
2932}
2933
2934fn string_literal_type(text: &str) -> Option<SemanticType> {
2935    let quote = text.chars().next()?;
2936    (matches!(quote, '\'' | '"') && text.ends_with(quote) && text.len() >= 2)
2937        .then(|| SemanticType::StringLiteral(text[1..text.len() - 1].to_string()))
2938}
2939
2940fn numeric_literal_type(text: &str) -> Option<SemanticType> {
2941    text.parse::<f64>()
2942        .ok()
2943        .filter(|value| value.is_finite())
2944        .map(|_| SemanticType::NumberLiteral(text.to_string()))
2945}
2946
2947/// Canonical semantic compatibility relation shared by compiler consumers.
2948#[must_use]
2949pub fn is_assignable(source: &SemanticType, target: &SemanticType) -> bool {
2950    match (source, target) {
2951        (_, SemanticType::Unknown)
2952        | (SemanticType::Unknown | SemanticType::Never, _)
2953        | (SemanticType::Null, SemanticType::Null)
2954        | (SemanticType::Boolean | SemanticType::BooleanLiteral(_), SemanticType::Boolean)
2955        | (SemanticType::Number | SemanticType::NumberLiteral(_), SemanticType::Number)
2956        | (SemanticType::String | SemanticType::StringLiteral(_), SemanticType::String)
2957        | (SemanticType::Form, SemanticType::Form)
2958        | (SemanticType::SlotContent, SemanticType::SlotContent) => true,
2959        (SemanticType::BooleanLiteral(source), SemanticType::BooleanLiteral(target)) => {
2960            source == target
2961        }
2962        (SemanticType::NumberLiteral(source), SemanticType::NumberLiteral(target))
2963        | (SemanticType::StringLiteral(source), SemanticType::StringLiteral(target)) => {
2964            source == target
2965        }
2966        (SemanticType::Tuple(source), SemanticType::Tuple(target)) => {
2967            source.len() == target.len()
2968                && source
2969                    .iter()
2970                    .zip(target)
2971                    .all(|(source, target)| is_assignable(source, target))
2972        }
2973        (SemanticType::Tuple(source), SemanticType::Array(target)) => {
2974            source.iter().all(|source| is_assignable(source, target))
2975        }
2976        (SemanticType::Array(source), SemanticType::Array(target)) => is_assignable(source, target),
2977        (SemanticType::Object(source), SemanticType::Object(target)) => {
2978            target.properties.iter().all(|(name, target)| {
2979                source
2980                    .properties
2981                    .get(name)
2982                    .is_some_and(|source| is_assignable(source, target))
2983            })
2984        }
2985        (SemanticType::Resource(source), SemanticType::Resource(target)) => {
2986            source.pending == target.pending
2987                && source.serializable == target.serializable
2988                && source.execution_boundary == target.execution_boundary
2989                && is_assignable(&source.data, &target.data)
2990                && is_assignable(&source.error, &target.error)
2991        }
2992        (SemanticType::Union(source), target) => {
2993            source.iter().all(|source| is_assignable(source, target))
2994        }
2995        (source, SemanticType::Union(target)) => {
2996            target.iter().any(|target| is_assignable(source, target))
2997        }
2998        _ => false,
2999    }
3000}
3001
3002/// Compatibility alias retained for C10 callers.
3003#[must_use]
3004pub fn is_state_initializer_assignable(source: &SemanticType, target: &SemanticType) -> bool {
3005    is_assignable(source, target)
3006}
3007
3008#[must_use]
3009pub fn state_initializer_value_type(value: &SerializableValue) -> SemanticType {
3010    match value {
3011        SerializableValue::Null => SemanticType::Null,
3012        SerializableValue::Number(value) => SemanticType::NumberLiteral(value.clone()),
3013        SerializableValue::String(value) => SemanticType::StringLiteral(value.clone()),
3014        SerializableValue::Boolean(value) => SemanticType::BooleanLiteral(*value),
3015        SerializableValue::Array(values) if values.is_empty() => {
3016            SemanticType::Array(Box::new(SemanticType::Unknown))
3017        }
3018        SerializableValue::Array(values) => {
3019            SemanticType::Tuple(values.iter().map(state_initializer_value_type).collect())
3020        }
3021        SerializableValue::Object(values) => SemanticType::Object(ObjectType {
3022            properties: values
3023                .iter()
3024                .map(|(name, value)| (name.clone(), state_initializer_value_type(value)))
3025                .collect(),
3026        }),
3027    }
3028}
3029
3030#[cfg(test)]
3031mod tests {
3032    use std::collections::BTreeMap;
3033
3034    use super::{
3035        boundary_compatibility, dom_binding_contract, is_assignable, normalize_semantic_type,
3036        operator_result_type, serialization_compatibility, BoundaryCompatibility,
3037        ExecutionBoundary, ObjectType, ResourceExecutionBoundary, ResourceType, SemanticOperator,
3038        SemanticType, SemanticTypeAssignment, SemanticTypeId, SemanticTypeStatus,
3039        SerializationCompatibility, TypeDiagnosticCode, TypeDiagnosticFamily,
3040    };
3041    use crate::{
3042        component_graph::UnaryOperator, ArithmeticOperator, ComparisonOperator, LogicalOperator,
3043        SemanticId, SourceProvenance,
3044    };
3045
3046    #[test]
3047    fn defines_typed_accessibility_attribute_bindings() {
3048        for name in [
3049            "role",
3050            "aria-label",
3051            "aria-describedby",
3052            "aria-errormessage",
3053            "aria-controls",
3054            "aria-current",
3055            "aria-live",
3056        ] {
3057            assert_eq!(
3058                dom_binding_contract(name).unwrap().semantic_type,
3059                SemanticType::String,
3060                "{name} must retain its compiler-owned string contract"
3061            );
3062        }
3063        for name in [
3064            "aria-invalid",
3065            "aria-busy",
3066            "aria-expanded",
3067            "aria-pressed",
3068            "aria-hidden",
3069        ] {
3070            assert_eq!(
3071                dom_binding_contract(name).unwrap().semantic_type,
3072                SemanticType::Boolean,
3073                "{name} must retain its compiler-owned boolean contract"
3074            );
3075        }
3076        assert!(dom_binding_contract("aria-owns").is_none());
3077    }
3078
3079    #[test]
3080    fn represents_core_compiler_owned_type_forms() {
3081        let todo = SemanticType::Object(ObjectType {
3082            properties: BTreeMap::from([
3083                ("completed".to_string(), SemanticType::Boolean),
3084                ("title".to_string(), SemanticType::String),
3085            ]),
3086        });
3087        let types = vec![
3088            SemanticType::Unknown,
3089            SemanticType::Never,
3090            SemanticType::Null,
3091            SemanticType::Boolean,
3092            SemanticType::Number,
3093            SemanticType::String,
3094            SemanticType::BooleanLiteral(true),
3095            SemanticType::NumberLiteral("42".to_string()),
3096            SemanticType::StringLiteral("all".to_string()),
3097            SemanticType::Array(Box::new(SemanticType::Number)),
3098            SemanticType::Tuple(vec![SemanticType::String, SemanticType::Number]),
3099            todo,
3100            SemanticType::Union(vec![SemanticType::String, SemanticType::Null]),
3101            SemanticType::Resource(ResourceType {
3102                data: Box::new(SemanticType::String),
3103                error: Box::new(SemanticType::Unknown),
3104                pending: true,
3105                serializable: true,
3106                execution_boundary: ResourceExecutionBoundary::Shared,
3107            }),
3108        ];
3109
3110        assert_eq!(types.len(), 14);
3111    }
3112
3113    #[test]
3114    fn assigns_stable_codes_to_canonical_type_diagnostic_families() {
3115        assert_eq!(TypeDiagnosticCode::UnknownType.as_str(), "PSC1032");
3116        assert_eq!(
3117            TypeDiagnosticCode::InvalidArithmeticOperator.family(),
3118            TypeDiagnosticFamily::InvalidOperator
3119        );
3120        assert_eq!(
3121            TypeDiagnosticCode::IncompatibleAssignment.family(),
3122            TypeDiagnosticFamily::IncompatibleAssignment
3123        );
3124        assert_eq!(
3125            TypeDiagnosticCode::NonSerializableState.family(),
3126            TypeDiagnosticFamily::NonSerializableState
3127        );
3128    }
3129
3130    #[test]
3131    fn preserves_type_assignment_identity_status_and_provenance() {
3132        let subject = SemanticId::component(Some("x-counter"), "Counter").state_field("count");
3133        let assignment = SemanticTypeAssignment {
3134            id: SemanticTypeId::for_subject(&subject),
3135            subject: subject.clone(),
3136            semantic_type: SemanticType::Number,
3137            origin: subject.clone(),
3138            status: SemanticTypeStatus::Declared,
3139            provenance: SourceProvenance::new(
3140                "src/Counter.tsx",
3141                presolve_parser::SourceSpan {
3142                    start: 42,
3143                    end: 48,
3144                    line: 4,
3145                    column: 10,
3146                },
3147            ),
3148        };
3149
3150        assert_eq!(
3151            assignment.id.to_string(),
3152            "component:x-counter/state:count/type:semantic"
3153        );
3154        assert_eq!(assignment.origin, subject);
3155        assert_eq!(assignment.status, SemanticTypeStatus::Declared);
3156        assert_eq!(
3157            assignment.provenance.path,
3158            std::path::Path::new("src/Counter.tsx")
3159        );
3160    }
3161
3162    #[test]
3163    fn determines_structural_serialization_compatibility() {
3164        assert_eq!(
3165            serialization_compatibility(&SemanticType::Number),
3166            SerializationCompatibility::Serializable
3167        );
3168        assert_eq!(
3169            serialization_compatibility(&SemanticType::Object(ObjectType {
3170                properties: BTreeMap::from([(
3171                    "todos".to_string(),
3172                    SemanticType::Array(Box::new(SemanticType::String)),
3173                )]),
3174            })),
3175            SerializationCompatibility::Serializable
3176        );
3177        assert_eq!(
3178            serialization_compatibility(&SemanticType::Unknown),
3179            SerializationCompatibility::NotSerializable
3180        );
3181        assert_eq!(
3182            serialization_compatibility(&SemanticType::Resource(ResourceType {
3183                data: Box::new(SemanticType::String),
3184                error: Box::new(SemanticType::Unknown),
3185                pending: true,
3186                serializable: true,
3187                execution_boundary: ResourceExecutionBoundary::Shared,
3188            })),
3189            SerializationCompatibility::NotSerializable
3190        );
3191    }
3192
3193    #[test]
3194    fn determines_server_client_boundary_compatibility() {
3195        assert_eq!(
3196            boundary_compatibility(
3197                &SemanticType::String,
3198                ExecutionBoundary::Server,
3199                ExecutionBoundary::Client,
3200            ),
3201            BoundaryCompatibility::Compatible
3202        );
3203        assert_eq!(
3204            boundary_compatibility(
3205                &SemanticType::Resource(ResourceType {
3206                    data: Box::new(SemanticType::String),
3207                    error: Box::new(SemanticType::String),
3208                    pending: true,
3209                    serializable: true,
3210                    execution_boundary: ResourceExecutionBoundary::Client,
3211                }),
3212                ExecutionBoundary::Client,
3213                ExecutionBoundary::Server,
3214            ),
3215            BoundaryCompatibility::Incompatible
3216        );
3217    }
3218
3219    #[test]
3220    fn normalizes_equivalent_union_forms_deterministically() {
3221        assert_eq!(
3222            normalize_semantic_type(SemanticType::Union(vec![
3223                SemanticType::String,
3224                SemanticType::Never,
3225                SemanticType::Union(vec![SemanticType::String, SemanticType::Null]),
3226                SemanticType::Null,
3227            ])),
3228            SemanticType::Union(vec![SemanticType::Null, SemanticType::String])
3229        );
3230        assert_eq!(
3231            normalize_semantic_type(SemanticType::Union(vec![SemanticType::Never])),
3232            SemanticType::Never
3233        );
3234    }
3235
3236    #[test]
3237    fn centralizes_assignability_across_normalized_type_forms() {
3238        assert!(is_assignable(
3239            &SemanticType::StringLiteral("all".to_string()),
3240            &SemanticType::Union(vec![
3241                SemanticType::StringLiteral("active".to_string()),
3242                SemanticType::StringLiteral("all".to_string()),
3243            ])
3244        ));
3245        assert!(is_assignable(
3246            &SemanticType::Tuple(vec![SemanticType::NumberLiteral("1".to_string())]),
3247            &SemanticType::Array(Box::new(SemanticType::Number))
3248        ));
3249        assert!(!is_assignable(&SemanticType::String, &SemanticType::Number));
3250    }
3251
3252    #[test]
3253    fn defines_explicit_operand_and_result_contracts_for_operators() {
3254        assert_eq!(
3255            operator_result_type(
3256                SemanticOperator::Arithmetic(ArithmeticOperator::Add),
3257                &[
3258                    SemanticType::NumberLiteral("1".to_string()),
3259                    SemanticType::Number,
3260                ],
3261            ),
3262            Some(SemanticType::Number)
3263        );
3264        assert_eq!(
3265            operator_result_type(
3266                SemanticOperator::Comparison(ComparisonOperator::LessThan),
3267                &[
3268                    SemanticType::Number,
3269                    SemanticType::NumberLiteral("2".to_string())
3270                ],
3271            ),
3272            Some(SemanticType::Boolean)
3273        );
3274        assert_eq!(
3275            operator_result_type(
3276                SemanticOperator::Logical(LogicalOperator::And),
3277                &[SemanticType::BooleanLiteral(true), SemanticType::Boolean],
3278            ),
3279            Some(SemanticType::Boolean)
3280        );
3281        assert_eq!(
3282            operator_result_type(
3283                SemanticOperator::Unary(UnaryOperator::Not),
3284                &[SemanticType::BooleanLiteral(false)],
3285            ),
3286            Some(SemanticType::Boolean)
3287        );
3288        assert_eq!(
3289            operator_result_type(
3290                SemanticOperator::NullishCoalescing,
3291                &[
3292                    SemanticType::Union(vec![SemanticType::String, SemanticType::Null]),
3293                    SemanticType::StringLiteral("fallback".to_string()),
3294                ],
3295            ),
3296            Some(SemanticType::Union(vec![
3297                SemanticType::String,
3298                SemanticType::StringLiteral("fallback".to_string()),
3299            ]))
3300        );
3301        assert_eq!(
3302            operator_result_type(
3303                SemanticOperator::Arithmetic(ArithmeticOperator::Add),
3304                &[SemanticType::String, SemanticType::Number],
3305            ),
3306            None
3307        );
3308        assert_eq!(
3309            operator_result_type(
3310                SemanticOperator::Logical(LogicalOperator::Or),
3311                &[SemanticType::Number, SemanticType::Boolean],
3312            ),
3313            None
3314        );
3315    }
3316}