Skip to main content

oxilean_codegen/php_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use super::types::{
8    PHPAnalysisCache, PHPBackend, PHPClass, PHPConstantFoldingHelper, PHPDepGraph,
9    PHPDominatorTree, PHPEnum, PHPEnumCase, PHPExpr, PHPExtCache, PHPExtConstFolder,
10    PHPExtDepGraph, PHPExtDomTree, PHPExtLiveness, PHPExtPassConfig, PHPExtPassPhase,
11    PHPExtPassRegistry, PHPExtPassStats, PHPExtWorklist, PHPFunction, PHPInterface,
12    PHPLivenessInfo, PHPParam, PHPPassConfig, PHPPassPhase, PHPPassRegistry, PHPPassStats,
13    PHPProperty, PHPScript, PHPType, PHPVisibility, PHPWorklist,
14};
15
16pub(super) fn format_param(p: &PHPParam) -> std::string::String {
17    let mut s = std::string::String::new();
18    if let Some(vis) = &p.promoted {
19        s.push_str(&format!("{} ", vis));
20    }
21    if let Some(ty) = &p.ty {
22        s.push_str(&format!("{} ", ty));
23    }
24    if p.by_ref {
25        s.push('&');
26    }
27    if p.variadic {
28        s.push_str("...");
29    }
30    s.push_str(&format!("${}", p.name));
31    if let Some(default) = &p.default {
32        s.push_str(&format!(" = {}", default));
33    }
34    s
35}
36/// Minimal OxiLean PHP runtime header embedded in emitted files.
37pub const PHP_RUNTIME: &str = r#"<?php
38// OxiLean PHP Runtime
39// Auto-generated — do not edit.
40
41declare(strict_types=1);
42
43namespace OxiLean\Runtime;
44
45/** Represents a lazy thunk (unevaluated computation). */
46final class Thunk
47{
48    private mixed $value = null;
49    private bool $evaluated = false;
50    /** @var callable */
51    private $thunk;
52
53    public function __construct(callable $thunk)
54    {
55        $this->thunk = $thunk;
56    }
57
58    public function force(): mixed
59    {
60        if (!$this->evaluated) {
61            $this->value = ($this->thunk)();
62            $this->evaluated = true;
63        }
64        return $this->value;
65    }
66}
67
68/** Represents an OxiLean product (pair/tuple). */
69final readonly class Prod
70{
71    public function __construct(
72        public readonly mixed $fst,
73        public readonly mixed $snd,
74    ) {}
75}
76
77/** Represents an OxiLean sum (Either). */
78abstract class Sum
79{
80    final public static function inl(mixed $val): self
81    {
82        return new class($val) extends Sum {
83            public function __construct(public readonly mixed $val) {}
84        };
85    }
86    final public static function inr(mixed $val): self
87    {
88        return new class($val) extends Sum {
89            public function __construct(public readonly mixed $val) {}
90        };
91    }
92}
93
94/** Nat operations. */
95final class Nat
96{
97    public static function succ(int $n): int { return $n + 1; }
98    public static function pred(int $n): int { return max(0, $n - 1); }
99    public static function add(int $a, int $b): int { return $a + $b; }
100    public static function mul(int $a, int $b): int { return $a * $b; }
101    public static function sub(int $a, int $b): int { return max(0, $a - $b); }
102}
103"#;
104#[cfg(test)]
105mod tests {
106    use super::*;
107    pub(super) fn backend() -> PHPBackend {
108        PHPBackend::new()
109    }
110    #[test]
111    pub(super) fn test_php_type_primitives() {
112        let b = backend();
113        assert_eq!(b.emit_type(&PHPType::Int), "int");
114        assert_eq!(b.emit_type(&PHPType::Float), "float");
115        assert_eq!(b.emit_type(&PHPType::String), "string");
116        assert_eq!(b.emit_type(&PHPType::Bool), "bool");
117        assert_eq!(b.emit_type(&PHPType::Null), "null");
118        assert_eq!(b.emit_type(&PHPType::Mixed), "mixed");
119        assert_eq!(b.emit_type(&PHPType::Void), "void");
120        assert_eq!(b.emit_type(&PHPType::Never), "never");
121        assert_eq!(b.emit_type(&PHPType::Array), "array");
122        assert_eq!(b.emit_type(&PHPType::Callable), "callable");
123        assert_eq!(b.emit_type(&PHPType::Self_), "self");
124        assert_eq!(b.emit_type(&PHPType::Static), "static");
125        assert_eq!(b.emit_type(&PHPType::Parent), "parent");
126    }
127    #[test]
128    pub(super) fn test_php_type_nullable() {
129        let b = backend();
130        let ty = PHPType::Nullable(Box::new(PHPType::String));
131        assert_eq!(b.emit_type(&ty), "?string");
132    }
133    #[test]
134    pub(super) fn test_php_type_union() {
135        let b = backend();
136        let ty = PHPType::Union(vec![PHPType::Int, PHPType::String, PHPType::Null]);
137        assert_eq!(b.emit_type(&ty), "int|string|null");
138    }
139    #[test]
140    pub(super) fn test_php_type_intersection() {
141        let b = backend();
142        let ty = PHPType::Intersection(vec![
143            PHPType::Named("Countable".to_string()),
144            PHPType::Named("Iterator".to_string()),
145        ]);
146        assert_eq!(b.emit_type(&ty), "Countable&Iterator");
147    }
148    #[test]
149    pub(super) fn test_php_type_named() {
150        let b = backend();
151        let ty = PHPType::Named("MyClass".to_string());
152        assert_eq!(b.emit_type(&ty), "MyClass");
153    }
154    #[test]
155    pub(super) fn test_php_expr_lit() {
156        let b = backend();
157        let expr = PHPExpr::Lit("42".to_string());
158        assert_eq!(b.emit_expr(&expr), "42");
159    }
160    #[test]
161    pub(super) fn test_php_expr_var() {
162        let b = backend();
163        let expr = PHPExpr::Var("count".to_string());
164        assert_eq!(b.emit_expr(&expr), "$count");
165    }
166    #[test]
167    pub(super) fn test_php_expr_binop() {
168        let b = backend();
169        let expr = PHPExpr::BinOp(
170            Box::new(PHPExpr::Var("a".to_string())),
171            "+".to_string(),
172            Box::new(PHPExpr::Lit("1".to_string())),
173        );
174        assert_eq!(b.emit_expr(&expr), "($a + 1)");
175    }
176    #[test]
177    pub(super) fn test_php_expr_func_call() {
178        let b = backend();
179        let expr = PHPExpr::FuncCall("strlen".to_string(), vec![PHPExpr::Var("s".to_string())]);
180        assert_eq!(b.emit_expr(&expr), "strlen($s)");
181    }
182    #[test]
183    pub(super) fn test_php_expr_array_lit() {
184        let b = backend();
185        let expr = PHPExpr::ArrayLit(vec![
186            PHPExpr::Lit("1".to_string()),
187            PHPExpr::Lit("2".to_string()),
188            PHPExpr::Lit("3".to_string()),
189        ]);
190        assert_eq!(b.emit_expr(&expr), "[1, 2, 3]");
191    }
192    #[test]
193    pub(super) fn test_php_expr_new() {
194        let b = backend();
195        let expr = PHPExpr::New("DateTime".to_string(), vec![]);
196        assert_eq!(b.emit_expr(&expr), "new DateTime()");
197    }
198    #[test]
199    pub(super) fn test_php_expr_arrow() {
200        let b = backend();
201        let expr = PHPExpr::Arrow(
202            Box::new(PHPExpr::Var("obj".to_string())),
203            "name".to_string(),
204        );
205        assert_eq!(b.emit_expr(&expr), "$obj->name");
206    }
207    #[test]
208    pub(super) fn test_php_expr_null_coalesce() {
209        let b = backend();
210        let expr = PHPExpr::NullCoalesce(
211            Box::new(PHPExpr::Var("val".to_string())),
212            Box::new(PHPExpr::Lit("'default'".to_string())),
213        );
214        assert_eq!(b.emit_expr(&expr), "($val ?? 'default')");
215    }
216    #[test]
217    pub(super) fn test_php_expr_static_access() {
218        let b = backend();
219        let expr = PHPExpr::StaticAccess("MyClass".to_string(), "CONST_VAL".to_string());
220        assert_eq!(b.emit_expr(&expr), "MyClass::CONST_VAL");
221    }
222    #[test]
223    pub(super) fn test_php_expr_index() {
224        let b = backend();
225        let expr = PHPExpr::Index(
226            Box::new(PHPExpr::Var("arr".to_string())),
227            Box::new(PHPExpr::Lit("0".to_string())),
228        );
229        assert_eq!(b.emit_expr(&expr), "$arr[0]");
230    }
231    #[test]
232    pub(super) fn test_php_expr_spread() {
233        let b = backend();
234        let expr = PHPExpr::Spread(Box::new(PHPExpr::Var("args".to_string())));
235        assert_eq!(b.emit_expr(&expr), "...$args");
236    }
237    #[test]
238    pub(super) fn test_php_expr_cast() {
239        let b = backend();
240        let expr = PHPExpr::Cast("int".to_string(), Box::new(PHPExpr::Var("x".to_string())));
241        assert_eq!(b.emit_expr(&expr), "(int) $x");
242    }
243    #[test]
244    pub(super) fn test_php_expr_isset_empty() {
245        let b = backend();
246        let isset = PHPExpr::Isset(Box::new(PHPExpr::Var("x".to_string())));
247        let empty = PHPExpr::Empty(Box::new(PHPExpr::Var("arr".to_string())));
248        assert_eq!(b.emit_expr(&isset), "isset($x)");
249        assert_eq!(b.emit_expr(&empty), "empty($arr)");
250    }
251    #[test]
252    pub(super) fn test_mangle_simple() {
253        let b = backend();
254        assert_eq!(b.mangle_name("myFunc"), "myFunc");
255        assert_eq!(b.mangle_name("my_func"), "my_func");
256    }
257    #[test]
258    pub(super) fn test_mangle_dot_separator() {
259        let b = backend();
260        assert_eq!(b.mangle_name("Nat.add"), "Nat_add");
261    }
262    #[test]
263    pub(super) fn test_mangle_reserved_word() {
264        let b = backend();
265        assert_eq!(b.mangle_name("match"), "match_ox");
266        assert_eq!(b.mangle_name("class"), "class_ox");
267        assert_eq!(b.mangle_name("enum"), "enum_ox");
268    }
269    #[test]
270    pub(super) fn test_mangle_leading_digit() {
271        let b = backend();
272        let result = b.mangle_name("1foo");
273        assert!(
274            result.starts_with('_'),
275            "should start with underscore, got: {}",
276            result
277        );
278    }
279    #[test]
280    pub(super) fn test_emit_simple_function() {
281        let b = backend();
282        let func = PHPFunction {
283            name: "add".to_string(),
284            params: vec![
285                PHPParam::typed("a", PHPType::Int),
286                PHPParam::typed("b", PHPType::Int),
287            ],
288            return_type: Some(PHPType::Int),
289            body: vec!["return $a + $b;".to_string()],
290            is_static: false,
291            is_abstract: false,
292            visibility: None,
293            doc_comment: None,
294        };
295        let code = b.emit_function(&func);
296        assert!(code.contains("function add(int $a, int $b): int"));
297        assert!(code.contains("return $a + $b;"));
298    }
299    #[test]
300    pub(super) fn test_emit_static_method() {
301        let b = backend();
302        let func = PHPFunction::method(
303            "create",
304            PHPVisibility::Public,
305            vec![],
306            Some(PHPType::Static),
307            vec!["return new static();".to_string()],
308        );
309        let mut f = func.clone();
310        f.is_static = true;
311        let code = b.emit_function(&f);
312        assert!(code.contains("public static"));
313        assert!(code.contains("function create()"));
314    }
315    #[test]
316    pub(super) fn test_emit_simple_class() {
317        let b = backend();
318        let mut cls = PHPClass::new("Calculator");
319        cls.properties
320            .push(PHPProperty::private("result", Some(PHPType::Int)));
321        cls.methods.push(PHPFunction::method(
322            "getResult",
323            PHPVisibility::Public,
324            vec![],
325            Some(PHPType::Int),
326            vec!["return $this->result;".to_string()],
327        ));
328        let code = b.emit_class(&cls);
329        assert!(code.contains("class Calculator"));
330        assert!(code.contains("private int $result;"));
331        assert!(code.contains("function getResult(): int"));
332    }
333    #[test]
334    pub(super) fn test_emit_class_with_parent() {
335        let b = backend();
336        let mut cls = PHPClass::new("Dog");
337        cls.parent = Some("Animal".to_string());
338        cls.interfaces.push("Serializable".to_string());
339        let code = b.emit_class(&cls);
340        assert!(code.contains("class Dog extends Animal implements Serializable"));
341    }
342    #[test]
343    pub(super) fn test_emit_abstract_class() {
344        let b = backend();
345        let cls = PHPClass::abstract_class("Shape");
346        let code = b.emit_class(&cls);
347        assert!(code.contains("abstract class Shape"));
348    }
349    #[test]
350    pub(super) fn test_emit_pure_enum() {
351        let b = backend();
352        let mut en = PHPEnum::new("Status");
353        en.cases.push(PHPEnumCase {
354            name: "Active".to_string(),
355            value: None,
356        });
357        en.cases.push(PHPEnumCase {
358            name: "Inactive".to_string(),
359            value: None,
360        });
361        let code = b.emit_enum(&en);
362        assert!(code.contains("enum Status"));
363        assert!(code.contains("case Active;"));
364        assert!(code.contains("case Inactive;"));
365    }
366    #[test]
367    pub(super) fn test_emit_backed_enum() {
368        let b = backend();
369        let mut en = PHPEnum::string_backed("Color");
370        en.cases.push(PHPEnumCase {
371            name: "Red".to_string(),
372            value: Some("'red'".to_string()),
373        });
374        en.cases.push(PHPEnumCase {
375            name: "Blue".to_string(),
376            value: Some("'blue'".to_string()),
377        });
378        let code = b.emit_enum(&en);
379        assert!(code.contains("enum Color: string"));
380        assert!(code.contains("case Red = 'red';"));
381    }
382    #[test]
383    pub(super) fn test_emit_interface() {
384        let b = backend();
385        let mut iface = PHPInterface::new("Drawable");
386        iface.methods.push(PHPFunction {
387            name: "draw".to_string(),
388            params: vec![],
389            return_type: Some(PHPType::Void),
390            body: vec![],
391            is_static: false,
392            is_abstract: true,
393            visibility: Some(PHPVisibility::Public),
394            doc_comment: None,
395        });
396        let code = b.emit_interface(&iface);
397        assert!(code.contains("interface Drawable"));
398        assert!(code.contains("function draw(): void"));
399    }
400    #[test]
401    pub(super) fn test_emit_script_strict_types() {
402        let b = backend();
403        let script = PHPScript::new();
404        let code = b.emit_script(&script);
405        assert!(code.starts_with("<?php\n"));
406        assert!(code.contains("declare(strict_types=1);"));
407    }
408    #[test]
409    pub(super) fn test_emit_script_namespace() {
410        let b = backend();
411        let mut script = PHPScript::new();
412        script.namespace = Some("OxiLean\\Generated".to_string());
413        let code = b.emit_script(&script);
414        assert!(code.contains("namespace OxiLean\\Generated;"));
415    }
416    #[test]
417    pub(super) fn test_emit_script_with_use() {
418        let b = backend();
419        let mut script = PHPScript::new();
420        script
421            .uses
422            .push(("OxiLean\\Runtime\\Thunk".to_string(), None));
423        script
424            .uses
425            .push(("OxiLean\\Runtime\\Nat".to_string(), Some("N".to_string())));
426        let code = b.emit_script(&script);
427        assert!(code.contains("use OxiLean\\Runtime\\Thunk;"));
428        assert!(code.contains("use OxiLean\\Runtime\\Nat as N;"));
429    }
430    #[test]
431    pub(super) fn test_emit_full_script() {
432        let b = backend();
433        let mut script = PHPScript::new();
434        script.namespace = Some("App".to_string());
435        let func = PHPFunction {
436            name: "greet".to_string(),
437            params: vec![PHPParam::typed("name", PHPType::String)],
438            return_type: Some(PHPType::String),
439            body: vec!["return 'Hello, ' . $name . '!';".to_string()],
440            is_static: false,
441            is_abstract: false,
442            visibility: None,
443            doc_comment: None,
444        };
445        script.functions.push(func);
446        script.main.push("echo greet('World');".to_string());
447        let code = b.emit_script(&script);
448        assert!(code.contains("<?php"));
449        assert!(code.contains("namespace App;"));
450        assert!(code.contains("function greet(string $name): string"));
451        assert!(code.contains("echo greet('World');"));
452    }
453    #[test]
454    pub(super) fn test_php_runtime_constant() {
455        assert!(PHP_RUNTIME.contains("OxiLean PHP Runtime"));
456        assert!(PHP_RUNTIME.contains("class Thunk"));
457        assert!(PHP_RUNTIME.contains("class Nat"));
458        assert!(PHP_RUNTIME.contains("declare(strict_types=1)"));
459    }
460    #[test]
461    pub(super) fn test_param_simple() {
462        let p = PHPParam::simple("x");
463        assert_eq!(p.name, "x");
464        assert!(p.ty.is_none());
465        assert!(p.default.is_none());
466        assert!(!p.by_ref);
467        assert!(!p.variadic);
468    }
469    #[test]
470    pub(super) fn test_param_typed() {
471        let p = PHPParam::typed("count", PHPType::Int);
472        assert_eq!(p.name, "count");
473        assert_eq!(p.ty, Some(PHPType::Int));
474    }
475    #[test]
476    pub(super) fn test_param_with_default() {
477        let p = PHPParam::with_default("limit", Some(PHPType::Int), PHPExpr::Lit("10".to_string()));
478        assert_eq!(p.name, "limit");
479        assert!(p.default.is_some());
480        let formatted = format_param(&p);
481        assert!(
482            formatted.contains("$limit"),
483            "expected $limit in: {}",
484            formatted
485        );
486        assert!(
487            formatted.contains("= 10"),
488            "expected = 10 in: {}",
489            formatted
490        );
491    }
492}
493#[cfg(test)]
494mod PHP_infra_tests {
495    use super::*;
496    #[test]
497    pub(super) fn test_pass_config() {
498        let config = PHPPassConfig::new("test_pass", PHPPassPhase::Transformation);
499        assert!(config.enabled);
500        assert!(config.phase.is_modifying());
501        assert_eq!(config.phase.name(), "transformation");
502    }
503    #[test]
504    pub(super) fn test_pass_stats() {
505        let mut stats = PHPPassStats::new();
506        stats.record_run(10, 100, 3);
507        stats.record_run(20, 200, 5);
508        assert_eq!(stats.total_runs, 2);
509        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
510        assert!((stats.success_rate() - 1.0).abs() < 0.01);
511        let s = stats.format_summary();
512        assert!(s.contains("Runs: 2/2"));
513    }
514    #[test]
515    pub(super) fn test_pass_registry() {
516        let mut reg = PHPPassRegistry::new();
517        reg.register(PHPPassConfig::new("pass_a", PHPPassPhase::Analysis));
518        reg.register(PHPPassConfig::new("pass_b", PHPPassPhase::Transformation).disabled());
519        assert_eq!(reg.total_passes(), 2);
520        assert_eq!(reg.enabled_count(), 1);
521        reg.update_stats("pass_a", 5, 50, 2);
522        let stats = reg.get_stats("pass_a").expect("stats should exist");
523        assert_eq!(stats.total_changes, 5);
524    }
525    #[test]
526    pub(super) fn test_analysis_cache() {
527        let mut cache = PHPAnalysisCache::new(10);
528        cache.insert("key1".to_string(), vec![1, 2, 3]);
529        assert!(cache.get("key1").is_some());
530        assert!(cache.get("key2").is_none());
531        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
532        cache.invalidate("key1");
533        assert!(!cache.entries["key1"].valid);
534        assert_eq!(cache.size(), 1);
535    }
536    #[test]
537    pub(super) fn test_worklist() {
538        let mut wl = PHPWorklist::new();
539        assert!(wl.push(1));
540        assert!(wl.push(2));
541        assert!(!wl.push(1));
542        assert_eq!(wl.len(), 2);
543        assert_eq!(wl.pop(), Some(1));
544        assert!(!wl.contains(1));
545        assert!(wl.contains(2));
546    }
547    #[test]
548    pub(super) fn test_dominator_tree() {
549        let mut dt = PHPDominatorTree::new(5);
550        dt.set_idom(1, 0);
551        dt.set_idom(2, 0);
552        dt.set_idom(3, 1);
553        assert!(dt.dominates(0, 3));
554        assert!(dt.dominates(1, 3));
555        assert!(!dt.dominates(2, 3));
556        assert!(dt.dominates(3, 3));
557    }
558    #[test]
559    pub(super) fn test_liveness() {
560        let mut liveness = PHPLivenessInfo::new(3);
561        liveness.add_def(0, 1);
562        liveness.add_use(1, 1);
563        assert!(liveness.defs[0].contains(&1));
564        assert!(liveness.uses[1].contains(&1));
565    }
566    #[test]
567    pub(super) fn test_constant_folding() {
568        assert_eq!(PHPConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
569        assert_eq!(PHPConstantFoldingHelper::fold_div_i64(10, 0), None);
570        assert_eq!(PHPConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
571        assert_eq!(
572            PHPConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
573            0b1000
574        );
575        assert_eq!(PHPConstantFoldingHelper::fold_bitnot_i64(0), -1);
576    }
577    #[test]
578    pub(super) fn test_dep_graph() {
579        let mut g = PHPDepGraph::new();
580        g.add_dep(1, 2);
581        g.add_dep(2, 3);
582        g.add_dep(1, 3);
583        assert_eq!(g.dependencies_of(2), vec![1]);
584        let topo = g.topological_sort();
585        assert_eq!(topo.len(), 3);
586        assert!(!g.has_cycle());
587        let pos: std::collections::HashMap<u32, usize> =
588            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
589        assert!(pos[&1] < pos[&2]);
590        assert!(pos[&1] < pos[&3]);
591        assert!(pos[&2] < pos[&3]);
592    }
593}
594#[cfg(test)]
595mod phpext_pass_tests {
596    use super::*;
597    #[test]
598    pub(super) fn test_phpext_phase_order() {
599        assert_eq!(PHPExtPassPhase::Early.order(), 0);
600        assert_eq!(PHPExtPassPhase::Middle.order(), 1);
601        assert_eq!(PHPExtPassPhase::Late.order(), 2);
602        assert_eq!(PHPExtPassPhase::Finalize.order(), 3);
603        assert!(PHPExtPassPhase::Early.is_early());
604        assert!(!PHPExtPassPhase::Early.is_late());
605    }
606    #[test]
607    pub(super) fn test_phpext_config_builder() {
608        let c = PHPExtPassConfig::new("p")
609            .with_phase(PHPExtPassPhase::Late)
610            .with_max_iter(50)
611            .with_debug(1);
612        assert_eq!(c.name, "p");
613        assert_eq!(c.max_iterations, 50);
614        assert!(c.is_debug_enabled());
615        assert!(c.enabled);
616        let c2 = c.disabled();
617        assert!(!c2.enabled);
618    }
619    #[test]
620    pub(super) fn test_phpext_stats() {
621        let mut s = PHPExtPassStats::new();
622        s.visit();
623        s.visit();
624        s.modify();
625        s.iterate();
626        assert_eq!(s.nodes_visited, 2);
627        assert_eq!(s.nodes_modified, 1);
628        assert!(s.changed);
629        assert_eq!(s.iterations, 1);
630        let e = s.efficiency();
631        assert!((e - 0.5).abs() < 1e-9);
632    }
633    #[test]
634    pub(super) fn test_phpext_registry() {
635        let mut r = PHPExtPassRegistry::new();
636        r.register(PHPExtPassConfig::new("a").with_phase(PHPExtPassPhase::Early));
637        r.register(PHPExtPassConfig::new("b").disabled());
638        assert_eq!(r.len(), 2);
639        assert_eq!(r.enabled_passes().len(), 1);
640        assert_eq!(r.passes_in_phase(&PHPExtPassPhase::Early).len(), 1);
641    }
642    #[test]
643    pub(super) fn test_phpext_cache() {
644        let mut c = PHPExtCache::new(4);
645        assert!(c.get(99).is_none());
646        c.put(99, vec![1, 2, 3]);
647        let v = c.get(99).expect("v should be present in map");
648        assert_eq!(v, &[1u8, 2, 3]);
649        assert!(c.hit_rate() > 0.0);
650        assert_eq!(c.live_count(), 1);
651    }
652    #[test]
653    pub(super) fn test_phpext_worklist() {
654        let mut w = PHPExtWorklist::new(10);
655        w.push(5);
656        w.push(3);
657        w.push(5);
658        assert_eq!(w.len(), 2);
659        assert!(w.contains(5));
660        let first = w.pop().expect("first should be available to pop");
661        assert!(!w.contains(first));
662    }
663    #[test]
664    pub(super) fn test_phpext_dom_tree() {
665        let mut dt = PHPExtDomTree::new(5);
666        dt.set_idom(1, 0);
667        dt.set_idom(2, 0);
668        dt.set_idom(3, 1);
669        dt.set_idom(4, 1);
670        assert!(dt.dominates(0, 3));
671        assert!(dt.dominates(1, 4));
672        assert!(!dt.dominates(2, 3));
673        assert_eq!(dt.depth_of(3), 2);
674    }
675    #[test]
676    pub(super) fn test_phpext_liveness() {
677        let mut lv = PHPExtLiveness::new(3);
678        lv.add_def(0, 1);
679        lv.add_use(1, 1);
680        assert!(lv.var_is_def_in_block(0, 1));
681        assert!(lv.var_is_used_in_block(1, 1));
682        assert!(!lv.var_is_def_in_block(1, 1));
683    }
684    #[test]
685    pub(super) fn test_phpext_const_folder() {
686        let mut cf = PHPExtConstFolder::new();
687        assert_eq!(cf.add_i64(3, 4), Some(7));
688        assert_eq!(cf.div_i64(10, 0), None);
689        assert_eq!(cf.mul_i64(6, 7), Some(42));
690        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
691        assert_eq!(cf.fold_count(), 3);
692        assert_eq!(cf.failure_count(), 1);
693    }
694    #[test]
695    pub(super) fn test_phpext_dep_graph() {
696        let mut g = PHPExtDepGraph::new(4);
697        g.add_edge(0, 1);
698        g.add_edge(1, 2);
699        g.add_edge(2, 3);
700        assert!(!g.has_cycle());
701        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
702        assert_eq!(g.reachable(0).len(), 4);
703        let sccs = g.scc();
704        assert_eq!(sccs.len(), 4);
705    }
706}