Skip to main content

presolve_compiler/
semantic_id.rs

1use std::fmt;
2use std::path::{Component, Path};
3
4use serde::{Deserialize, Serialize};
5
6/// Globally stable identity for a compiler semantic entity.
7///
8/// IDs use the component element name when available because it is the
9/// application-facing component identity. Invalid components without an
10/// element declaration fall back to their class name so diagnostics and later
11/// ASM validation can still refer to them deterministically.
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct SemanticId(String);
15
16/// Stable semantic identity for the subject of an effect diagnostic.
17///
18/// Effects continue to use `SemanticId` throughout the compiler graph. This
19/// wrapper exists only at the shared diagnostic boundary so consumers cannot
20/// confuse an effect subject with an arbitrary semantic entity.
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22#[serde(transparent)]
23pub struct EffectId(SemanticId);
24
25/// Stable identity for an authored statement owned by an effect.
26///
27/// This deliberately remains distinct from `SemanticId` in diagnostic output:
28/// statement identity is meaningful only together with its effect subject.
29#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
30#[serde(transparent)]
31pub struct EffectStatementId(SemanticId);
32
33/// Stable identity for one compiler-owned Context semantic entity.
34///
35/// Context identity is intentionally distinct from the authored field and all
36/// future provider, consumer, and runtime-slot identity domains.
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
38#[serde(transparent)]
39pub struct ContextId(SemanticId);
40
41/// Stable identity for one compiler-owned Context Provider semantic entity.
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
43#[serde(transparent)]
44pub struct ProviderId(SemanticId);
45
46/// Stable identity for one compiler-owned Context Consumer semantic entity.
47#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
48#[serde(transparent)]
49pub struct ConsumerId(SemanticId);
50
51/// Stable identity for one compiler-owned Form semantic entity.
52#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
53#[serde(transparent)]
54pub struct FormId(SemanticId);
55
56/// Stable identity for one compiler-owned Form instance.
57///
58/// A Form definition and each of its component-instance-qualified executions
59/// are distinct identity domains.
60#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
61#[serde(transparent)]
62pub struct FormInstanceId(SemanticId);
63
64/// Source-qualified identity for one recognized `@form` declaration candidate.
65///
66/// Candidate identity remains available even when no canonical Form identity
67/// can be constructed from the authored target.
68#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69#[serde(transparent)]
70pub struct FormDeclarationCandidateId(SemanticId);
71
72/// Source-qualified identity for one recognized `@field` declaration
73/// candidate. It is intentionally distinct from canonical `FieldId`.
74#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
75#[serde(transparent)]
76pub struct FormFieldDeclarationCandidateId(SemanticId);
77
78/// Source-qualified identity for one recognized JSX `field` binding candidate.
79/// It remains distinct from a valid template-occurrence binding identity.
80#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
81#[serde(transparent)]
82pub struct FormFieldBindingCandidateId(SemanticId);
83
84/// Stable identity for one compiler-owned Field owned by a Form.
85#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86#[serde(transparent)]
87pub struct FieldId(SemanticId);
88
89/// Stable identity for one authored template control occurrence bound to a
90/// canonical Form Field.
91#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
92#[serde(transparent)]
93pub struct FieldBindingId(SemanticId);
94
95/// Source-qualified candidate identity for a compiler-owned submission-host
96/// attribute. Invalid candidates never acquire a host semantic identity.
97#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
98#[serde(transparent)]
99pub struct SubmissionHostCandidateId(SemanticId);
100
101/// Stable declaration-level identity for one explicit template submission host.
102#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
103#[serde(transparent)]
104pub struct SubmissionHostId(SemanticId);
105
106/// Stable identity for the declaration-level Form ownership projection of one
107/// canonical application/build-root set.
108#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
109#[serde(transparent)]
110pub struct FormOwnershipGraphId(SemanticId);
111
112/// Source-qualified identity for every recognized `@validate` placement.
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
114#[serde(transparent)]
115pub struct ValidationRuleCandidateId(SemanticId);
116
117/// Stable identity for one valid compiler-owned validation rule.
118#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
119#[serde(transparent)]
120pub struct ValidationRuleId(SemanticId);
121
122/// Stable identity for the declaration-level validation graph of one build.
123#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
124#[serde(transparent)]
125pub struct ValidationGraphId(SemanticId);
126
127/// Stable identity for the declaration-level validation dependency plan of one
128/// canonical Form. This is deliberately distinct from both Form declarations
129/// and future Form-instance-qualified execution plans.
130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
131#[serde(transparent)]
132pub struct ValidationPlanId(SemanticId);
133
134/// Stable identity for one direct I6 validation-rule read dependency.
135#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
136#[serde(transparent)]
137pub struct FieldDependencyId(SemanticId);
138
139/// Stable identity for one canonical dependency-cycle field set.
140#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
141#[serde(transparent)]
142pub struct ValidationDependencyCycleId(SemanticId);
143
144/// Stable identity for one declaration-level Form dirty-tracking plan.
145#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
146#[serde(transparent)]
147pub struct DirtyTrackingPlanId(SemanticId);
148
149/// Stable identity for one declaration-level Form touched-tracking plan.
150#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
151#[serde(transparent)]
152pub struct TouchedTrackingPlanId(SemanticId);
153
154/// Stable identity for the tracking facts associated with one declaration-level
155/// Form Field. It is intentionally not a runtime storage identity.
156#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
157#[serde(transparent)]
158pub struct FieldTrackingId(SemanticId);
159
160/// Source-qualified identity for an authored `@submit` declaration candidate.
161#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
162#[serde(transparent)]
163pub struct SubmissionDeclarationCandidateId(SemanticId);
164
165/// Stable identity for the submission plan of one canonical Form.
166#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
167#[serde(transparent)]
168pub struct SubmissionPlanId(SemanticId);
169
170/// Stable identity for the serialization plan of one canonical Form.
171#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
172#[serde(transparent)]
173pub struct SerializationPlanId(SemanticId);
174
175#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
176#[serde(transparent)]
177pub struct ResetPlanId(SemanticId);
178
179#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
180#[serde(transparent)]
181pub struct FieldResetOperationId(SemanticId);
182
183#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
184#[serde(transparent)]
185pub struct FormFieldValueSlotId(SemanticId);
186#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
187#[serde(transparent)]
188pub struct FormFieldDirtySlotId(SemanticId);
189#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
190#[serde(transparent)]
191pub struct FormFieldTouchedSlotId(SemanticId);
192#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
193#[serde(transparent)]
194pub struct FormFieldValidationSlotId(SemanticId);
195#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
196#[serde(transparent)]
197pub struct FormValidationAggregateSlotId(SemanticId);
198#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
199#[serde(transparent)]
200pub struct FormSubmissionStateSlotId(SemanticId);
201
202/// Stable identity for one compiler-owned component Slot semantic entity.
203#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
204#[serde(transparent)]
205pub struct SlotId(SemanticId);
206
207/// Stable identity for one authored `@slot()` declaration candidate.
208///
209/// Candidate identity is source-qualified and remains distinct from `SlotId`,
210/// so invalid declarations never acquire a valid Slot identity.
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
212#[serde(transparent)]
213pub struct SlotDeclarationCandidateId(SemanticId);
214
215/// Stable identity for one authored component use in a caller template.
216#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
217#[serde(transparent)]
218pub struct ComponentInvocationId(SemanticId);
219
220/// Typed identity for one canonical authored position in a template traversal.
221#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
222#[serde(transparent)]
223pub struct TemplatePositionId(SemanticId);
224
225/// Stable identity for one caller-owned content fragment supplied to an invocation Slot.
226#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
227#[serde(transparent)]
228pub struct SlotContentFragmentId(SemanticId);
229
230/// Stable identity for one callee-authored compile-time Slot outlet.
231#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
232#[serde(transparent)]
233pub struct SlotOutletId(SemanticId);
234
235/// Stable identity for one invocation- and callee-instance-specific Slot binding.
236#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
237#[serde(transparent)]
238pub struct SlotBindingId(SemanticId);
239
240/// Stable identity for one compiler-owned build/page component root.
241#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
242#[serde(transparent)]
243pub struct ComponentRootId(SemanticId);
244
245/// Stable identity for one component instance or blocked instance boundary.
246#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
247#[serde(transparent)]
248pub struct ComponentInstanceId(SemanticId);
249
250/// Stable identity for a conditional or keyed-list component instance template region.
251#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
252#[serde(transparent)]
253pub struct ComponentStructuralRegionId(SemanticId);
254
255/// Stable identity for one compiler-owned Resource declaration.
256///
257/// A Resource declaration describes the application-level operation. Its
258/// per-component-instance activation has a distinct identity.
259#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
260#[serde(transparent)]
261pub struct ResourceId(SemanticId);
262
263/// Stable identity for one component-instance-qualified Resource activation.
264#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
265#[serde(transparent)]
266pub struct ResourceActivationId(SemanticId);
267
268/// Stable identity for an authored Context-family declaration candidate.
269///
270/// Candidates are source-qualified compiler facts.  They intentionally do not
271/// share an identity domain with valid Context, Provider, or Consumer entities.
272#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
273#[serde(transparent)]
274pub struct ContextDeclarationCandidateId(SemanticId);
275
276/// Direct owner of a semantic entity within one compiled application.
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub enum SemanticOwner {
279    Application,
280    Entity(SemanticId),
281}
282
283impl SemanticOwner {
284    #[must_use]
285    pub fn entity(id: SemanticId) -> Self {
286        Self::Entity(id)
287    }
288
289    #[must_use]
290    pub fn entity_id(&self) -> Option<&SemanticId> {
291        match self {
292            Self::Application => None,
293            Self::Entity(id) => Some(id),
294        }
295    }
296}
297
298impl SemanticId {
299    #[must_use]
300    pub fn component_root(component: &Self) -> Self {
301        Self(format!("root:{}", component.as_str()))
302    }
303    #[must_use]
304    pub fn component(element_name: Option<&str>, class_name: &str) -> Self {
305        Self(format!("component:{}", element_name.unwrap_or(class_name)))
306    }
307
308    #[must_use]
309    pub fn component_in_module(
310        module_path: impl AsRef<Path>,
311        element_name: Option<&str>,
312        class_name: &str,
313    ) -> Self {
314        Self(format!(
315            "module:{}/component:{}",
316            normalized_module_path(module_path.as_ref()),
317            element_name.unwrap_or(class_name)
318        ))
319    }
320
321    #[must_use]
322    pub fn type_alias_in_module(module_path: impl AsRef<Path>, name: &str) -> Self {
323        Self(format!(
324            "module:{}/type-alias:{name}",
325            normalized_module_path(module_path.as_ref())
326        ))
327    }
328
329    #[must_use]
330    pub fn state_field(&self, name: &str) -> Self {
331        self.child("state", name)
332    }
333
334    #[must_use]
335    pub fn method(&self, name: &str) -> Self {
336        self.child("method", name)
337    }
338
339    #[must_use]
340    pub fn computed(&self, name: &str) -> Self {
341        self.child("computed", name)
342    }
343
344    #[must_use]
345    pub fn effect(&self, name: &str) -> Self {
346        self.child("effect", name)
347    }
348
349    #[must_use]
350    pub fn context(&self, name: &str) -> Self {
351        self.child("context", name)
352    }
353
354    #[must_use]
355    pub fn context_field(&self, name: &str) -> Self {
356        self.child("context-field", name)
357    }
358
359    #[must_use]
360    pub fn provider(&self, name: &str) -> Self {
361        self.child("provider", name)
362    }
363
364    #[must_use]
365    pub fn provider_field(&self, name: &str) -> Self {
366        self.child("provider-field", name)
367    }
368
369    #[must_use]
370    pub fn consumer(&self, name: &str) -> Self {
371        self.child("consumer", name)
372    }
373
374    #[must_use]
375    pub fn consumer_field(&self, name: &str) -> Self {
376        self.child("consumer-field", name)
377    }
378
379    #[must_use]
380    pub fn form(&self, name: &str) -> Self {
381        self.child("form", name)
382    }
383
384    #[must_use]
385    pub fn resource(&self, name: &str) -> Self {
386        self.child("resource", name)
387    }
388
389    #[must_use]
390    pub fn route_loader(&self, name: &str) -> Self {
391        self.child("route-loader", name)
392    }
393
394    #[must_use]
395    pub fn server_action(&self, name: &str) -> Self {
396        self.child("server-action", name)
397    }
398
399    #[must_use]
400    pub fn form_field(&self, name: &str) -> Self {
401        self.child("form-field", name)
402    }
403
404    #[must_use]
405    pub fn form_declaration_candidate_in_module(
406        module_path: impl AsRef<Path>,
407        position: usize,
408    ) -> Self {
409        Self(format!(
410            "module:{}/form-declaration:{position}",
411            normalized_module_path(module_path.as_ref())
412        ))
413    }
414
415    #[must_use]
416    pub fn form_field_declaration_candidate_in_module(
417        module_path: impl AsRef<Path>,
418        position: usize,
419    ) -> Self {
420        Self(format!(
421            "module:{}/form-field-declaration:{position}",
422            normalized_module_path(module_path.as_ref())
423        ))
424    }
425
426    #[must_use]
427    pub fn form_field_binding_candidate_in_module(
428        module_path: impl AsRef<Path>,
429        position: usize,
430    ) -> Self {
431        Self(format!(
432            "module:{}/form-field-binding-candidate:{position}",
433            normalized_module_path(module_path.as_ref())
434        ))
435    }
436
437    #[must_use]
438    pub fn validation_rule_candidate_in_module(
439        module_path: impl AsRef<Path>,
440        position: usize,
441    ) -> Self {
442        Self(format!(
443            "module:{}/validation-rule-candidate:{position}",
444            normalized_module_path(module_path.as_ref())
445        ))
446    }
447
448    #[must_use]
449    pub fn submission_declaration_candidate_in_module(
450        module_path: impl AsRef<Path>,
451        position: usize,
452    ) -> Self {
453        Self(format!(
454            "module:{}/submission-declaration-candidate:{position}",
455            normalized_module_path(module_path.as_ref())
456        ))
457    }
458
459    #[must_use]
460    pub fn field(&self, name: &str) -> Self {
461        self.child("field", name)
462    }
463
464    #[must_use]
465    pub fn validation_rule(&self, ordinal: usize) -> Self {
466        self.child("validation-rule", &ordinal.to_string())
467    }
468
469    #[must_use]
470    pub fn validation_plan(&self) -> Self {
471        Self(format!("{}/validation-plan", self.as_str()))
472    }
473
474    #[must_use]
475    pub fn field_dependency(&self, source_field: &Self) -> Self {
476        self.child("field-dependency", source_field.as_str())
477    }
478
479    #[must_use]
480    pub fn dirty_tracking_plan(&self) -> Self {
481        Self(format!("{}/dirty-plan", self.as_str()))
482    }
483
484    #[must_use]
485    pub fn touched_tracking_plan(&self) -> Self {
486        Self(format!("{}/touched-plan", self.as_str()))
487    }
488
489    #[must_use]
490    pub fn field_tracking(&self) -> Self {
491        Self(format!("{}/tracking", self.as_str()))
492    }
493
494    #[must_use]
495    pub fn submission_plan(&self) -> Self {
496        Self(format!("{}/submission-plan", self.as_str()))
497    }
498
499    #[must_use]
500    pub fn serialization_plan(&self) -> Self {
501        Self(format!("{}/serialization-plan", self.as_str()))
502    }
503
504    #[must_use]
505    pub fn reset_plan(&self) -> Self {
506        Self(format!("{}/reset-plan", self.as_str()))
507    }
508
509    #[must_use]
510    pub fn field_reset_operation(&self, field: &Self) -> Self {
511        self.child("field-reset", field.as_str())
512    }
513
514    #[must_use]
515    pub fn form_instance(&self, form: &str) -> Self {
516        self.child("form-instance", form)
517    }
518
519    #[must_use]
520    pub fn slot(&self, name: &str) -> Self {
521        self.child("slot", name)
522    }
523
524    #[must_use]
525    pub fn slot_field(&self, name: &str) -> Self {
526        self.child("slot-field", name)
527    }
528
529    #[must_use]
530    pub fn slot_declaration_candidate(&self, position: usize) -> Self {
531        self.child("slot-declaration", &position.to_string())
532    }
533
534    #[must_use]
535    pub fn context_declaration_candidate(&self, position: usize) -> Self {
536        self.child("context-declaration", &position.to_string())
537    }
538
539    /// Stable generated-function identity for a Provider Context source.
540    #[must_use]
541    pub fn context_provider_function(&self) -> Self {
542        self.child("context-source", "evaluation")
543    }
544
545    /// Stable generated-function identity for a Context-default source.
546    #[must_use]
547    pub fn context_default_function(&self) -> Self {
548        self.child("context-default-source", "evaluation")
549    }
550
551    /// Stable semantic origin used only to scope a retained Context Consumer load value.
552    #[must_use]
553    pub fn context_consumer_load(&self) -> Self {
554        self.child("context-load", "value")
555    }
556
557    #[must_use]
558    pub fn effect_activation_slot(&self, name: &str) -> Self {
559        self.child("effect-activation", name)
560    }
561
562    #[must_use]
563    pub fn effect_statement(&self, index: usize) -> Self {
564        self.child("statement", &index.to_string())
565    }
566
567    #[must_use]
568    pub fn action(&self, method: &str, index: usize) -> Self {
569        self.child("action", &format!("{method}:{index}"))
570    }
571
572    #[must_use]
573    pub fn action_batch(&self, method: &str) -> Self {
574        self.child("action-batch", method)
575    }
576
577    #[must_use]
578    pub fn opaque_activation(&self, method: &str) -> Self {
579        self.child("opaque-activation", method)
580    }
581
582    #[must_use]
583    pub fn local_variable(&self, name: &str, index: usize) -> Self {
584        self.child("local", &format!("{name}:{index}"))
585    }
586
587    #[must_use]
588    pub fn parameter(&self, name: &str, index: usize) -> Self {
589        self.child("parameter", &format!("{name}:{index}"))
590    }
591
592    #[must_use]
593    pub fn expression(&self, path: &str) -> Self {
594        self.child("expression", path)
595    }
596
597    #[must_use]
598    pub fn semantic_type(&self) -> Self {
599        self.child("type", "semantic")
600    }
601
602    #[must_use]
603    pub fn event_handler(&self, event: &str, index: usize) -> Self {
604        self.child("event", &format!("{event}:{index}"))
605    }
606
607    #[must_use]
608    pub fn template(&self) -> Self {
609        self.child("template", "render")
610    }
611
612    #[must_use]
613    pub fn template_entity(&self, kind: &str, path: &str) -> Self {
614        self.child(kind, path)
615    }
616
617    #[must_use]
618    pub fn component_invocation(&self, target: &str) -> Self {
619        self.child("component-invocation", target)
620    }
621
622    /// Compiler-issued identity for one automatic file-route layout edge.
623    #[must_use]
624    pub fn layout_composition_invocation(&self, route: &Self, position: usize) -> Self {
625        self.child(
626            "layout-composition",
627            &format!("{}:{position}", route.as_str()),
628        )
629    }
630
631    #[must_use]
632    pub fn template_position(&self) -> Self {
633        self.child("template-position", "authored")
634    }
635
636    #[must_use]
637    pub fn field_binding(&self, field: &str) -> Self {
638        self.child("field-binding", field)
639    }
640
641    #[must_use]
642    pub fn slot_content_fragment(&self, slot_name: &str) -> Self {
643        self.child("slot-content", slot_name)
644    }
645
646    #[must_use]
647    pub fn slot_outlet(&self, slot_name: &str) -> Self {
648        self.child("slot-outlet", slot_name)
649    }
650
651    #[must_use]
652    pub fn slot_binding(&self, binding_key: &str) -> Self {
653        self.child("slot-binding", binding_key)
654    }
655
656    #[must_use]
657    pub fn component_instance_invocation(&self, invocation: &str) -> Self {
658        self.child("invocation", invocation)
659    }
660
661    #[must_use]
662    pub fn component_structural_region(&self, kind: &str) -> Self {
663        self.child("component-structural-region", kind)
664    }
665
666    #[must_use]
667    pub fn as_str(&self) -> &str {
668        &self.0
669    }
670
671    fn child(&self, kind: &str, name: &str) -> Self {
672        Self(format!("{}/{kind}:{name}", self.0))
673    }
674}
675
676impl EffectId {
677    #[must_use]
678    pub fn from_semantic(id: &SemanticId) -> Self {
679        Self(id.clone())
680    }
681
682    #[must_use]
683    pub fn as_str(&self) -> &str {
684        self.0.as_str()
685    }
686}
687
688impl EffectStatementId {
689    #[must_use]
690    pub fn from_semantic(id: &SemanticId) -> Self {
691        Self(id.clone())
692    }
693
694    #[must_use]
695    pub fn as_str(&self) -> &str {
696        self.0.as_str()
697    }
698}
699
700impl ContextId {
701    #[must_use]
702    pub fn for_component(component: &SemanticId, name: &str) -> Self {
703        Self(component.context(name))
704    }
705
706    #[must_use]
707    pub const fn as_semantic_id(&self) -> &SemanticId {
708        &self.0
709    }
710
711    #[must_use]
712    pub fn as_str(&self) -> &str {
713        self.0.as_str()
714    }
715}
716
717impl ProviderId {
718    #[must_use]
719    pub fn for_component(component: &SemanticId, name: &str) -> Self {
720        Self(component.provider(name))
721    }
722
723    #[must_use]
724    pub const fn as_semantic_id(&self) -> &SemanticId {
725        &self.0
726    }
727
728    #[must_use]
729    pub fn as_str(&self) -> &str {
730        self.0.as_str()
731    }
732}
733
734impl ConsumerId {
735    #[must_use]
736    pub fn for_component(component: &SemanticId, name: &str) -> Self {
737        Self(component.consumer(name))
738    }
739
740    #[must_use]
741    pub const fn as_semantic_id(&self) -> &SemanticId {
742        &self.0
743    }
744
745    #[must_use]
746    pub fn as_str(&self) -> &str {
747        self.0.as_str()
748    }
749}
750
751impl FormId {
752    #[must_use]
753    pub fn for_owner(owner: &SemanticId, name: &str) -> Self {
754        Self(owner.form(name))
755    }
756
757    #[must_use]
758    pub const fn as_semantic_id(&self) -> &SemanticId {
759        &self.0
760    }
761
762    #[must_use]
763    pub fn as_str(&self) -> &str {
764        self.0.as_str()
765    }
766}
767
768impl ResourceId {
769    #[must_use]
770    pub fn for_owner(owner: &SemanticId, name: &str) -> Self {
771        Self(owner.resource(name))
772    }
773
774    #[must_use]
775    pub const fn as_semantic_id(&self) -> &SemanticId {
776        &self.0
777    }
778
779    #[must_use]
780    pub fn as_str(&self) -> &str {
781        self.0.as_str()
782    }
783}
784
785impl ResourceActivationId {
786    #[must_use]
787    pub fn for_component_instance(instance: &ComponentInstanceId, resource: &ResourceId) -> Self {
788        Self(
789            instance
790                .as_semantic_id()
791                .child("resource-activation", resource.as_str()),
792        )
793    }
794
795    #[must_use]
796    pub const fn as_semantic_id(&self) -> &SemanticId {
797        &self.0
798    }
799
800    #[must_use]
801    pub fn as_str(&self) -> &str {
802        self.0.as_str()
803    }
804}
805
806impl FormInstanceId {
807    #[must_use]
808    pub fn for_component_instance(instance: &ComponentInstanceId, form: &FormId) -> Self {
809        Self(instance.as_semantic_id().form_instance(form.as_str()))
810    }
811
812    #[must_use]
813    pub const fn as_semantic_id(&self) -> &SemanticId {
814        &self.0
815    }
816
817    #[must_use]
818    pub fn as_str(&self) -> &str {
819        self.0.as_str()
820    }
821}
822
823impl FormDeclarationCandidateId {
824    #[must_use]
825    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
826        Self(SemanticId::form_declaration_candidate_in_module(
827            module_path,
828            position,
829        ))
830    }
831
832    #[must_use]
833    pub const fn as_semantic_id(&self) -> &SemanticId {
834        &self.0
835    }
836
837    #[must_use]
838    pub fn as_str(&self) -> &str {
839        self.0.as_str()
840    }
841}
842
843impl FormFieldDeclarationCandidateId {
844    #[must_use]
845    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
846        Self(SemanticId::form_field_declaration_candidate_in_module(
847            module_path,
848            position,
849        ))
850    }
851
852    #[must_use]
853    pub const fn as_semantic_id(&self) -> &SemanticId {
854        &self.0
855    }
856
857    #[must_use]
858    pub fn as_str(&self) -> &str {
859        self.0.as_str()
860    }
861}
862
863impl FormFieldBindingCandidateId {
864    #[must_use]
865    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
866        Self(SemanticId::form_field_binding_candidate_in_module(
867            module_path,
868            position,
869        ))
870    }
871
872    #[must_use]
873    pub const fn as_semantic_id(&self) -> &SemanticId {
874        &self.0
875    }
876
877    #[must_use]
878    pub fn as_str(&self) -> &str {
879        self.0.as_str()
880    }
881}
882
883impl FieldId {
884    #[must_use]
885    pub fn for_form(form: &FormId, name: &str) -> Self {
886        Self(form.as_semantic_id().field(name))
887    }
888
889    #[must_use]
890    pub const fn as_semantic_id(&self) -> &SemanticId {
891        &self.0
892    }
893
894    #[must_use]
895    pub fn as_str(&self) -> &str {
896        self.0.as_str()
897    }
898}
899
900impl FieldBindingId {
901    #[must_use]
902    pub fn for_control(control: &SemanticId, field: &FieldId) -> Self {
903        Self(control.field_binding(field.as_str()))
904    }
905
906    #[must_use]
907    pub const fn as_semantic_id(&self) -> &SemanticId {
908        &self.0
909    }
910
911    #[must_use]
912    pub fn as_str(&self) -> &str {
913        self.0.as_str()
914    }
915}
916
917impl SubmissionHostCandidateId {
918    #[must_use]
919    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
920        Self(
921            SemanticId::form_field_binding_candidate_in_module(module_path, position)
922                .child("submission-host-candidate", "host"),
923        )
924    }
925
926    #[must_use]
927    pub const fn as_semantic_id(&self) -> &SemanticId {
928        &self.0
929    }
930
931    #[must_use]
932    pub fn as_str(&self) -> &str {
933        self.0.as_str()
934    }
935}
936
937impl SubmissionHostId {
938    #[must_use]
939    pub fn for_element(element: &SemanticId, form: &FormId) -> Self {
940        Self(element.child("submission-host", form.as_str()))
941    }
942
943    #[must_use]
944    pub const fn as_semantic_id(&self) -> &SemanticId {
945        &self.0
946    }
947
948    #[must_use]
949    pub fn as_str(&self) -> &str {
950        self.0.as_str()
951    }
952}
953
954impl FormOwnershipGraphId {
955    /// Derives one product identity from the existing canonical build-root
956    /// identities. The root set is sorted and deduplicated so file input order
957    /// cannot affect the result.
958    #[must_use]
959    pub fn for_build_roots<'a>(roots: impl IntoIterator<Item = &'a ComponentRootId>) -> Self {
960        let mut roots = roots
961            .into_iter()
962            .map(ComponentRootId::as_str)
963            .collect::<Vec<_>>();
964        roots.sort_unstable();
965        roots.dedup();
966        let authority = if roots.is_empty() {
967            "application".to_string()
968        } else {
969            roots.join("+")
970        };
971        Self(SemanticId(format!("form-ownership-graph:{authority}")))
972    }
973
974    #[must_use]
975    pub const fn as_semantic_id(&self) -> &SemanticId {
976        &self.0
977    }
978
979    #[must_use]
980    pub fn as_str(&self) -> &str {
981        self.0.as_str()
982    }
983}
984
985impl ValidationRuleCandidateId {
986    #[must_use]
987    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
988        Self(SemanticId::validation_rule_candidate_in_module(
989            module_path,
990            position,
991        ))
992    }
993
994    #[must_use]
995    pub const fn as_semantic_id(&self) -> &SemanticId {
996        &self.0
997    }
998
999    #[must_use]
1000    pub fn as_str(&self) -> &str {
1001        self.0.as_str()
1002    }
1003}
1004
1005impl ValidationRuleId {
1006    #[must_use]
1007    pub fn for_field(field: &FieldId, authored_ordinal: usize) -> Self {
1008        Self(field.as_semantic_id().validation_rule(authored_ordinal))
1009    }
1010
1011    #[must_use]
1012    pub const fn as_semantic_id(&self) -> &SemanticId {
1013        &self.0
1014    }
1015
1016    #[must_use]
1017    pub fn as_str(&self) -> &str {
1018        self.0.as_str()
1019    }
1020}
1021
1022impl ValidationGraphId {
1023    #[must_use]
1024    pub fn for_build_roots<'a>(roots: impl IntoIterator<Item = &'a ComponentRootId>) -> Self {
1025        let mut roots = roots
1026            .into_iter()
1027            .map(ComponentRootId::as_str)
1028            .collect::<Vec<_>>();
1029        roots.sort_unstable();
1030        roots.dedup();
1031        let authority = if roots.is_empty() {
1032            "application".to_string()
1033        } else {
1034            roots.join("+")
1035        };
1036        Self(SemanticId(format!("validation-graph:{authority}")))
1037    }
1038
1039    #[must_use]
1040    pub const fn as_semantic_id(&self) -> &SemanticId {
1041        &self.0
1042    }
1043
1044    #[must_use]
1045    pub fn as_str(&self) -> &str {
1046        self.0.as_str()
1047    }
1048}
1049
1050impl ValidationPlanId {
1051    #[must_use]
1052    pub fn for_form(form: &FormId) -> Self {
1053        Self(form.as_semantic_id().validation_plan())
1054    }
1055
1056    #[must_use]
1057    pub const fn as_semantic_id(&self) -> &SemanticId {
1058        &self.0
1059    }
1060
1061    #[must_use]
1062    pub fn as_str(&self) -> &str {
1063        self.0.as_str()
1064    }
1065}
1066
1067impl FieldDependencyId {
1068    #[must_use]
1069    pub fn for_rule_and_source(rule: &ValidationRuleId, source_field: &FieldId) -> Self {
1070        Self(
1071            rule.as_semantic_id()
1072                .field_dependency(source_field.as_semantic_id()),
1073        )
1074    }
1075
1076    #[must_use]
1077    pub const fn as_semantic_id(&self) -> &SemanticId {
1078        &self.0
1079    }
1080
1081    #[must_use]
1082    pub fn as_str(&self) -> &str {
1083        self.0.as_str()
1084    }
1085}
1086
1087impl DirtyTrackingPlanId {
1088    #[must_use]
1089    pub fn for_form(form: &FormId) -> Self {
1090        Self(form.as_semantic_id().dirty_tracking_plan())
1091    }
1092
1093    #[must_use]
1094    pub const fn as_semantic_id(&self) -> &SemanticId {
1095        &self.0
1096    }
1097
1098    #[must_use]
1099    pub fn as_str(&self) -> &str {
1100        self.0.as_str()
1101    }
1102}
1103
1104impl TouchedTrackingPlanId {
1105    #[must_use]
1106    pub fn for_form(form: &FormId) -> Self {
1107        Self(form.as_semantic_id().touched_tracking_plan())
1108    }
1109
1110    #[must_use]
1111    pub const fn as_semantic_id(&self) -> &SemanticId {
1112        &self.0
1113    }
1114
1115    #[must_use]
1116    pub fn as_str(&self) -> &str {
1117        self.0.as_str()
1118    }
1119}
1120
1121impl FieldTrackingId {
1122    #[must_use]
1123    pub fn for_field(field: &FieldId) -> Self {
1124        Self(field.as_semantic_id().field_tracking())
1125    }
1126
1127    #[must_use]
1128    pub const fn as_semantic_id(&self) -> &SemanticId {
1129        &self.0
1130    }
1131
1132    #[must_use]
1133    pub fn as_str(&self) -> &str {
1134        self.0.as_str()
1135    }
1136}
1137
1138impl SubmissionDeclarationCandidateId {
1139    #[must_use]
1140    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
1141        Self(SemanticId::submission_declaration_candidate_in_module(
1142            module_path,
1143            position,
1144        ))
1145    }
1146
1147    #[must_use]
1148    pub const fn as_semantic_id(&self) -> &SemanticId {
1149        &self.0
1150    }
1151
1152    #[must_use]
1153    pub fn as_str(&self) -> &str {
1154        self.0.as_str()
1155    }
1156}
1157
1158impl SubmissionPlanId {
1159    #[must_use]
1160    pub fn for_form(form: &FormId) -> Self {
1161        Self(form.as_semantic_id().submission_plan())
1162    }
1163
1164    #[must_use]
1165    pub const fn as_semantic_id(&self) -> &SemanticId {
1166        &self.0
1167    }
1168
1169    #[must_use]
1170    pub fn as_str(&self) -> &str {
1171        self.0.as_str()
1172    }
1173}
1174
1175impl SerializationPlanId {
1176    #[must_use]
1177    pub fn for_form(form: &FormId) -> Self {
1178        Self(form.as_semantic_id().serialization_plan())
1179    }
1180
1181    #[must_use]
1182    pub const fn as_semantic_id(&self) -> &SemanticId {
1183        &self.0
1184    }
1185
1186    #[must_use]
1187    pub fn as_str(&self) -> &str {
1188        self.0.as_str()
1189    }
1190}
1191
1192impl ResetPlanId {
1193    #[must_use]
1194    pub fn for_form(form: &FormId) -> Self {
1195        Self(form.as_semantic_id().reset_plan())
1196    }
1197    #[must_use]
1198    pub const fn as_semantic_id(&self) -> &SemanticId {
1199        &self.0
1200    }
1201    #[must_use]
1202    pub fn as_str(&self) -> &str {
1203        self.0.as_str()
1204    }
1205}
1206
1207impl FieldResetOperationId {
1208    #[must_use]
1209    pub fn for_plan_and_field(plan: &ResetPlanId, field: &FieldId) -> Self {
1210        Self(
1211            plan.as_semantic_id()
1212                .field_reset_operation(field.as_semantic_id()),
1213        )
1214    }
1215    #[must_use]
1216    pub const fn as_semantic_id(&self) -> &SemanticId {
1217        &self.0
1218    }
1219    #[must_use]
1220    pub fn as_str(&self) -> &str {
1221        self.0.as_str()
1222    }
1223}
1224
1225macro_rules! impl_form_slot_id {
1226    ($name:ident) => {
1227        impl $name {
1228            #[must_use]
1229            pub fn for_instance(instance: &FormInstanceId, name: &str) -> Self {
1230                Self(
1231                    instance
1232                        .as_semantic_id()
1233                        .child("form-slot", &format!("{}:{name}", stringify!($name))),
1234                )
1235            }
1236
1237            #[must_use]
1238            pub const fn as_semantic_id(&self) -> &SemanticId {
1239                &self.0
1240            }
1241
1242            #[must_use]
1243            pub fn as_str(&self) -> &str {
1244                self.0.as_str()
1245            }
1246        }
1247    };
1248}
1249impl_form_slot_id!(FormFieldValueSlotId);
1250impl_form_slot_id!(FormFieldDirtySlotId);
1251impl_form_slot_id!(FormFieldTouchedSlotId);
1252impl_form_slot_id!(FormFieldValidationSlotId);
1253impl_form_slot_id!(FormValidationAggregateSlotId);
1254impl_form_slot_id!(FormSubmissionStateSlotId);
1255
1256impl ValidationDependencyCycleId {
1257    #[must_use]
1258    pub fn for_fields(form: &FormId, fields: &[FieldId]) -> Self {
1259        let mut fields = fields.iter().map(FieldId::as_str).collect::<Vec<_>>();
1260        fields.sort_unstable();
1261        fields.dedup();
1262        Self(SemanticId(format!(
1263            "validation-cycle:{}/{}",
1264            form.as_str(),
1265            fields.join("+")
1266        )))
1267    }
1268
1269    #[must_use]
1270    pub const fn as_semantic_id(&self) -> &SemanticId {
1271        &self.0
1272    }
1273
1274    #[must_use]
1275    pub fn as_str(&self) -> &str {
1276        self.0.as_str()
1277    }
1278}
1279
1280impl SlotId {
1281    #[must_use]
1282    pub fn for_component(component: &SemanticId, name: &str) -> Self {
1283        Self(component.slot(name))
1284    }
1285
1286    #[must_use]
1287    pub const fn as_semantic_id(&self) -> &SemanticId {
1288        &self.0
1289    }
1290
1291    #[must_use]
1292    pub fn as_str(&self) -> &str {
1293        self.0.as_str()
1294    }
1295}
1296
1297impl SlotDeclarationCandidateId {
1298    #[must_use]
1299    pub fn for_component_position(component: &SemanticId, position: usize) -> Self {
1300        Self(component.slot_declaration_candidate(position))
1301    }
1302
1303    #[must_use]
1304    pub const fn as_semantic_id(&self) -> &SemanticId {
1305        &self.0
1306    }
1307
1308    #[must_use]
1309    pub fn as_str(&self) -> &str {
1310        self.0.as_str()
1311    }
1312}
1313
1314impl ComponentInvocationId {
1315    #[must_use]
1316    pub fn for_template_entity(template_entity: &SemanticId, target: &str) -> Self {
1317        Self(template_entity.component_invocation(target))
1318    }
1319
1320    /// Builds a virtual compiler-only invocation ID for automatic file-route
1321    /// layout composition. It is intentionally distinct from authored JSX.
1322    #[must_use]
1323    pub fn for_layout_composition(
1324        layout: &SemanticId,
1325        route_component: &SemanticId,
1326        position: usize,
1327    ) -> Self {
1328        Self(layout.layout_composition_invocation(route_component, position))
1329    }
1330
1331    #[must_use]
1332    pub const fn as_semantic_id(&self) -> &SemanticId {
1333        &self.0
1334    }
1335
1336    #[must_use]
1337    pub fn as_str(&self) -> &str {
1338        self.0.as_str()
1339    }
1340}
1341
1342impl TemplatePositionId {
1343    #[must_use]
1344    pub fn for_template_entity(template_entity: &SemanticId) -> Self {
1345        Self(template_entity.template_position())
1346    }
1347
1348    #[must_use]
1349    pub const fn as_semantic_id(&self) -> &SemanticId {
1350        &self.0
1351    }
1352
1353    #[must_use]
1354    pub fn as_str(&self) -> &str {
1355        self.0.as_str()
1356    }
1357}
1358
1359impl SlotContentFragmentId {
1360    #[must_use]
1361    pub fn for_invocation(invocation: &ComponentInvocationId, slot_name: &str) -> Self {
1362        Self(invocation.as_semantic_id().slot_content_fragment(slot_name))
1363    }
1364
1365    #[must_use]
1366    pub const fn as_semantic_id(&self) -> &SemanticId {
1367        &self.0
1368    }
1369
1370    #[must_use]
1371    pub fn as_str(&self) -> &str {
1372        self.0.as_str()
1373    }
1374}
1375
1376impl SlotOutletId {
1377    #[must_use]
1378    pub fn for_template_entity(template_entity: &SemanticId, slot_name: &str) -> Self {
1379        Self(template_entity.slot_outlet(slot_name))
1380    }
1381
1382    #[must_use]
1383    pub const fn as_semantic_id(&self) -> &SemanticId {
1384        &self.0
1385    }
1386
1387    #[must_use]
1388    pub fn as_str(&self) -> &str {
1389        self.0.as_str()
1390    }
1391}
1392
1393impl SlotBindingId {
1394    #[must_use]
1395    pub fn for_instance(instance: &ComponentInstanceId, binding_key: &str) -> Self {
1396        Self(instance.as_semantic_id().slot_binding(binding_key))
1397    }
1398
1399    #[must_use]
1400    pub const fn as_semantic_id(&self) -> &SemanticId {
1401        &self.0
1402    }
1403
1404    #[must_use]
1405    pub fn as_str(&self) -> &str {
1406        self.0.as_str()
1407    }
1408}
1409
1410impl ComponentRootId {
1411    #[must_use]
1412    pub fn for_component(component: &SemanticId) -> Self {
1413        Self(SemanticId::component_root(component))
1414    }
1415
1416    #[must_use]
1417    pub const fn as_semantic_id(&self) -> &SemanticId {
1418        &self.0
1419    }
1420
1421    #[must_use]
1422    pub fn as_str(&self) -> &str {
1423        self.0.as_str()
1424    }
1425}
1426
1427impl ComponentInstanceId {
1428    #[must_use]
1429    pub fn for_root(root: &ComponentRootId) -> Self {
1430        Self(root.as_semantic_id().clone())
1431    }
1432
1433    #[must_use]
1434    pub fn for_invocation(parent: &Self, invocation: &ComponentInvocationId) -> Self {
1435        Self(
1436            parent
1437                .as_semantic_id()
1438                .component_instance_invocation(invocation.as_str()),
1439        )
1440    }
1441
1442    #[must_use]
1443    pub const fn as_semantic_id(&self) -> &SemanticId {
1444        &self.0
1445    }
1446
1447    #[must_use]
1448    pub fn as_str(&self) -> &str {
1449        self.0.as_str()
1450    }
1451}
1452
1453impl ComponentStructuralRegionId {
1454    #[must_use]
1455    pub fn for_template_entity(entity: &SemanticId, kind: &str) -> Self {
1456        Self(entity.component_structural_region(kind))
1457    }
1458
1459    #[must_use]
1460    pub const fn as_semantic_id(&self) -> &SemanticId {
1461        &self.0
1462    }
1463
1464    #[must_use]
1465    pub fn as_str(&self) -> &str {
1466        self.0.as_str()
1467    }
1468}
1469
1470impl ContextDeclarationCandidateId {
1471    #[must_use]
1472    pub fn for_component_position(component: &SemanticId, position: usize) -> Self {
1473        Self(component.context_declaration_candidate(position))
1474    }
1475
1476    #[must_use]
1477    pub const fn as_semantic_id(&self) -> &SemanticId {
1478        &self.0
1479    }
1480
1481    #[must_use]
1482    pub fn as_str(&self) -> &str {
1483        self.0.as_str()
1484    }
1485}
1486
1487fn normalized_module_path(path: &Path) -> String {
1488    let mut segments = Vec::new();
1489    let absolute = path.is_absolute();
1490
1491    for component in path.components() {
1492        match component {
1493            Component::CurDir | Component::RootDir | Component::Prefix(_) => {}
1494            Component::ParentDir => {
1495                if segments.last().is_some_and(|segment| *segment != "..") {
1496                    segments.pop();
1497                } else {
1498                    segments.push("..".to_string());
1499                }
1500            }
1501            Component::Normal(segment) => segments.push(segment.to_string_lossy().into_owned()),
1502        }
1503    }
1504
1505    let path = segments.join("/");
1506    if absolute {
1507        format!("/{path}")
1508    } else {
1509        path
1510    }
1511}
1512
1513impl fmt::Display for SemanticId {
1514    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1515        self.0.fmt(formatter)
1516    }
1517}
1518
1519impl fmt::Display for EffectId {
1520    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1521        self.0.fmt(formatter)
1522    }
1523}
1524
1525impl fmt::Display for EffectStatementId {
1526    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1527        self.0.fmt(formatter)
1528    }
1529}
1530
1531impl fmt::Display for ContextId {
1532    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1533        self.0.fmt(formatter)
1534    }
1535}
1536
1537impl fmt::Display for ProviderId {
1538    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1539        self.0.fmt(formatter)
1540    }
1541}
1542
1543impl fmt::Display for ConsumerId {
1544    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1545        self.0.fmt(formatter)
1546    }
1547}
1548
1549impl fmt::Display for FormId {
1550    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1551        self.0.fmt(formatter)
1552    }
1553}
1554
1555impl fmt::Display for FormInstanceId {
1556    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1557        self.0.fmt(formatter)
1558    }
1559}
1560
1561impl fmt::Display for FormDeclarationCandidateId {
1562    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1563        self.0.fmt(formatter)
1564    }
1565}
1566
1567impl fmt::Display for FormFieldDeclarationCandidateId {
1568    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1569        self.0.fmt(formatter)
1570    }
1571}
1572
1573impl fmt::Display for FormFieldBindingCandidateId {
1574    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1575        self.0.fmt(formatter)
1576    }
1577}
1578
1579impl fmt::Display for FieldId {
1580    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1581        self.0.fmt(formatter)
1582    }
1583}
1584
1585impl fmt::Display for FieldBindingId {
1586    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1587        self.0.fmt(formatter)
1588    }
1589}
1590
1591impl fmt::Display for SubmissionHostCandidateId {
1592    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1593        self.0.fmt(formatter)
1594    }
1595}
1596impl fmt::Display for SubmissionHostId {
1597    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1598        self.0.fmt(formatter)
1599    }
1600}
1601
1602impl fmt::Display for FormOwnershipGraphId {
1603    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1604        self.0.fmt(formatter)
1605    }
1606}
1607
1608impl fmt::Display for ValidationRuleCandidateId {
1609    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1610        self.0.fmt(formatter)
1611    }
1612}
1613
1614impl fmt::Display for ValidationRuleId {
1615    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1616        self.0.fmt(formatter)
1617    }
1618}
1619
1620impl fmt::Display for ValidationGraphId {
1621    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1622        self.0.fmt(formatter)
1623    }
1624}
1625
1626impl fmt::Display for ValidationPlanId {
1627    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1628        self.0.fmt(formatter)
1629    }
1630}
1631
1632impl fmt::Display for FieldDependencyId {
1633    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1634        self.0.fmt(formatter)
1635    }
1636}
1637
1638impl fmt::Display for ValidationDependencyCycleId {
1639    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1640        self.0.fmt(formatter)
1641    }
1642}
1643
1644impl fmt::Display for SlotId {
1645    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1646        self.0.fmt(formatter)
1647    }
1648}
1649
1650impl fmt::Display for SlotDeclarationCandidateId {
1651    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1652        self.0.fmt(formatter)
1653    }
1654}
1655
1656impl fmt::Display for ComponentInvocationId {
1657    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1658        self.0.fmt(formatter)
1659    }
1660}
1661
1662impl fmt::Display for TemplatePositionId {
1663    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1664        self.0.fmt(formatter)
1665    }
1666}
1667
1668impl fmt::Display for SlotContentFragmentId {
1669    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1670        self.0.fmt(formatter)
1671    }
1672}
1673
1674impl fmt::Display for SlotOutletId {
1675    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1676        self.0.fmt(formatter)
1677    }
1678}
1679
1680impl fmt::Display for SlotBindingId {
1681    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1682        self.0.fmt(formatter)
1683    }
1684}
1685
1686impl fmt::Display for ComponentRootId {
1687    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1688        self.0.fmt(formatter)
1689    }
1690}
1691
1692impl fmt::Display for ComponentInstanceId {
1693    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1694        self.0.fmt(formatter)
1695    }
1696}
1697
1698impl fmt::Display for ComponentStructuralRegionId {
1699    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1700        self.0.fmt(formatter)
1701    }
1702}
1703
1704impl fmt::Display for ContextDeclarationCandidateId {
1705    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1706        self.0.fmt(formatter)
1707    }
1708}
1709
1710#[cfg(test)]
1711mod tests {
1712    use super::{
1713        ComponentInstanceId, ComponentInvocationId, ConsumerId, ContextId, FieldDependencyId,
1714        FieldId, FormId, FormInstanceId, ProviderId, SemanticId, SemanticOwner, SlotId,
1715        TemplatePositionId, ValidationPlanId, ValidationRuleId,
1716    };
1717
1718    #[test]
1719    fn derives_component_scoped_ids() {
1720        let component = SemanticId::component(Some("x-counter"), "Counter");
1721
1722        assert_eq!(component.as_str(), "component:x-counter");
1723        assert_eq!(
1724            component.state_field("count").as_str(),
1725            "component:x-counter/state:count"
1726        );
1727        assert_eq!(
1728            component.method("increment").as_str(),
1729            "component:x-counter/method:increment"
1730        );
1731        assert_eq!(
1732            component.computed("remainingCount").as_str(),
1733            "component:x-counter/computed:remainingCount"
1734        );
1735        assert_eq!(
1736            component.effect("syncTitle").as_str(),
1737            "component:x-counter/effect:syncTitle"
1738        );
1739        assert_eq!(
1740            ContextId::for_component(&component, "theme").as_str(),
1741            "component:x-counter/context:theme"
1742        );
1743        assert_eq!(
1744            ProviderId::for_component(&component, "providedTheme").as_str(),
1745            "component:x-counter/provider:providedTheme"
1746        );
1747        assert_eq!(
1748            ConsumerId::for_component(&component, "theme").as_str(),
1749            "component:x-counter/consumer:theme"
1750        );
1751        assert_eq!(
1752            SlotId::for_component(&component, "children").as_str(),
1753            "component:x-counter/slot:children"
1754        );
1755        assert_eq!(
1756            component.effect_activation_slot("syncTitle").as_str(),
1757            "component:x-counter/effect-activation:syncTitle"
1758        );
1759        assert_eq!(
1760            component.action("increment", 0).as_str(),
1761            "component:x-counter/action:increment:0"
1762        );
1763        assert_eq!(
1764            component
1765                .method("render")
1766                .local_variable("title", 0)
1767                .as_str(),
1768            "component:x-counter/method:render/local:title:0"
1769        );
1770        assert_eq!(
1771            component.event_handler("click", 0).as_str(),
1772            "component:x-counter/event:click:0"
1773        );
1774        assert_eq!(
1775            component.template().as_str(),
1776            "component:x-counter/template:render"
1777        );
1778        let element = component.template().template_entity("element", "root.0");
1779        assert_eq!(
1780            TemplatePositionId::for_template_entity(&element).as_str(),
1781            "component:x-counter/template:render/element:root.0/template-position:authored"
1782        );
1783        assert_eq!(
1784            ComponentInvocationId::for_template_entity(&element, "component:x-card").as_str(),
1785            "component:x-counter/template:render/element:root.0/component-invocation:component:x-card"
1786        );
1787    }
1788
1789    #[test]
1790    fn derives_module_qualified_component_ids() {
1791        let component =
1792            SemanticId::component_in_module("src/../src/Counter.tsx", Some("x-counter"), "Counter");
1793
1794        assert_eq!(
1795            component.as_str(),
1796            "module:src/Counter.tsx/component:x-counter"
1797        );
1798        assert_eq!(
1799            component.state_field("count").as_str(),
1800            "module:src/Counter.tsx/component:x-counter/state:count"
1801        );
1802    }
1803
1804    #[test]
1805    fn derives_distinct_form_definition_instance_and_field_identities() {
1806        let component =
1807            SemanticId::component_in_module("src/Profile.tsx", Some("x-profile"), "Profile");
1808        let component_instance = serde_json::from_str::<ComponentInstanceId>(
1809            r#""root:module:src/Profile.tsx/component:x-profile""#,
1810        )
1811        .expect("canonical component instance id");
1812        let form = FormId::for_owner(&component, "profile");
1813        let instance = FormInstanceId::for_component_instance(&component_instance, &form);
1814        let nested_component_instance = serde_json::from_str::<ComponentInstanceId>(
1815            r#""root:module:src/Profile.tsx/component:x-profile/invocation:nested""#,
1816        )
1817        .expect("canonical nested component instance id");
1818        let nested_instance =
1819            FormInstanceId::for_component_instance(&nested_component_instance, &form);
1820        let field = FieldId::for_form(&form, "email");
1821        let rule = ValidationRuleId::for_field(&field, 2);
1822        let plan = ValidationPlanId::for_form(&form);
1823        let dependency = FieldDependencyId::for_rule_and_source(&rule, &field);
1824
1825        assert_eq!(
1826            form.as_str(),
1827            "module:src/Profile.tsx/component:x-profile/form:profile"
1828        );
1829        assert_eq!(
1830            field.as_str(),
1831            "module:src/Profile.tsx/component:x-profile/form:profile/field:email"
1832        );
1833        assert_eq!(
1834            instance.as_str(),
1835            "root:module:src/Profile.tsx/component:x-profile/form-instance:module:src/Profile.tsx/component:x-profile/form:profile"
1836        );
1837        assert_ne!(form.as_semantic_id(), field.as_semantic_id());
1838        assert_ne!(form.as_semantic_id(), instance.as_semantic_id());
1839        assert_ne!(field.as_semantic_id(), instance.as_semantic_id());
1840        assert_ne!(instance, nested_instance);
1841        assert_eq!(
1842            plan.as_str(),
1843            "module:src/Profile.tsx/component:x-profile/form:profile/validation-plan"
1844        );
1845        assert_eq!(
1846            dependency.as_str(),
1847            "module:src/Profile.tsx/component:x-profile/form:profile/field:email/validation-rule:2/field-dependency:module:src/Profile.tsx/component:x-profile/form:profile/field:email"
1848        );
1849    }
1850
1851    #[test]
1852    fn falls_back_to_class_name_for_invalid_components() {
1853        assert_eq!(
1854            SemanticId::component(None, "MissingDecorator").as_str(),
1855            "component:MissingDecorator"
1856        );
1857    }
1858
1859    #[test]
1860    fn distinguishes_application_roots_from_entity_owners() {
1861        let component = SemanticId::component(Some("x-counter"), "Counter");
1862
1863        assert_eq!(SemanticOwner::Application.entity_id(), None);
1864        assert_eq!(
1865            SemanticOwner::entity(component.clone()).entity_id(),
1866            Some(&component)
1867        );
1868    }
1869}