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