1use super::types::{
6 TSAnalysisCache, TSConstantFoldingHelper, TSDepGraph, TSDominatorTree, TSLivenessInfo,
7 TSPassConfig, TSPassPhase, TSPassRegistry, TSPassStats, TSWorklist, TsClass, TsClassField,
8 TsClassMethod, TsDeclaration, TsEnum, TsEnumMember, TsExpr, TsExtConfig, TsExtDiagCollector,
9 TsExtDiagMsg, TsExtEmitStats, TsExtEventLog, TsExtFeatures, TsExtIdGen, TsExtIncrKey,
10 TsExtNameScope, TsExtPassTiming, TsExtProfiler, TsExtSourceBuffer, TsExtVersion, TsFunction,
11 TsImport, TsInterface, TsInterfaceMember, TsLit, TsModule, TsParam, TsStmt, TsTemplatePart,
12 TsType, TsTypeAlias, TypeScriptBackend,
13};
14
15pub(super) fn format_ts_stmt(stmt: &TsStmt, indent: usize) -> std::string::String {
16 let pad = " ".repeat(indent);
17 let _ipad = " ".repeat(indent + 2);
18 match stmt {
19 TsStmt::Expr(e) => format!("{};", e),
20 TsStmt::Const(name, ty, expr) => {
21 if let Some(t) = ty {
22 format!("const {}: {} = {};", name, t, expr)
23 } else {
24 format!("const {} = {};", name, expr)
25 }
26 }
27 TsStmt::Let(name, ty, expr) => {
28 if let Some(t) = ty {
29 format!("let {}: {} = {};", name, t, expr)
30 } else {
31 format!("let {} = {};", name, expr)
32 }
33 }
34 TsStmt::Var(name, ty, expr) => {
35 if let Some(t) = ty {
36 format!("var {}: {} = {};", name, t, expr)
37 } else {
38 format!("var {} = {};", name, expr)
39 }
40 }
41 TsStmt::If(cond, then_s, else_s) => {
42 let then_body = format_ts_stmts(then_s, indent + 2);
43 let mut s = format!("if ({}) {{\n{}\n{}}}", cond, then_body, pad);
44 if !else_s.is_empty() {
45 let else_body = format_ts_stmts(else_s, indent + 2);
46 s.push_str(&format!(" else {{\n{}\n{}}}", else_body, pad));
47 }
48 s
49 }
50 TsStmt::Switch(expr, cases, default) => {
51 let mut s = format!("switch ({}) {{\n", expr);
52 for (case_val, case_stmts) in cases {
53 s.push_str(&format!("{} case {}:\n", pad, case_val));
54 for cs in case_stmts {
55 s.push_str(&format!("{} {}\n", pad, format_ts_stmt(cs, indent + 4)));
56 }
57 s.push_str(&format!("{} break;\n", pad));
58 }
59 if !default.is_empty() {
60 s.push_str(&format!("{} default:\n", pad));
61 for ds in default {
62 s.push_str(&format!("{} {}\n", pad, format_ts_stmt(ds, indent + 4)));
63 }
64 }
65 s.push_str(&format!("{}}}", pad));
66 s
67 }
68 TsStmt::For(init, cond, step, body) => {
69 let body_text = format_ts_stmts(body, indent + 2);
70 format!(
71 "for ({}; {}; {}) {{\n{}\n{}}}",
72 format_ts_stmt(init, 0).trim_end_matches(';'),
73 cond,
74 step,
75 body_text,
76 pad
77 )
78 }
79 TsStmt::ForOf(var, iter, body) => {
80 let body_text = format_ts_stmts(body, indent + 2);
81 format!(
82 "for (const {} of {}) {{\n{}\n{}}}",
83 var, iter, body_text, pad
84 )
85 }
86 TsStmt::ForIn(var, obj, body) => {
87 let body_text = format_ts_stmts(body, indent + 2);
88 format!(
89 "for (const {} in {}) {{\n{}\n{}}}",
90 var, obj, body_text, pad
91 )
92 }
93 TsStmt::While(cond, body) => {
94 let body_text = format_ts_stmts(body, indent + 2);
95 format!("while ({}) {{\n{}\n{}}}", cond, body_text, pad)
96 }
97 TsStmt::Return(e) => format!("return {};", e),
98 TsStmt::Throw(e) => format!("throw {};", e),
99 TsStmt::TryCatch(try_body, catch_var, catch_body, fin_body) => {
100 let try_text = format_ts_stmts(try_body, indent + 2);
101 let catch_text = format_ts_stmts(catch_body, indent + 2);
102 let mut s = format!(
103 "try {{\n{}\n{}}} catch ({}) {{\n{}\n{}}}",
104 try_text, pad, catch_var, catch_text, pad
105 );
106 if !fin_body.is_empty() {
107 let fin_text = format_ts_stmts(fin_body, indent + 2);
108 s.push_str(&format!(" finally {{\n{}\n{}}}", fin_text, pad));
109 }
110 s
111 }
112 TsStmt::Block(stmts) => {
113 let body = format_ts_stmts(stmts, indent + 2);
114 format!("{{\n{}\n{}}}", body, pad)
115 }
116 TsStmt::Break => "break;".to_string(),
117 TsStmt::Continue => "continue;".to_string(),
118 }
119}
120pub(super) fn format_ts_stmts(stmts: &[TsStmt], indent: usize) -> std::string::String {
121 let pad = " ".repeat(indent);
122 let mut out = std::string::String::new();
123 for stmt in stmts {
124 let text = format_ts_stmt(stmt, indent);
125 for line in text.lines() {
126 out.push_str(&pad);
127 out.push_str(line);
128 out.push('\n');
129 }
130 }
131 if out.ends_with('\n') {
132 out.pop();
133 }
134 out
135}
136#[cfg(test)]
137mod tests {
138 use super::*;
139 #[test]
140 pub(super) fn test_ts_type_primitives() {
141 assert_eq!(TsType::Number.to_string(), "number");
142 assert_eq!(TsType::String.to_string(), "string");
143 assert_eq!(TsType::Boolean.to_string(), "boolean");
144 assert_eq!(TsType::Void.to_string(), "void");
145 assert_eq!(TsType::Never.to_string(), "never");
146 assert_eq!(TsType::Unknown.to_string(), "unknown");
147 assert_eq!(TsType::Any.to_string(), "any");
148 assert_eq!(TsType::Null.to_string(), "null");
149 assert_eq!(TsType::Undefined.to_string(), "undefined");
150 }
151 #[test]
152 pub(super) fn test_ts_type_array() {
153 let t = TsType::Array(Box::new(TsType::Number));
154 assert_eq!(t.to_string(), "number[]");
155 }
156 #[test]
157 pub(super) fn test_ts_type_tuple() {
158 let t = TsType::Tuple(vec![TsType::Number, TsType::String, TsType::Boolean]);
159 assert_eq!(t.to_string(), "[number, string, boolean]");
160 }
161 #[test]
162 pub(super) fn test_ts_type_union() {
163 let t = TsType::Union(vec![TsType::Number, TsType::Null, TsType::Undefined]);
164 assert_eq!(t.to_string(), "number | null | undefined");
165 }
166 #[test]
167 pub(super) fn test_ts_type_intersection() {
168 let t = TsType::Intersection(vec![
169 TsType::Custom("Serializable".to_string()),
170 TsType::Custom("Loggable".to_string()),
171 ]);
172 assert_eq!(t.to_string(), "Serializable & Loggable");
173 }
174 #[test]
175 pub(super) fn test_ts_type_function() {
176 let t = TsType::Function {
177 params: vec![TsType::Number, TsType::String],
178 ret: Box::new(TsType::Boolean),
179 };
180 assert_eq!(t.to_string(), "(p0: number, p1: string) => boolean");
181 }
182 #[test]
183 pub(super) fn test_ts_type_generic() {
184 let t = TsType::Generic("Promise".to_string(), vec![TsType::String]);
185 assert_eq!(t.to_string(), "Promise<string>");
186 }
187 #[test]
188 pub(super) fn test_ts_type_generic_map() {
189 let t = TsType::Generic("Map".to_string(), vec![TsType::String, TsType::Number]);
190 assert_eq!(t.to_string(), "Map<string, number>");
191 }
192 #[test]
193 pub(super) fn test_ts_type_readonly() {
194 let t = TsType::ReadOnly(Box::new(TsType::Array(Box::new(TsType::Number))));
195 assert_eq!(t.to_string(), "readonly number[]");
196 }
197 #[test]
198 pub(super) fn test_ts_lit_number() {
199 assert_eq!(TsLit::Num(42.0).to_string(), "42");
200 assert_eq!(TsLit::Num(3.14).to_string(), "3.14");
201 }
202 #[test]
203 pub(super) fn test_ts_lit_string_escape() {
204 assert_eq!(TsLit::Str("hello".to_string()).to_string(), "\"hello\"");
205 assert_eq!(
206 TsLit::Str("say \"hi\"".to_string()).to_string(),
207 "\"say \\\"hi\\\"\""
208 );
209 }
210 #[test]
211 pub(super) fn test_ts_lit_bool_and_null() {
212 assert_eq!(TsLit::Bool(true).to_string(), "true");
213 assert_eq!(TsLit::Bool(false).to_string(), "false");
214 assert_eq!(TsLit::Null.to_string(), "null");
215 assert_eq!(TsLit::Undefined.to_string(), "undefined");
216 }
217 #[test]
218 pub(super) fn test_ts_expr_binop() {
219 let e = TsExpr::BinOp(
220 "+".to_string(),
221 Box::new(TsExpr::Lit(TsLit::Num(1.0))),
222 Box::new(TsExpr::Lit(TsLit::Num(2.0))),
223 );
224 assert_eq!(e.to_string(), "(1 + 2)");
225 }
226 #[test]
227 pub(super) fn test_ts_expr_ternary() {
228 let e = TsExpr::Ternary(
229 Box::new(TsExpr::Var("cond".to_string())),
230 Box::new(TsExpr::Lit(TsLit::Num(1.0))),
231 Box::new(TsExpr::Lit(TsLit::Num(0.0))),
232 );
233 assert_eq!(e.to_string(), "(cond) ? (1) : (0)");
234 }
235 #[test]
236 pub(super) fn test_ts_expr_as() {
237 let e = TsExpr::As(Box::new(TsExpr::Var("x".to_string())), TsType::Number);
238 assert_eq!(e.to_string(), "(x as number)");
239 }
240 #[test]
241 pub(super) fn test_ts_expr_satisfies() {
242 let e = TsExpr::Satisfies(
243 Box::new(TsExpr::Var("config".to_string())),
244 TsType::Custom("Config".to_string()),
245 );
246 assert_eq!(e.to_string(), "(config satisfies Config)");
247 }
248 #[test]
249 pub(super) fn test_ts_expr_template_literal() {
250 let e = TsExpr::Template(vec![
251 TsTemplatePart::Text("Hello, ".to_string()),
252 TsTemplatePart::Expr(TsExpr::Var("name".to_string())),
253 TsTemplatePart::Text("!".to_string()),
254 ]);
255 assert_eq!(e.to_string(), "`Hello, ${name}!`");
256 }
257 #[test]
258 pub(super) fn test_ts_expr_nullish() {
259 let e = TsExpr::Nullish(
260 Box::new(TsExpr::Var("a".to_string())),
261 Box::new(TsExpr::Lit(TsLit::Str("default".to_string()))),
262 );
263 assert_eq!(e.to_string(), "(a ?? \"default\")");
264 }
265 #[test]
266 pub(super) fn test_ts_expr_optchain() {
267 let e = TsExpr::OptChain(
268 Box::new(TsExpr::Var("user".to_string())),
269 "address".to_string(),
270 );
271 assert_eq!(e.to_string(), "user?.address");
272 }
273 #[test]
274 pub(super) fn test_ts_expr_object_lit() {
275 let e = TsExpr::ObjectLit(vec![
276 (
277 "kind".to_string(),
278 TsExpr::Lit(TsLit::Str("circle".to_string())),
279 ),
280 ("radius".to_string(), TsExpr::Lit(TsLit::Num(5.0))),
281 ]);
282 assert_eq!(e.to_string(), "{ kind: \"circle\", radius: 5 }");
283 }
284 #[test]
285 pub(super) fn test_ts_expr_array_lit() {
286 let e = TsExpr::ArrayLit(vec![
287 TsExpr::Lit(TsLit::Num(1.0)),
288 TsExpr::Lit(TsLit::Num(2.0)),
289 TsExpr::Lit(TsLit::Num(3.0)),
290 ]);
291 assert_eq!(e.to_string(), "[1, 2, 3]");
292 }
293 #[test]
294 pub(super) fn test_ts_interface_basic() {
295 let iface = TsInterface {
296 name: "Animal".to_string(),
297 extends: vec![],
298 members: vec![
299 TsInterfaceMember {
300 name: "name".to_string(),
301 ty: TsType::String,
302 optional: false,
303 readonly: true,
304 },
305 TsInterfaceMember {
306 name: "age".to_string(),
307 ty: TsType::Number,
308 optional: true,
309 readonly: false,
310 },
311 ],
312 type_params: vec![],
313 };
314 let src = iface.to_string();
315 assert!(src.contains("interface Animal {"));
316 assert!(src.contains("readonly name: string;"));
317 assert!(src.contains("age?: number;"));
318 }
319 #[test]
320 pub(super) fn test_ts_interface_extends() {
321 let iface = TsInterface {
322 name: "Dog".to_string(),
323 extends: vec!["Animal".to_string(), "Pet".to_string()],
324 members: vec![],
325 type_params: vec![],
326 };
327 let src = iface.to_string();
328 assert!(src.contains("extends Animal, Pet"));
329 }
330 #[test]
331 pub(super) fn test_ts_type_alias() {
332 let alias = TsTypeAlias {
333 name: "StringOrNumber".to_string(),
334 type_params: vec![],
335 definition: TsType::Union(vec![TsType::String, TsType::Number]),
336 };
337 assert_eq!(alias.to_string(), "type StringOrNumber = string | number;");
338 }
339 #[test]
340 pub(super) fn test_ts_type_alias_generic() {
341 let alias = TsTypeAlias {
342 name: "Nullable".to_string(),
343 type_params: vec!["T".to_string()],
344 definition: TsType::Union(vec![TsType::Custom("T".to_string()), TsType::Null]),
345 };
346 assert_eq!(alias.to_string(), "type Nullable<T> = T | null;");
347 }
348 #[test]
349 pub(super) fn test_ts_enum() {
350 let e = TsEnum {
351 name: "Direction".to_string(),
352 is_const: false,
353 members: vec![
354 TsEnumMember {
355 name: "North".to_string(),
356 value: None,
357 },
358 TsEnumMember {
359 name: "South".to_string(),
360 value: None,
361 },
362 ],
363 };
364 let src = e.to_string();
365 assert!(src.contains("enum Direction {"));
366 assert!(src.contains("North,"));
367 assert!(src.contains("South,"));
368 }
369 #[test]
370 pub(super) fn test_ts_const_enum_with_values() {
371 let e = TsEnum {
372 name: "Status".to_string(),
373 is_const: true,
374 members: vec![
375 TsEnumMember {
376 name: "OK".to_string(),
377 value: Some(TsLit::Num(200.0)),
378 },
379 TsEnumMember {
380 name: "NotFound".to_string(),
381 value: Some(TsLit::Num(404.0)),
382 },
383 ],
384 };
385 let src = e.to_string();
386 assert!(src.contains("const enum Status {"));
387 assert!(src.contains("OK = 200,"));
388 assert!(src.contains("NotFound = 404,"));
389 }
390 #[test]
391 pub(super) fn test_ts_function_emit() {
392 let f = TsFunction {
393 name: "add".to_string(),
394 params: vec![
395 TsParam {
396 name: "a".to_string(),
397 ty: TsType::Number,
398 optional: false,
399 rest: false,
400 },
401 TsParam {
402 name: "b".to_string(),
403 ty: TsType::Number,
404 optional: false,
405 rest: false,
406 },
407 ],
408 return_type: TsType::Number,
409 body: vec![TsStmt::Return(TsExpr::BinOp(
410 "+".to_string(),
411 Box::new(TsExpr::Var("a".to_string())),
412 Box::new(TsExpr::Var("b".to_string())),
413 ))],
414 is_async: false,
415 type_params: vec![],
416 is_exported: true,
417 };
418 let src = f.to_string();
419 assert!(src.contains("export function add(a: number, b: number): number {"));
420 assert!(src.contains("return (a + b);"));
421 }
422 #[test]
423 pub(super) fn test_backend_discriminated_union() {
424 let backend = TypeScriptBackend::new();
425 let alias = backend.make_discriminated_union(
426 "Shape",
427 &[
428 ("circle", vec![("radius", TsType::Number)]),
429 ("rect", vec![("w", TsType::Number), ("h", TsType::Number)]),
430 ],
431 );
432 let src = alias.to_string();
433 assert!(src.contains("type Shape ="));
434 assert!(src.contains("'circle'"));
435 assert!(src.contains("radius: number"));
436 assert!(src.contains("'rect'"));
437 assert!(src.contains("w: number"));
438 }
439 #[test]
440 pub(super) fn test_module_emit() {
441 let mut module = TsModule::new();
442 module.imports.push(TsImport {
443 names: vec!["readFileSync".to_string()],
444 from: "fs".to_string(),
445 is_type: false,
446 });
447 module.declarations.push(TsDeclaration::Const(
448 "VERSION".to_string(),
449 Some(TsType::String),
450 TsExpr::Lit(TsLit::Str("1.0.0".to_string())),
451 ));
452 let src = module.emit();
453 assert!(src.contains("import { readFileSync } from \"fs\";"));
454 assert!(src.contains("export const VERSION: string = \"1.0.0\";"));
455 }
456 #[test]
457 pub(super) fn test_module_emit_d_ts() {
458 let mut module = TsModule::new();
459 module
460 .declarations
461 .push(TsDeclaration::Function(TsFunction {
462 name: "greet".to_string(),
463 params: vec![TsParam {
464 name: "name".to_string(),
465 ty: TsType::String,
466 optional: false,
467 rest: false,
468 }],
469 return_type: TsType::String,
470 body: vec![],
471 is_async: false,
472 type_params: vec![],
473 is_exported: true,
474 }));
475 let dts = module.emit_d_ts();
476 assert!(dts.contains("declare function greet"));
477 assert!(dts.contains("name: string"));
478 assert!(dts.contains(": string;"));
479 }
480 #[test]
481 pub(super) fn test_ts_class_emit() {
482 let cls = TsClass {
483 name: "Counter".to_string(),
484 extends: None,
485 implements: vec!["ICounter".to_string()],
486 fields: vec![TsClassField {
487 name: "count".to_string(),
488 ty: TsType::Number,
489 readonly: false,
490 optional: false,
491 is_private: true,
492 is_static: false,
493 }],
494 methods: vec![TsClassMethod {
495 name: "increment".to_string(),
496 params: vec![],
497 return_type: TsType::Void,
498 body: vec![TsStmt::Expr(TsExpr::BinOp(
499 "+=".to_string(),
500 Box::new(TsExpr::Var("this.count".to_string())),
501 Box::new(TsExpr::Lit(TsLit::Num(1.0))),
502 ))],
503 is_async: false,
504 is_static: false,
505 is_private: false,
506 is_getter: false,
507 is_setter: false,
508 }],
509 type_params: vec![],
510 is_exported: true,
511 };
512 let src = cls.to_string();
513 assert!(src.contains("export class Counter"));
514 assert!(src.contains("implements ICounter"));
515 assert!(src.contains("private count: number;"));
516 assert!(src.contains("increment()"));
517 }
518 #[test]
519 pub(super) fn test_ts_stmt_try_catch_finally() {
520 let stmt = TsStmt::TryCatch(
521 vec![TsStmt::Expr(TsExpr::Call(
522 Box::new(TsExpr::Var("risky".to_string())),
523 vec![],
524 ))],
525 "err".to_string(),
526 vec![TsStmt::Throw(TsExpr::Var("err".to_string()))],
527 vec![TsStmt::Expr(TsExpr::Call(
528 Box::new(TsExpr::Var("cleanup".to_string())),
529 vec![],
530 ))],
531 );
532 let src = stmt.to_string();
533 assert!(src.contains("try {"));
534 assert!(src.contains("catch (err)"));
535 assert!(src.contains("finally {"));
536 }
537 #[test]
538 pub(super) fn test_ts_stmt_for_of() {
539 let stmt = TsStmt::ForOf(
540 "item".to_string(),
541 TsExpr::Var("items".to_string()),
542 vec![TsStmt::Expr(TsExpr::Call(
543 Box::new(TsExpr::Var("process".to_string())),
544 vec![TsExpr::Var("item".to_string())],
545 ))],
546 );
547 let src = stmt.to_string();
548 assert!(src.contains("for (const item of items)"));
549 }
550 #[test]
551 pub(super) fn test_ts_stmt_switch() {
552 let stmt = TsStmt::Switch(
553 TsExpr::Var("x".to_string()),
554 vec![
555 (
556 TsExpr::Lit(TsLit::Num(1.0)),
557 vec![TsStmt::Return(TsExpr::Lit(TsLit::Str("one".to_string())))],
558 ),
559 (
560 TsExpr::Lit(TsLit::Num(2.0)),
561 vec![TsStmt::Return(TsExpr::Lit(TsLit::Str("two".to_string())))],
562 ),
563 ],
564 vec![TsStmt::Return(TsExpr::Lit(TsLit::Str("other".to_string())))],
565 );
566 let src = stmt.to_string();
567 assert!(src.contains("switch (x)"));
568 assert!(src.contains("case 1:"));
569 assert!(src.contains("case 2:"));
570 assert!(src.contains("default:"));
571 }
572}
573#[cfg(test)]
574mod tests_ts_ext_extra {
575 use super::*;
576 #[test]
577 pub(super) fn test_ts_ext_config() {
578 let mut cfg = TsExtConfig::new();
579 cfg.set("mode", "release");
580 cfg.set("verbose", "true");
581 assert_eq!(cfg.get("mode"), Some("release"));
582 assert!(cfg.get_bool("verbose"));
583 assert!(cfg.get_int("mode").is_none());
584 assert_eq!(cfg.len(), 2);
585 }
586 #[test]
587 pub(super) fn test_ts_ext_source_buffer() {
588 let mut buf = TsExtSourceBuffer::new();
589 buf.push_line("fn main() {");
590 buf.indent();
591 buf.push_line("println!(\"hello\");");
592 buf.dedent();
593 buf.push_line("}");
594 assert!(buf.as_str().contains("fn main()"));
595 assert!(buf.as_str().contains(" println!"));
596 assert_eq!(buf.line_count(), 3);
597 buf.reset();
598 assert!(buf.is_empty());
599 }
600 #[test]
601 pub(super) fn test_ts_ext_name_scope() {
602 let mut scope = TsExtNameScope::new();
603 assert!(scope.declare("x"));
604 assert!(!scope.declare("x"));
605 assert!(scope.is_declared("x"));
606 let scope = scope.push_scope();
607 assert_eq!(scope.depth(), 1);
608 let mut scope = scope.pop_scope();
609 assert_eq!(scope.depth(), 0);
610 scope.declare("y");
611 assert_eq!(scope.len(), 2);
612 }
613 #[test]
614 pub(super) fn test_ts_ext_diag_collector() {
615 let mut col = TsExtDiagCollector::new();
616 col.emit(TsExtDiagMsg::warning("pass_a", "slow"));
617 col.emit(TsExtDiagMsg::error("pass_b", "fatal"));
618 assert!(col.has_errors());
619 assert_eq!(col.errors().len(), 1);
620 assert_eq!(col.warnings().len(), 1);
621 col.clear();
622 assert!(col.is_empty());
623 }
624 #[test]
625 pub(super) fn test_ts_ext_id_gen() {
626 let mut gen = TsExtIdGen::new();
627 assert_eq!(gen.next_id(), 0);
628 assert_eq!(gen.next_id(), 1);
629 gen.skip(10);
630 assert_eq!(gen.next_id(), 12);
631 gen.reset();
632 assert_eq!(gen.peek_next(), 0);
633 }
634 #[test]
635 pub(super) fn test_ts_ext_incr_key() {
636 let k1 = TsExtIncrKey::new(100, 200);
637 let k2 = TsExtIncrKey::new(100, 200);
638 let k3 = TsExtIncrKey::new(999, 200);
639 assert!(k1.matches(&k2));
640 assert!(!k1.matches(&k3));
641 }
642 #[test]
643 pub(super) fn test_ts_ext_profiler() {
644 let mut p = TsExtProfiler::new();
645 p.record(TsExtPassTiming::new("pass_a", 1000, 50, 200, 100));
646 p.record(TsExtPassTiming::new("pass_b", 500, 30, 100, 200));
647 assert_eq!(p.total_elapsed_us(), 1500);
648 assert_eq!(
649 p.slowest_pass()
650 .expect("slowest pass should exist")
651 .pass_name,
652 "pass_a"
653 );
654 assert_eq!(p.profitable_passes().len(), 1);
655 }
656 #[test]
657 pub(super) fn test_ts_ext_event_log() {
658 let mut log = TsExtEventLog::new(3);
659 log.push("event1");
660 log.push("event2");
661 log.push("event3");
662 assert_eq!(log.len(), 3);
663 log.push("event4");
664 assert_eq!(log.len(), 3);
665 assert_eq!(
666 log.iter()
667 .next()
668 .expect("iterator should have next element"),
669 "event2"
670 );
671 }
672 #[test]
673 pub(super) fn test_ts_ext_version() {
674 let v = TsExtVersion::new(1, 2, 3).with_pre("alpha");
675 assert!(!v.is_stable());
676 assert_eq!(format!("{}", v), "1.2.3-alpha");
677 let stable = TsExtVersion::new(2, 0, 0);
678 assert!(stable.is_stable());
679 assert!(stable.is_compatible_with(&TsExtVersion::new(2, 0, 0)));
680 assert!(!stable.is_compatible_with(&TsExtVersion::new(3, 0, 0)));
681 }
682 #[test]
683 pub(super) fn test_ts_ext_features() {
684 let mut f = TsExtFeatures::new();
685 f.enable("sse2");
686 f.enable("avx2");
687 assert!(f.is_enabled("sse2"));
688 assert!(!f.is_enabled("avx512"));
689 f.disable("avx2");
690 assert!(!f.is_enabled("avx2"));
691 let mut g = TsExtFeatures::new();
692 g.enable("sse2");
693 g.enable("neon");
694 let union = f.union(&g);
695 assert!(union.is_enabled("sse2") && union.is_enabled("neon"));
696 let inter = f.intersection(&g);
697 assert!(inter.is_enabled("sse2"));
698 }
699 #[test]
700 pub(super) fn test_ts_ext_emit_stats() {
701 let mut s = TsExtEmitStats::new();
702 s.bytes_emitted = 50_000;
703 s.items_emitted = 500;
704 s.elapsed_ms = 100;
705 assert!(s.is_clean());
706 assert!((s.throughput_bps() - 500_000.0).abs() < 1.0);
707 let disp = format!("{}", s);
708 assert!(disp.contains("bytes=50000"));
709 }
710}
711#[cfg(test)]
712mod TS_infra_tests {
713 use super::*;
714 #[test]
715 pub(super) fn test_pass_config() {
716 let config = TSPassConfig::new("test_pass", TSPassPhase::Transformation);
717 assert!(config.enabled);
718 assert!(config.phase.is_modifying());
719 assert_eq!(config.phase.name(), "transformation");
720 }
721 #[test]
722 pub(super) fn test_pass_stats() {
723 let mut stats = TSPassStats::new();
724 stats.record_run(10, 100, 3);
725 stats.record_run(20, 200, 5);
726 assert_eq!(stats.total_runs, 2);
727 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
728 assert!((stats.success_rate() - 1.0).abs() < 0.01);
729 let s = stats.format_summary();
730 assert!(s.contains("Runs: 2/2"));
731 }
732 #[test]
733 pub(super) fn test_pass_registry() {
734 let mut reg = TSPassRegistry::new();
735 reg.register(TSPassConfig::new("pass_a", TSPassPhase::Analysis));
736 reg.register(TSPassConfig::new("pass_b", TSPassPhase::Transformation).disabled());
737 assert_eq!(reg.total_passes(), 2);
738 assert_eq!(reg.enabled_count(), 1);
739 reg.update_stats("pass_a", 5, 50, 2);
740 let stats = reg.get_stats("pass_a").expect("stats should exist");
741 assert_eq!(stats.total_changes, 5);
742 }
743 #[test]
744 pub(super) fn test_analysis_cache() {
745 let mut cache = TSAnalysisCache::new(10);
746 cache.insert("key1".to_string(), vec![1, 2, 3]);
747 assert!(cache.get("key1").is_some());
748 assert!(cache.get("key2").is_none());
749 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
750 cache.invalidate("key1");
751 assert!(!cache.entries["key1"].valid);
752 assert_eq!(cache.size(), 1);
753 }
754 #[test]
755 pub(super) fn test_worklist() {
756 let mut wl = TSWorklist::new();
757 assert!(wl.push(1));
758 assert!(wl.push(2));
759 assert!(!wl.push(1));
760 assert_eq!(wl.len(), 2);
761 assert_eq!(wl.pop(), Some(1));
762 assert!(!wl.contains(1));
763 assert!(wl.contains(2));
764 }
765 #[test]
766 pub(super) fn test_dominator_tree() {
767 let mut dt = TSDominatorTree::new(5);
768 dt.set_idom(1, 0);
769 dt.set_idom(2, 0);
770 dt.set_idom(3, 1);
771 assert!(dt.dominates(0, 3));
772 assert!(dt.dominates(1, 3));
773 assert!(!dt.dominates(2, 3));
774 assert!(dt.dominates(3, 3));
775 }
776 #[test]
777 pub(super) fn test_liveness() {
778 let mut liveness = TSLivenessInfo::new(3);
779 liveness.add_def(0, 1);
780 liveness.add_use(1, 1);
781 assert!(liveness.defs[0].contains(&1));
782 assert!(liveness.uses[1].contains(&1));
783 }
784 #[test]
785 pub(super) fn test_constant_folding() {
786 assert_eq!(TSConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
787 assert_eq!(TSConstantFoldingHelper::fold_div_i64(10, 0), None);
788 assert_eq!(TSConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
789 assert_eq!(
790 TSConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
791 0b1000
792 );
793 assert_eq!(TSConstantFoldingHelper::fold_bitnot_i64(0), -1);
794 }
795 #[test]
796 pub(super) fn test_dep_graph() {
797 let mut g = TSDepGraph::new();
798 g.add_dep(1, 2);
799 g.add_dep(2, 3);
800 g.add_dep(1, 3);
801 assert_eq!(g.dependencies_of(2), vec![1]);
802 let topo = g.topological_sort();
803 assert_eq!(topo.len(), 3);
804 assert!(!g.has_cycle());
805 let pos: std::collections::HashMap<u32, usize> =
806 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
807 assert!(pos[&1] < pos[&2]);
808 assert!(pos[&1] < pos[&3]);
809 assert!(pos[&2] < pos[&3]);
810 }
811}