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