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 package_invocation(&self, action: &str) -> Self {
617        self.child("package-invocation", action)
618    }
619
620    #[must_use]
621    pub fn local_variable(&self, name: &str, index: usize) -> Self {
622        self.child("local", &format!("{name}:{index}"))
623    }
624
625    #[must_use]
626    pub fn parameter(&self, name: &str, index: usize) -> Self {
627        self.child("parameter", &format!("{name}:{index}"))
628    }
629
630    #[must_use]
631    pub fn expression(&self, path: &str) -> Self {
632        self.child("expression", path)
633    }
634
635    #[must_use]
636    pub fn semantic_type(&self) -> Self {
637        self.child("type", "semantic")
638    }
639
640    #[must_use]
641    pub fn event_handler(&self, event: &str, index: usize) -> Self {
642        self.child("event", &format!("{event}:{index}"))
643    }
644
645    #[must_use]
646    pub fn template(&self) -> Self {
647        self.child("template", "render")
648    }
649
650    #[must_use]
651    pub fn template_entity(&self, kind: &str, path: &str) -> Self {
652        self.child(kind, path)
653    }
654
655    #[must_use]
656    pub fn component_invocation(&self, target: &str) -> Self {
657        self.child("component-invocation", target)
658    }
659
660    /// Compiler-issued identity for one automatic file-route layout edge.
661    #[must_use]
662    pub fn layout_composition_invocation(&self, route: &Self, position: usize) -> Self {
663        self.child(
664            "layout-composition",
665            &format!("{}:{position}", route.as_str()),
666        )
667    }
668
669    #[must_use]
670    pub fn template_position(&self) -> Self {
671        self.child("template-position", "authored")
672    }
673
674    #[must_use]
675    pub fn field_binding(&self, field: &str) -> Self {
676        self.child("field-binding", field)
677    }
678
679    #[must_use]
680    pub fn slot_content_fragment(&self, slot_name: &str) -> Self {
681        self.child("slot-content", slot_name)
682    }
683
684    #[must_use]
685    pub fn slot_outlet(&self, slot_name: &str) -> Self {
686        self.child("slot-outlet", slot_name)
687    }
688
689    #[must_use]
690    pub fn slot_binding(&self, binding_key: &str) -> Self {
691        self.child("slot-binding", binding_key)
692    }
693
694    #[must_use]
695    pub fn component_instance_invocation(&self, invocation: &str) -> Self {
696        self.child("invocation", invocation)
697    }
698
699    #[must_use]
700    pub fn component_structural_region(&self, kind: &str) -> Self {
701        self.child("component-structural-region", kind)
702    }
703
704    #[must_use]
705    pub fn as_str(&self) -> &str {
706        &self.0
707    }
708
709    fn child(&self, kind: &str, name: &str) -> Self {
710        Self(format!("{}/{kind}:{name}", self.0))
711    }
712}
713
714impl EffectId {
715    #[must_use]
716    pub fn from_semantic(id: &SemanticId) -> Self {
717        Self(id.clone())
718    }
719
720    #[must_use]
721    pub fn as_str(&self) -> &str {
722        self.0.as_str()
723    }
724}
725
726impl EffectStatementId {
727    #[must_use]
728    pub fn from_semantic(id: &SemanticId) -> Self {
729        Self(id.clone())
730    }
731
732    #[must_use]
733    pub fn as_str(&self) -> &str {
734        self.0.as_str()
735    }
736}
737
738impl ContextId {
739    #[must_use]
740    pub fn for_component(component: &SemanticId, name: &str) -> Self {
741        Self(component.context(name))
742    }
743
744    #[must_use]
745    pub const fn as_semantic_id(&self) -> &SemanticId {
746        &self.0
747    }
748
749    #[must_use]
750    pub fn as_str(&self) -> &str {
751        self.0.as_str()
752    }
753}
754
755impl ProviderId {
756    #[must_use]
757    pub fn for_component(component: &SemanticId, name: &str) -> Self {
758        Self(component.provider(name))
759    }
760
761    #[must_use]
762    pub const fn as_semantic_id(&self) -> &SemanticId {
763        &self.0
764    }
765
766    #[must_use]
767    pub fn as_str(&self) -> &str {
768        self.0.as_str()
769    }
770}
771
772impl ConsumerId {
773    #[must_use]
774    pub fn for_component(component: &SemanticId, name: &str) -> Self {
775        Self(component.consumer(name))
776    }
777
778    #[must_use]
779    pub const fn as_semantic_id(&self) -> &SemanticId {
780        &self.0
781    }
782
783    #[must_use]
784    pub fn as_str(&self) -> &str {
785        self.0.as_str()
786    }
787}
788
789impl FormId {
790    #[must_use]
791    pub fn for_owner(owner: &SemanticId, name: &str) -> Self {
792        Self(owner.form(name))
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 ResourceId {
807    #[must_use]
808    pub fn for_owner(owner: &SemanticId, name: &str) -> Self {
809        Self(owner.resource(name))
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 ResourceActivationId {
824    #[must_use]
825    pub fn for_component_instance(instance: &ComponentInstanceId, resource: &ResourceId) -> Self {
826        Self(
827            instance
828                .as_semantic_id()
829                .child("resource-activation", resource.as_str()),
830        )
831    }
832
833    /// Canonical completed-value resume storage for this activation.
834    #[must_use]
835    pub fn data_slot(&self) -> SemanticId {
836        self.0.child("resource-slot", "data")
837    }
838
839    /// Canonical completed-error resume storage for this activation.
840    #[must_use]
841    pub fn error_slot(&self) -> SemanticId {
842        self.0.child("resource-slot", "error")
843    }
844
845    /// Canonical lifecycle/generation resume storage for this activation.
846    #[must_use]
847    pub fn state_slot(&self) -> SemanticId {
848        self.0.child("resource-slot", "state")
849    }
850
851    #[must_use]
852    pub const fn as_semantic_id(&self) -> &SemanticId {
853        &self.0
854    }
855
856    #[must_use]
857    pub fn as_str(&self) -> &str {
858        self.0.as_str()
859    }
860}
861
862impl EffectInstanceId {
863    #[must_use]
864    pub fn for_component_instance(instance: &ComponentInstanceId, effect: &SemanticId) -> Self {
865        Self(
866            instance
867                .as_semantic_id()
868                .child("effect-instance", effect.as_str()),
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 FormInstanceId {
884    #[must_use]
885    pub fn for_component_instance(instance: &ComponentInstanceId, form: &FormId) -> Self {
886        Self(instance.as_semantic_id().form_instance(form.as_str()))
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 FormDeclarationCandidateId {
901    #[must_use]
902    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
903        Self(SemanticId::form_declaration_candidate_in_module(
904            module_path,
905            position,
906        ))
907    }
908
909    #[must_use]
910    pub const fn as_semantic_id(&self) -> &SemanticId {
911        &self.0
912    }
913
914    #[must_use]
915    pub fn as_str(&self) -> &str {
916        self.0.as_str()
917    }
918}
919
920impl FormFieldDeclarationCandidateId {
921    #[must_use]
922    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
923        Self(SemanticId::form_field_declaration_candidate_in_module(
924            module_path,
925            position,
926        ))
927    }
928
929    #[must_use]
930    pub const fn as_semantic_id(&self) -> &SemanticId {
931        &self.0
932    }
933
934    #[must_use]
935    pub fn as_str(&self) -> &str {
936        self.0.as_str()
937    }
938}
939
940impl FormFieldBindingCandidateId {
941    #[must_use]
942    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
943        Self(SemanticId::form_field_binding_candidate_in_module(
944            module_path,
945            position,
946        ))
947    }
948
949    #[must_use]
950    pub const fn as_semantic_id(&self) -> &SemanticId {
951        &self.0
952    }
953
954    #[must_use]
955    pub fn as_str(&self) -> &str {
956        self.0.as_str()
957    }
958}
959
960impl FieldId {
961    #[must_use]
962    pub fn for_form(form: &FormId, name: &str) -> Self {
963        Self(form.as_semantic_id().field(name))
964    }
965
966    #[must_use]
967    pub const fn as_semantic_id(&self) -> &SemanticId {
968        &self.0
969    }
970
971    #[must_use]
972    pub fn as_str(&self) -> &str {
973        self.0.as_str()
974    }
975}
976
977impl FieldBindingId {
978    #[must_use]
979    pub fn for_control(control: &SemanticId, field: &FieldId) -> Self {
980        Self(control.field_binding(field.as_str()))
981    }
982
983    #[must_use]
984    pub const fn as_semantic_id(&self) -> &SemanticId {
985        &self.0
986    }
987
988    #[must_use]
989    pub fn as_str(&self) -> &str {
990        self.0.as_str()
991    }
992}
993
994impl SubmissionHostCandidateId {
995    #[must_use]
996    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
997        Self(
998            SemanticId::form_field_binding_candidate_in_module(module_path, position)
999                .child("submission-host-candidate", "host"),
1000        )
1001    }
1002
1003    #[must_use]
1004    pub const fn as_semantic_id(&self) -> &SemanticId {
1005        &self.0
1006    }
1007
1008    #[must_use]
1009    pub fn as_str(&self) -> &str {
1010        self.0.as_str()
1011    }
1012}
1013
1014impl SubmissionHostId {
1015    #[must_use]
1016    pub fn for_element(element: &SemanticId, form: &FormId) -> Self {
1017        Self(element.child("submission-host", form.as_str()))
1018    }
1019
1020    #[must_use]
1021    pub const fn as_semantic_id(&self) -> &SemanticId {
1022        &self.0
1023    }
1024
1025    #[must_use]
1026    pub fn as_str(&self) -> &str {
1027        self.0.as_str()
1028    }
1029}
1030
1031impl FormOwnershipGraphId {
1032    /// Derives one product identity from the existing canonical build-root
1033    /// identities. The root set is sorted and deduplicated so file input order
1034    /// cannot affect the result.
1035    #[must_use]
1036    pub fn for_build_roots<'a>(roots: impl IntoIterator<Item = &'a ComponentRootId>) -> Self {
1037        let mut roots = roots
1038            .into_iter()
1039            .map(ComponentRootId::as_str)
1040            .collect::<Vec<_>>();
1041        roots.sort_unstable();
1042        roots.dedup();
1043        let authority = if roots.is_empty() {
1044            "application".to_string()
1045        } else {
1046            roots.join("+")
1047        };
1048        Self(SemanticId(format!("form-ownership-graph:{authority}")))
1049    }
1050
1051    #[must_use]
1052    pub const fn as_semantic_id(&self) -> &SemanticId {
1053        &self.0
1054    }
1055
1056    #[must_use]
1057    pub fn as_str(&self) -> &str {
1058        self.0.as_str()
1059    }
1060}
1061
1062impl ValidationRuleCandidateId {
1063    #[must_use]
1064    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
1065        Self(SemanticId::validation_rule_candidate_in_module(
1066            module_path,
1067            position,
1068        ))
1069    }
1070
1071    #[must_use]
1072    pub const fn as_semantic_id(&self) -> &SemanticId {
1073        &self.0
1074    }
1075
1076    #[must_use]
1077    pub fn as_str(&self) -> &str {
1078        self.0.as_str()
1079    }
1080}
1081
1082impl ValidationRuleId {
1083    #[must_use]
1084    pub fn for_field(field: &FieldId, authored_ordinal: usize) -> Self {
1085        Self(field.as_semantic_id().validation_rule(authored_ordinal))
1086    }
1087
1088    #[must_use]
1089    pub const fn as_semantic_id(&self) -> &SemanticId {
1090        &self.0
1091    }
1092
1093    #[must_use]
1094    pub fn as_str(&self) -> &str {
1095        self.0.as_str()
1096    }
1097}
1098
1099impl ValidationGraphId {
1100    #[must_use]
1101    pub fn for_build_roots<'a>(roots: impl IntoIterator<Item = &'a ComponentRootId>) -> Self {
1102        let mut roots = roots
1103            .into_iter()
1104            .map(ComponentRootId::as_str)
1105            .collect::<Vec<_>>();
1106        roots.sort_unstable();
1107        roots.dedup();
1108        let authority = if roots.is_empty() {
1109            "application".to_string()
1110        } else {
1111            roots.join("+")
1112        };
1113        Self(SemanticId(format!("validation-graph:{authority}")))
1114    }
1115
1116    #[must_use]
1117    pub const fn as_semantic_id(&self) -> &SemanticId {
1118        &self.0
1119    }
1120
1121    #[must_use]
1122    pub fn as_str(&self) -> &str {
1123        self.0.as_str()
1124    }
1125}
1126
1127impl ValidationPlanId {
1128    #[must_use]
1129    pub fn for_form(form: &FormId) -> Self {
1130        Self(form.as_semantic_id().validation_plan())
1131    }
1132
1133    #[must_use]
1134    pub const fn as_semantic_id(&self) -> &SemanticId {
1135        &self.0
1136    }
1137
1138    #[must_use]
1139    pub fn as_str(&self) -> &str {
1140        self.0.as_str()
1141    }
1142}
1143
1144impl FieldDependencyId {
1145    #[must_use]
1146    pub fn for_rule_and_source(rule: &ValidationRuleId, source_field: &FieldId) -> Self {
1147        Self(
1148            rule.as_semantic_id()
1149                .field_dependency(source_field.as_semantic_id()),
1150        )
1151    }
1152
1153    #[must_use]
1154    pub const fn as_semantic_id(&self) -> &SemanticId {
1155        &self.0
1156    }
1157
1158    #[must_use]
1159    pub fn as_str(&self) -> &str {
1160        self.0.as_str()
1161    }
1162}
1163
1164impl DirtyTrackingPlanId {
1165    #[must_use]
1166    pub fn for_form(form: &FormId) -> Self {
1167        Self(form.as_semantic_id().dirty_tracking_plan())
1168    }
1169
1170    #[must_use]
1171    pub const fn as_semantic_id(&self) -> &SemanticId {
1172        &self.0
1173    }
1174
1175    #[must_use]
1176    pub fn as_str(&self) -> &str {
1177        self.0.as_str()
1178    }
1179}
1180
1181impl TouchedTrackingPlanId {
1182    #[must_use]
1183    pub fn for_form(form: &FormId) -> Self {
1184        Self(form.as_semantic_id().touched_tracking_plan())
1185    }
1186
1187    #[must_use]
1188    pub const fn as_semantic_id(&self) -> &SemanticId {
1189        &self.0
1190    }
1191
1192    #[must_use]
1193    pub fn as_str(&self) -> &str {
1194        self.0.as_str()
1195    }
1196}
1197
1198impl FieldTrackingId {
1199    #[must_use]
1200    pub fn for_field(field: &FieldId) -> Self {
1201        Self(field.as_semantic_id().field_tracking())
1202    }
1203
1204    #[must_use]
1205    pub const fn as_semantic_id(&self) -> &SemanticId {
1206        &self.0
1207    }
1208
1209    #[must_use]
1210    pub fn as_str(&self) -> &str {
1211        self.0.as_str()
1212    }
1213}
1214
1215impl SubmissionDeclarationCandidateId {
1216    #[must_use]
1217    pub fn for_source_position(module_path: impl AsRef<Path>, position: usize) -> Self {
1218        Self(SemanticId::submission_declaration_candidate_in_module(
1219            module_path,
1220            position,
1221        ))
1222    }
1223
1224    #[must_use]
1225    pub const fn as_semantic_id(&self) -> &SemanticId {
1226        &self.0
1227    }
1228
1229    #[must_use]
1230    pub fn as_str(&self) -> &str {
1231        self.0.as_str()
1232    }
1233}
1234
1235impl SubmissionPlanId {
1236    #[must_use]
1237    pub fn for_form(form: &FormId) -> Self {
1238        Self(form.as_semantic_id().submission_plan())
1239    }
1240
1241    #[must_use]
1242    pub const fn as_semantic_id(&self) -> &SemanticId {
1243        &self.0
1244    }
1245
1246    #[must_use]
1247    pub fn as_str(&self) -> &str {
1248        self.0.as_str()
1249    }
1250}
1251
1252impl SerializationPlanId {
1253    #[must_use]
1254    pub fn for_form(form: &FormId) -> Self {
1255        Self(form.as_semantic_id().serialization_plan())
1256    }
1257
1258    #[must_use]
1259    pub const fn as_semantic_id(&self) -> &SemanticId {
1260        &self.0
1261    }
1262
1263    #[must_use]
1264    pub fn as_str(&self) -> &str {
1265        self.0.as_str()
1266    }
1267}
1268
1269impl ResetPlanId {
1270    #[must_use]
1271    pub fn for_form(form: &FormId) -> Self {
1272        Self(form.as_semantic_id().reset_plan())
1273    }
1274    #[must_use]
1275    pub const fn as_semantic_id(&self) -> &SemanticId {
1276        &self.0
1277    }
1278    #[must_use]
1279    pub fn as_str(&self) -> &str {
1280        self.0.as_str()
1281    }
1282}
1283
1284impl FieldResetOperationId {
1285    #[must_use]
1286    pub fn for_plan_and_field(plan: &ResetPlanId, field: &FieldId) -> Self {
1287        Self(
1288            plan.as_semantic_id()
1289                .field_reset_operation(field.as_semantic_id()),
1290        )
1291    }
1292    #[must_use]
1293    pub const fn as_semantic_id(&self) -> &SemanticId {
1294        &self.0
1295    }
1296    #[must_use]
1297    pub fn as_str(&self) -> &str {
1298        self.0.as_str()
1299    }
1300}
1301
1302macro_rules! impl_form_slot_id {
1303    ($name:ident) => {
1304        impl $name {
1305            #[must_use]
1306            pub fn for_instance(instance: &FormInstanceId, name: &str) -> Self {
1307                Self(
1308                    instance
1309                        .as_semantic_id()
1310                        .child("form-slot", &format!("{}:{name}", stringify!($name))),
1311                )
1312            }
1313
1314            #[must_use]
1315            pub const fn as_semantic_id(&self) -> &SemanticId {
1316                &self.0
1317            }
1318
1319            #[must_use]
1320            pub fn as_str(&self) -> &str {
1321                self.0.as_str()
1322            }
1323        }
1324    };
1325}
1326impl_form_slot_id!(FormFieldValueSlotId);
1327impl_form_slot_id!(FormFieldDirtySlotId);
1328impl_form_slot_id!(FormFieldTouchedSlotId);
1329impl_form_slot_id!(FormFieldValidationSlotId);
1330impl_form_slot_id!(FormValidationAggregateSlotId);
1331impl_form_slot_id!(FormSubmissionStateSlotId);
1332
1333impl ValidationDependencyCycleId {
1334    #[must_use]
1335    pub fn for_fields(form: &FormId, fields: &[FieldId]) -> Self {
1336        let mut fields = fields.iter().map(FieldId::as_str).collect::<Vec<_>>();
1337        fields.sort_unstable();
1338        fields.dedup();
1339        Self(SemanticId(format!(
1340            "validation-cycle:{}/{}",
1341            form.as_str(),
1342            fields.join("+")
1343        )))
1344    }
1345
1346    #[must_use]
1347    pub const fn as_semantic_id(&self) -> &SemanticId {
1348        &self.0
1349    }
1350
1351    #[must_use]
1352    pub fn as_str(&self) -> &str {
1353        self.0.as_str()
1354    }
1355}
1356
1357impl SlotId {
1358    #[must_use]
1359    pub fn for_component(component: &SemanticId, name: &str) -> Self {
1360        Self(component.slot(name))
1361    }
1362
1363    #[must_use]
1364    pub const fn as_semantic_id(&self) -> &SemanticId {
1365        &self.0
1366    }
1367
1368    #[must_use]
1369    pub fn as_str(&self) -> &str {
1370        self.0.as_str()
1371    }
1372}
1373
1374impl SlotDeclarationCandidateId {
1375    #[must_use]
1376    pub fn for_component_position(component: &SemanticId, position: usize) -> Self {
1377        Self(component.slot_declaration_candidate(position))
1378    }
1379
1380    #[must_use]
1381    pub const fn as_semantic_id(&self) -> &SemanticId {
1382        &self.0
1383    }
1384
1385    #[must_use]
1386    pub fn as_str(&self) -> &str {
1387        self.0.as_str()
1388    }
1389}
1390
1391impl ComponentInvocationId {
1392    #[must_use]
1393    pub fn for_template_entity(template_entity: &SemanticId, target: &str) -> Self {
1394        Self(template_entity.component_invocation(target))
1395    }
1396
1397    /// Builds a virtual compiler-only invocation ID for automatic file-route
1398    /// layout composition. It is intentionally distinct from authored JSX.
1399    #[must_use]
1400    pub fn for_layout_composition(
1401        layout: &SemanticId,
1402        route_component: &SemanticId,
1403        position: usize,
1404    ) -> Self {
1405        Self(layout.layout_composition_invocation(route_component, position))
1406    }
1407
1408    #[must_use]
1409    pub const fn as_semantic_id(&self) -> &SemanticId {
1410        &self.0
1411    }
1412
1413    #[must_use]
1414    pub fn as_str(&self) -> &str {
1415        self.0.as_str()
1416    }
1417}
1418
1419impl TemplatePositionId {
1420    #[must_use]
1421    pub fn for_template_entity(template_entity: &SemanticId) -> Self {
1422        Self(template_entity.template_position())
1423    }
1424
1425    #[must_use]
1426    pub const fn as_semantic_id(&self) -> &SemanticId {
1427        &self.0
1428    }
1429
1430    #[must_use]
1431    pub fn as_str(&self) -> &str {
1432        self.0.as_str()
1433    }
1434}
1435
1436impl SlotContentFragmentId {
1437    #[must_use]
1438    pub fn for_invocation(invocation: &ComponentInvocationId, slot_name: &str) -> Self {
1439        Self(invocation.as_semantic_id().slot_content_fragment(slot_name))
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 SlotOutletId {
1454    #[must_use]
1455    pub fn for_template_entity(template_entity: &SemanticId, slot_name: &str) -> Self {
1456        Self(template_entity.slot_outlet(slot_name))
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 SlotBindingId {
1471    #[must_use]
1472    pub fn for_instance(instance: &ComponentInstanceId, binding_key: &str) -> Self {
1473        Self(instance.as_semantic_id().slot_binding(binding_key))
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
1487impl ComponentRootId {
1488    #[must_use]
1489    pub fn for_component(component: &SemanticId) -> Self {
1490        Self(SemanticId::component_root(component))
1491    }
1492
1493    #[must_use]
1494    pub const fn as_semantic_id(&self) -> &SemanticId {
1495        &self.0
1496    }
1497
1498    #[must_use]
1499    pub fn as_str(&self) -> &str {
1500        self.0.as_str()
1501    }
1502}
1503
1504impl ComponentInstanceId {
1505    #[must_use]
1506    pub fn for_root(root: &ComponentRootId) -> Self {
1507        Self(root.as_semantic_id().clone())
1508    }
1509
1510    #[must_use]
1511    pub fn for_invocation(parent: &Self, invocation: &ComponentInvocationId) -> Self {
1512        Self(
1513            parent
1514                .as_semantic_id()
1515                .component_instance_invocation(invocation.as_str()),
1516        )
1517    }
1518
1519    #[must_use]
1520    pub const fn as_semantic_id(&self) -> &SemanticId {
1521        &self.0
1522    }
1523
1524    #[must_use]
1525    pub fn as_str(&self) -> &str {
1526        self.0.as_str()
1527    }
1528}
1529
1530impl ComponentStructuralRegionId {
1531    #[must_use]
1532    pub fn for_template_entity(entity: &SemanticId, kind: &str) -> Self {
1533        Self(entity.component_structural_region(kind))
1534    }
1535
1536    #[must_use]
1537    pub const fn as_semantic_id(&self) -> &SemanticId {
1538        &self.0
1539    }
1540
1541    #[must_use]
1542    pub fn as_str(&self) -> &str {
1543        self.0.as_str()
1544    }
1545}
1546
1547impl ContextDeclarationCandidateId {
1548    #[must_use]
1549    pub fn for_component_position(component: &SemanticId, position: usize) -> Self {
1550        Self(component.context_declaration_candidate(position))
1551    }
1552
1553    #[must_use]
1554    pub const fn as_semantic_id(&self) -> &SemanticId {
1555        &self.0
1556    }
1557
1558    #[must_use]
1559    pub fn as_str(&self) -> &str {
1560        self.0.as_str()
1561    }
1562}
1563
1564fn normalized_module_path(path: &Path) -> String {
1565    let mut segments = Vec::new();
1566    let absolute = path.is_absolute();
1567
1568    for component in path.components() {
1569        match component {
1570            Component::CurDir | Component::RootDir | Component::Prefix(_) => {}
1571            Component::ParentDir => {
1572                if segments.last().is_some_and(|segment| *segment != "..") {
1573                    segments.pop();
1574                } else {
1575                    segments.push("..".to_string());
1576                }
1577            }
1578            Component::Normal(segment) => segments.push(segment.to_string_lossy().into_owned()),
1579        }
1580    }
1581
1582    let path = segments.join("/");
1583    if absolute {
1584        format!("/{path}")
1585    } else {
1586        path
1587    }
1588}
1589
1590impl fmt::Display for SemanticId {
1591    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1592        self.0.fmt(formatter)
1593    }
1594}
1595
1596impl fmt::Display for EffectId {
1597    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1598        self.0.fmt(formatter)
1599    }
1600}
1601
1602impl fmt::Display for EffectStatementId {
1603    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1604        self.0.fmt(formatter)
1605    }
1606}
1607
1608impl fmt::Display for ContextId {
1609    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1610        self.0.fmt(formatter)
1611    }
1612}
1613
1614impl fmt::Display for ProviderId {
1615    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1616        self.0.fmt(formatter)
1617    }
1618}
1619
1620impl fmt::Display for ConsumerId {
1621    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1622        self.0.fmt(formatter)
1623    }
1624}
1625
1626impl fmt::Display for FormId {
1627    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1628        self.0.fmt(formatter)
1629    }
1630}
1631
1632impl fmt::Display for FormInstanceId {
1633    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1634        self.0.fmt(formatter)
1635    }
1636}
1637
1638impl fmt::Display for FormDeclarationCandidateId {
1639    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1640        self.0.fmt(formatter)
1641    }
1642}
1643
1644impl fmt::Display for FormFieldDeclarationCandidateId {
1645    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1646        self.0.fmt(formatter)
1647    }
1648}
1649
1650impl fmt::Display for FormFieldBindingCandidateId {
1651    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1652        self.0.fmt(formatter)
1653    }
1654}
1655
1656impl fmt::Display for FieldId {
1657    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1658        self.0.fmt(formatter)
1659    }
1660}
1661
1662impl fmt::Display for FieldBindingId {
1663    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1664        self.0.fmt(formatter)
1665    }
1666}
1667
1668impl fmt::Display for SubmissionHostCandidateId {
1669    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1670        self.0.fmt(formatter)
1671    }
1672}
1673impl fmt::Display for SubmissionHostId {
1674    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1675        self.0.fmt(formatter)
1676    }
1677}
1678
1679impl fmt::Display for FormOwnershipGraphId {
1680    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1681        self.0.fmt(formatter)
1682    }
1683}
1684
1685impl fmt::Display for ValidationRuleCandidateId {
1686    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1687        self.0.fmt(formatter)
1688    }
1689}
1690
1691impl fmt::Display for ValidationRuleId {
1692    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1693        self.0.fmt(formatter)
1694    }
1695}
1696
1697impl fmt::Display for ValidationGraphId {
1698    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1699        self.0.fmt(formatter)
1700    }
1701}
1702
1703impl fmt::Display for ValidationPlanId {
1704    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1705        self.0.fmt(formatter)
1706    }
1707}
1708
1709impl fmt::Display for FieldDependencyId {
1710    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1711        self.0.fmt(formatter)
1712    }
1713}
1714
1715impl fmt::Display for ValidationDependencyCycleId {
1716    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1717        self.0.fmt(formatter)
1718    }
1719}
1720
1721impl fmt::Display for SlotId {
1722    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1723        self.0.fmt(formatter)
1724    }
1725}
1726
1727impl fmt::Display for SlotDeclarationCandidateId {
1728    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1729        self.0.fmt(formatter)
1730    }
1731}
1732
1733impl fmt::Display for ComponentInvocationId {
1734    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1735        self.0.fmt(formatter)
1736    }
1737}
1738
1739impl fmt::Display for TemplatePositionId {
1740    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1741        self.0.fmt(formatter)
1742    }
1743}
1744
1745impl fmt::Display for SlotContentFragmentId {
1746    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1747        self.0.fmt(formatter)
1748    }
1749}
1750
1751impl fmt::Display for SlotOutletId {
1752    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1753        self.0.fmt(formatter)
1754    }
1755}
1756
1757impl fmt::Display for SlotBindingId {
1758    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1759        self.0.fmt(formatter)
1760    }
1761}
1762
1763impl fmt::Display for ComponentRootId {
1764    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1765        self.0.fmt(formatter)
1766    }
1767}
1768
1769impl fmt::Display for ComponentInstanceId {
1770    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1771        self.0.fmt(formatter)
1772    }
1773}
1774
1775impl fmt::Display for ComponentStructuralRegionId {
1776    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1777        self.0.fmt(formatter)
1778    }
1779}
1780
1781impl fmt::Display for ContextDeclarationCandidateId {
1782    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1783        self.0.fmt(formatter)
1784    }
1785}
1786
1787#[cfg(test)]
1788mod tests {
1789    use super::{
1790        ComponentInstanceId, ComponentInvocationId, ConsumerId, ContextId, FieldDependencyId,
1791        FieldId, FormId, FormInstanceId, ProviderId, SemanticId, SemanticOwner, SlotId,
1792        TemplatePositionId, ValidationPlanId, ValidationRuleId,
1793    };
1794
1795    #[test]
1796    fn derives_component_scoped_ids() {
1797        let component = SemanticId::component(Some("x-counter"), "Counter");
1798
1799        assert_eq!(component.as_str(), "component:x-counter");
1800        assert_eq!(
1801            component.state_field("count").as_str(),
1802            "component:x-counter/state:count"
1803        );
1804        assert_eq!(
1805            component.method("increment").as_str(),
1806            "component:x-counter/method:increment"
1807        );
1808        assert_eq!(
1809            component.computed("remainingCount").as_str(),
1810            "component:x-counter/computed:remainingCount"
1811        );
1812        assert_eq!(
1813            component.effect("syncTitle").as_str(),
1814            "component:x-counter/effect:syncTitle"
1815        );
1816        assert_eq!(
1817            ContextId::for_component(&component, "theme").as_str(),
1818            "component:x-counter/context:theme"
1819        );
1820        assert_eq!(
1821            ProviderId::for_component(&component, "providedTheme").as_str(),
1822            "component:x-counter/provider:providedTheme"
1823        );
1824        assert_eq!(
1825            ConsumerId::for_component(&component, "theme").as_str(),
1826            "component:x-counter/consumer:theme"
1827        );
1828        assert_eq!(
1829            SlotId::for_component(&component, "children").as_str(),
1830            "component:x-counter/slot:children"
1831        );
1832        assert_eq!(
1833            component.effect_activation_slot("syncTitle").as_str(),
1834            "component:x-counter/effect-activation:syncTitle"
1835        );
1836        assert_eq!(
1837            component.action("increment", 0).as_str(),
1838            "component:x-counter/action:increment:0"
1839        );
1840        assert_eq!(
1841            component
1842                .method("render")
1843                .local_variable("title", 0)
1844                .as_str(),
1845            "component:x-counter/method:render/local:title:0"
1846        );
1847        assert_eq!(
1848            component.event_handler("click", 0).as_str(),
1849            "component:x-counter/event:click:0"
1850        );
1851        assert_eq!(
1852            component.template().as_str(),
1853            "component:x-counter/template:render"
1854        );
1855        let element = component.template().template_entity("element", "root.0");
1856        assert_eq!(
1857            TemplatePositionId::for_template_entity(&element).as_str(),
1858            "component:x-counter/template:render/element:root.0/template-position:authored"
1859        );
1860        assert_eq!(
1861            ComponentInvocationId::for_template_entity(&element, "component:x-card").as_str(),
1862            "component:x-counter/template:render/element:root.0/component-invocation:component:x-card"
1863        );
1864    }
1865
1866    #[test]
1867    fn derives_module_qualified_component_ids() {
1868        let component =
1869            SemanticId::component_in_module("src/../src/Counter.tsx", Some("x-counter"), "Counter");
1870
1871        assert_eq!(
1872            component.as_str(),
1873            "module:src/Counter.tsx/component:x-counter"
1874        );
1875        assert_eq!(
1876            component.state_field("count").as_str(),
1877            "module:src/Counter.tsx/component:x-counter/state:count"
1878        );
1879        assert_eq!(
1880            SemanticId::environment_read_in_module("src/../src/environment.ts", 41).as_str(),
1881            "module:src/environment.ts/environment-read:41"
1882        );
1883    }
1884
1885    #[test]
1886    fn derives_distinct_form_definition_instance_and_field_identities() {
1887        let component =
1888            SemanticId::component_in_module("src/Profile.tsx", Some("x-profile"), "Profile");
1889        let component_instance = serde_json::from_str::<ComponentInstanceId>(
1890            r#""root:module:src/Profile.tsx/component:x-profile""#,
1891        )
1892        .expect("canonical component instance id");
1893        let form = FormId::for_owner(&component, "profile");
1894        let instance = FormInstanceId::for_component_instance(&component_instance, &form);
1895        let nested_component_instance = serde_json::from_str::<ComponentInstanceId>(
1896            r#""root:module:src/Profile.tsx/component:x-profile/invocation:nested""#,
1897        )
1898        .expect("canonical nested component instance id");
1899        let nested_instance =
1900            FormInstanceId::for_component_instance(&nested_component_instance, &form);
1901        let field = FieldId::for_form(&form, "email");
1902        let rule = ValidationRuleId::for_field(&field, 2);
1903        let plan = ValidationPlanId::for_form(&form);
1904        let dependency = FieldDependencyId::for_rule_and_source(&rule, &field);
1905
1906        assert_eq!(
1907            form.as_str(),
1908            "module:src/Profile.tsx/component:x-profile/form:profile"
1909        );
1910        assert_eq!(
1911            field.as_str(),
1912            "module:src/Profile.tsx/component:x-profile/form:profile/field:email"
1913        );
1914        assert_eq!(
1915            instance.as_str(),
1916            "root:module:src/Profile.tsx/component:x-profile/form-instance:module:src/Profile.tsx/component:x-profile/form:profile"
1917        );
1918        assert_ne!(form.as_semantic_id(), field.as_semantic_id());
1919        assert_ne!(form.as_semantic_id(), instance.as_semantic_id());
1920        assert_ne!(field.as_semantic_id(), instance.as_semantic_id());
1921        assert_ne!(instance, nested_instance);
1922        assert_eq!(
1923            plan.as_str(),
1924            "module:src/Profile.tsx/component:x-profile/form:profile/validation-plan"
1925        );
1926        assert_eq!(
1927            dependency.as_str(),
1928            "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"
1929        );
1930    }
1931
1932    #[test]
1933    fn falls_back_to_class_name_for_invalid_components() {
1934        assert_eq!(
1935            SemanticId::component(None, "MissingDecorator").as_str(),
1936            "component:MissingDecorator"
1937        );
1938    }
1939
1940    #[test]
1941    fn distinguishes_application_roots_from_entity_owners() {
1942        let component = SemanticId::component(Some("x-counter"), "Counter");
1943
1944        assert_eq!(SemanticOwner::Application.entity_id(), None);
1945        assert_eq!(
1946            SemanticOwner::entity(component.clone()).entity_id(),
1947            Some(&component)
1948        );
1949    }
1950}