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