1use crate::lcnf::*;
6use std::collections::HashSet;
7
8use super::functions::RUBY_RUNTIME;
9
10use super::functions::*;
11use std::collections::{HashMap, VecDeque};
12use std::fmt;
13use std::fmt::Write as FmtWrite;
14
15#[allow(dead_code)]
17#[derive(Debug, Default)]
18pub struct RubyNameMangler {
19 pub used: std::collections::HashSet<String>,
20 pub map: std::collections::HashMap<String, String>,
21}
22#[allow(dead_code)]
23impl RubyNameMangler {
24 pub fn new() -> Self {
25 Self::default()
26 }
27 pub fn mangle_constant(&mut self, name: &str) -> String {
28 let mut mangled: String = name
29 .chars()
30 .map(|c| {
31 if c.is_alphanumeric() || c == '_' {
32 c
33 } else {
34 '_'
35 }
36 })
37 .collect();
38 if !mangled.starts_with(|c: char| c.is_uppercase()) {
39 mangled = format!("Ox{}", mangled);
40 }
41 let base = mangled.clone();
42 let mut cnt = 0;
43 while self.used.contains(&mangled) {
44 cnt += 1;
45 mangled = format!("{}_{}", base, cnt);
46 }
47 self.used.insert(mangled.clone());
48 self.map.insert(name.to_string(), mangled.clone());
49 mangled
50 }
51 pub fn mangle_method(&mut self, name: &str) -> String {
52 let mangled: String = name
53 .chars()
54 .map(|c| {
55 if c.is_alphanumeric() || c == '_' {
56 c
57 } else {
58 '_'
59 }
60 })
61 .collect();
62 let ruby_reserved = [
63 "__method__",
64 "__dir__",
65 "__callee__",
66 "begin",
67 "end",
68 "do",
69 "if",
70 "unless",
71 "while",
72 "until",
73 "for",
74 "return",
75 "yield",
76 "class",
77 "module",
78 "def",
79 "alias",
80 "and",
81 "or",
82 "not",
83 "in",
84 "then",
85 "case",
86 "when",
87 "rescue",
88 "ensure",
89 ];
90 let base = if ruby_reserved.contains(&mangled.as_str()) {
91 format!("ox_{}", mangled)
92 } else {
93 mangled
94 };
95 let mut candidate = base.clone();
96 let mut cnt = 0;
97 while self.used.contains(&candidate) {
98 cnt += 1;
99 candidate = format!("{}_{}", base, cnt);
100 }
101 self.used.insert(candidate.clone());
102 self.map.insert(name.to_string(), candidate.clone());
103 candidate
104 }
105}
106#[allow(dead_code)]
108#[derive(Debug, Clone)]
109pub struct RubyAlias {
110 pub new_name: String,
111 pub old_name: String,
112}
113#[allow(dead_code)]
114#[derive(Debug, Clone)]
115pub struct RubyLivenessInfo {
116 pub live_in: Vec<std::collections::HashSet<u32>>,
117 pub live_out: Vec<std::collections::HashSet<u32>>,
118 pub defs: Vec<std::collections::HashSet<u32>>,
119 pub uses: Vec<std::collections::HashSet<u32>>,
120}
121impl RubyLivenessInfo {
122 #[allow(dead_code)]
123 pub fn new(block_count: usize) -> Self {
124 RubyLivenessInfo {
125 live_in: vec![std::collections::HashSet::new(); block_count],
126 live_out: vec![std::collections::HashSet::new(); block_count],
127 defs: vec![std::collections::HashSet::new(); block_count],
128 uses: vec![std::collections::HashSet::new(); block_count],
129 }
130 }
131 #[allow(dead_code)]
132 pub fn add_def(&mut self, block: usize, var: u32) {
133 if block < self.defs.len() {
134 self.defs[block].insert(var);
135 }
136 }
137 #[allow(dead_code)]
138 pub fn add_use(&mut self, block: usize, var: u32) {
139 if block < self.uses.len() {
140 self.uses[block].insert(var);
141 }
142 }
143 #[allow(dead_code)]
144 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
145 self.live_in
146 .get(block)
147 .map(|s| s.contains(&var))
148 .unwrap_or(false)
149 }
150 #[allow(dead_code)]
151 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
152 self.live_out
153 .get(block)
154 .map(|s| s.contains(&var))
155 .unwrap_or(false)
156 }
157}
158#[allow(dead_code)]
159pub struct RubyConstantFoldingHelper;
160impl RubyConstantFoldingHelper {
161 #[allow(dead_code)]
162 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
163 a.checked_add(b)
164 }
165 #[allow(dead_code)]
166 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
167 a.checked_sub(b)
168 }
169 #[allow(dead_code)]
170 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
171 a.checked_mul(b)
172 }
173 #[allow(dead_code)]
174 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
175 if b == 0 {
176 None
177 } else {
178 a.checked_div(b)
179 }
180 }
181 #[allow(dead_code)]
182 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
183 a + b
184 }
185 #[allow(dead_code)]
186 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
187 a * b
188 }
189 #[allow(dead_code)]
190 pub fn fold_neg_i64(a: i64) -> Option<i64> {
191 a.checked_neg()
192 }
193 #[allow(dead_code)]
194 pub fn fold_not_bool(a: bool) -> bool {
195 !a
196 }
197 #[allow(dead_code)]
198 pub fn fold_and_bool(a: bool, b: bool) -> bool {
199 a && b
200 }
201 #[allow(dead_code)]
202 pub fn fold_or_bool(a: bool, b: bool) -> bool {
203 a || b
204 }
205 #[allow(dead_code)]
206 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
207 a.checked_shl(b)
208 }
209 #[allow(dead_code)]
210 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
211 a.checked_shr(b)
212 }
213 #[allow(dead_code)]
214 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
215 if b == 0 {
216 None
217 } else {
218 Some(a % b)
219 }
220 }
221 #[allow(dead_code)]
222 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
223 a & b
224 }
225 #[allow(dead_code)]
226 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
227 a | b
228 }
229 #[allow(dead_code)]
230 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
231 a ^ b
232 }
233 #[allow(dead_code)]
234 pub fn fold_bitnot_i64(a: i64) -> i64 {
235 !a
236 }
237}
238#[allow(dead_code)]
239#[derive(Debug, Clone)]
240pub struct RubyDepGraph {
241 pub(super) nodes: Vec<u32>,
242 pub(super) edges: Vec<(u32, u32)>,
243}
244impl RubyDepGraph {
245 #[allow(dead_code)]
246 pub fn new() -> Self {
247 RubyDepGraph {
248 nodes: Vec::new(),
249 edges: Vec::new(),
250 }
251 }
252 #[allow(dead_code)]
253 pub fn add_node(&mut self, id: u32) {
254 if !self.nodes.contains(&id) {
255 self.nodes.push(id);
256 }
257 }
258 #[allow(dead_code)]
259 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
260 self.add_node(dep);
261 self.add_node(dependent);
262 self.edges.push((dep, dependent));
263 }
264 #[allow(dead_code)]
265 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
266 self.edges
267 .iter()
268 .filter(|(d, _)| *d == node)
269 .map(|(_, dep)| *dep)
270 .collect()
271 }
272 #[allow(dead_code)]
273 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
274 self.edges
275 .iter()
276 .filter(|(_, dep)| *dep == node)
277 .map(|(d, _)| *d)
278 .collect()
279 }
280 #[allow(dead_code)]
281 pub fn topological_sort(&self) -> Vec<u32> {
282 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
283 for &n in &self.nodes {
284 in_degree.insert(n, 0);
285 }
286 for (_, dep) in &self.edges {
287 *in_degree.entry(*dep).or_insert(0) += 1;
288 }
289 let mut queue: std::collections::VecDeque<u32> = self
290 .nodes
291 .iter()
292 .filter(|&&n| in_degree[&n] == 0)
293 .copied()
294 .collect();
295 let mut result = Vec::new();
296 while let Some(node) = queue.pop_front() {
297 result.push(node);
298 for dep in self.dependents_of(node) {
299 let cnt = in_degree.entry(dep).or_insert(0);
300 *cnt = cnt.saturating_sub(1);
301 if *cnt == 0 {
302 queue.push_back(dep);
303 }
304 }
305 }
306 result
307 }
308 #[allow(dead_code)]
309 pub fn has_cycle(&self) -> bool {
310 self.topological_sort().len() < self.nodes.len()
311 }
312}
313#[allow(dead_code)]
315#[derive(Debug, Default)]
316pub struct RubyExtSourceBuffer {
317 pub sections: Vec<(String, String)>,
318 pub current: String,
319 pub indent: usize,
320}
321#[allow(dead_code)]
322impl RubyExtSourceBuffer {
323 pub fn new() -> Self {
324 Self::default()
325 }
326 pub fn write(&mut self, s: &str) {
327 let pad = " ".repeat(self.indent);
328 self.current.push_str(&pad);
329 self.current.push_str(s);
330 }
331 pub fn writeln(&mut self, s: &str) {
332 let pad = " ".repeat(self.indent);
333 self.current.push_str(&pad);
334 self.current.push_str(s);
335 self.current.push('\n');
336 }
337 pub fn indent(&mut self) {
338 self.indent += 1;
339 }
340 pub fn dedent(&mut self) {
341 if self.indent > 0 {
342 self.indent -= 1;
343 }
344 }
345 pub fn finish(mut self) -> String {
346 let done = std::mem::take(&mut self.current);
347 if !done.is_empty() {
348 self.sections.push(("".to_string(), done));
349 }
350 self.sections
351 .iter()
352 .map(|(_, s)| s.as_str())
353 .collect::<Vec<_>>()
354 .join("")
355 }
356}
357#[allow(dead_code)]
359pub struct RubyProcLambdaDiff {
360 pub arity_strict: bool,
361 pub return_behavior: String,
362}
363#[allow(dead_code)]
365#[derive(Debug, Clone)]
366pub struct RubyClassDef {
367 pub name: String,
368 pub superclass: Option<String>,
369 pub includes: Vec<String>,
370 pub extends: Vec<String>,
371 pub prepends: Vec<String>,
372 pub methods: Vec<RubyMethodDef>,
373 pub attrs: Vec<(String, bool, bool)>,
374 pub constants: Vec<(String, String)>,
375}
376#[allow(dead_code)]
378#[derive(Debug, Default, Clone)]
379pub struct RubyPassSummary {
380 pub pass_name: String,
381 pub functions_compiled: usize,
382 pub classes_emitted: usize,
383 pub modules_emitted: usize,
384 pub duration_us: u64,
385}
386#[allow(dead_code)]
388#[derive(Debug, Default)]
389pub struct RubyClassRegistry {
390 pub classes: Vec<RubyClassDef>,
391 pub modules: Vec<RubyModuleDef>,
392}
393#[allow(dead_code)]
394impl RubyClassRegistry {
395 pub fn new() -> Self {
396 Self::default()
397 }
398 pub fn add_class(&mut self, c: RubyClassDef) {
399 self.classes.push(c);
400 }
401 pub fn add_module(&mut self, m: RubyModuleDef) {
402 self.modules.push(m);
403 }
404 pub fn total_items(&self) -> usize {
405 self.classes.len() + self.modules.len()
406 }
407 pub fn emit_all(&self) -> String {
408 let mut out = String::new();
409 for m in &self.modules {
410 out.push_str(&format!("{}\n\n", m));
411 }
412 for c in &self.classes {
413 out.push_str(&format!("{}\n\n", c));
414 }
415 out
416 }
417}
418#[allow(dead_code)]
419#[derive(Debug, Clone)]
420pub struct RubyAnalysisCache {
421 pub(super) entries: std::collections::HashMap<String, RubyCacheEntry>,
422 pub(super) max_size: usize,
423 pub(super) hits: u64,
424 pub(super) misses: u64,
425}
426impl RubyAnalysisCache {
427 #[allow(dead_code)]
428 pub fn new(max_size: usize) -> Self {
429 RubyAnalysisCache {
430 entries: std::collections::HashMap::new(),
431 max_size,
432 hits: 0,
433 misses: 0,
434 }
435 }
436 #[allow(dead_code)]
437 pub fn get(&mut self, key: &str) -> Option<&RubyCacheEntry> {
438 if self.entries.contains_key(key) {
439 self.hits += 1;
440 self.entries.get(key)
441 } else {
442 self.misses += 1;
443 None
444 }
445 }
446 #[allow(dead_code)]
447 pub fn insert(&mut self, key: String, data: Vec<u8>) {
448 if self.entries.len() >= self.max_size {
449 if let Some(oldest) = self.entries.keys().next().cloned() {
450 self.entries.remove(&oldest);
451 }
452 }
453 self.entries.insert(
454 key.clone(),
455 RubyCacheEntry {
456 key,
457 data,
458 timestamp: 0,
459 valid: true,
460 },
461 );
462 }
463 #[allow(dead_code)]
464 pub fn invalidate(&mut self, key: &str) {
465 if let Some(entry) = self.entries.get_mut(key) {
466 entry.valid = false;
467 }
468 }
469 #[allow(dead_code)]
470 pub fn clear(&mut self) {
471 self.entries.clear();
472 }
473 #[allow(dead_code)]
474 pub fn hit_rate(&self) -> f64 {
475 let total = self.hits + self.misses;
476 if total == 0 {
477 return 0.0;
478 }
479 self.hits as f64 / total as f64
480 }
481 #[allow(dead_code)]
482 pub fn size(&self) -> usize {
483 self.entries.len()
484 }
485}
486#[derive(Debug, Clone, PartialEq)]
488pub struct RubyMethod {
489 pub name: std::string::String,
491 pub params: Vec<std::string::String>,
493 pub body: Vec<RubyStmt>,
495 pub visibility: RubyVisibility,
497}
498impl RubyMethod {
499 pub fn new(name: &str, params: Vec<&str>, body: Vec<RubyStmt>) -> Self {
501 RubyMethod {
502 name: name.to_string(),
503 params: params.into_iter().map(|s| s.to_string()).collect(),
504 body,
505 visibility: RubyVisibility::Public,
506 }
507 }
508 pub fn private(name: &str, params: Vec<&str>, body: Vec<RubyStmt>) -> Self {
510 RubyMethod {
511 name: name.to_string(),
512 params: params.into_iter().map(|s| s.to_string()).collect(),
513 body,
514 visibility: RubyVisibility::Private,
515 }
516 }
517}
518#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520pub enum RubyVisibility {
521 Public,
523 Protected,
525 Private,
527}
528#[allow(dead_code)]
530#[derive(Debug, Default)]
531pub struct RubyExtIdGen {
532 pub(super) counter: u64,
533 pub(super) prefix: String,
534}
535#[allow(dead_code)]
536impl RubyExtIdGen {
537 pub fn new(prefix: &str) -> Self {
538 Self {
539 counter: 0,
540 prefix: prefix.to_string(),
541 }
542 }
543 pub fn next(&mut self) -> String {
544 let id = self.counter;
545 self.counter += 1;
546 format!("{}_{}", self.prefix, id)
547 }
548}
549#[allow(dead_code)]
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub enum RubyDiagLevel {
553 Info,
554 Warning,
555 Error,
556}
557#[allow(dead_code)]
559#[derive(Debug, Clone)]
560pub struct RubyFiber {
561 pub name: String,
562 pub params: Vec<String>,
563 pub body: String,
564 pub is_async: bool,
565}
566#[allow(dead_code)]
567#[derive(Debug, Clone)]
568pub struct RubyCacheEntry {
569 pub key: String,
570 pub data: Vec<u8>,
571 pub timestamp: u64,
572 pub valid: bool,
573}
574#[allow(dead_code)]
575#[derive(Debug, Clone)]
576pub struct RubyPassConfig {
577 pub phase: RubyPassPhase,
578 pub enabled: bool,
579 pub max_iterations: u32,
580 pub debug_output: bool,
581 pub pass_name: String,
582}
583impl RubyPassConfig {
584 #[allow(dead_code)]
585 pub fn new(name: impl Into<String>, phase: RubyPassPhase) -> Self {
586 RubyPassConfig {
587 phase,
588 enabled: true,
589 max_iterations: 10,
590 debug_output: false,
591 pass_name: name.into(),
592 }
593 }
594 #[allow(dead_code)]
595 pub fn disabled(mut self) -> Self {
596 self.enabled = false;
597 self
598 }
599 #[allow(dead_code)]
600 pub fn with_debug(mut self) -> Self {
601 self.debug_output = true;
602 self
603 }
604 #[allow(dead_code)]
605 pub fn max_iter(mut self, n: u32) -> Self {
606 self.max_iterations = n;
607 self
608 }
609}
610#[allow(dead_code)]
611#[derive(Debug, Clone)]
612pub struct RubyWorklist {
613 pub(super) items: std::collections::VecDeque<u32>,
614 pub(super) in_worklist: std::collections::HashSet<u32>,
615}
616impl RubyWorklist {
617 #[allow(dead_code)]
618 pub fn new() -> Self {
619 RubyWorklist {
620 items: std::collections::VecDeque::new(),
621 in_worklist: std::collections::HashSet::new(),
622 }
623 }
624 #[allow(dead_code)]
625 pub fn push(&mut self, item: u32) -> bool {
626 if self.in_worklist.insert(item) {
627 self.items.push_back(item);
628 true
629 } else {
630 false
631 }
632 }
633 #[allow(dead_code)]
634 pub fn pop(&mut self) -> Option<u32> {
635 let item = self.items.pop_front()?;
636 self.in_worklist.remove(&item);
637 Some(item)
638 }
639 #[allow(dead_code)]
640 pub fn is_empty(&self) -> bool {
641 self.items.is_empty()
642 }
643 #[allow(dead_code)]
644 pub fn len(&self) -> usize {
645 self.items.len()
646 }
647 #[allow(dead_code)]
648 pub fn contains(&self, item: u32) -> bool {
649 self.in_worklist.contains(&item)
650 }
651}
652#[allow(dead_code)]
653#[derive(Debug, Clone)]
654pub struct RubyDominatorTree {
655 pub idom: Vec<Option<u32>>,
656 pub dom_children: Vec<Vec<u32>>,
657 pub dom_depth: Vec<u32>,
658}
659impl RubyDominatorTree {
660 #[allow(dead_code)]
661 pub fn new(size: usize) -> Self {
662 RubyDominatorTree {
663 idom: vec![None; size],
664 dom_children: vec![Vec::new(); size],
665 dom_depth: vec![0; size],
666 }
667 }
668 #[allow(dead_code)]
669 pub fn set_idom(&mut self, node: usize, idom: u32) {
670 self.idom[node] = Some(idom);
671 }
672 #[allow(dead_code)]
673 pub fn dominates(&self, a: usize, b: usize) -> bool {
674 if a == b {
675 return true;
676 }
677 let mut cur = b;
678 loop {
679 match self.idom[cur] {
680 Some(parent) if parent as usize == a => return true,
681 Some(parent) if parent as usize == cur => return false,
682 Some(parent) => cur = parent as usize,
683 None => return false,
684 }
685 }
686 }
687 #[allow(dead_code)]
688 pub fn depth(&self, node: usize) -> u32 {
689 self.dom_depth.get(node).copied().unwrap_or(0)
690 }
691}
692#[allow(dead_code)]
694#[derive(Debug, Clone)]
695pub struct RubyMethodContract {
696 pub preconditions: Vec<String>,
697 pub postconditions: Vec<String>,
698}
699#[allow(dead_code)]
700impl RubyMethodContract {
701 pub fn new() -> Self {
702 Self {
703 preconditions: Vec::new(),
704 postconditions: Vec::new(),
705 }
706 }
707 pub fn add_pre(&mut self, cond: &str) {
708 self.preconditions.push(cond.to_string());
709 }
710 pub fn add_post(&mut self, cond: &str) {
711 self.postconditions.push(cond.to_string());
712 }
713 pub fn emit_assertions(&self) -> String {
714 let mut out = String::new();
715 for pre in &self.preconditions {
716 out.push_str(&format!("raise ArgumentError unless {}\n", pre));
717 }
718 for post in &self.postconditions {
719 out.push_str(&format!("raise RuntimeError unless {}\n", post));
720 }
721 out
722 }
723}
724#[derive(Debug, Clone, PartialEq, Eq, Hash)]
726pub enum RubyType {
727 Integer,
729 Float,
731 String,
733 Bool,
735 Nil,
737 Array(Box<RubyType>),
739 Hash(Box<RubyType>, Box<RubyType>),
741 Symbol,
743 Object(std::string::String),
745 Proc,
747}
748#[allow(dead_code)]
750#[derive(Debug, Clone)]
751pub enum RubyPattern {
752 Pin(String),
753 Variable(String),
754 Literal(String),
755 Array(Vec<RubyPattern>),
756 Hash(Vec<(String, Option<RubyPattern>)>),
757 Find(Vec<RubyPattern>),
758 Deconstruct(String, Vec<(String, RubyPattern)>),
759 Guard(Box<RubyPattern>, String),
760}
761#[allow(dead_code)]
762pub struct RubyPassRegistry {
763 pub(super) configs: Vec<RubyPassConfig>,
764 pub(super) stats: std::collections::HashMap<String, RubyPassStats>,
765}
766impl RubyPassRegistry {
767 #[allow(dead_code)]
768 pub fn new() -> Self {
769 RubyPassRegistry {
770 configs: Vec::new(),
771 stats: std::collections::HashMap::new(),
772 }
773 }
774 #[allow(dead_code)]
775 pub fn register(&mut self, config: RubyPassConfig) {
776 self.stats
777 .insert(config.pass_name.clone(), RubyPassStats::new());
778 self.configs.push(config);
779 }
780 #[allow(dead_code)]
781 pub fn enabled_passes(&self) -> Vec<&RubyPassConfig> {
782 self.configs.iter().filter(|c| c.enabled).collect()
783 }
784 #[allow(dead_code)]
785 pub fn get_stats(&self, name: &str) -> Option<&RubyPassStats> {
786 self.stats.get(name)
787 }
788 #[allow(dead_code)]
789 pub fn total_passes(&self) -> usize {
790 self.configs.len()
791 }
792 #[allow(dead_code)]
793 pub fn enabled_count(&self) -> usize {
794 self.enabled_passes().len()
795 }
796 #[allow(dead_code)]
797 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
798 if let Some(stats) = self.stats.get_mut(name) {
799 stats.record_run(changes, time_ms, iter);
800 }
801 }
802}
803#[allow(dead_code)]
805#[derive(Debug, Clone, PartialEq)]
806pub enum RubyTypeExt {
807 Integer,
808 Float,
809 String,
810 Symbol,
811 Bool,
812 Nil,
813 Array(Box<RubyTypeExt>),
814 Hash(Box<RubyTypeExt>, Box<RubyTypeExt>),
815 Proc,
816 Lambda,
817 Range,
818 Struct(String),
819 Class(String),
820 Module(String),
821 Any,
822}
823#[derive(Debug, Clone, PartialEq)]
825pub enum RubyLit {
826 Int(i64),
828 Float(f64),
830 Str(std::string::String),
832 Bool(bool),
834 Nil,
836 Symbol(std::string::String),
838}
839#[allow(dead_code)]
841#[derive(Debug, Clone)]
842pub struct RubyRescueBlock {
843 pub body: String,
844 pub rescues: Vec<(Vec<String>, Option<String>, String)>,
845 pub ensure: Option<String>,
846}
847#[allow(dead_code)]
849#[derive(Debug, Clone)]
850pub struct RubyCaseIn {
851 pub scrutinee: String,
852 pub arms: Vec<(RubyPattern, String)>,
853 pub else_body: Option<String>,
854}
855#[derive(Debug, Clone, PartialEq)]
857pub enum RubyStmt {
858 Expr(RubyExpr),
860 Assign(std::string::String, RubyExpr),
862 Def(RubyMethod),
864 Class(RubyClass),
866 Mod(RubyModule),
868 If(
870 RubyExpr,
871 Vec<RubyStmt>,
872 Vec<(RubyExpr, Vec<RubyStmt>)>,
873 Option<Vec<RubyStmt>>,
874 ),
875 While(RubyExpr, Vec<RubyStmt>),
877 Return(RubyExpr),
879 Begin(
881 Vec<RubyStmt>,
882 Option<(std::string::String, Vec<RubyStmt>)>,
883 Option<Vec<RubyStmt>>,
884 ),
885}
886#[derive(Debug, Clone, PartialEq)]
888pub enum RubyExpr {
889 Lit(RubyLit),
891 Var(std::string::String),
893 BinOp(std::string::String, Box<RubyExpr>, Box<RubyExpr>),
895 UnaryOp(std::string::String, Box<RubyExpr>),
897 Call(std::string::String, Vec<RubyExpr>),
899 MethodCall(Box<RubyExpr>, std::string::String, Vec<RubyExpr>),
901 Block(Vec<std::string::String>, Vec<RubyStmt>),
903 Lambda(Vec<std::string::String>, Vec<RubyStmt>),
905 If(Box<RubyExpr>, Box<RubyExpr>, Box<RubyExpr>),
907 Case(
909 Box<RubyExpr>,
910 Vec<(RubyExpr, RubyExpr)>,
911 Option<Box<RubyExpr>>,
912 ),
913 Array(Vec<RubyExpr>),
915 Hash(Vec<(RubyExpr, RubyExpr)>),
917 Assign(std::string::String, Box<RubyExpr>),
919 Return(Box<RubyExpr>),
921}
922#[allow(dead_code)]
924#[derive(Debug, Clone)]
925pub struct RubyExtConfig {
926 pub ruby_version: String,
927 pub use_sorbet: bool,
928 pub use_rbs: bool,
929 pub frozen_string_literals: bool,
930 pub encoding: String,
931 pub indent_size: usize,
932 pub use_keyword_args: bool,
933}
934#[allow(dead_code)]
935#[derive(Debug, Clone, PartialEq)]
936pub enum RubyPassPhase {
937 Analysis,
938 Transformation,
939 Verification,
940 Cleanup,
941}
942impl RubyPassPhase {
943 #[allow(dead_code)]
944 pub fn name(&self) -> &str {
945 match self {
946 RubyPassPhase::Analysis => "analysis",
947 RubyPassPhase::Transformation => "transformation",
948 RubyPassPhase::Verification => "verification",
949 RubyPassPhase::Cleanup => "cleanup",
950 }
951 }
952 #[allow(dead_code)]
953 pub fn is_modifying(&self) -> bool {
954 matches!(self, RubyPassPhase::Transformation | RubyPassPhase::Cleanup)
955 }
956}
957#[allow(dead_code)]
959#[derive(Debug, Clone)]
960pub struct RubyModuleDef {
961 pub name: String,
962 pub includes: Vec<String>,
963 pub methods: Vec<RubyMethodDef>,
964 pub constants: Vec<(String, String)>,
965}
966#[allow(dead_code)]
968#[derive(Debug, Clone)]
969pub struct RubyBlock {
970 pub params: Vec<String>,
971 pub body: String,
972 pub is_proc: bool,
973}
974#[allow(dead_code)]
976#[derive(Debug, Clone)]
977pub enum RubyRequire {
978 Require(String),
979 RequireRelative(String),
980 Autoload(String, String),
981}
982#[allow(dead_code)]
984#[derive(Debug, Clone)]
985pub struct RubyMethodDef {
986 pub name: String,
987 pub params: Vec<(String, Option<RubyTypeExt>)>,
988 pub return_type: Option<RubyTypeExt>,
989 pub body: String,
990 pub visibility: RubyVisibility,
991 pub is_class_method: bool,
992 pub is_abstract: bool,
993}
994#[allow(dead_code)]
996#[derive(Debug, Clone)]
997pub struct RubyLazyEnumerator {
998 pub source: String,
999 pub transforms: Vec<String>,
1000}
1001#[derive(Debug, Clone, PartialEq)]
1003pub struct RubyClass {
1004 pub name: std::string::String,
1006 pub superclass: Option<std::string::String>,
1008 pub methods: Vec<RubyMethod>,
1010 pub class_methods: Vec<RubyMethod>,
1012 pub attr_readers: Vec<std::string::String>,
1014 pub attr_writers: Vec<std::string::String>,
1016}
1017impl RubyClass {
1018 pub fn new(name: &str) -> Self {
1020 RubyClass {
1021 name: name.to_string(),
1022 superclass: None,
1023 methods: Vec::new(),
1024 class_methods: Vec::new(),
1025 attr_readers: Vec::new(),
1026 attr_writers: Vec::new(),
1027 }
1028 }
1029 pub fn with_superclass(mut self, superclass: &str) -> Self {
1031 self.superclass = Some(superclass.to_string());
1032 self
1033 }
1034 pub fn add_attr_reader(&mut self, name: &str) {
1036 self.attr_readers.push(name.to_string());
1037 }
1038 pub fn add_method(&mut self, method: RubyMethod) {
1040 self.methods.push(method);
1041 }
1042 pub fn add_class_method(&mut self, method: RubyMethod) {
1044 self.class_methods.push(method);
1045 }
1046}
1047#[allow(dead_code)]
1049#[derive(Debug, Default, Clone)]
1050pub struct RubyCodeStats {
1051 pub classes: usize,
1052 pub modules: usize,
1053 pub methods: usize,
1054 pub lambdas: usize,
1055 pub blocks: usize,
1056 pub total_lines: usize,
1057}
1058#[allow(dead_code)]
1060#[derive(Debug, Default, Clone)]
1061pub struct RubyBackendCodeStats {
1062 pub files: usize,
1063 pub classes: usize,
1064 pub modules: usize,
1065 pub methods: usize,
1066 pub lines: usize,
1067}
1068#[allow(dead_code)]
1070#[derive(Debug, Default, Clone)]
1071pub struct RubyExtEmitStats {
1072 pub bytes_written: usize,
1073 pub items_emitted: usize,
1074 pub errors: usize,
1075 pub warnings: usize,
1076 pub classes_emitted: usize,
1077 pub modules_emitted: usize,
1078 pub methods_emitted: usize,
1079}
1080#[allow(dead_code)]
1082#[derive(Debug, Clone)]
1083pub struct RubyStructDef {
1084 pub name: String,
1085 pub members: Vec<(String, Option<RubyTypeExt>)>,
1086}
1087#[derive(Debug, Clone, PartialEq)]
1089pub struct RubyModule {
1090 pub name: std::string::String,
1092 pub constants: Vec<(std::string::String, RubyExpr)>,
1094 pub functions: Vec<RubyMethod>,
1096 pub classes: Vec<RubyClass>,
1098 pub submodules: Vec<RubyModule>,
1100 pub module_function: bool,
1102}
1103impl RubyModule {
1104 pub fn new(name: &str) -> Self {
1106 RubyModule {
1107 name: name.to_string(),
1108 constants: Vec::new(),
1109 functions: Vec::new(),
1110 classes: Vec::new(),
1111 submodules: Vec::new(),
1112 module_function: true,
1113 }
1114 }
1115 pub fn emit(&self) -> std::string::String {
1117 let mut out = std::string::String::new();
1118 writeln!(out, "# frozen_string_literal: true").expect("writing to String never fails");
1119 writeln!(out).expect("writing to String never fails");
1120 self.emit_module_body(&mut out, "");
1121 out
1122 }
1123 pub(super) fn emit_module_body(&self, out: &mut std::string::String, indent: &str) {
1124 let inner = format!("{} ", indent);
1125 writeln!(out, "{}module {}", indent, self.name).expect("writing to String never fails");
1126 for (name, expr) in &self.constants {
1127 writeln!(out, "{}{} = {}", inner, name, expr).expect("writing to String never fails");
1128 }
1129 if !self.constants.is_empty() {
1130 writeln!(out).expect("writing to String never fails");
1131 }
1132 for submod in &self.submodules {
1133 submod.emit_module_body(out, &inner);
1134 writeln!(out).expect("writing to String never fails");
1135 }
1136 for class in &self.classes {
1137 let mut fmt_buf = std::string::String::new();
1138 struct Wrapper<'a>(&'a RubyClass, &'a str);
1139 impl fmt::Display for Wrapper<'_> {
1140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1141 fmt_ruby_class(self.0, self.1, f)
1142 }
1143 }
1144 write!(fmt_buf, "{}", Wrapper(class, &inner)).expect("writing to String never fails");
1145 out.push_str(&fmt_buf);
1146 writeln!(out).expect("writing to String never fails");
1147 }
1148 if !self.functions.is_empty() {
1149 if self.module_function {
1150 writeln!(out, "{}module_function", inner).expect("writing to String never fails");
1151 writeln!(out).expect("writing to String never fails");
1152 }
1153 for method in &self.functions {
1154 let mut fmt_buf = std::string::String::new();
1155 struct Wrapper<'a>(&'a RubyMethod, &'a str);
1156 impl fmt::Display for Wrapper<'_> {
1157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158 fmt_ruby_method(self.0, self.1, f)
1159 }
1160 }
1161 write!(fmt_buf, "{}", Wrapper(method, &inner))
1162 .expect("writing to String never fails");
1163 out.push_str(&fmt_buf);
1164 }
1165 }
1166 writeln!(out, "{}end", indent).expect("writing to String never fails");
1167 }
1168}
1169#[allow(dead_code)]
1171#[derive(Debug, Clone, Default)]
1172pub struct RubyFeatureFlags {
1173 pub use_data_class: bool,
1174 pub use_pattern_matching: bool,
1175 pub use_numbered_params: bool,
1176 pub use_hash_shorthand: bool,
1177}
1178pub struct RubyBackend {
1183 pub(super) tmp_counter: u64,
1185 pub(super) mangle_cache: std::collections::HashMap<std::string::String, std::string::String>,
1187}
1188impl RubyBackend {
1189 pub fn new() -> Self {
1191 RubyBackend {
1192 tmp_counter: 0,
1193 mangle_cache: std::collections::HashMap::new(),
1194 }
1195 }
1196 pub(super) fn fresh_tmp(&mut self) -> std::string::String {
1198 let n = self.tmp_counter;
1199 self.tmp_counter += 1;
1200 format!("_t{}", n)
1201 }
1202 pub fn mangle_name(&mut self, name: &str) -> std::string::String {
1204 if let Some(cached) = self.mangle_cache.get(name) {
1205 return cached.clone();
1206 }
1207 let result = ruby_mangle(name);
1208 self.mangle_cache.insert(name.to_string(), result.clone());
1209 result
1210 }
1211 pub fn compile_decl(&mut self, decl: &LcnfFunDecl) -> Result<RubyMethod, std::string::String> {
1213 let method_name = self.mangle_name(&decl.name);
1214 let params: Vec<std::string::String> = decl
1215 .params
1216 .iter()
1217 .map(|p| {
1218 if p.name.is_empty() || p.name == "_" {
1219 format!("_x{}", p.id.0)
1220 } else {
1221 ruby_mangle(&p.name)
1222 }
1223 })
1224 .collect();
1225 let mut stmts: Vec<RubyStmt> = Vec::new();
1226 let result_expr = self.compile_expr(&decl.body, &mut stmts)?;
1227 let already_returns = matches!(stmts.last(), Some(RubyStmt::Return(_)));
1228 if !already_returns {
1229 stmts.push(RubyStmt::Return(result_expr));
1230 }
1231 Ok(RubyMethod {
1232 name: method_name,
1233 params: params.iter().map(|s| s.to_string()).collect(),
1234 body: stmts,
1235 visibility: RubyVisibility::Public,
1236 })
1237 }
1238 pub fn emit_module(decls: &[LcnfFunDecl]) -> Result<std::string::String, std::string::String> {
1240 let mut backend = RubyBackend::new();
1241 let mut module = RubyModule::new("OxiLean");
1242 module.module_function = true;
1243 let mut ctor_names: HashSet<std::string::String> = HashSet::new();
1244 for decl in decls {
1245 collect_ctor_names_from_expr(&decl.body, &mut ctor_names);
1246 }
1247 for ctor in &ctor_names {
1248 let class_name = ruby_const_name(ctor);
1249 let mut class = RubyClass::new(&class_name);
1250 class.superclass = Some("Data".to_string());
1251 module.constants.push((
1252 class_name.clone(),
1253 RubyExpr::MethodCall(
1254 Box::new(RubyExpr::Var("Data".to_string())),
1255 "define".to_string(),
1256 vec![
1257 RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1258 RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1259 ],
1260 ),
1261 ));
1262 }
1263 for decl in decls {
1264 let method = backend.compile_decl(decl)?;
1265 module.functions.push(method);
1266 }
1267 let mut source = RUBY_RUNTIME.to_string();
1268 source.push('\n');
1269 source.push_str(&module.emit());
1270 Ok(source)
1271 }
1272 pub(super) fn compile_expr(
1275 &mut self,
1276 expr: &LcnfExpr,
1277 stmts: &mut Vec<RubyStmt>,
1278 ) -> Result<RubyExpr, std::string::String> {
1279 match expr {
1280 LcnfExpr::Return(arg) => Ok(self.compile_arg(arg)),
1281 LcnfExpr::Unreachable => {
1282 let raise_call = RubyExpr::Call(
1283 "raise".to_string(),
1284 vec![
1285 RubyExpr::Var("RuntimeError".to_string()),
1286 RubyExpr::Lit(RubyLit::Str("OxiLean: unreachable".to_string())),
1287 ],
1288 );
1289 stmts.push(RubyStmt::Expr(raise_call));
1290 Ok(RubyExpr::Lit(RubyLit::Nil))
1291 }
1292 LcnfExpr::TailCall(func, args) => {
1293 let callee = self.compile_arg(func);
1294 let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1295 match callee {
1296 RubyExpr::Var(name) => Ok(RubyExpr::Call(name, rb_args)),
1297 other => Ok(RubyExpr::MethodCall(
1298 Box::new(other),
1299 "call".to_string(),
1300 rb_args,
1301 )),
1302 }
1303 }
1304 LcnfExpr::Let {
1305 id,
1306 name,
1307 ty: _,
1308 value,
1309 body,
1310 } => {
1311 let var_name = if name.is_empty() || name == "_" {
1312 format!("_x{}", id.0)
1313 } else {
1314 ruby_mangle(name)
1315 };
1316 let val_expr = self.compile_let_value(value)?;
1317 stmts.push(RubyStmt::Assign(var_name, val_expr));
1318 self.compile_expr(body, stmts)
1319 }
1320 LcnfExpr::Case {
1321 scrutinee,
1322 alts,
1323 default,
1324 ..
1325 } => {
1326 let scrutinee_expr = RubyExpr::Var(format!("_x{}", scrutinee.0));
1327 let tag_expr = RubyExpr::MethodCall(
1328 Box::new(scrutinee_expr.clone()),
1329 "tag".to_string(),
1330 vec![],
1331 );
1332 let result_var = self.fresh_tmp();
1333 stmts.push(RubyStmt::Assign(
1334 result_var.clone(),
1335 RubyExpr::Lit(RubyLit::Nil),
1336 ));
1337 let mut when_branches: Vec<(RubyExpr, Vec<RubyStmt>)> = Vec::new();
1338 for alt in alts {
1339 let mut branch_stmts: Vec<RubyStmt> = Vec::new();
1340 for (field_idx, param) in alt.params.iter().enumerate() {
1341 let field_var = if param.name.is_empty() || param.name == "_" {
1342 format!("_x{}", param.id.0)
1343 } else {
1344 ruby_mangle(¶m.name)
1345 };
1346 let field_access = RubyExpr::MethodCall(
1347 Box::new(RubyExpr::MethodCall(
1348 Box::new(scrutinee_expr.clone()),
1349 "fields".to_string(),
1350 vec![],
1351 )),
1352 "[]".to_string(),
1353 vec![RubyExpr::Lit(RubyLit::Int(field_idx as i64))],
1354 );
1355 branch_stmts.push(RubyStmt::Assign(field_var, field_access));
1356 }
1357 let branch_result = self.compile_expr(&alt.body, &mut branch_stmts)?;
1358 branch_stmts.push(RubyStmt::Assign(result_var.clone(), branch_result));
1359 when_branches.push((
1360 RubyExpr::Lit(RubyLit::Int(alt.ctor_tag as i64)),
1361 branch_stmts,
1362 ));
1363 }
1364 let mut default_stmts: Vec<RubyStmt> = Vec::new();
1365 if let Some(def) = default {
1366 let def_result = self.compile_expr(def, &mut default_stmts)?;
1367 default_stmts.push(RubyStmt::Assign(result_var.clone(), def_result));
1368 } else {
1369 default_stmts.push(RubyStmt::Expr(RubyExpr::Call(
1370 "raise".to_string(),
1371 vec![
1372 RubyExpr::Var("RuntimeError".to_string()),
1373 RubyExpr::Lit(RubyLit::Str("OxiLean: unreachable".to_string())),
1374 ],
1375 )));
1376 }
1377 let mut all_stmts_flat: Vec<RubyStmt> = Vec::new();
1378 if when_branches.is_empty() {
1379 for s in default_stmts {
1380 all_stmts_flat.push(s);
1381 }
1382 } else {
1383 let (first_pat, first_body) = when_branches.remove(0);
1384 let cond = RubyExpr::BinOp(
1385 "==".to_string(),
1386 Box::new(tag_expr.clone()),
1387 Box::new(first_pat),
1388 );
1389 let elsif: Vec<(RubyExpr, Vec<RubyStmt>)> = when_branches
1390 .into_iter()
1391 .map(|(pat, body)| {
1392 let c = RubyExpr::BinOp(
1393 "==".to_string(),
1394 Box::new(tag_expr.clone()),
1395 Box::new(pat),
1396 );
1397 (c, body)
1398 })
1399 .collect();
1400 all_stmts_flat.push(RubyStmt::If(cond, first_body, elsif, Some(default_stmts)));
1401 }
1402 for s in all_stmts_flat {
1403 stmts.push(s);
1404 }
1405 Ok(RubyExpr::Var(result_var))
1406 }
1407 }
1408 }
1409 pub(super) fn compile_let_value(
1411 &mut self,
1412 value: &LcnfLetValue,
1413 ) -> Result<RubyExpr, std::string::String> {
1414 match value {
1415 LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
1416 LcnfLetValue::Erased => Ok(RubyExpr::Lit(RubyLit::Nil)),
1417 LcnfLetValue::FVar(id) => Ok(RubyExpr::Var(format!("_x{}", id.0))),
1418 LcnfLetValue::App(func, args) => {
1419 let callee = self.compile_arg(func);
1420 let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1421 match callee {
1422 RubyExpr::Var(name) => Ok(RubyExpr::Call(name, rb_args)),
1423 other => Ok(RubyExpr::MethodCall(
1424 Box::new(other),
1425 "call".to_string(),
1426 rb_args,
1427 )),
1428 }
1429 }
1430 LcnfLetValue::Proj(_name, idx, var) => {
1431 let base = RubyExpr::Var(format!("_x{}", var.0));
1432 Ok(RubyExpr::MethodCall(
1433 Box::new(RubyExpr::MethodCall(
1434 Box::new(base),
1435 "fields".to_string(),
1436 vec![],
1437 )),
1438 "[]".to_string(),
1439 vec![RubyExpr::Lit(RubyLit::Int(*idx as i64))],
1440 ))
1441 }
1442 LcnfLetValue::Ctor(name, tag, args) => {
1443 let class_name = ruby_const_name(name);
1444 let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1445 Ok(RubyExpr::MethodCall(
1446 Box::new(RubyExpr::Var(class_name)),
1447 "new".to_string(),
1448 vec![RubyExpr::Hash(vec![
1449 (
1450 RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1451 RubyExpr::Lit(RubyLit::Int(*tag as i64)),
1452 ),
1453 (
1454 RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1455 RubyExpr::Array(rb_args),
1456 ),
1457 ])],
1458 ))
1459 }
1460 LcnfLetValue::Reset(_var) => Ok(RubyExpr::Lit(RubyLit::Nil)),
1461 LcnfLetValue::Reuse(_slot, name, tag, args) => {
1462 let class_name = ruby_const_name(name);
1463 let rb_args: Vec<RubyExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1464 Ok(RubyExpr::MethodCall(
1465 Box::new(RubyExpr::Var(class_name)),
1466 "new".to_string(),
1467 vec![RubyExpr::Hash(vec![
1468 (
1469 RubyExpr::Lit(RubyLit::Symbol("tag".to_string())),
1470 RubyExpr::Lit(RubyLit::Int(*tag as i64)),
1471 ),
1472 (
1473 RubyExpr::Lit(RubyLit::Symbol("fields".to_string())),
1474 RubyExpr::Array(rb_args),
1475 ),
1476 ])],
1477 ))
1478 }
1479 }
1480 }
1481 pub(super) fn compile_arg(&self, arg: &LcnfArg) -> RubyExpr {
1483 match arg {
1484 LcnfArg::Var(id) => RubyExpr::Var(format!("_x{}", id.0)),
1485 LcnfArg::Lit(lit) => self.compile_lit(lit),
1486 LcnfArg::Erased => RubyExpr::Lit(RubyLit::Nil),
1487 LcnfArg::Type(_) => RubyExpr::Lit(RubyLit::Nil),
1488 }
1489 }
1490 pub(super) fn compile_lit(&self, lit: &LcnfLit) -> RubyExpr {
1492 match lit {
1493 LcnfLit::Nat(n) => RubyExpr::Lit(RubyLit::Int(*n as i64)),
1494 LcnfLit::Str(s) => RubyExpr::Lit(RubyLit::Str(s.clone())),
1495 }
1496 }
1497}
1498#[allow(dead_code)]
1499#[derive(Debug, Clone, Default)]
1500pub struct RubyPassStats {
1501 pub total_runs: u32,
1502 pub successful_runs: u32,
1503 pub total_changes: u64,
1504 pub time_ms: u64,
1505 pub iterations_used: u32,
1506}
1507impl RubyPassStats {
1508 #[allow(dead_code)]
1509 pub fn new() -> Self {
1510 Self::default()
1511 }
1512 #[allow(dead_code)]
1513 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1514 self.total_runs += 1;
1515 self.successful_runs += 1;
1516 self.total_changes += changes;
1517 self.time_ms += time_ms;
1518 self.iterations_used = iterations;
1519 }
1520 #[allow(dead_code)]
1521 pub fn average_changes_per_run(&self) -> f64 {
1522 if self.total_runs == 0 {
1523 return 0.0;
1524 }
1525 self.total_changes as f64 / self.total_runs as f64
1526 }
1527 #[allow(dead_code)]
1528 pub fn success_rate(&self) -> f64 {
1529 if self.total_runs == 0 {
1530 return 0.0;
1531 }
1532 self.successful_runs as f64 / self.total_runs as f64
1533 }
1534 #[allow(dead_code)]
1535 pub fn format_summary(&self) -> String {
1536 format!(
1537 "Runs: {}/{}, Changes: {}, Time: {}ms",
1538 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1539 )
1540 }
1541}
1542#[allow(dead_code)]
1543#[derive(Debug, Default)]
1544pub struct RubyDiagSink {
1545 pub diags: Vec<(RubyDiagLevel, String)>,
1546}
1547#[allow(dead_code)]
1548impl RubyDiagSink {
1549 pub fn new() -> Self {
1550 Self::default()
1551 }
1552 pub fn push(&mut self, level: RubyDiagLevel, msg: &str) {
1553 self.diags.push((level, msg.to_string()));
1554 }
1555 pub fn has_errors(&self) -> bool {
1556 self.diags.iter().any(|(l, _)| *l == RubyDiagLevel::Error)
1557 }
1558}