1#[cfg(feature = "test-support")]
8use std::cell::Cell;
9use std::collections::{BTreeMap, BTreeSet};
10
11pub use engine_input_producers::{
12 ClassExpressionInputV2, EngineInputV2, ExpressionDomainCallSiteFlowAnalysisV0,
13 ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
14 ExpressionDomainProvenanceExplanationsV0, ExpressionDomainReducedProductIterationV0,
15 ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0, PositionV2,
16 RangeV2, SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
17 SourceAnalysisInputV2, SourceDocumentV2, SourceResolutionCanonicalProducerSignalV0,
18 SourceResolutionQueryFragmentsV0, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
19 StyleSelectorV2, TypeFactEntryV2,
20};
21use engine_input_producers::{
22 collect_expression_domain_flow_graphs,
23 summarize_expression_domain_call_site_flow_analysis_input,
24 summarize_expression_domain_control_flow_analysis_input,
25 summarize_expression_domain_flow_analysis_input,
26 summarize_expression_domain_provenance_explanations_input,
27 summarize_expression_domain_reduced_product_iteration_input,
28 summarize_expression_semantics_canonical_producer_signal_input,
29 summarize_expression_semantics_query_fragments_input,
30 summarize_selector_usage_canonical_producer_signal_input,
31 summarize_selector_usage_query_fragments_input,
32};
33use omena_abstract_value::{
34 AbstractClassValueProvenanceV0, ClassValueFlowSealedIncrementalAnalysisV0,
35 SealedClassValueFlowAnalysisArtifactV0, analyze_class_value_flow_incremental,
36 analyze_class_value_flow_incremental_with_artifact, class_value_flow_incremental_input,
37 project_abstract_value_selectors, summarize_omena_abstract_value_domain,
38 summarize_reduced_class_value_product,
39};
40pub use omena_abstract_value::{
41 AbstractClassValueV0, AbstractPropertyValueCandidateV0, AbstractPropertyValueNarrowingV0,
42 AbstractPropertyValueV0, AbstractValueDomainSummaryV0, CascadeContextV0,
43 CascadeValueFamilyMemberV0, ClassBoundaryEffectV0, ClassValueFlowAnalysisV0,
44 ClassValueFlowIncrementalAnalysisV0, CssValueValidationClassV0, ExternalStringTypeFactsV0,
45 FactPrecision, FirstWitnessErrorV0, GuardAtomV0, GuardedTokenInputV0, GuardedTokenLanguageV0,
46 GuardedTokenMapInputV0, GuardedTokenMapV0, GuardedTokenObserverV0, Lin01ProvenanceSemiringV0,
47 LinearProvenancePathV0, LinearProvenanceV0, NaturalCountProvenanceSemiringV0,
48 OmenaAbstractValueCoverageDirectionV0, OmenaAbstractValuePrecisionBasisV0,
49 OmenaAbstractValuePrecisionWitnessV0, PolynomialProvenanceProjectionV0,
50 PolynomialProvenanceTermV0, PolynomialProvenanceV0, PolynomialProvenanceVariableV0,
51 ProvenanceSemiringLawReportV0, ReducedClassValueProductIterationV0, ReducedClassValueProductV0,
52 SelectorProjectionCertaintyV0, SpecStandardPropertyValueValidatorV0, TokenObserverProjectionV0,
53 abstract_class_value_from_facts, abstract_class_value_kind,
54 derive_context_indexed_cascade_restriction_maps_v0, fact_precision_from_class_value,
55 fact_precision_from_class_value_with_witness, iterate_reduced_class_value_product_constraints,
56 join_abstract_class_values, narrow_abstract_property_value_for_authored_cascade_branch,
57 narrow_abstract_property_value_for_cascade_branch,
58 narrow_abstract_property_value_for_pseudo_state, prefix_suffix_class_value,
59 summarize_context_indexed_cascade_value_family_v0,
60 summarize_polynomial_provenance_from_linear_v0, top_class_value,
61 validate_registered_property_value_v0, validate_standard_property_value_v0,
62 verify_provenance_semiring_laws_on_fixtures,
63};
64#[allow(deprecated)]
65#[deprecated(
66 since = "0.4.0",
67 note = "use the context-indexed cascade value family adapters; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
68)]
69pub use omena_abstract_value::{
70 derive_cascade_restriction_maps_v0, summarize_cascade_value_family_v0,
71};
72pub use omena_incremental::{
73 IncrementalEditDistancePriorityInputV0, IncrementalGraphInputV0,
74 IncrementalInvalidationPriorityPlanV0, IncrementalNodeInputV0, IncrementalRevisionV0,
75 OmenaIncrementalDatabaseV0, OmenaSalsaDatabaseV0, OmenaWorkspaceSnapshotIdV0,
76 snapshot_from_graph_input,
77};
78pub use omena_refinement::{
79 CascadeDimensionalRefinementBridgeV0, RefinementPropertyPredicateV0,
80 summarize_cascade_dimensional_refinement_bridge_v0,
81};
82pub use omena_resolver::OmenaResolverSourceResolutionRuntimeIndexV0;
83use omena_resolver::{
84 summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
85 summarize_omena_resolver_source_resolution_runtime,
86};
87pub use omena_value_lattice::{
88 canonicalize_css_value, split_top_level_value_arguments,
89 split_top_level_whitespace_value_components,
90};
91use serde::{Deserialize, Serialize};
92
93pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION: &str = "0";
94pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION_LABEL: &str = "V0";
95
96#[cfg(feature = "test-support")]
97thread_local! {
98 static SELECTOR_PROJECTION_EVALUATION_COUNT: Cell<usize> = const { Cell::new(0) };
99}
100
101#[cfg(feature = "test-support")]
102pub fn reset_selector_projection_evaluation_count_for_test() {
103 SELECTOR_PROJECTION_EVALUATION_COUNT.with(|counter| counter.set(0));
104}
105
106#[cfg(feature = "test-support")]
107pub fn selector_projection_evaluation_count_for_test() -> usize {
108 SELECTOR_PROJECTION_EVALUATION_COUNT.with(Cell::get)
109}
110
111#[cfg(feature = "test-support")]
112fn record_selector_projection_evaluation_for_test() {
113 SELECTOR_PROJECTION_EVALUATION_COUNT.with(|counter| counter.set(counter.get() + 1));
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct OmenaQueryAnalysisPrecisionV0 {
119 pub product: String,
120 pub value_domain: String,
121 pub flow_sensitivity: String,
122 pub context_sensitivity: String,
123 pub revision_axis: String,
124}
125
126const OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN: &[(&str, FactPrecision)] = &[
127 ("cascadeAtPosition", FactPrecision::Exact),
128 ("styleModuleResolution", FactPrecision::Exact),
129 ("classValueResolution", FactPrecision::Conservative),
130 ("classValueUniverse", FactPrecision::Conservative),
131 ("classValueFlow", FactPrecision::Heuristic),
132 ("unknown", FactPrecision::Unknown),
133];
134
135pub fn fact_precision_from_analysis_precision(
136 precision: &OmenaQueryAnalysisPrecisionV0,
137) -> FactPrecision {
138 OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN
139 .iter()
140 .find_map(|(value_domain, mapped)| {
141 (*value_domain == precision.value_domain).then_some(*mapped)
142 })
143 .unwrap_or(FactPrecision::Unknown)
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct OmenaQueryAnalysisResultV0<TValue> {
149 pub schema_version: String,
150 pub product: String,
151 pub value: TValue,
152 pub precision: OmenaQueryAnalysisPrecisionV0,
153 pub provenance: Vec<String>,
154 pub revision: u64,
155}
156
157impl<TValue> OmenaQueryAnalysisResultV0<TValue> {
158 pub fn new(
159 value: TValue,
160 precision: OmenaQueryAnalysisPrecisionV0,
161 provenance: Vec<String>,
162 revision: u64,
163 ) -> Self {
164 Self {
165 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION.to_string(),
166 product: "omena-query.analysis-result".to_string(),
167 value,
168 precision,
169 provenance,
170 revision,
171 }
172 }
173}
174
175#[derive(Debug, Serialize)]
176#[serde(rename_all = "camelCase")]
177pub struct OmenaQueryFragmentBundleV0 {
178 pub schema_version: &'static str,
179 pub product: &'static str,
180 pub input_version: String,
181 pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
182 pub source_resolution: SourceResolutionQueryFragmentsV0,
183 pub selector_usage: SelectorUsageQueryFragmentsV0,
184}
185
186#[derive(Debug, Serialize)]
187#[serde(rename_all = "camelCase")]
188pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
189 pub schema_version: &'static str,
190 pub product: &'static str,
191 pub input_version: String,
192 pub revision: u64,
193 pub graph_count: usize,
194 pub dirty_graph_count: usize,
195 pub reused_graph_count: usize,
196 pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
197}
198
199#[derive(Debug, Serialize)]
200#[serde(rename_all = "camelCase")]
201pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
202 pub graph_id: String,
203 pub file_path: String,
204 pub analysis: ClassValueFlowIncrementalAnalysisV0,
205}
206
207#[derive(Debug, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
210 pub schema_version: &'static str,
211 pub product: &'static str,
212 pub input_version: String,
213 pub projection_count: usize,
214 pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
215}
216
217#[derive(Debug, Serialize)]
218#[serde(rename_all = "camelCase")]
219pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
220 pub graph_id: String,
221 pub file_path: String,
222 pub node_id: String,
223 pub target_style_paths: Vec<String>,
224 pub value_kind: &'static str,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub reduced_product: Option<ReducedClassValueProductV0>,
227 pub selector_names: Vec<String>,
228 pub certainty: SelectorProjectionCertaintyV0,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
232#[serde(rename_all = "camelCase")]
233pub struct OmenaQueryExpressionDomainSelectorPrecisionV0 {
234 pub graph_id: String,
235 pub node_id: String,
236 pub precision: FactPrecision,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240struct ExpressionDomainSelectorCertaintyFlowHedgeV0 {
241 graph_converged: bool,
242 contains_flow_iteration_limit: bool,
243}
244
245#[derive(Default)]
246pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
247 revision: u64,
248 artifacts_by_graph_id: BTreeMap<String, SealedClassValueFlowAnalysisArtifactV0>,
249 read_set_digest_check_count: usize,
250 analysis_rebuild_count: usize,
251 artifact_refusal_count: usize,
252}
253
254impl OmenaQueryExpressionDomainFlowRuntimeV0 {
255 pub fn revision(&self) -> u64 {
256 self.revision
257 }
258
259 pub fn graph_count(&self) -> usize {
260 self.artifacts_by_graph_id.len()
261 }
262
263 pub fn read_set_digest_check_count(&self) -> usize {
266 self.read_set_digest_check_count
267 }
268
269 pub fn analysis_rebuild_count(&self) -> usize {
271 self.analysis_rebuild_count
272 }
273
274 pub fn artifact_refusal_count(&self) -> usize {
276 self.artifact_refusal_count
277 }
278
279 pub fn analyze_input(
280 &mut self,
281 input: &EngineInputV2,
282 ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
283 self.analyze_input_with_artifact_analyzer(
284 input,
285 analyze_class_value_flow_incremental_with_artifact,
286 )
287 }
288
289 fn analyze_input_with_artifact_analyzer<F>(
290 &mut self,
291 input: &EngineInputV2,
292 mut analyze_artifact: F,
293 ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0
294 where
295 F: FnMut(
296 &omena_abstract_value::ClassValueFlowGraphV0,
297 Option<&SealedClassValueFlowAnalysisArtifactV0>,
298 u64,
299 ) -> Result<
300 ClassValueFlowSealedIncrementalAnalysisV0,
301 omena_abstract_value::ClassValueFlowArtifactRefusalV0,
302 >,
303 {
304 self.revision += 1;
305 let revision = self.revision;
306 let flow_graphs = collect_expression_domain_flow_graphs(input);
307 let live_graph_ids = flow_graphs
308 .iter()
309 .map(|entry| entry.graph_id.clone())
310 .collect::<BTreeSet<_>>();
311
312 self.artifacts_by_graph_id
313 .retain(|graph_id, _| live_graph_ids.contains(graph_id));
314
315 let analyses = flow_graphs
316 .into_iter()
317 .map(|entry| {
318 let previous_artifact = self.artifacts_by_graph_id.get(&entry.graph_id);
319 let sealed_analysis = analyze_artifact(&entry.graph, previous_artifact, revision);
320 let (analysis, next_artifact) = match sealed_analysis {
321 Ok(sealed_analysis) => {
322 self.read_set_digest_check_count = self
323 .read_set_digest_check_count
324 .saturating_add(sealed_analysis.read_set_digest_check_count);
325 self.analysis_rebuild_count = self
326 .analysis_rebuild_count
327 .saturating_add(sealed_analysis.analysis_rebuild_count);
328 let (analysis, next_artifact) = legacy_flow_analysis_from_sealed(
329 &entry.graph,
330 revision,
331 previous_artifact,
332 sealed_analysis,
333 );
334 (analysis, Some(next_artifact))
335 }
336 Err(_) => {
337 self.read_set_digest_check_count = self
338 .read_set_digest_check_count
339 .saturating_add(usize::from(previous_artifact.is_some()));
340 self.analysis_rebuild_count = self.analysis_rebuild_count.saturating_add(1);
341 self.artifact_refusal_count = self.artifact_refusal_count.saturating_add(1);
342 (
343 analyze_class_value_flow_incremental(&entry.graph, None, revision),
344 None,
345 )
346 }
347 };
348 if let Some(next_artifact) = next_artifact {
349 self.artifacts_by_graph_id
350 .insert(entry.graph_id.clone(), next_artifact);
351 } else {
352 self.artifacts_by_graph_id.remove(&entry.graph_id);
353 }
354
355 OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
356 graph_id: entry.graph_id,
357 file_path: entry.file_path,
358 analysis,
359 }
360 })
361 .collect::<Vec<_>>();
362
363 let dirty_graph_count = analyses
364 .iter()
365 .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
366 .count();
367 let reused_graph_count = analyses
368 .iter()
369 .filter(|entry| entry.analysis.reused_previous_analysis)
370 .count();
371
372 OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
373 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
374 product: "omena-query.expression-domain-incremental-flow-analysis",
375 input_version: input.version.clone(),
376 revision,
377 graph_count: analyses.len(),
378 dirty_graph_count,
379 reused_graph_count,
380 analyses,
381 }
382 }
383}
384
385fn legacy_flow_analysis_from_sealed(
386 graph: &omena_abstract_value::ClassValueFlowGraphV0,
387 revision: u64,
388 previous_artifact: Option<&SealedClassValueFlowAnalysisArtifactV0>,
389 sealed: ClassValueFlowSealedIncrementalAnalysisV0,
390) -> (
391 ClassValueFlowIncrementalAnalysisV0,
392 SealedClassValueFlowAnalysisArtifactV0,
393) {
394 let ClassValueFlowSealedIncrementalAnalysisV0 {
395 reused_previous_analysis,
396 incremental_plan,
397 analysis,
398 next_artifact,
399 ..
400 } = sealed;
401 let (incremental_plan, next_snapshot) = match incremental_plan {
402 Some(incremental_plan) => (incremental_plan, next_artifact.snapshot().clone()),
403 None => {
404 let mut database = OmenaIncrementalDatabaseV0::default();
412 if let Some(previous_artifact) = previous_artifact {
413 database.restore_snapshot(previous_artifact.snapshot());
414 }
415 let update = database
416 .plan_and_upsert_graph_input(&class_value_flow_incremental_input(graph, revision));
417 (update.incremental_plan, update.next_snapshot)
418 }
419 };
420 (
421 ClassValueFlowIncrementalAnalysisV0 {
422 schema_version: "0",
423 product: "omena-abstract-value.incremental-flow-analysis",
424 reused_previous_analysis,
425 incremental_plan,
426 next_snapshot,
427 analysis,
428 },
429 next_artifact,
430 )
431}
432
433pub fn summarize_omena_query_core_abstract_value_domain() -> AbstractValueDomainSummaryV0 {
434 summarize_omena_abstract_value_domain()
435}
436
437pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
438 OmenaQueryFragmentBundleV0 {
439 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
440 product: "omena-query.fragment-bundle",
441 input_version: input.version.clone(),
442 expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
443 source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
444 selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
445 }
446}
447
448pub fn summarize_omena_query_expression_semantics_query_fragments(
449 input: &EngineInputV2,
450) -> ExpressionSemanticsQueryFragmentsV0 {
451 summarize_expression_semantics_query_fragments_input(input)
452}
453
454pub fn summarize_omena_query_expression_domain_flow_analysis(
455 input: &EngineInputV2,
456) -> ExpressionDomainFlowAnalysisV0 {
457 summarize_expression_domain_flow_analysis_input(input)
458}
459
460pub fn summarize_omena_query_expression_domain_control_flow_analysis(
461 input: &EngineInputV2,
462) -> ExpressionDomainControlFlowAnalysisV0 {
463 summarize_expression_domain_control_flow_analysis_input(input)
464}
465
466pub fn summarize_omena_query_expression_domain_call_site_flow_analysis(
467 input: &EngineInputV2,
468) -> ExpressionDomainCallSiteFlowAnalysisV0 {
469 summarize_expression_domain_call_site_flow_analysis_input(input)
470}
471
472pub fn summarize_omena_query_expression_domain_provenance_explanations(
473 input: &EngineInputV2,
474) -> ExpressionDomainProvenanceExplanationsV0 {
475 summarize_expression_domain_provenance_explanations_input(input)
476}
477
478pub fn summarize_omena_query_expression_domain_reduced_product_iteration(
479 input: &EngineInputV2,
480) -> ExpressionDomainReducedProductIterationV0 {
481 summarize_expression_domain_reduced_product_iteration_input(input)
482}
483
484pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
485 input: &EngineInputV2,
486 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
487) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
488 runtime.analyze_input(input)
489}
490
491pub fn summarize_omena_query_expression_domain_incremental_flow_analysis_result(
492 input: &EngineInputV2,
493 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
494) -> OmenaQueryAnalysisResultV0<OmenaQueryExpressionDomainIncrementalFlowAnalysisV0> {
495 let value = runtime.analyze_input(input);
496 let revision = value.revision;
497 OmenaQueryAnalysisResultV0::new(
498 value,
499 OmenaQueryAnalysisPrecisionV0 {
500 product: "omena-query.analysis-precision".to_string(),
501 value_domain: "classValueFlow".to_string(),
502 flow_sensitivity: "incrementalDataflow".to_string(),
503 context_sensitivity: "perExpressionGraph".to_string(),
504 revision_axis: "OmenaQueryExpressionDomainFlowRuntimeV0.revision".to_string(),
505 },
506 vec![
507 "omena-query-core.expression-domain-runtime".to_string(),
508 "omena-abstract-value.incremental-class-value-flow".to_string(),
509 ],
510 revision,
511 )
512}
513
514pub fn summarize_omena_query_expression_domain_selector_projection(
515 input: &EngineInputV2,
516) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
517 summarize_omena_query_expression_domain_selector_projection_with_precision(input).0
518}
519
520pub fn summarize_omena_query_expression_domain_selector_projection_with_precision(
521 input: &EngineInputV2,
522) -> (
523 OmenaQueryExpressionDomainSelectorProjectionV0,
524 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
525) {
526 summarize_omena_query_expression_domain_selector_projection_with_precision_and_style_path_resolver(
527 input,
528 |target, _known| Some(target.to_string()),
529 )
530}
531
532pub fn summarize_omena_query_expression_domain_selector_projection_with_precision_and_style_path_resolver<
533 F,
534>(
535 input: &EngineInputV2,
536 resolve_style_path: F,
537) -> (
538 OmenaQueryExpressionDomainSelectorProjectionV0,
539 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
540)
541where
542 F: Fn(&str, &[String]) -> Option<String>,
543{
544 #[cfg(feature = "test-support")]
545 record_selector_projection_evaluation_for_test();
546 let selector_certainty_flow_hedges = expression_domain_selector_certainty_flow_hedges(input);
547 summarize_omena_query_expression_domain_selector_projection_with_flow_hedges(
548 input,
549 resolve_style_path,
550 &selector_certainty_flow_hedges,
551 )
552}
553
554fn summarize_omena_query_expression_domain_selector_projection_with_flow_hedges<F>(
555 input: &EngineInputV2,
556 resolve_style_path: F,
557 selector_certainty_flow_hedges: &BTreeMap<
558 (String, String),
559 ExpressionDomainSelectorCertaintyFlowHedgeV0,
560 >,
561) -> (
562 OmenaQueryExpressionDomainSelectorProjectionV0,
563 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
564)
565where
566 F: Fn(&str, &[String]) -> Option<String>,
567{
568 let style_selectors_by_path = style_selector_universe_by_path(input);
569 let known_style_paths = style_selectors_by_path.keys().cloned().collect::<Vec<_>>();
570 let expression_targets = expression_target_style_paths(input);
571 let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
572 let mut projections = Vec::new();
573 let mut precisions = Vec::new();
574
575 for graph in flow_analysis.analyses {
576 for node in graph.analysis.nodes {
577 let target_style_paths = target_style_paths_for_flow_node(
578 node.id.as_str(),
579 node.predecessor_ids.as_slice(),
580 &expression_targets,
581 );
582 let resolved_target_style_paths = target_style_paths
583 .iter()
584 .map(|path| resolve_style_path(path, known_style_paths.as_slice()))
585 .collect::<Option<Vec<_>>>();
586 let selector_universe = selector_universe_for_targets(
587 resolved_target_style_paths.as_deref(),
588 &style_selectors_by_path,
589 );
590 let projection = project_abstract_value_selectors(&node.value, &selector_universe);
591 let certainty = hedge_selector_projection_certainty(
592 projection.certainty,
593 selector_certainty_flow_hedges.get(&(graph.file_path.clone(), node.id.clone())),
594 );
595 precisions.push(OmenaQueryExpressionDomainSelectorPrecisionV0 {
596 graph_id: graph.graph_id.clone(),
597 node_id: node.id.clone(),
598 precision: fact_precision_from_class_value(&node.value),
599 });
600 projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
601 graph_id: graph.graph_id.clone(),
602 file_path: graph.file_path.clone(),
603 node_id: node.id,
604 target_style_paths,
605 value_kind: node.value_kind,
606 reduced_product: summarize_reduced_class_value_product(&node.value),
607 selector_names: projection.selector_names,
608 certainty,
609 });
610 }
611 }
612
613 (
614 OmenaQueryExpressionDomainSelectorProjectionV0 {
615 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
616 product: "omena-query.expression-domain-selector-projection",
617 input_version: input.version.clone(),
618 projection_count: projections.len(),
619 projections,
620 },
621 precisions,
622 )
623}
624
625fn expression_domain_selector_certainty_flow_hedges(
626 input: &EngineInputV2,
627) -> BTreeMap<(String, String), ExpressionDomainSelectorCertaintyFlowHedgeV0> {
628 bind_expression_domain_selector_certainty_flow_hedges(
629 input,
630 summarize_omena_query_expression_domain_control_flow_analysis(input),
631 )
632}
633
634fn bind_expression_domain_selector_certainty_flow_hedges(
635 input: &EngineInputV2,
636 control_flow: ExpressionDomainControlFlowAnalysisV0,
637) -> BTreeMap<(String, String), ExpressionDomainSelectorCertaintyFlowHedgeV0> {
638 let control_flow_facts = input
639 .type_facts
640 .iter()
641 .filter(|entry| entry.control_flow_graph.is_some())
642 .collect::<Vec<_>>();
643 assert_eq!(
644 control_flow_facts.len(),
645 control_flow.analyses.len(),
646 "selector-certainty flow hedge requires one control analysis per control-flow type fact"
647 );
648
649 let mut hedges = BTreeMap::new();
650 for (entry, analyzed) in control_flow_facts.into_iter().zip(control_flow.analyses) {
651 let diagnostic_graph_id = format!(
652 "{}:{}:expression-domain-control-flow",
653 entry.file_path, entry.expression_id
654 );
655 assert_eq!(
656 analyzed.file_path, entry.file_path,
657 "selector-certainty flow hedge control analysis order/file mismatch"
658 );
659 assert_eq!(
660 analyzed.graph_id, diagnostic_graph_id,
661 "selector-certainty flow hedge control analysis order/graph mismatch"
662 );
663
664 let contains_flow_iteration_limit = analyzed
665 .analysis
666 .flow_analysis
667 .nodes
668 .iter()
669 .any(|node| abstract_value_contains_flow_iteration_limit(&node.value));
670 let key = (entry.file_path.clone(), entry.expression_id.clone());
671 let previous = hedges.insert(
672 key.clone(),
673 ExpressionDomainSelectorCertaintyFlowHedgeV0 {
674 graph_converged: analyzed.analysis.flow_analysis.converged,
675 contains_flow_iteration_limit,
676 },
677 );
678 assert!(
679 previous.is_none(),
680 "selector-certainty flow hedge duplicate type-fact key: file_path={:?} expression_id={:?}",
681 key.0,
682 key.1
683 );
684 }
685
686 hedges
687}
688
689fn hedge_selector_projection_certainty(
690 base: SelectorProjectionCertaintyV0,
691 flow_hedge: Option<&ExpressionDomainSelectorCertaintyFlowHedgeV0>,
692) -> SelectorProjectionCertaintyV0 {
693 flow_hedge.map_or(base, |hedge| {
694 hedge_selector_certainty_for_flow(
695 base,
696 hedge.graph_converged,
697 hedge.contains_flow_iteration_limit,
698 )
699 })
700}
701
702fn hedge_selector_certainty_for_flow(
703 base: SelectorProjectionCertaintyV0,
704 graph_converged: bool,
705 contains_flow_iteration_limit: bool,
706) -> SelectorProjectionCertaintyV0 {
707 if graph_converged && !contains_flow_iteration_limit {
708 base
709 } else {
710 SelectorProjectionCertaintyV0::Possible
711 }
712}
713
714fn abstract_value_contains_flow_iteration_limit(value: &AbstractClassValueV0) -> bool {
715 let provenance = match value {
716 AbstractClassValueV0::Automaton { provenance, .. }
717 | AbstractClassValueV0::Prefix { provenance, .. }
718 | AbstractClassValueV0::Suffix { provenance, .. }
719 | AbstractClassValueV0::PrefixSuffix { provenance, .. }
720 | AbstractClassValueV0::CharInclusion { provenance, .. }
721 | AbstractClassValueV0::Composite { provenance, .. }
722 | AbstractClassValueV0::Top { provenance } => *provenance,
723 AbstractClassValueV0::Bottom
724 | AbstractClassValueV0::Exact { .. }
725 | AbstractClassValueV0::FiniteSet { .. } => None,
726 };
727
728 provenance == Some(AbstractClassValueProvenanceV0::FlowIterationLimit)
729}
730
731fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
732 input
733 .sources
734 .iter()
735 .flat_map(|source| source.document.class_expressions.iter())
736 .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
737 .collect()
738}
739
740fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
741 input
742 .styles
743 .iter()
744 .map(|style| {
745 let selector_names = style
746 .document
747 .selectors
748 .iter()
749 .map(|selector| {
750 selector
751 .canonical_name
752 .clone()
753 .unwrap_or_else(|| selector.name.clone())
754 })
755 .collect::<BTreeSet<_>>()
756 .into_iter()
757 .collect::<Vec<_>>();
758 (style.file_path.clone(), selector_names)
759 })
760 .collect()
761}
762
763fn target_style_paths_for_flow_node(
764 node_id: &str,
765 predecessor_ids: &[String],
766 expression_targets: &BTreeMap<String, String>,
767) -> Vec<String> {
768 let mut targets = BTreeSet::new();
769 if let Some(target) = expression_targets.get(node_id) {
770 targets.insert(target.clone());
771 }
772 for predecessor_id in predecessor_ids {
773 if let Some(target) = expression_targets.get(predecessor_id) {
774 targets.insert(target.clone());
775 }
776 }
777 targets.into_iter().collect()
778}
779
780fn selector_universe_for_targets(
781 target_style_paths: Option<&[String]>,
782 style_selectors_by_path: &BTreeMap<String, Vec<String>>,
783) -> Vec<String> {
784 let mut selectors = BTreeSet::new();
785 if target_style_paths.is_none_or(<[String]>::is_empty) {
786 for selector_names in style_selectors_by_path.values() {
787 selectors.extend(selector_names.iter().cloned());
788 }
789 } else if let Some(target_style_paths) = target_style_paths {
790 for target_style_path in target_style_paths {
791 if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
792 selectors.extend(selector_names.iter().cloned());
793 }
794 }
795 }
796 selectors.into_iter().collect()
797}
798
799pub fn summarize_omena_query_source_resolution_query_fragments(
800 input: &EngineInputV2,
801) -> SourceResolutionQueryFragmentsV0 {
802 summarize_omena_resolver_query_fragments(input)
803}
804
805pub fn summarize_omena_query_selector_usage_query_fragments(
806 input: &EngineInputV2,
807) -> SelectorUsageQueryFragmentsV0 {
808 summarize_selector_usage_query_fragments_input(input)
809}
810
811pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
812 input: &EngineInputV2,
813) -> SourceResolutionCanonicalProducerSignalV0 {
814 summarize_omena_resolver_canonical_producer_signal(input)
815}
816
817pub fn summarize_omena_query_source_resolution_runtime(
818 input: &EngineInputV2,
819) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
820 summarize_omena_resolver_source_resolution_runtime(input)
821}
822
823pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
824 input: &EngineInputV2,
825) -> ExpressionSemanticsCanonicalProducerSignalV0 {
826 summarize_expression_semantics_canonical_producer_signal_input(input)
827}
828
829pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
830 input: &EngineInputV2,
831) -> SelectorUsageCanonicalProducerSignalV0 {
832 summarize_selector_usage_canonical_producer_signal_input(input)
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838
839 fn serialized_json_bytes<T: serde::Serialize>(
840 value: &T,
841 label: &str,
842 ) -> Result<Vec<u8>, String> {
843 serde_json::to_vec(value).map_err(|error| format!("{label} serialization failed: {error}"))
844 }
845
846 fn selector_certainty_product_input() -> EngineInputV2 {
847 let range = RangeV2 {
848 start: PositionV2 {
849 line: 0,
850 character: 0,
851 },
852 end: PositionV2 {
853 line: 0,
854 character: 11,
855 },
856 };
857 EngineInputV2 {
858 version: "selector-certainty-product".to_string(),
859 sources: vec![SourceAnalysisInputV2 {
860 document: SourceDocumentV2 {
861 class_expressions: vec![ClassExpressionInputV2 {
862 id: "expr-certainty".to_string(),
863 kind: "styleAccess".to_string(),
864 scss_module_path: "/tmp/App.module.scss".to_string(),
865 range: range.clone(),
866 class_name: Some("x".to_string()),
867 root_binding_decl_id: None,
868 access_path: Some(vec!["styles".to_string(), "x".to_string()]),
869 }],
870 },
871 }],
872 styles: vec![StyleAnalysisInputV2 {
873 file_path: "/tmp/App.module.scss".to_string(),
874 source: None,
875 document: StyleDocumentV2 {
876 selectors: vec![StyleSelectorV2 {
877 name: "x".to_string(),
878 view_kind: "canonical".to_string(),
879 canonical_name: Some("x".to_string()),
880 range,
881 nested_safety: Some("safe".to_string()),
882 composes: None,
883 bem_suffix: None,
884 }],
885 },
886 }],
887 type_facts: vec![TypeFactEntryV2 {
888 file_path: "/tmp/App.tsx".to_string(),
889 expression_id: "expr-certainty".to_string(),
890 facts: StringTypeFactsV2 {
891 kind: "exact".to_string(),
892 constraint_kind: None,
893 values: Some(vec!["x".to_string()]),
894 prefix: None,
895 suffix: None,
896 min_len: None,
897 max_len: None,
898 char_must: None,
899 char_may: None,
900 may_include_other_chars: None,
901 provenance: None,
902 },
903 control_flow_graph: Some(engine_input_producers::TypeFactControlFlowGraphV2 {
904 entry_block_id: "seed".to_string(),
905 blocks: vec![
906 engine_input_producers::TypeFactControlFlowBlockV2 {
907 id: "seed".to_string(),
908 kind: "assignment".to_string(),
909 transfer_kind: "assignFacts".to_string(),
910 successor_block_ids: vec!["loop".to_string()],
911 symbol_ordinal: None,
912 variable_name: None,
913 expression_kind: None,
914 boundary_effect: "unknownBoundary".to_string(),
915 facts: Some(StringTypeFactsV2 {
916 kind: "finiteSet".to_string(),
917 constraint_kind: None,
918 values: Some(vec!["a".to_string(), "b".to_string()]),
919 prefix: None,
920 suffix: None,
921 min_len: None,
922 max_len: None,
923 char_must: None,
924 char_may: None,
925 may_include_other_chars: None,
926 provenance: None,
927 }),
928 },
929 engine_input_producers::TypeFactControlFlowBlockV2 {
930 id: "loop".to_string(),
931 kind: "loop".to_string(),
932 transfer_kind: "concatFacts".to_string(),
933 successor_block_ids: vec!["loop".to_string()],
934 symbol_ordinal: None,
935 variable_name: None,
936 expression_kind: None,
937 boundary_effect: "unknownBoundary".to_string(),
938 facts: None,
939 },
940 ],
941 }),
942 }],
943 }
944 }
945
946 fn selector_certainty_colon_collision_input() -> EngineInputV2 {
947 let mut input = selector_certainty_product_input();
948 input.sources[0].document.class_expressions[0].id = "b:c".to_string();
949 input.sources[0].document.class_expressions[0].class_name = Some("x".to_string());
950 input.sources[0].document.class_expressions[0].access_path =
951 Some(vec!["styles".to_string(), "x".to_string()]);
952 let second_expression = ClassExpressionInputV2 {
953 id: "c".to_string(),
954 kind: "styleAccess".to_string(),
955 scss_module_path: "/tmp/App.module.scss".to_string(),
956 range: input.sources[0].document.class_expressions[0].range.clone(),
957 class_name: Some("y".to_string()),
958 root_binding_decl_id: None,
959 access_path: Some(vec!["styles".to_string(), "y".to_string()]),
960 };
961 input.sources[0]
962 .document
963 .class_expressions
964 .push(second_expression);
965
966 let second_selector = StyleSelectorV2 {
967 name: "y".to_string(),
968 view_kind: "canonical".to_string(),
969 canonical_name: Some("y".to_string()),
970 range: input.styles[0].document.selectors[0].range.clone(),
971 nested_safety: Some("safe".to_string()),
972 composes: None,
973 bem_suffix: None,
974 };
975 input.styles[0].document.selectors.push(second_selector);
976
977 let mut first_fact = input.type_facts[0].clone();
978 first_fact.file_path = "/tmp/A".to_string();
979 first_fact.expression_id = "b:c".to_string();
980 let mut second_fact = first_fact.clone();
981 second_fact.file_path = "/tmp/A:b".to_string();
982 second_fact.expression_id = "c".to_string();
983 second_fact.facts.values = Some(vec!["y".to_string()]);
984 if let Some(second_graph) = second_fact.control_flow_graph.as_mut() {
985 second_graph.blocks.truncate(1);
986 second_graph.blocks[0].successor_block_ids.clear();
987 }
988 input.type_facts = vec![first_fact, second_fact];
989 input
990 }
991
992 #[test]
993 fn expression_domain_runtime_reuses_graph_databases_across_revisions() {
994 let input = EngineInputV2 {
995 version: "core-runtime".to_string(),
996 sources: Vec::new(),
997 styles: Vec::new(),
998 type_facts: Vec::new(),
999 };
1000 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
1001
1002 let first =
1003 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
1004 let second =
1005 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
1006
1007 assert_eq!(first.revision, 1);
1008 assert_eq!(second.revision, 2);
1009 assert_eq!(runtime.revision(), 2);
1010 }
1011
1012 #[test]
1013 fn expression_domain_runtime_checks_sealed_artifacts_before_product_reuse() {
1014 let input = selector_certainty_product_input();
1015 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
1016
1017 let first =
1018 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
1019 assert!(
1020 first.graph_count > 0,
1021 "fixture must exercise product flow graphs"
1022 );
1023 assert_eq!(runtime.read_set_digest_check_count(), 0);
1024 assert_eq!(runtime.analysis_rebuild_count(), first.graph_count);
1025
1026 let second =
1027 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
1028 eprintln!(
1029 "productFlowArtifactCounters graphs={} digestChecks={} rebuilds={} refusals={}",
1030 second.graph_count,
1031 runtime.read_set_digest_check_count(),
1032 runtime.analysis_rebuild_count(),
1033 runtime.artifact_refusal_count(),
1034 );
1035 assert_eq!(second.reused_graph_count, second.graph_count);
1036 assert_eq!(runtime.read_set_digest_check_count(), second.graph_count);
1037 assert_eq!(
1038 runtime.analysis_rebuild_count(),
1039 first.graph_count,
1040 "verified reuse must not rebuild the flow analysis"
1041 );
1042 assert_eq!(runtime.artifact_refusal_count(), 0);
1043 }
1044
1045 #[test]
1046 fn expression_domain_runtime_preserves_legacy_incremental_plan_bytes_across_reuse_revisions()
1047 -> Result<(), String> {
1048 let input = selector_certainty_product_input();
1049 let flow_graphs = collect_expression_domain_flow_graphs(&input);
1050 assert_eq!(
1051 flow_graphs.len(),
1052 1,
1053 "fixture must isolate one product graph"
1054 );
1055 let graph = &flow_graphs[0].graph;
1056 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
1057 let mut legacy_snapshot = None;
1058 let mut legacy_analysis = None;
1059
1060 for revision in 1..=5 {
1061 let product = summarize_omena_query_expression_domain_incremental_flow_analysis(
1062 &input,
1063 &mut runtime,
1064 );
1065 let legacy = omena_abstract_value::analyze_class_value_flow_incremental_with_reuse(
1066 graph,
1067 legacy_snapshot.as_ref(),
1068 legacy_analysis.as_ref(),
1069 revision,
1070 );
1071 let product_analysis = &product.analyses[0].analysis;
1072 let product_plan_bytes = serialized_json_bytes(
1073 &product_analysis.incremental_plan,
1074 "product incremental plan",
1075 )?;
1076 let legacy_plan_bytes =
1077 serialized_json_bytes(&legacy.incremental_plan, "legacy incremental plan")?;
1078 let product_changed_at = product_analysis
1079 .incremental_plan
1080 .nodes
1081 .iter()
1082 .map(|node| node.changed_at.value)
1083 .collect::<Vec<_>>();
1084 let legacy_changed_at = legacy
1085 .incremental_plan
1086 .nodes
1087 .iter()
1088 .map(|node| node.changed_at.value)
1089 .collect::<Vec<_>>();
1090 assert_eq!(
1091 product_changed_at, legacy_changed_at,
1092 "unchanged product nodes must preserve the legacy changedAt stamps at revision {revision}"
1093 );
1094 assert_eq!(
1095 product_plan_bytes, legacy_plan_bytes,
1096 "incrementalPlan bytes diverged from the independent legacy oracle at revision {revision}"
1097 );
1098 assert_eq!(
1099 serialized_json_bytes(product_analysis, "product incremental analysis")?,
1100 serialized_json_bytes(&legacy, "legacy incremental analysis")?,
1101 "public incremental analysis bytes diverged at revision {revision}"
1102 );
1103 if revision > 1 {
1104 assert_eq!(product.reused_graph_count, 1);
1105 assert!(product_analysis.reused_previous_analysis);
1106 assert!(legacy.reused_previous_analysis);
1107 }
1108 legacy_snapshot = Some(legacy.next_snapshot);
1109 legacy_analysis = Some(legacy.analysis);
1110 }
1111
1112 let mut changed_input = selector_certainty_product_input();
1113 changed_input.type_facts[0].facts.values = Some(vec!["changed".to_string()]);
1114 let changed_graphs = collect_expression_domain_flow_graphs(&changed_input);
1115 let changed_product = summarize_omena_query_expression_domain_incremental_flow_analysis(
1116 &changed_input,
1117 &mut runtime,
1118 );
1119 let changed_legacy = omena_abstract_value::analyze_class_value_flow_incremental_with_reuse(
1120 &changed_graphs[0].graph,
1121 legacy_snapshot.as_ref(),
1122 legacy_analysis.as_ref(),
1123 6,
1124 );
1125 assert_eq!(changed_product.reused_graph_count, 0);
1126 assert!(
1127 !changed_product.analyses[0]
1128 .analysis
1129 .reused_previous_analysis
1130 );
1131 assert!(!changed_legacy.reused_previous_analysis);
1132 assert_eq!(
1133 serialized_json_bytes(
1134 &changed_product.analyses[0].analysis,
1135 "changed product analysis",
1136 )?,
1137 serialized_json_bytes(&changed_legacy, "changed legacy analysis")?,
1138 "a member change after consecutive reuse revisions must match the independent legacy oracle"
1139 );
1140
1141 eprintln!(
1142 "productFlowArtifactByteIdentity graphs=1 reuseRevisions=4 memberChanges=1 digestChecks={} rebuilds={} refusals={}",
1143 runtime.read_set_digest_check_count(),
1144 runtime.analysis_rebuild_count(),
1145 runtime.artifact_refusal_count(),
1146 );
1147 assert_eq!(runtime.read_set_digest_check_count(), 5);
1148 assert_eq!(runtime.analysis_rebuild_count(), 2);
1149 assert_eq!(runtime.artifact_refusal_count(), 0);
1150 Ok(())
1151 }
1152
1153 #[test]
1154 fn expression_domain_runtime_refusal_fallback_rebuilds_and_recovers() -> Result<(), String> {
1155 let input = selector_certainty_product_input();
1156 let graph = collect_expression_domain_flow_graphs(&input)
1157 .pop()
1158 .ok_or_else(|| "fixture must produce one graph".to_string())?
1159 .graph;
1160 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
1161
1162 let first = runtime.analyze_input(&input);
1163 let second = runtime.analyze_input(&input);
1164 assert_eq!(first.graph_count, 1);
1165 assert_eq!(second.reused_graph_count, 1);
1166
1167 let mut injected = false;
1168 let third = runtime.analyze_input_with_artifact_analyzer(
1169 &input,
1170 |graph, previous_artifact, revision| {
1171 if revision == 3 && previous_artifact.is_some() && !injected {
1172 injected = true;
1173 return Err(omena_abstract_value::ClassValueFlowArtifactRefusalV0 {
1174 schema_version: "0",
1175 product: "omena-abstract-value.flow-analysis-artifact-refusal",
1176 cause: omena_abstract_value::ClassValueFlowArtifactRefusalCauseV0::ArtifactDigestMismatch,
1177 });
1178 }
1179 analyze_class_value_flow_incremental_with_artifact(
1180 graph,
1181 previous_artifact,
1182 revision,
1183 )
1184 },
1185 );
1186 assert!(injected, "the product refusal seam must be exercised");
1187 assert_eq!(third.reused_graph_count, 0);
1188 assert_eq!(runtime.artifact_refusal_count(), 1);
1189 assert_eq!(runtime.analysis_rebuild_count(), 2);
1190 assert_eq!(
1191 serialized_json_bytes(&third.analyses[0].analysis, "fallback analysis")?,
1192 serialized_json_bytes(
1193 &analyze_class_value_flow_incremental(&graph, None, 3),
1194 "fresh fallback oracle",
1195 )?,
1196 "a refused artifact must serve the fresh fallback value"
1197 );
1198
1199 let fourth = runtime.analyze_input(&input);
1200 assert_eq!(fourth.reused_graph_count, 0);
1201 assert_eq!(runtime.analysis_rebuild_count(), 3);
1202 assert_eq!(runtime.artifact_refusal_count(), 1);
1203 let fifth = runtime.analyze_input(&input);
1204 assert_eq!(fifth.reused_graph_count, 1);
1205 assert_eq!(runtime.analysis_rebuild_count(), 3);
1206 assert_eq!(runtime.artifact_refusal_count(), 1);
1207 assert_eq!(
1208 fifth.analyses[0].analysis.analysis,
1209 fourth.analyses[0].analysis.analysis
1210 );
1211 eprintln!(
1212 "productFlowArtifactRefusalRecovery graphs=1 refusalRevision=3 fallbackRebuilds=1 reseedRebuilds=1 reuseResumedRevision=5 totalRebuilds={} refusals={}",
1213 runtime.analysis_rebuild_count(),
1214 runtime.artifact_refusal_count(),
1215 );
1216 Ok(())
1217 }
1218
1219 #[test]
1220 fn nonconverged_flow_hedge_demotes_typed_query_projection() {
1221 let input = selector_certainty_product_input();
1222 let graph_id = "/tmp/App.tsx:expr-certainty:expression-domain-control-flow";
1223 let control_flow = summarize_omena_query_expression_domain_control_flow_analysis(&input);
1224 let projection = summarize_omena_query_expression_domain_selector_projection(&input);
1225 let control_entry = &control_flow.analyses[0];
1226 let entry = &projection.projections[0];
1227
1228 println!(
1229 "certainty-hedge-census graphId={graph_id} hedged=1 base=exact certainty=possible"
1230 );
1231 assert_eq!(control_entry.graph_id, graph_id);
1232 assert!(!control_entry.analysis.flow_analysis.converged);
1233 assert!(
1234 control_entry
1235 .analysis
1236 .flow_analysis
1237 .nodes
1238 .iter()
1239 .all(|node| {
1240 matches!(
1241 &node.value,
1242 AbstractClassValueV0::Top {
1243 provenance: Some(AbstractClassValueProvenanceV0::FlowIterationLimit)
1244 }
1245 )
1246 })
1247 );
1248 assert_eq!(entry.value_kind, "exact");
1249 assert_eq!(entry.selector_names, vec!["x".to_string()]);
1250 assert_eq!(entry.certainty, SelectorProjectionCertaintyV0::Possible);
1251 }
1252
1253 #[test]
1254 fn colon_colliding_graph_ids_bind_selector_certainty_by_type_fact_tuple() {
1255 let input = selector_certainty_colon_collision_input();
1256 let control_flow = summarize_omena_query_expression_domain_control_flow_analysis(&input);
1257 let projection = summarize_omena_query_expression_domain_selector_projection(&input);
1258
1259 assert_eq!(control_flow.analyses.len(), 2);
1260 assert_eq!(
1261 control_flow.analyses[0].graph_id, control_flow.analyses[1].graph_id,
1262 "fixture must retain the diagnostic graph-id collision"
1263 );
1264 let nonconverged = projection
1265 .projections
1266 .iter()
1267 .filter(|entry| entry.file_path == "/tmp/A" && entry.node_id == "b:c")
1268 .collect::<Vec<_>>();
1269 let converged = projection
1270 .projections
1271 .iter()
1272 .filter(|entry| entry.file_path == "/tmp/A:b" && entry.node_id == "c")
1273 .collect::<Vec<_>>();
1274
1275 assert_eq!(nonconverged.len(), 1);
1276 assert_eq!(converged.len(), 1);
1277 assert_eq!(
1278 nonconverged[0].certainty,
1279 SelectorProjectionCertaintyV0::Possible
1280 );
1281 assert_eq!(converged[0].certainty, SelectorProjectionCertaintyV0::Exact);
1282 }
1283
1284 #[test]
1285 #[should_panic(expected = "selector-certainty flow hedge duplicate type-fact key")]
1286 fn duplicate_selector_certainty_type_fact_key_fails_closed() {
1287 let mut input = selector_certainty_product_input();
1288 input.type_facts.push(input.type_facts[0].clone());
1289
1290 let _ = summarize_omena_query_expression_domain_selector_projection(&input);
1291 }
1292
1293 #[test]
1294 #[should_panic(
1295 expected = "selector-certainty flow hedge requires one control analysis per control-flow type fact"
1296 )]
1297 fn missing_selector_certainty_control_analysis_fails_closed() {
1298 let input = selector_certainty_product_input();
1299 let mut control_flow =
1300 summarize_omena_query_expression_domain_control_flow_analysis(&input);
1301 control_flow.analyses.clear();
1302
1303 let _ = bind_expression_domain_selector_certainty_flow_hedges(&input, control_flow);
1304 }
1305
1306 #[test]
1307 fn analysis_precision_view_maps_known_producers_and_fails_closed() {
1308 let precision = |value_domain: &str| OmenaQueryAnalysisPrecisionV0 {
1309 product: "omena-query.analysis-precision".to_string(),
1310 value_domain: value_domain.to_string(),
1311 flow_sensitivity: "fixture".to_string(),
1312 context_sensitivity: "fixture".to_string(),
1313 revision_axis: "fixture".to_string(),
1314 };
1315
1316 assert_eq!(
1317 fact_precision_from_analysis_precision(&precision("cascadeAtPosition")),
1318 FactPrecision::Exact
1319 );
1320 assert_eq!(
1321 fact_precision_from_analysis_precision(&precision("styleModuleResolution")),
1322 FactPrecision::Exact
1323 );
1324 assert_eq!(
1325 fact_precision_from_analysis_precision(&precision("classValueResolution")),
1326 FactPrecision::Conservative
1327 );
1328 assert_eq!(
1329 fact_precision_from_analysis_precision(&precision("classValueUniverse")),
1330 FactPrecision::Conservative
1331 );
1332 assert_eq!(
1333 fact_precision_from_analysis_precision(&precision("classValueFlow")),
1334 FactPrecision::Heuristic
1335 );
1336 assert_eq!(
1337 fact_precision_from_analysis_precision(&precision("unknown")),
1338 FactPrecision::Unknown
1339 );
1340 assert_eq!(
1341 fact_precision_from_analysis_precision(&precision("unregistered")),
1342 FactPrecision::Unknown
1343 );
1344 }
1345}