1use 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}
36pub 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}