1use std::fmt::Write as _;
8
9use egg::{
10 Analysis, Applier, EGraph, Extractor, Id, Pattern, PatternAst, RecExpr, Rewrite, Runner, Subst,
11 Symbol, Var, define_language, rewrite as egg_rewrite,
12};
13use omena_evidence_graph::ObligationFamilyIdV0;
14use omena_parser::StyleDialect;
15use omena_transform_cst::TransformPassKind;
16use omena_transform_passes::{
17 TransformPassPlanV0, collect_stale_vendor_prefix_removal_proof_candidates_from_source,
18 plan_transform_passes,
19};
20use serde::Serialize;
21
22mod mdl_cost;
23pub use mdl_cost::*;
24#[cfg(feature = "lawvere-saturation")]
25mod lawvere_analysis;
26#[cfg(feature = "lawvere-saturation")]
27pub use lawvere_analysis::*;
28
29define_language! {
30 enum CssRewriteLanguage {
31 Num(i64),
32 Symbol(Symbol),
33 "+" = Add([Id; 2]),
34 "-" = Sub([Id; 2]),
35 "*" = Mul([Id; 2]),
36 "/" = Div([Id; 2]),
37 "calc" = Calc(Id),
38 "unit" = Unit([Id; 2]),
39 "is" = Is(Id),
40 "where" = Where(Id),
41 "list" = List([Id; 2]),
42 "decl" = Declaration([Id; 3]),
43 "stale-prefix-decl" = StalePrefixDeclaration([Id; 4]),
44 "box1" = Box1(Id),
45 "box2" = Box2([Id; 2]),
46 "box3" = Box3([Id; 3]),
47 "box4" = Box4([Id; 4]),
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52#[serde(rename_all = "camelCase")]
53pub struct EggRewriteProofV0 {
54 pub specificity_preserved: bool,
55 #[serde(skip_serializing)]
56 obligation_family: ObligationFamilyIdV0,
57 pub computed_value_preserved: bool,
58 pub provenance_preserved: bool,
59 pub cascade_safe_witness: String,
60}
61
62impl EggRewriteProofV0 {
63 pub fn new(
64 specificity_preserved: bool,
65 obligation_family: ObligationFamilyIdV0,
66 provenance_preserved: bool,
67 cascade_safe_witness: impl Into<String>,
68 ) -> Self {
69 Self {
70 specificity_preserved,
71 obligation_family,
72 computed_value_preserved: obligation_family.preserves_computed_value(),
73 provenance_preserved,
74 cascade_safe_witness: cascade_safe_witness.into(),
75 }
76 }
77
78 pub const fn obligation_family(&self) -> ObligationFamilyIdV0 {
79 self.obligation_family
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct EggRewriteCandidateV0 {
86 pub pass_id: &'static str,
87 pub before: String,
88 pub after: String,
89 pub proof: EggRewriteProofV0,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub struct EggRewriteDecisionV0 {
95 pub schema_version: &'static str,
96 pub product: &'static str,
97 pub pass_id: &'static str,
98 pub accepted: bool,
99 pub blocked_reason: Option<&'static str>,
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct EggRewriteExecutionV0 {
105 pub schema_version: &'static str,
106 pub product: &'static str,
107 pub pass_id: &'static str,
108 pub accepted: bool,
109 pub blocked_reason: Option<&'static str>,
110 pub before: String,
111 pub after: String,
112 pub expected_after: String,
113 pub after_matches_candidate: bool,
114 pub engine: &'static str,
115 pub iteration_limit: usize,
116 pub iteration_count: usize,
117 pub eclass_count: usize,
118 pub enode_count: usize,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub mdl_bits: Option<f64>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub mdl_residual_bits: Option<f64>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub mdl_unit: Option<&'static str>,
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct EggRewriteSourceWitnessV0 {
130 pub pass_id: &'static str,
131 pub source_kind: &'static str,
132 pub byte_offset: usize,
133 pub css_before: String,
134 pub css_after: String,
135 pub execution: EggRewriteExecutionV0,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
139#[serde(rename_all = "camelCase")]
140pub struct TransformEggBoundarySummaryV0 {
141 pub schema_version: &'static str,
142 pub product: &'static str,
143 pub managed_pass_ids: Vec<&'static str>,
144 pub optional_engine: &'static str,
145 pub proof_obligations: Vec<&'static str>,
146 pub planner_surface: &'static str,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct TransformEggPlanV0 {
152 pub schema_version: &'static str,
153 pub product: &'static str,
154 pub requested_pass_ids: Vec<&'static str>,
155 pub planned_pass_ids: Vec<&'static str>,
156 pub pass_plan: TransformPassPlanV0,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct ContextualEqSatScaffoldV0 {
162 pub schema_version: &'static str,
163 pub product: &'static str,
164 pub claim_level: &'static str,
165 pub scaffold_kind: &'static str,
166 pub execution_view: &'static str,
167 pub current_engine: &'static str,
168 pub egg_engine_ready: bool,
169 pub egglog_binding_ready: bool,
170 pub external_datalog_host_ready: bool,
171 pub three_view_fusion_ready: bool,
172 pub theorem_claimed: bool,
173 pub public_safety_claim_ready: bool,
174 pub modal_witness_product: &'static str,
175 pub modal_bridge_claim_level: &'static str,
176 pub paper_substrate_claim_level: &'static str,
177 pub managed_pass_ids: Vec<&'static str>,
178 pub substrate_products: Vec<&'static str>,
179 pub supported_claims: Vec<&'static str>,
180 pub deferred_claims: Vec<&'static str>,
181}
182
183#[derive(Debug, Clone, Copy)]
184enum CalcFoldOperator {
185 Add,
186 Sub,
187}
188
189#[derive(Debug, Clone)]
190struct ConstFoldSameUnitApplier {
191 left_var: Var,
192 right_var: Var,
193 unit_var: Option<Var>,
194 operator: CalcFoldOperator,
195}
196
197impl ConstFoldSameUnitApplier {
198 fn new(operator: CalcFoldOperator, unit_var: Option<Var>) -> Option<Self> {
199 Some(Self {
200 left_var: "?a".parse().ok()?,
201 right_var: "?b".parse().ok()?,
202 unit_var,
203 operator,
204 })
205 }
206}
207
208impl<N> Applier<CssRewriteLanguage, N> for ConstFoldSameUnitApplier
209where
210 N: Analysis<CssRewriteLanguage>,
211{
212 fn apply_one(
213 &self,
214 egraph: &mut EGraph<CssRewriteLanguage, N>,
215 eclass: Id,
216 subst: &Subst,
217 _searcher_ast: Option<&PatternAst<CssRewriteLanguage>>,
218 _rule_name: Symbol,
219 ) -> Vec<Id> {
220 let Some(left) = numeric_value_from_eclass(egraph, subst[self.left_var]) else {
221 return Vec::new();
222 };
223 let Some(right) = numeric_value_from_eclass(egraph, subst[self.right_var]) else {
224 return Vec::new();
225 };
226 let value = match self.operator {
227 CalcFoldOperator::Add => left + right,
228 CalcFoldOperator::Sub => left - right,
229 };
230 let value_id = egraph.add(CssRewriteLanguage::Num(value));
231 let result_id = if let Some(unit_var) = self.unit_var {
232 egraph.add(CssRewriteLanguage::Unit([value_id, subst[unit_var]]))
233 } else {
234 value_id
235 };
236 egraph.union(eclass, result_id);
237 vec![eclass]
238 }
239
240 fn vars(&self) -> Vec<Var> {
241 let mut vars = vec![self.left_var, self.right_var];
242 if let Some(unit_var) = self.unit_var {
243 vars.push(unit_var);
244 }
245 vars
246 }
247}
248
249pub fn summarize_omena_transform_egg_boundary() -> TransformEggBoundarySummaryV0 {
250 TransformEggBoundarySummaryV0 {
251 schema_version: "0",
252 product: "omena-transform-egg.boundary",
253 managed_pass_ids: managed_egg_passes().iter().map(|pass| pass.id()).collect(),
254 optional_engine: "egg-compatible equality saturation engine",
255 proof_obligations: vec![
256 "selector rewrites preserve specificity",
257 "calc rewrites preserve computed value",
258 "shorthand rewrites preserve computed value",
259 "stale-prefix removals preserve an exact unprefixed declaration peer",
260 "all rewrites preserve provenance",
261 "all accepted rewrites carry a cascade-safe witness",
262 ],
263 planner_surface: "omena-transform-passes.plan",
264 }
265}
266
267pub fn plan_egg_rewrite_passes(include_selector: bool, include_calc: bool) -> TransformEggPlanV0 {
268 let mut requested_passes = Vec::new();
269 if include_selector {
270 requested_passes.push(TransformPassKind::SelectorIsWhereCompression);
271 }
272 if include_calc {
273 requested_passes.push(TransformPassKind::CalcReduction);
274 }
275 let pass_plan = plan_transform_passes(&requested_passes);
276
277 TransformEggPlanV0 {
278 schema_version: "0",
279 product: "omena-transform-egg.plan",
280 requested_pass_ids: requested_passes.iter().map(|pass| pass.id()).collect(),
281 planned_pass_ids: pass_plan.ordered_pass_ids.clone(),
282 pass_plan,
283 }
284}
285
286pub fn plan_egg_rewrite_passes_for_source(source: &str) -> TransformEggPlanV0 {
287 plan_egg_rewrite_passes(
288 source.contains(":is(") || source.contains(":where("),
289 source.contains("calc("),
290 )
291}
292
293pub fn summarize_contextual_eqsat_scaffold_v0() -> ContextualEqSatScaffoldV0 {
294 let boundary = summarize_omena_transform_egg_boundary();
295
296 ContextualEqSatScaffoldV0 {
297 schema_version: "0",
298 product: "omena-transform-egg.contextual-eqsat-scaffold",
299 claim_level: "m6ScaffoldOnlyNoEgglogBinding",
300 scaffold_kind: "contextualEqualitySaturationExecutionView",
301 execution_view: "m6BridgeNodeExecutionView",
302 current_engine: "egg",
303 egg_engine_ready: true,
304 egglog_binding_ready: false,
305 external_datalog_host_ready: false,
306 three_view_fusion_ready: false,
307 theorem_claimed: false,
308 public_safety_claim_ready: false,
309 modal_witness_product: "omena-cascade.modal-check-witness",
310 modal_bridge_claim_level: "dependencyDeclaredOnly",
311 paper_substrate_claim_level: "draftScaffoldOnly",
312 managed_pass_ids: boundary.managed_pass_ids,
313 substrate_products: vec![
314 "omena-transform-egg.boundary",
315 "omena-transform-egg.plan",
316 "omena-transform-egg.execution",
317 "omena-cascade.modal-check-witness",
318 ],
319 supported_claims: vec![
320 "optional egg equality-saturation rewrite boundary",
321 "selector, calc, and shorthand rewrite proof obligations",
322 "contextual equality-saturation scaffold for M6 positioning",
323 "modal witness dependency declaration for #66/#73 paper substrate",
324 ],
325 deferred_claims: vec![
326 "egglog Rust binding",
327 "external Datalog host execution",
328 "full three-view fusion",
329 "Contextual EqSat theorem",
330 "production research-tier execution view",
331 ],
332 }
333}
334
335pub fn decide_egg_rewrite(candidate: EggRewriteCandidateV0) -> EggRewriteDecisionV0 {
336 let blocked_reason = if !is_managed_egg_pass_id(candidate.pass_id) {
337 Some("pass is not managed by omena-transform-egg")
338 } else if candidate.proof.cascade_safe_witness.is_empty() {
339 Some("missing cascade-safe witness")
340 } else if !candidate.proof.provenance_preserved {
341 Some("rewrite does not preserve provenance")
342 } else if candidate.pass_id == TransformPassKind::SelectorIsWhereCompression.id()
343 && !candidate.proof.specificity_preserved
344 {
345 Some("selector rewrite does not preserve specificity")
346 } else if candidate.pass_id == TransformPassKind::CalcReduction.id()
347 && !candidate.proof.computed_value_preserved
348 {
349 Some("calc rewrite does not preserve computed value")
350 } else if candidate.pass_id == TransformPassKind::ShorthandCombining.id()
351 && !candidate.proof.computed_value_preserved
352 {
353 Some("shorthand rewrite does not preserve computed value")
354 } else if candidate.pass_id == TransformPassKind::StalePrefixRemoval.id()
355 && !candidate.proof.computed_value_preserved
356 {
357 Some("stale-prefix removal does not preserve computed value")
358 } else {
359 None
360 };
361
362 EggRewriteDecisionV0 {
363 schema_version: "0",
364 product: "omena-transform-egg.decision",
365 pass_id: candidate.pass_id,
366 accepted: blocked_reason.is_none(),
367 blocked_reason,
368 }
369}
370
371pub fn execute_egg_rewrite(candidate: EggRewriteCandidateV0) -> EggRewriteExecutionV0 {
372 let decision = decide_egg_rewrite(candidate.clone());
373 if !decision.accepted {
374 return blocked_execution(candidate, decision.blocked_reason);
375 }
376
377 let expression = match candidate.before.parse::<RecExpr<CssRewriteLanguage>>() {
378 Ok(expression) => expression,
379 Err(_) => return blocked_execution(candidate, Some("rewrite expression could not parse")),
380 };
381 let Some(rules) = rewrite_rules_for_pass::<()>(candidate.pass_id) else {
382 return blocked_execution(
383 candidate,
384 Some("pass is not managed by omena-transform-egg"),
385 );
386 };
387
388 let iteration_limit = 8;
389 let runner = Runner::default()
390 .with_expr(&expression)
391 .with_iter_limit(iteration_limit)
392 .run(rules.as_slice());
393 let root = runner.roots[0];
394 let extractor = Extractor::new(&runner.egraph, MdlExtractionCostV0::default_ast_size());
395 let (_, extracted) = extractor.find_best(root);
396 let after = extracted.to_string();
397 let after_matches_candidate = after == candidate.after;
398
399 EggRewriteExecutionV0 {
400 schema_version: "0",
401 product: "omena-transform-egg.execution",
402 pass_id: candidate.pass_id,
403 accepted: after_matches_candidate,
404 blocked_reason: (!after_matches_candidate)
405 .then_some("egg extraction did not match candidate output"),
406 before: candidate.before,
407 after,
408 expected_after: candidate.after,
409 after_matches_candidate,
410 engine: "egg",
411 iteration_limit,
412 iteration_count: runner.iterations.len(),
413 eclass_count: runner.egraph.number_of_classes(),
414 enode_count: runner.egraph.total_size(),
415 mdl_bits: None,
416 mdl_residual_bits: None,
417 mdl_unit: None,
418 }
419}
420
421pub fn execute_egg_rewrite_witnesses_for_css_source(
422 source: &str,
423 dialect: StyleDialect,
424 transformed_source: &str,
425 planned_pass_ids: &[&'static str],
426) -> Vec<EggRewriteSourceWitnessV0> {
427 let mut witnesses = Vec::new();
428 if planned_pass_ids.contains(&TransformPassKind::SelectorIsWhereCompression.id()) {
429 witnesses.extend(selector_rewrite_witnesses(source, transformed_source));
430 }
431 if planned_pass_ids.contains(&TransformPassKind::CalcReduction.id()) {
432 witnesses.extend(calc_rewrite_witnesses(source, transformed_source));
433 }
434 if planned_pass_ids.contains(&TransformPassKind::StalePrefixRemoval.id()) {
435 witnesses.extend(stale_prefix_removal_witnesses(
436 source,
437 dialect,
438 transformed_source,
439 ));
440 }
441 witnesses
442}
443
444fn managed_egg_passes() -> [TransformPassKind; 4] {
445 [
446 TransformPassKind::SelectorIsWhereCompression,
447 TransformPassKind::CalcReduction,
448 TransformPassKind::ShorthandCombining,
449 TransformPassKind::StalePrefixRemoval,
450 ]
451}
452
453fn is_managed_egg_pass_id(pass_id: &str) -> bool {
454 managed_egg_passes().iter().any(|pass| pass.id() == pass_id)
455}
456
457fn numeric_value_from_eclass<N>(egraph: &EGraph<CssRewriteLanguage, N>, id: Id) -> Option<i64>
458where
459 N: Analysis<CssRewriteLanguage>,
460{
461 egraph[id].nodes.iter().find_map(|node| match node {
462 CssRewriteLanguage::Num(value) => Some(*value),
463 _ => None,
464 })
465}
466
467fn selector_rewrite_witnesses(
468 source: &str,
469 transformed_source: &str,
470) -> Vec<EggRewriteSourceWitnessV0> {
471 let mut witnesses = Vec::new();
472 for (prefix, source_kind) in [(":is(", "selectorIs"), (":where(", "selectorWhere")] {
473 let mut cursor = 0usize;
474 while let Some(relative_start) = source[cursor..].find(prefix) {
475 let start = cursor + relative_start;
476 let inner_start = start + prefix.len();
477 let Some(relative_end) = source[inner_start..].find(')') else {
478 break;
479 };
480 let end = inner_start + relative_end;
481 let inner = source[inner_start..end].trim();
482 let css_before = source[start..=end].to_string();
483 let pseudo_name = prefix.trim_start_matches(':').trim_end_matches('(');
484 if let Some((source_kind, css_after, before, after, witness)) =
485 selector_witness_candidate(pseudo_name, source_kind, inner)
486 && transformed_source.contains(&css_after)
487 && !transformed_source.contains(&css_before)
488 {
489 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
490 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
491 before,
492 after,
493 proof: EggRewriteProofV0::new(
494 true,
495 ObligationFamilyIdV0::CascadeSafetyFloor,
496 true,
497 witness,
498 ),
499 });
500 witnesses.push(EggRewriteSourceWitnessV0 {
501 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
502 source_kind,
503 byte_offset: start,
504 css_before,
505 css_after,
506 execution,
507 });
508 }
509 cursor = end + 1;
510 }
511 }
512 witnesses
513}
514
515fn calc_rewrite_witnesses(
516 source: &str,
517 transformed_source: &str,
518) -> Vec<EggRewriteSourceWitnessV0> {
519 let mut witnesses = Vec::new();
520 let mut cursor = 0usize;
521 while let Some(relative_start) = source[cursor..].find("calc(") {
522 let start = cursor + relative_start;
523 let inner_start = start + "calc(".len();
524 let Some(relative_end) = source[inner_start..].find(')') else {
525 break;
526 };
527 let end = inner_start + relative_end;
528 let inner = source[inner_start..end].trim();
529 let css_before = source[start..=end].to_string();
530 if let Some(candidate) = calc_rewrite_candidate(inner)
531 && transformed_source.contains(candidate.css_after.as_str())
532 && !transformed_source.contains(&css_before)
533 {
534 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
535 pass_id: TransformPassKind::CalcReduction.id(),
536 before: format!("(calc {})", candidate.before),
537 after: candidate.after,
538 proof: EggRewriteProofV0::new(
539 false,
540 ObligationFamilyIdV0::ComputedValuePreservation,
541 true,
542 candidate.witness,
543 ),
544 });
545 witnesses.push(EggRewriteSourceWitnessV0 {
546 pass_id: TransformPassKind::CalcReduction.id(),
547 source_kind: candidate.source_kind,
548 byte_offset: start,
549 css_before,
550 css_after: candidate.css_after,
551 execution,
552 });
553 }
554 cursor = end + 1;
555 }
556 witnesses
557}
558
559fn stale_prefix_removal_witnesses(
560 source: &str,
561 dialect: StyleDialect,
562 transformed_source: &str,
563) -> Vec<EggRewriteSourceWitnessV0> {
564 collect_stale_vendor_prefix_removal_proof_candidates_from_source(source, dialect)
565 .into_iter()
566 .filter_map(|candidate| {
567 let css_before =
568 source[candidate.source_span_start..candidate.source_span_end].to_string();
569 if transformed_source.contains(&css_before) {
570 return None;
571 }
572 let css_after = source
573 [candidate.unprefixed_peer_span_start..candidate.unprefixed_peer_span_end]
574 .to_string();
575 if !transformed_source.contains(&css_after) {
576 return None;
577 }
578
579 let prefixed_property = egg_safe_symbol(candidate.prefixed_property.as_str());
580 let unprefixed_property = egg_safe_symbol(candidate.unprefixed_property);
581 let value = egg_safe_symbol(candidate.value.as_str());
582 let importance = if candidate.important {
583 "important"
584 } else {
585 "normal"
586 };
587 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
588 pass_id: TransformPassKind::StalePrefixRemoval.id(),
589 before: format!(
590 "(stale-prefix-decl {prefixed_property} {unprefixed_property} {value} {importance})"
591 ),
592 after: format!("(decl {unprefixed_property} {value} {importance})"),
593 proof: EggRewriteProofV0::new(
594 false,
595 ObligationFamilyIdV0::ComputedValuePreservation,
596 true,
597 format!(
598 "{} has exact unprefixed declaration peer {} with the same value and importance",
599 candidate.prefixed_property, candidate.unprefixed_property
600 ),
601 ),
602 });
603 Some(EggRewriteSourceWitnessV0 {
604 pass_id: TransformPassKind::StalePrefixRemoval.id(),
605 source_kind: "stalePrefixExactPeer",
606 byte_offset: candidate.source_span_start,
607 css_before,
608 css_after,
609 execution,
610 })
611 })
612 .collect()
613}
614
615fn selector_witness_candidate(
616 pseudo_name: &str,
617 source_kind: &'static str,
618 inner: &str,
619) -> Option<(&'static str, String, String, String, String)> {
620 if pseudo_name == "is"
621 && let Some((symbol, css_ident)) = selector_single_argument_parts(inner)
622 {
623 return Some((
624 source_kind,
625 format!(".{css_ident}"),
626 format!("(is {symbol})"),
627 symbol,
628 "actual CSS selectorIs single-argument rewrite".to_string(),
629 ));
630 }
631
632 let args = split_simple_selector_arguments(inner)?;
633 let [left, right] = args.as_slice() else {
634 return None;
635 };
636 if left != right {
637 return None;
638 }
639 let (symbol, css_ident) = selector_single_argument_parts(left)?;
640 match pseudo_name {
641 "is" => Some((
642 "selectorIsDedup",
643 format!(".{css_ident}"),
644 format!("(is (list {symbol} {symbol}))"),
645 symbol,
646 "actual CSS selectorIs duplicate-argument rewrite".to_string(),
647 )),
648 "where" => Some((
649 "selectorWhereDedup",
650 format!(":where(.{css_ident})"),
651 format!("(where (list {symbol} {symbol}))"),
652 format!("(where {symbol})"),
653 "actual CSS selectorWhere duplicate-argument rewrite".to_string(),
654 )),
655 _ => None,
656 }
657}
658
659fn egg_safe_symbol(value: &str) -> String {
660 let mut symbol = String::with_capacity(value.len().max(1));
661 for byte in value.bytes() {
662 let character = byte as char;
663 if character.is_ascii_alphanumeric() {
664 symbol.push(character.to_ascii_lowercase());
665 } else {
666 let _ = write!(&mut symbol, "_{byte:02x}");
667 }
668 }
669 if symbol.is_empty() {
670 "empty".to_string()
671 } else if symbol
672 .as_bytes()
673 .first()
674 .is_some_and(|byte| byte.is_ascii_digit())
675 {
676 format!("v_{symbol}")
677 } else {
678 symbol
679 }
680}
681
682fn split_simple_selector_arguments(inner: &str) -> Option<Vec<String>> {
683 let args = inner
684 .split(',')
685 .map(str::trim)
686 .map(str::to_string)
687 .collect::<Vec<_>>();
688 (!args.is_empty() && args.iter().all(|arg| !arg.is_empty())).then_some(args)
689}
690
691fn selector_single_argument_parts(inner: &str) -> Option<(String, String)> {
692 let class_name = inner.trim().strip_prefix('.')?;
693 if class_name.is_empty()
694 || !class_name
695 .chars()
696 .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))
697 {
698 return None;
699 }
700 Some((symbol_for_css_ident(class_name), class_name.to_string()))
701}
702
703fn symbol_for_css_ident(value: &str) -> String {
704 value.replace('-', "_")
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708struct CalcRewriteCandidate {
709 before: String,
710 after: String,
711 css_after: String,
712 source_kind: &'static str,
713 witness: String,
714}
715
716#[derive(Debug, Clone, PartialEq, Eq)]
717struct CalcNumericValue {
718 value: i64,
719 unit: String,
720}
721
722fn calc_rewrite_candidate(inner: &str) -> Option<CalcRewriteCandidate> {
723 let parts = inner.split_whitespace().collect::<Vec<_>>();
724 let [left, operator, right] = parts.as_slice() else {
725 return None;
726 };
727 let left_value = parse_calc_numeric_value(left)?;
728 let right_value = parse_calc_numeric_value(right)?;
729 if left_value.unit != right_value.unit {
730 return None;
731 }
732 let term_left = calc_numeric_term(&left_value);
733 let term_right = calc_numeric_term(&right_value);
734 match *operator {
735 "+" => Some(calc_fold_candidate(
736 format!("(+ {term_left} {term_right})"),
737 left_value.value + right_value.value,
738 &left_value.unit,
739 "calcSameUnitAdd",
740 "actual CSS calc same-unit addition rewrite",
741 )),
742 "-" => Some(calc_fold_candidate(
743 format!("(- {term_left} {term_right})"),
744 left_value.value - right_value.value,
745 &left_value.unit,
746 "calcSameUnitSub",
747 "actual CSS calc same-unit subtraction rewrite",
748 )),
749 "*" if right_value.value == 1 && right_value.unit.is_empty() => {
750 Some(calc_passthrough_candidate(
751 format!("(* {term_left} 1)"),
752 &left_value,
753 "calcIdentity",
754 "actual CSS calc multiplicative identity rewrite",
755 ))
756 }
757 "*" if left_value.value == 1 && left_value.unit.is_empty() => {
758 Some(calc_passthrough_candidate(
759 format!("(* 1 {term_right})"),
760 &right_value,
761 "calcIdentity",
762 "actual CSS calc multiplicative identity rewrite",
763 ))
764 }
765 "*" if right_value.value == 0 && right_value.unit.is_empty() => Some(calc_fold_candidate(
766 format!("(* {term_left} 0)"),
767 0,
768 "",
769 "calcZero",
770 "actual CSS calc safe zero multiplication rewrite",
771 )),
772 "*" if left_value.value == 0 && left_value.unit.is_empty() => Some(calc_fold_candidate(
773 format!("(* 0 {term_right})"),
774 0,
775 "",
776 "calcZero",
777 "actual CSS calc safe zero multiplication rewrite",
778 )),
779 "/" if right_value.value == 1 && right_value.unit.is_empty() => {
780 Some(calc_passthrough_candidate(
781 format!("(/ {term_left} 1)"),
782 &left_value,
783 "calcIdentity",
784 "actual CSS calc division identity rewrite",
785 ))
786 }
787 _ => None,
788 }
789}
790
791fn parse_calc_numeric_value(text: &str) -> Option<CalcNumericValue> {
792 let split = text
793 .char_indices()
794 .find_map(|(index, ch)| (!matches!(ch, '-' | '+') && !ch.is_ascii_digit()).then_some(index))
795 .unwrap_or(text.len());
796 let (value, unit) = text.split_at(split);
797 let value = value.parse::<i64>().ok()?;
798 unit.chars()
799 .all(|ch| ch.is_ascii_alphabetic() || ch == '%')
800 .then_some(CalcNumericValue {
801 value,
802 unit: unit.to_string(),
803 })
804}
805
806fn calc_numeric_term(value: &CalcNumericValue) -> String {
807 if value.unit.is_empty() {
808 value.value.to_string()
809 } else {
810 format!("(unit {} {})", value.value, value.unit)
811 }
812}
813
814fn calc_fold_candidate(
815 before: String,
816 value: i64,
817 unit: &str,
818 source_kind: &'static str,
819 witness: &'static str,
820) -> CalcRewriteCandidate {
821 let result = CalcNumericValue {
822 value,
823 unit: unit.to_string(),
824 };
825 CalcRewriteCandidate {
826 before,
827 after: calc_numeric_term(&result),
828 css_after: format!("{}{}", result.value, result.unit),
829 source_kind,
830 witness: witness.to_string(),
831 }
832}
833
834fn calc_passthrough_candidate(
835 before: String,
836 value: &CalcNumericValue,
837 source_kind: &'static str,
838 witness: &'static str,
839) -> CalcRewriteCandidate {
840 CalcRewriteCandidate {
841 before,
842 after: calc_numeric_term(value),
843 css_after: format!("{}{}", value.value, value.unit),
844 source_kind,
845 witness: witness.to_string(),
846 }
847}
848
849fn rewrite_pattern(text: &str) -> Option<Pattern<CssRewriteLanguage>> {
850 text.parse().ok()
851}
852
853pub(crate) fn calc_const_fold_rule<N>(
854 name: &'static str,
855 search: &'static str,
856 operator: CalcFoldOperator,
857 unit_var: Option<Var>,
858) -> Option<Rewrite<CssRewriteLanguage, N>>
859where
860 N: Analysis<CssRewriteLanguage>,
861{
862 Rewrite::new(
863 name,
864 rewrite_pattern(search)?,
865 ConstFoldSameUnitApplier::new(operator, unit_var)?,
866 )
867 .ok()
868}
869
870fn egg_var(name: &str) -> Option<Var> {
871 name.parse().ok()
872}
873
874pub(crate) fn rewrite_rules_for_pass<N>(
875 pass_id: &'static str,
876) -> Option<Vec<Rewrite<CssRewriteLanguage, N>>>
877where
878 N: Analysis<CssRewriteLanguage>,
879{
880 if pass_id == TransformPassKind::SelectorIsWhereCompression.id() {
881 return Some(vec![
882 egg_rewrite!("single-is-selector"; "(is ?a)" => "?a"),
883 egg_rewrite!("nested-is-selector"; "(is (is ?a))" => "?a"),
884 egg_rewrite!("duplicate-is-selector"; "(is (list ?a ?a))" => "?a"),
885 egg_rewrite!("duplicate-where-selector"; "(where (list ?a ?a))" => "(where ?a)"),
886 ]);
887 }
888 if pass_id == TransformPassKind::CalcReduction.id() {
889 let mut rules = vec![
890 egg_rewrite!("unwrap-calc"; "(calc ?a)" => "?a"),
891 egg_rewrite!("add-zero-right"; "(+ ?a 0)" => "?a"),
892 egg_rewrite!("add-zero-left"; "(+ 0 ?a)" => "?a"),
893 egg_rewrite!("sub-zero-right"; "(- ?a 0)" => "?a"),
894 egg_rewrite!("self-sub"; "(- ?a ?a)" => "0"),
895 egg_rewrite!("mul-one-right"; "(* ?a 1)" => "?a"),
896 egg_rewrite!("mul-one-left"; "(* 1 ?a)" => "?a"),
897 egg_rewrite!("mul-zero-right"; "(* ?a 0)" => "0"),
898 egg_rewrite!("mul-zero-left"; "(* 0 ?a)" => "0"),
899 egg_rewrite!("div-one-right"; "(/ ?a 1)" => "?a"),
900 ];
901 if let Some(rule) = calc_const_fold_rule(
902 "constfold-add-number",
903 "(+ ?a ?b)",
904 CalcFoldOperator::Add,
905 None,
906 ) {
907 rules.push(rule);
908 }
909 if let Some(unit_var) = egg_var("?u")
910 && let Some(rule) = calc_const_fold_rule(
911 "constfold-add-same-unit",
912 "(+ (unit ?a ?u) (unit ?b ?u))",
913 CalcFoldOperator::Add,
914 Some(unit_var),
915 )
916 {
917 rules.push(rule);
918 }
919 if let Some(rule) = calc_const_fold_rule(
920 "constfold-sub-number",
921 "(- ?a ?b)",
922 CalcFoldOperator::Sub,
923 None,
924 ) {
925 rules.push(rule);
926 }
927 if let Some(unit_var) = egg_var("?u")
928 && let Some(rule) = calc_const_fold_rule(
929 "constfold-sub-same-unit",
930 "(- (unit ?a ?u) (unit ?b ?u))",
931 CalcFoldOperator::Sub,
932 Some(unit_var),
933 )
934 {
935 rules.push(rule);
936 }
937 return Some(rules);
938 }
939 if pass_id == TransformPassKind::ShorthandCombining.id() {
940 return Some(vec![
941 egg_rewrite!("box4-all-equal"; "(box4 ?a ?a ?a ?a)" => "(box1 ?a)"),
942 egg_rewrite!("box4-vertical-horizontal"; "(box4 ?a ?b ?a ?b)" => "(box2 ?a ?b)"),
943 egg_rewrite!("box4-horizontal-pair"; "(box4 ?a ?b ?c ?b)" => "(box3 ?a ?b ?c)"),
944 ]);
945 }
946 if pass_id == TransformPassKind::StalePrefixRemoval.id() {
947 return Some(vec![
948 egg_rewrite!("stale-prefix-exact-peer"; "(stale-prefix-decl ?p ?u ?v ?i)" => "(decl ?u ?v ?i)"),
949 ]);
950 }
951 None
952}
953
954fn blocked_execution(
955 candidate: EggRewriteCandidateV0,
956 blocked_reason: Option<&'static str>,
957) -> EggRewriteExecutionV0 {
958 EggRewriteExecutionV0 {
959 schema_version: "0",
960 product: "omena-transform-egg.execution",
961 pass_id: candidate.pass_id,
962 accepted: false,
963 blocked_reason,
964 before: candidate.before.clone(),
965 after: candidate.before,
966 expected_after: candidate.after,
967 after_matches_candidate: false,
968 engine: "egg",
969 iteration_limit: 0,
970 iteration_count: 0,
971 eclass_count: 0,
972 enode_count: 0,
973 mdl_bits: None,
974 mdl_residual_bits: None,
975 mdl_unit: None,
976 }
977}
978
979#[cfg(test)]
980mod tests {
981 use super::{
982 EggRewriteCandidateV0, EggRewriteProofV0, decide_egg_rewrite, execute_egg_rewrite,
983 execute_egg_rewrite_witnesses_for_css_source, plan_egg_rewrite_passes,
984 plan_egg_rewrite_passes_for_source, rewrite_rules_for_pass,
985 summarize_contextual_eqsat_scaffold_v0, summarize_mdl_extraction_mode,
986 summarize_omena_transform_egg_boundary,
987 };
988 use omena_evidence_graph::ObligationFamilyIdV0;
989 use omena_parser::StyleDialect;
990 use omena_transform_cst::TransformPassKind;
991
992 #[test]
993 fn exposes_selector_calc_and_shorthand_optional_egg_boundary() {
994 let boundary = summarize_omena_transform_egg_boundary();
995
996 assert_eq!(boundary.product, "omena-transform-egg.boundary");
997 assert_eq!(
998 boundary.managed_pass_ids,
999 vec![
1000 "selector-is-where-compression",
1001 "calc-reduction",
1002 "shorthand-combining",
1003 "stale-prefix-removal"
1004 ]
1005 );
1006 assert_eq!(boundary.proof_obligations.len(), 6);
1007 }
1008
1009 #[test]
1010 fn mdl_extraction_default_preserves_ast_size() {
1011 let summary = summarize_mdl_extraction_mode();
1012
1013 assert_eq!(summary.schema_version, "0");
1014 assert_eq!(summary.product, "omena-transform-egg.mdl-extraction");
1015 assert!(summary.default_preserves_ast_size);
1016 assert_eq!(summary.layer_marker, "mdl-bits");
1017 assert_eq!(summary.unit, "bit");
1018 assert_eq!(summary.feature_gate, "mdl");
1019 }
1020
1021 #[test]
1022 fn plans_requested_egg_passes_through_transform_pass_planner() {
1023 let plan = plan_egg_rewrite_passes(true, true);
1024
1025 assert_eq!(
1026 plan.planned_pass_ids,
1027 vec!["selector-is-where-compression", "calc-reduction"]
1028 );
1029 assert_eq!(plan.pass_plan.violated_dag_edge_count, 0);
1030 }
1031
1032 #[test]
1033 fn plans_egg_passes_from_css_source() {
1034 let plan = plan_egg_rewrite_passes_for_source(".a:is(.ready) { width: calc(7 + 0); }");
1035
1036 assert_eq!(
1037 plan.planned_pass_ids,
1038 vec!["selector-is-where-compression", "calc-reduction"]
1039 );
1040 assert_eq!(plan.pass_plan.violated_dag_edge_count, 0);
1041 }
1042
1043 #[test]
1044 fn contextual_eqsat_scaffold_stays_no_egglog_binding() {
1045 let scaffold = summarize_contextual_eqsat_scaffold_v0();
1046
1047 assert_eq!(scaffold.schema_version, "0");
1048 assert_eq!(
1049 scaffold.product,
1050 "omena-transform-egg.contextual-eqsat-scaffold"
1051 );
1052 assert_eq!(scaffold.claim_level, "m6ScaffoldOnlyNoEgglogBinding");
1053 assert_eq!(scaffold.current_engine, "egg");
1054 assert!(scaffold.egg_engine_ready);
1055 assert!(!scaffold.egglog_binding_ready);
1056 assert!(!scaffold.external_datalog_host_ready);
1057 assert!(!scaffold.three_view_fusion_ready);
1058 assert!(!scaffold.theorem_claimed);
1059 assert!(!scaffold.public_safety_claim_ready);
1060 assert_eq!(
1061 scaffold.modal_witness_product,
1062 "omena-cascade.modal-check-witness"
1063 );
1064 assert_eq!(scaffold.modal_bridge_claim_level, "dependencyDeclaredOnly");
1065 assert_eq!(scaffold.paper_substrate_claim_level, "draftScaffoldOnly");
1066 assert_eq!(
1067 scaffold.managed_pass_ids,
1068 vec![
1069 "selector-is-where-compression",
1070 "calc-reduction",
1071 "shorthand-combining",
1072 "stale-prefix-removal"
1073 ]
1074 );
1075 assert!(
1076 scaffold
1077 .supported_claims
1078 .contains(&"contextual equality-saturation scaffold for M6 positioning")
1079 );
1080 assert!(scaffold.deferred_claims.contains(&"egglog Rust binding"));
1081 assert!(scaffold.deferred_claims.contains(&"full three-view fusion"));
1082 }
1083
1084 #[test]
1085 fn accepts_selector_rewrite_only_with_specificity_and_provenance_witnesses() {
1086 let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1087 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1088 before: ":is(.a, .b)".to_string(),
1089 after: ".a,.b".to_string(),
1090 proof: EggRewriteProofV0::new(
1091 true,
1092 ObligationFamilyIdV0::CascadeSafetyFloor,
1093 true,
1094 "specificity tuple preserved",
1095 ),
1096 });
1097
1098 assert!(decision.accepted);
1099 assert_eq!(decision.blocked_reason, None);
1100 }
1101
1102 #[test]
1103 fn rejects_calc_rewrite_without_computed_value_witness() {
1104 let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1105 pass_id: TransformPassKind::CalcReduction.id(),
1106 before: "calc(1rem + 2px)".to_string(),
1107 after: "1rem".to_string(),
1108 proof: EggRewriteProofV0::new(
1109 false,
1110 ObligationFamilyIdV0::CascadeSafetyFloor,
1111 true,
1112 "candidate generated",
1113 ),
1114 });
1115
1116 assert!(!decision.accepted);
1117 assert_eq!(
1118 decision.blocked_reason,
1119 Some("calc rewrite does not preserve computed value")
1120 );
1121 }
1122
1123 #[test]
1124 fn rejects_shorthand_rewrite_without_computed_value_witness() {
1125 let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1126 pass_id: TransformPassKind::ShorthandCombining.id(),
1127 before: "(box4 0 0 0 0)".to_string(),
1128 after: "(box1 0)".to_string(),
1129 proof: EggRewriteProofV0::new(
1130 false,
1131 ObligationFamilyIdV0::CascadeSafetyFloor,
1132 true,
1133 "candidate generated",
1134 ),
1135 });
1136
1137 assert!(!decision.accepted);
1138 assert_eq!(
1139 decision.blocked_reason,
1140 Some("shorthand rewrite does not preserve computed value")
1141 );
1142 }
1143
1144 #[test]
1145 fn egg_rewrite_family_derivation_preserves_legacy_json_contract()
1146 -> Result<(), serde_json::Error> {
1147 for (family, expected_preserved, witness) in [
1148 (
1149 ObligationFamilyIdV0::CascadeSafetyFloor,
1150 false,
1151 "candidate generated",
1152 ),
1153 (
1154 ObligationFamilyIdV0::ComputedValuePreservation,
1155 true,
1156 "computed value preserved",
1157 ),
1158 ] {
1159 let proof = EggRewriteProofV0::new(true, family, false, witness);
1160 let json = serde_json::to_value(&proof)?;
1161
1162 assert_eq!(
1163 json,
1164 serde_json::json!({
1165 "specificityPreserved": true,
1166 "computedValuePreserved": expected_preserved,
1167 "provenancePreserved": false,
1168 "cascadeSafeWitness": witness,
1169 })
1170 );
1171 assert!(json.get("obligationFamily").is_none());
1172 assert_eq!(proof.obligation_family(), family);
1173 }
1174
1175 Ok(())
1176 }
1177
1178 #[test]
1179 fn executes_selector_rewrite_through_egg_engine() {
1180 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1181 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1182 before: "(is buttonPrimary)".to_string(),
1183 after: "buttonPrimary".to_string(),
1184 proof: EggRewriteProofV0::new(
1185 true,
1186 ObligationFamilyIdV0::CascadeSafetyFloor,
1187 true,
1188 "single :is() argument keeps specificity",
1189 ),
1190 });
1191
1192 assert!(execution.accepted);
1193 assert_eq!(execution.product, "omena-transform-egg.execution");
1194 assert_eq!(execution.engine, "egg");
1195 assert_eq!(execution.after, "buttonPrimary");
1196 assert_eq!(execution.iteration_limit, 8);
1197 assert!(execution.iteration_count > 0);
1198 assert!(execution.eclass_count > 0);
1199 assert!(execution.enode_count > 0);
1200 }
1201
1202 #[test]
1203 fn executes_selector_dedup_rewrites_through_egg_engine() {
1204 let is_execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1205 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1206 before: "(is (list ready ready))".to_string(),
1207 after: "ready".to_string(),
1208 proof: EggRewriteProofV0::new(
1209 true,
1210 ObligationFamilyIdV0::CascadeSafetyFloor,
1211 true,
1212 "duplicate :is() argument keeps specificity",
1213 ),
1214 });
1215 let where_execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1216 pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1217 before: "(where (list ready ready))".to_string(),
1218 after: "(where ready)".to_string(),
1219 proof: EggRewriteProofV0::new(
1220 true,
1221 ObligationFamilyIdV0::CascadeSafetyFloor,
1222 true,
1223 "duplicate :where() argument keeps zero specificity",
1224 ),
1225 });
1226
1227 assert!(is_execution.accepted);
1228 assert_eq!(is_execution.after, "ready");
1229 assert!(where_execution.accepted);
1230 assert_eq!(where_execution.after, "(where ready)");
1231 }
1232
1233 #[test]
1234 fn executes_calc_rewrite_through_egg_engine() {
1235 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1236 pass_id: TransformPassKind::CalcReduction.id(),
1237 before: "(calc (+ width 0))".to_string(),
1238 after: "width".to_string(),
1239 proof: EggRewriteProofV0::new(
1240 false,
1241 ObligationFamilyIdV0::ComputedValuePreservation,
1242 true,
1243 "additive identity preserves computed value",
1244 ),
1245 });
1246
1247 assert!(execution.accepted);
1248 assert_eq!(execution.after, "width");
1249 assert!(execution.after_matches_candidate);
1250 }
1251
1252 #[test]
1253 fn executes_extended_calc_identity_rewrites_through_egg_engine() {
1254 for (before, after) in [
1255 ("(calc (- width 0))", "width"),
1256 ("(calc (/ width 1))", "width"),
1257 ("(calc (* width 0))", "0"),
1258 ("(calc (- width width))", "0"),
1259 ] {
1260 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1261 pass_id: TransformPassKind::CalcReduction.id(),
1262 before: before.to_string(),
1263 after: after.to_string(),
1264 proof: EggRewriteProofV0::new(
1265 false,
1266 ObligationFamilyIdV0::ComputedValuePreservation,
1267 true,
1268 "calc algebra identity preserves computed value",
1269 ),
1270 });
1271
1272 assert!(execution.accepted, "{before} -> {after}");
1273 assert_eq!(execution.after, after);
1274 }
1275 }
1276
1277 #[test]
1278 fn executes_same_unit_calc_const_folding_through_egg_engine() {
1279 for (before, after) in [
1280 ("(calc (+ (unit 1 px) (unit 2 px)))", "(unit 3 px)"),
1281 ("(calc (- (unit 10 rem) (unit 2 rem)))", "(unit 8 rem)"),
1282 ("(calc (+ 1 2))", "3"),
1283 ] {
1284 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1285 pass_id: TransformPassKind::CalcReduction.id(),
1286 before: before.to_string(),
1287 after: after.to_string(),
1288 proof: EggRewriteProofV0::new(
1289 false,
1290 ObligationFamilyIdV0::ComputedValuePreservation,
1291 true,
1292 "same-unit calc arithmetic preserves computed value",
1293 ),
1294 });
1295
1296 assert!(execution.accepted, "{before} -> {after}");
1297 assert_eq!(execution.after, after);
1298 }
1299 }
1300
1301 #[test]
1302 fn executes_box_shorthand_rewrites_through_egg_engine() {
1303 for (before, after) in [
1304 ("(box4 0 0 0 0)", "(box1 0)"),
1305 ("(box4 1 2 1 2)", "(box2 1 2)"),
1306 ("(box4 1 2 3 2)", "(box3 1 2 3)"),
1307 ] {
1308 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1309 pass_id: TransformPassKind::ShorthandCombining.id(),
1310 before: before.to_string(),
1311 after: after.to_string(),
1312 proof: EggRewriteProofV0::new(
1313 false,
1314 ObligationFamilyIdV0::ComputedValuePreservation,
1315 true,
1316 "box shorthand expansion preserves computed value",
1317 ),
1318 });
1319
1320 assert!(execution.accepted, "{before} -> {after}");
1321 assert_eq!(execution.after, after);
1322 }
1323 }
1324
1325 #[test]
1326 fn exposes_shorthand_rewrite_rules_for_managed_pass() {
1327 let rules = rewrite_rules_for_pass::<()>(TransformPassKind::ShorthandCombining.id());
1328
1329 assert!(rules.is_some_and(|rules| rules.len() == 3));
1330 }
1331
1332 #[test]
1333 fn executes_css_source_witnesses_through_egg_engine() {
1334 let source = ".a:is(.ready) { width: calc(1px + 2px); } .b:is(.x, .x) { color: red; } .c:where(.y, .y) { color: blue; }";
1335 let transformed =
1336 ".a.ready { width: 3px; } .b.x { color: red; } .c:where(.y) { color: blue; }";
1337 let plan = plan_egg_rewrite_passes_for_source(source);
1338 let witnesses = execute_egg_rewrite_witnesses_for_css_source(
1339 source,
1340 StyleDialect::Css,
1341 transformed,
1342 &plan.planned_pass_ids,
1343 );
1344
1345 assert_eq!(witnesses.len(), 4);
1346 assert!(witnesses.iter().all(|witness| witness.execution.accepted));
1347 assert!(
1348 witnesses
1349 .iter()
1350 .any(|witness| witness.pass_id == "selector-is-where-compression")
1351 );
1352 assert!(
1353 witnesses
1354 .iter()
1355 .any(|witness| witness.pass_id == "calc-reduction")
1356 );
1357 assert!(witnesses.iter().any(|witness| {
1358 witness.source_kind == "selectorIsDedup" && witness.css_after == ".x"
1359 }));
1360 assert!(witnesses.iter().any(|witness| {
1361 witness.source_kind == "selectorWhereDedup" && witness.css_after == ":where(.y)"
1362 }));
1363 assert!(witnesses.iter().any(|witness| {
1364 witness.source_kind == "calcSameUnitAdd"
1365 && witness.css_after == "3px"
1366 && witness.execution.after == "(unit 3 px)"
1367 }));
1368 }
1369
1370 #[test]
1371 fn executes_stale_prefix_removal_source_witness_through_egg_engine() {
1372 let source = ".a { -webkit-user-select: none; user-select: none; }";
1373 let transformed = ".a { user-select: none; }";
1374 let witnesses = execute_egg_rewrite_witnesses_for_css_source(
1375 source,
1376 StyleDialect::Css,
1377 transformed,
1378 &[TransformPassKind::StalePrefixRemoval.id()],
1379 );
1380
1381 assert_eq!(witnesses.len(), 1);
1382 let witness = &witnesses[0];
1383 assert_eq!(witness.pass_id, "stale-prefix-removal");
1384 assert_eq!(witness.source_kind, "stalePrefixExactPeer");
1385 assert_eq!(witness.css_before, "-webkit-user-select: none;");
1386 assert_eq!(witness.css_after, "user-select: none;");
1387 assert!(witness.execution.accepted);
1388 assert_eq!(witness.execution.engine, "egg");
1389 assert_eq!(witness.execution.after, witness.execution.expected_after);
1390 }
1391
1392 #[test]
1393 fn mdl_default_ast_size_matches_100_fixture_differential_corpus() {
1394 let selector_cases = (0..50).map(|index| {
1395 (
1396 TransformPassKind::SelectorIsWhereCompression.id(),
1397 format!("(is token{index})"),
1398 format!("token{index}"),
1399 true,
1400 false,
1401 "single :is() argument keeps specificity",
1402 )
1403 });
1404 let calc_cases = (0..50).map(|index| {
1405 let left = index + 1;
1406 let right = 50 - index;
1407 (
1408 TransformPassKind::CalcReduction.id(),
1409 format!("(calc (+ (unit {left} px) (unit {right} px)))"),
1410 format!("(unit {} px)", left + right),
1411 false,
1412 true,
1413 "same-unit calc arithmetic preserves computed value",
1414 )
1415 });
1416 let cases = selector_cases.chain(calc_cases).collect::<Vec<_>>();
1417
1418 assert_eq!(cases.len(), 100);
1419 for (
1420 pass_id,
1421 before,
1422 expected_after,
1423 specificity_preserved,
1424 computed_value_preserved,
1425 witness,
1426 ) in cases
1427 {
1428 let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1429 pass_id,
1430 before: before.clone(),
1431 after: expected_after.clone(),
1432 proof: EggRewriteProofV0::new(
1433 specificity_preserved,
1434 ObligationFamilyIdV0::from_computed_value_preservation(
1435 computed_value_preserved,
1436 ),
1437 true,
1438 witness,
1439 ),
1440 });
1441
1442 assert!(execution.accepted, "{before} -> {expected_after}");
1443 assert_eq!(execution.after, expected_after);
1444 assert!(execution.after_matches_candidate);
1445 assert_eq!(execution.mdl_bits, None);
1446 assert_eq!(execution.mdl_unit, None);
1447 }
1448 }
1449}