• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

type-ruby / t-ruby / 20477240232

24 Dec 2025 03:05AM UTC coverage: 75.685% (+1.4%) from 74.246%
20477240232

push

github

yhk1038
chore: add rbs gem and fix watcher/spec for type inference

- Add rbs gem dependency for E2E test RBS validation
- Fix watcher to handle single file paths (not just directories)
- Fix ir_spec to use keyword argument format (name: String)

9 of 9 new or added lines in 1 file covered. (100.0%)

166 existing lines in 3 files now uncovered.

4974 of 6572 relevant lines covered (75.68%)

1181.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.07
/lib/t_ruby/ir.rb
1
# frozen_string_literal: true
2

3
module TRuby
1✔
4
  module IR
1✔
5
    # Base class for all IR nodes
6
    class Node
1✔
7
      attr_accessor :location, :type_info, :metadata
1✔
8

9
      def initialize(location: nil)
1✔
10
        @location = location
33,651✔
11
        @type_info = nil
33,651✔
12
        @metadata = {}
33,651✔
13
      end
14

15
      def accept(visitor)
1✔
16
        visitor.visit(self)
×
17
      end
18

19
      def children
1✔
20
        []
×
21
      end
22

23
      def transform(&block)
1✔
24
        block.call(self)
×
25
      end
26
    end
27

28
    # Program - root node containing all top-level declarations
29
    class Program < Node
1✔
30
      attr_accessor :declarations, :source_file
1✔
31

32
      def initialize(declarations: [], source_file: nil, **opts)
1✔
33
        super(**opts)
238✔
34
        @declarations = declarations
238✔
35
        @source_file = source_file
238✔
36
      end
37

38
      def children
1✔
39
        @declarations
2✔
40
      end
41
    end
42

43
    # Type alias declaration: type Name = Definition
44
    class TypeAlias < Node
1✔
45
      attr_accessor :name, :definition, :type_params
1✔
46

47
      def initialize(name:, definition:, type_params: [], **opts)
1✔
48
        super(**opts)
95✔
49
        @name = name
95✔
50
        @definition = definition
95✔
51
        @type_params = type_params
95✔
52
      end
53
    end
54

55
    # Interface declaration
56
    class Interface < Node
1✔
57
      attr_accessor :name, :members, :extends, :type_params
1✔
58

59
      def initialize(name:, members: [], extends: [], type_params: [], **opts)
1✔
60
        super(**opts)
49✔
61
        @name = name
49✔
62
        @members = members
49✔
63
        @extends = extends
49✔
64
        @type_params = type_params
49✔
65
      end
66

67
      def children
1✔
68
        @members
×
69
      end
70
    end
71

72
    # Interface member (method signature)
73
    class InterfaceMember < Node
1✔
74
      attr_accessor :name, :type_signature, :optional
1✔
75

76
      def initialize(name:, type_signature:, optional: false, **opts)
1✔
77
        super(**opts)
93✔
78
        @name = name
93✔
79
        @type_signature = type_signature
93✔
80
        @optional = optional
93✔
81
      end
82
    end
83

84
    # Class declaration
85
    class ClassDecl < Node
1✔
86
      attr_accessor :name, :superclass, :implements, :type_params, :body, :instance_vars
1✔
87

88
      def initialize(name:, superclass: nil, implements: [], type_params: [], body: [], instance_vars: [], **opts)
1✔
89
        super(**opts)
15✔
90
        @name = name
15✔
91
        @superclass = superclass
15✔
92
        @implements = implements
15✔
93
        @type_params = type_params
15✔
94
        @body = body
15✔
95
        @instance_vars = instance_vars
15✔
96
      end
97

98
      def children
1✔
UNCOV
99
        @body
×
100
      end
101
    end
102

103
    # Instance variable declaration
104
    class InstanceVariable < Node
1✔
105
      attr_accessor :name, :type_annotation
1✔
106

107
      def initialize(name:, type_annotation: nil, **opts)
1✔
108
        super(**opts)
6✔
109
        @name = name
6✔
110
        @type_annotation = type_annotation
6✔
111
      end
112
    end
113

114
    # Module declaration
115
    class ModuleDecl < Node
1✔
116
      attr_accessor :name, :body
1✔
117

118
      def initialize(name:, body: [], **opts)
1✔
UNCOV
119
        super(**opts)
×
UNCOV
120
        @name = name
×
UNCOV
121
        @body = body
×
122
      end
123

124
      def children
1✔
UNCOV
125
        @body
×
126
      end
127
    end
128

129
    # Method definition
130
    class MethodDef < Node
1✔
131
      attr_accessor :name, :params, :return_type, :body, :visibility, :type_params
1✔
132

133
      def initialize(name:, params: [], return_type: nil, body: nil, visibility: :public, type_params: [], **opts)
1✔
134
        super(**opts)
3,632✔
135
        @name = name
3,632✔
136
        @params = params
3,632✔
137
        @return_type = return_type
3,632✔
138
        @body = body
3,632✔
139
        @visibility = visibility
3,632✔
140
        @type_params = type_params
3,632✔
141
      end
142

143
      def children
1✔
UNCOV
144
        [@body].compact
×
145
      end
146
    end
147

148
    # Method parameter
149
    class Parameter < Node
1✔
150
      attr_accessor :name, :type_annotation, :default_value, :kind
1✔
151

152
      # kind: :required, :optional, :rest, :keyrest, :block
153
      def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, **opts)
1✔
154
        super(**opts)
3,595✔
155
        @name = name
3,595✔
156
        @type_annotation = type_annotation
3,595✔
157
        @default_value = default_value
3,595✔
158
        @kind = kind
3,595✔
159
      end
160
    end
161

162
    # Block of statements
163
    class Block < Node
1✔
164
      attr_accessor :statements
1✔
165

166
      def initialize(statements: [], **opts)
1✔
167
        super(**opts)
3,595✔
168
        @statements = statements
3,595✔
169
      end
170

171
      def children
1✔
UNCOV
172
        @statements
×
173
      end
174
    end
175

176
    # Variable assignment
177
    class Assignment < Node
1✔
178
      attr_accessor :target, :value, :type_annotation
1✔
179

180
      def initialize(target:, value:, type_annotation: nil, **opts)
1✔
181
        super(**opts)
17✔
182
        @target = target
17✔
183
        @value = value
17✔
184
        @type_annotation = type_annotation
17✔
185
      end
186

187
      def children
1✔
UNCOV
188
        [@value]
×
189
      end
190
    end
191

192
    # Variable reference
193
    class VariableRef < Node
1✔
194
      attr_accessor :name, :scope
1✔
195

196
      # scope: :local, :instance, :class, :global
197
      def initialize(name:, scope: :local, **opts)
1✔
198
        super(**opts)
3,551✔
199
        @name = name
3,551✔
200
        @scope = scope
3,551✔
201
      end
202
    end
203

204
    # Method call
205
    class MethodCall < Node
1✔
206
      attr_accessor :receiver, :method_name, :arguments, :block, :type_args
1✔
207

208
      def initialize(method_name:, receiver: nil, arguments: [], block: nil, type_args: [], **opts)
1✔
209
        super(**opts)
3,501✔
210
        @receiver = receiver
3,501✔
211
        @method_name = method_name
3,501✔
212
        @arguments = arguments
3,501✔
213
        @block = block
3,501✔
214
        @type_args = type_args
3,501✔
215
      end
216

217
      def children
1✔
UNCOV
218
        ([@receiver, @block] + @arguments).compact
×
219
      end
220
    end
221

222
    # Literal values
223
    class Literal < Node
1✔
224
      attr_accessor :value, :literal_type
1✔
225

226
      def initialize(value:, literal_type:, **opts)
1✔
227
        super(**opts)
119✔
228
        @value = value
119✔
229
        @literal_type = literal_type
119✔
230
      end
231
    end
232

233
    # Array literal
234
    class ArrayLiteral < Node
1✔
235
      attr_accessor :elements, :element_type
1✔
236

237
      def initialize(elements: [], element_type: nil, **opts)
1✔
238
        super(**opts)
5✔
239
        @elements = elements
5✔
240
        @element_type = element_type
5✔
241
      end
242

243
      def children
1✔
244
        @elements
×
245
      end
246
    end
247

248
    # Hash literal
249
    class HashLiteral < Node
1✔
250
      attr_accessor :pairs, :key_type, :value_type
1✔
251

252
      def initialize(pairs: [], key_type: nil, value_type: nil, **opts)
1✔
253
        super(**opts)
4✔
254
        @pairs = pairs
4✔
255
        @key_type = key_type
4✔
256
        @value_type = value_type
4✔
257
      end
258
    end
259

260
    # Hash pair (key => value)
261
    class HashPair < Node
1✔
262
      attr_accessor :key, :value
1✔
263

264
      def initialize(key:, value:, **opts)
1✔
265
        super(**opts)
5✔
266
        @key = key
5✔
267
        @value = value
5✔
268
      end
269

270
      def children
1✔
271
        [@key, @value]
×
272
      end
273
    end
274

275
    # Conditional (if/unless)
276
    class Conditional < Node
1✔
277
      attr_accessor :condition, :then_branch, :else_branch, :kind
1✔
278

279
      # kind: :if, :unless, :ternary
280
      def initialize(condition:, then_branch:, else_branch: nil, kind: :if, **opts)
1✔
UNCOV
281
        super(**opts)
×
UNCOV
282
        @condition = condition
×
UNCOV
283
        @then_branch = then_branch
×
UNCOV
284
        @else_branch = else_branch
×
UNCOV
285
        @kind = kind
×
286
      end
287

288
      def children
1✔
289
        [@condition, @then_branch, @else_branch].compact
×
290
      end
291
    end
292

293
    # Case/when expression
294
    class CaseExpr < Node
1✔
295
      attr_accessor :subject, :when_clauses, :else_clause
1✔
296

297
      def initialize(subject: nil, when_clauses: [], else_clause: nil, **opts)
1✔
UNCOV
298
        super(**opts)
×
UNCOV
299
        @subject = subject
×
UNCOV
300
        @when_clauses = when_clauses
×
UNCOV
301
        @else_clause = else_clause
×
302
      end
303

304
      def children
1✔
UNCOV
305
        ([@subject, @else_clause] + @when_clauses).compact
×
306
      end
307
    end
308

309
    # When clause
310
    class WhenClause < Node
1✔
311
      attr_accessor :patterns, :body
1✔
312

313
      def initialize(patterns:, body:, **opts)
1✔
UNCOV
314
        super(**opts)
×
UNCOV
315
        @patterns = patterns
×
UNCOV
316
        @body = body
×
317
      end
318

319
      def children
1✔
320
        [@body] + @patterns
×
321
      end
322
    end
323

324
    # Loop constructs
325
    class Loop < Node
1✔
326
      attr_accessor :kind, :condition, :body
1✔
327

328
      # kind: :while, :until, :loop
329
      def initialize(kind:, body:, condition: nil, **opts)
1✔
UNCOV
330
        super(**opts)
×
UNCOV
331
        @kind = kind
×
UNCOV
332
        @condition = condition
×
UNCOV
333
        @body = body
×
334
      end
335

336
      def children
1✔
337
        [@condition, @body].compact
×
338
      end
339
    end
340

341
    # For loop / each iteration
342
    class ForLoop < Node
1✔
343
      attr_accessor :variable, :iterable, :body
1✔
344

345
      def initialize(variable:, iterable:, body:, **opts)
1✔
UNCOV
346
        super(**opts)
×
UNCOV
347
        @variable = variable
×
UNCOV
348
        @iterable = iterable
×
UNCOV
349
        @body = body
×
350
      end
351

352
      def children
1✔
UNCOV
353
        [@iterable, @body]
×
354
      end
355
    end
356

357
    # Return statement
358
    class Return < Node
1✔
359
      attr_accessor :value
1✔
360

361
      def initialize(value: nil, **opts)
1✔
362
        super(**opts)
12✔
363
        @value = value
12✔
364
      end
365

366
      def children
1✔
UNCOV
367
        [@value].compact
×
368
      end
369
    end
370

371
    # Binary operation
372
    class BinaryOp < Node
1✔
373
      attr_accessor :operator, :left, :right
1✔
374

375
      def initialize(operator:, left:, right:, **opts)
1✔
376
        super(**opts)
40✔
377
        @operator = operator
40✔
378
        @left = left
40✔
379
        @right = right
40✔
380
      end
381

382
      def children
1✔
UNCOV
383
        [@left, @right]
×
384
      end
385
    end
386

387
    # Unary operation
388
    class UnaryOp < Node
1✔
389
      attr_accessor :operator, :operand
1✔
390

391
      def initialize(operator:, operand:, **opts)
1✔
UNCOV
392
        super(**opts)
×
UNCOV
393
        @operator = operator
×
UNCOV
394
        @operand = operand
×
395
      end
396

397
      def children
1✔
398
        [@operand]
×
399
      end
400
    end
401

402
    # Type cast / assertion
403
    class TypeCast < Node
1✔
404
      attr_accessor :expression, :target_type, :kind
1✔
405

406
      # kind: :as, :assert
407
      def initialize(expression:, target_type:, kind: :as, **opts)
1✔
UNCOV
408
        super(**opts)
×
UNCOV
409
        @expression = expression
×
UNCOV
410
        @target_type = target_type
×
UNCOV
411
        @kind = kind
×
412
      end
413

414
      def children
1✔
415
        [@expression]
×
416
      end
417
    end
418

419
    # Type guard (is_a?, respond_to?)
420
    class TypeGuard < Node
1✔
421
      attr_accessor :expression, :type_check, :narrowed_type
1✔
422

423
      def initialize(expression:, type_check:, narrowed_type: nil, **opts)
1✔
UNCOV
424
        super(**opts)
×
UNCOV
425
        @expression = expression
×
UNCOV
426
        @type_check = type_check
×
UNCOV
427
        @narrowed_type = narrowed_type
×
428
      end
429

430
      def children
1✔
431
        [@expression]
×
432
      end
433
    end
434

435
    # Lambda/Proc definition
436
    class Lambda < Node
1✔
437
      attr_accessor :params, :body, :return_type
1✔
438

439
      def initialize(body:, params: [], return_type: nil, **opts)
1✔
UNCOV
440
        super(**opts)
×
UNCOV
441
        @params = params
×
UNCOV
442
        @body = body
×
UNCOV
443
        @return_type = return_type
×
444
      end
445

446
      def children
1✔
447
        [@body]
×
448
      end
449
    end
450

451
    # Begin/rescue/ensure block
452
    class BeginBlock < Node
1✔
453
      attr_accessor :body, :rescue_clauses, :else_clause, :ensure_clause
1✔
454

455
      def initialize(body:, rescue_clauses: [], else_clause: nil, ensure_clause: nil, **opts)
1✔
UNCOV
456
        super(**opts)
×
UNCOV
457
        @body = body
×
UNCOV
458
        @rescue_clauses = rescue_clauses
×
UNCOV
459
        @else_clause = else_clause
×
UNCOV
460
        @ensure_clause = ensure_clause
×
461
      end
462

463
      def children
1✔
464
        [@body, @else_clause, @ensure_clause].compact + @rescue_clauses
×
465
      end
466
    end
467

468
    # Rescue clause
469
    class RescueClause < Node
1✔
470
      attr_accessor :exception_types, :variable, :body
1✔
471

472
      def initialize(body:, exception_types: [], variable: nil, **opts)
1✔
UNCOV
473
        super(**opts)
×
UNCOV
474
        @exception_types = exception_types
×
UNCOV
475
        @variable = variable
×
UNCOV
476
        @body = body
×
477
      end
478

479
      def children
1✔
UNCOV
480
        [@body]
×
481
      end
482
    end
483

484
    # Raw Ruby code (for passthrough)
485
    class RawCode < Node
1✔
486
      attr_accessor :code
1✔
487

488
      def initialize(code:, **opts)
1✔
489
        super(**opts)
7✔
490
        @code = code
7✔
491
      end
492
    end
493

494
    #==========================================================================
495
    # Type Representation Nodes
496
    #==========================================================================
497

498
    # Base type node
499
    class TypeNode < Node
1✔
500
      def to_rbs
1✔
UNCOV
501
        raise NotImplementedError
×
502
      end
503

504
      def to_trb
1✔
UNCOV
505
        raise NotImplementedError
×
506
      end
507
    end
508

509
    # Simple type (String, Integer, etc.)
510
    class SimpleType < TypeNode
1✔
511
      attr_accessor :name
1✔
512

513
      def initialize(name:, **opts)
1✔
514
        super(**opts)
14,919✔
515
        @name = name
14,919✔
516
      end
517

518
      def to_rbs
1✔
519
        @name
105✔
520
      end
521

522
      def to_trb
1✔
523
        @name
14✔
524
      end
525
    end
526

527
    # Generic type (Array<String>, Map<K, V>)
528
    class GenericType < TypeNode
1✔
529
      attr_accessor :base, :type_args
1✔
530

531
      def initialize(base:, type_args: [], **opts)
1✔
532
        super(**opts)
68✔
533
        @base = base
68✔
534
        @type_args = type_args
68✔
535
      end
536

537
      def to_rbs
1✔
538
        "#{@base}[#{@type_args.map(&:to_rbs).join(", ")}]"
2✔
539
      end
540

541
      def to_trb
1✔
542
        "#{@base}<#{@type_args.map(&:to_trb).join(", ")}>"
2✔
543
      end
544
    end
545

546
    # Union type (String | Integer | nil)
547
    class UnionType < TypeNode
1✔
548
      attr_accessor :types
1✔
549

550
      def initialize(types: [], **opts)
1✔
551
        super(**opts)
67✔
552
        @types = types
67✔
553
      end
554

555
      def to_rbs
1✔
556
        @types.map(&:to_rbs).join(" | ")
3✔
557
      end
558

559
      def to_trb
1✔
560
        @types.map(&:to_trb).join(" | ")
1✔
561
      end
562
    end
563

564
    # Intersection type (Readable & Writable)
565
    class IntersectionType < TypeNode
1✔
566
      attr_accessor :types
1✔
567

568
      def initialize(types: [], **opts)
1✔
569
        super(**opts)
6✔
570
        @types = types
6✔
571
      end
572

573
      def to_rbs
1✔
574
        @types.map(&:to_rbs).join(" & ")
1✔
575
      end
576

577
      def to_trb
1✔
578
        @types.map(&:to_trb).join(" & ")
1✔
579
      end
580
    end
581

582
    # Function/Proc type ((String, Integer) -> Boolean)
583
    class FunctionType < TypeNode
1✔
584
      attr_accessor :param_types, :return_type
1✔
585

586
      def initialize(return_type:, param_types: [], **opts)
1✔
587
        super(**opts)
4✔
588
        @param_types = param_types
4✔
589
        @return_type = return_type
4✔
590
      end
591

592
      def to_rbs
1✔
593
        params = @param_types.map(&:to_rbs).join(", ")
1✔
594
        "^(#{params}) -> #{@return_type.to_rbs}"
1✔
595
      end
596

597
      def to_trb
1✔
598
        params = @param_types.map(&:to_trb).join(", ")
1✔
599
        "(#{params}) -> #{@return_type.to_trb}"
1✔
600
      end
601
    end
602

603
    # Tuple type ([String, Integer, Boolean])
604
    class TupleType < TypeNode
1✔
605
      attr_accessor :element_types
1✔
606

607
      def initialize(element_types: [], **opts)
1✔
608
        super(**opts)
2✔
609
        @element_types = element_types
2✔
610
      end
611

612
      def to_rbs
1✔
613
        "[#{@element_types.map(&:to_rbs).join(", ")}]"
1✔
614
      end
615

616
      def to_trb
1✔
617
        "[#{@element_types.map(&:to_trb).join(", ")}]"
1✔
618
      end
619
    end
620

621
    # Nullable type (String?)
622
    class NullableType < TypeNode
1✔
623
      attr_accessor :inner_type
1✔
624

625
      def initialize(inner_type:, **opts)
1✔
626
        super(**opts)
5✔
627
        @inner_type = inner_type
5✔
628
      end
629

630
      def to_rbs
1✔
631
        "#{@inner_type.to_rbs}?"
1✔
632
      end
633

634
      def to_trb
1✔
635
        "#{@inner_type.to_trb}?"
1✔
636
      end
637
    end
638

639
    # Literal type (literal value as type)
640
    class LiteralType < TypeNode
1✔
641
      attr_accessor :value
1✔
642

643
      def initialize(value:, **opts)
1✔
UNCOV
644
        super(**opts)
×
UNCOV
645
        @value = value
×
646
      end
647

648
      def to_rbs
1✔
UNCOV
649
        @value.inspect
×
650
      end
651

652
      def to_trb
1✔
UNCOV
653
        @value.inspect
×
654
      end
655
    end
656

657
    #==========================================================================
658
    # Visitor Pattern
659
    #==========================================================================
660

661
    class Visitor
1✔
662
      def visit(node)
1✔
663
        method_name = "visit_#{node.class.name.split("::").last.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, "")}"
144✔
664
        if respond_to?(method_name)
144✔
665
          send(method_name, node)
143✔
666
        else
667
          visit_default(node)
1✔
668
        end
669
      end
670

671
      def visit_default(node)
1✔
672
        node.children.each { |child| visit(child) }
2✔
673
      end
674

675
      def visit_children(node)
1✔
UNCOV
676
        node.children.each { |child| visit(child) }
×
677
      end
678
    end
679

680
    #==========================================================================
681
    # IR Builder - Converts parsed AST to IR
682
    #==========================================================================
683

684
    class Builder
1✔
685
      def initialize
1✔
686
        @type_registry = {}
224✔
687
      end
688

689
      # Build IR from parser output
690
      def build(parse_result, source: nil)
1✔
691
        # Build type aliases
692
        declarations = (parse_result[:type_aliases] || []).map do |alias_info|
224✔
693
          build_type_alias(alias_info)
85✔
694
        end
695

696
        # Build interfaces
697
        (parse_result[:interfaces] || []).each do |interface_info|
224✔
698
          declarations << build_interface(interface_info)
48✔
699
        end
700

701
        # Build classes
702
        (parse_result[:classes] || []).each do |class_info|
224✔
703
          declarations << build_class(class_info)
15✔
704
        end
705

706
        # Build functions/methods
707
        (parse_result[:functions] || []).each do |func_info|
224✔
708
          declarations << build_method(func_info)
3,591✔
709
        end
710

711
        Program.new(declarations: declarations, source_file: source)
224✔
712
      end
713

714
      # Build from source code
715
      def build_from_source(source)
1✔
716
        parser = Parser.new(source)
1✔
717
        result = parser.parse
1✔
718
        build(result, source: source)
1✔
719
      end
720

721
      private
1✔
722

723
      def build_type_alias(info)
1✔
724
        TypeAlias.new(
85✔
725
          name: info[:name],
726
          definition: parse_type(info[:definition])
727
        )
728
      end
729

730
      def build_interface(info)
1✔
731
        members = (info[:members] || []).map do |member|
48✔
732
          InterfaceMember.new(
92✔
733
            name: member[:name],
734
            type_signature: parse_type(member[:type])
735
          )
736
        end
737

738
        Interface.new(
48✔
739
          name: info[:name],
740
          members: members
741
        )
742
      end
743

744
      def build_class(info)
1✔
745
        # Build methods
746
        methods = (info[:methods] || []).map do |method_info|
15✔
747
          build_method(method_info)
20✔
748
        end
749

750
        # Build instance variables
751
        instance_vars = (info[:instance_vars] || []).map do |ivar|
15✔
752
          InstanceVariable.new(
6✔
753
            name: ivar[:name],
754
            type_annotation: ivar[:type] ? parse_type(ivar[:type]) : nil
6✔
755
          )
756
        end
757

758
        ClassDecl.new(
15✔
759
          name: info[:name],
760
          superclass: info[:superclass],
761
          body: methods,
762
          instance_vars: instance_vars
763
        )
764
      end
765

766
      def build_method(info)
1✔
767
        params = (info[:params] || []).map do |param|
3,611✔
768
          Parameter.new(
3,582✔
769
            name: param[:name],
770
            type_annotation: param[:type] ? parse_type(param[:type]) : nil
3,582✔
771
          )
772
        end
773

774
        # 본문 IR이 있으면 사용 (BodyParser에서 파싱됨)
775
        body = info[:body_ir]
3,611✔
776

777
        MethodDef.new(
3,611✔
778
          name: info[:name],
779
          params: params,
780
          return_type: info[:return_type] ? parse_type(info[:return_type]) : nil,
3,611✔
781
          body: body
782
        )
783
      end
784

785
      def parse_type(type_str)
1✔
786
        return nil unless type_str
7,471✔
787

788
        type_str = type_str.strip
7,471✔
789

790
        # Union type
791
        if type_str.include?("|")
7,471✔
792
          types = type_str.split("|").map { |t| parse_type(t.strip) }
112✔
793
          return UnionType.new(types: types)
31✔
794
        end
795

796
        # Intersection type
797
        if type_str.include?("&")
7,440✔
798
          types = type_str.split("&").map { |t| parse_type(t.strip) }
6✔
799
          return IntersectionType.new(types: types)
2✔
800
        end
801

802
        # Nullable type
803
        if type_str.end_with?("?")
7,438✔
804
          inner = parse_type(type_str[0..-2])
2✔
805
          return NullableType.new(inner_type: inner)
2✔
806
        end
807

808
        # Generic type
809
        if type_str.include?("<") && type_str.include?(">")
7,436✔
810
          match = type_str.match(/^(\w+)<(.+)>$/)
31✔
811
          if match
31✔
812
            base = match[1]
31✔
813
            args = parse_generic_args(match[2])
31✔
814
            return GenericType.new(base: base, type_args: args)
31✔
815
          end
816
        end
817

818
        # Function type
819
        if type_str.include?("->")
7,405✔
820
          match = type_str.match(/^\((.*)?\)\s*->\s*(.+)$/)
1✔
821
          if match
1✔
822
            param_types = match[1] ? match[1].split(",").map { |t| parse_type(t.strip) } : []
2✔
823
            return_type = parse_type(match[2])
1✔
824
            return FunctionType.new(param_types: param_types, return_type: return_type)
1✔
825
          end
826
        end
827

828
        # Simple type
829
        SimpleType.new(name: type_str)
7,404✔
830
      end
831

832
      def parse_generic_args(args_str)
1✔
833
        args = []
31✔
834
        current = ""
31✔
835
        depth = 0
31✔
836

837
        args_str.each_char do |char|
31✔
838
          case char
168✔
839
          when "<"
840
            depth += 1
1✔
841
            current += char
1✔
842
          when ">"
843
            depth -= 1
1✔
844
            current += char
1✔
845
          when ","
846
            if depth.zero?
7✔
847
              args << parse_type(current.strip)
7✔
848
              current = ""
7✔
849
            else
UNCOV
850
              current += char
×
851
            end
852
          else
853
            current += char
159✔
854
          end
855
        end
856

857
        args << parse_type(current.strip) unless current.empty?
31✔
858
        args
31✔
859
      end
860
    end
861

862
    #==========================================================================
863
    # Code Generator - Converts IR to Ruby code
864
    #==========================================================================
865

866
    class CodeGenerator < Visitor
1✔
867
      attr_reader :output
1✔
868

869
      def initialize
1✔
870
        @output = []
2✔
871
        @indent = 0
2✔
872
      end
873

874
      def generate(program)
1✔
875
        @output = []
2✔
876
        visit(program)
2✔
877
        @output.join("\n")
2✔
878
      end
879

880
      def visit_program(node)
1✔
881
        node.declarations.each do |decl|
2✔
882
          visit(decl)
2✔
883
          @output << ""
2✔
884
        end
885
      end
886

887
      def visit_type_alias(node)
1✔
888
        # Type aliases are erased in Ruby output
889
        emit_comment("type #{node.name} = #{node.definition.to_trb}")
1✔
890
      end
891

892
      def visit_interface(node)
1✔
893
        # Interfaces are erased in Ruby output
894
        emit_comment("interface #{node.name}")
×
895
        node.members.each do |member|
×
896
          emit_comment("  #{member.name}: #{member.type_signature.to_trb}")
×
897
        end
898
        emit_comment("end")
×
899
      end
900

901
      def visit_method_def(node)
1✔
902
        params_str = node.params.map(&:name).join(", ")
1✔
903
        emit("def #{node.name}(#{params_str})")
1✔
904
        @indent += 1
1✔
905

906
        if node.body
1✔
UNCOV
907
          visit(node.body)
×
908
        end
909

910
        @indent -= 1
1✔
911
        emit("end")
1✔
912
      end
913

914
      def visit_block(node)
1✔
UNCOV
915
        node.statements.each { |stmt| visit(stmt) }
×
916
      end
917

918
      def visit_assignment(node)
1✔
UNCOV
919
        emit("#{node.target} = #{generate_expression(node.value)}")
×
920
      end
921

922
      def visit_return(node)
1✔
923
        if node.value
×
UNCOV
924
          emit("return #{generate_expression(node.value)}")
×
925
        else
UNCOV
926
          emit("return")
×
927
        end
928
      end
929

930
      def visit_conditional(node)
1✔
931
        keyword = node.kind == :unless ? "unless" : "if"
×
UNCOV
932
        emit("#{keyword} #{generate_expression(node.condition)}")
×
UNCOV
933
        @indent += 1
×
934
        visit(node.then_branch) if node.then_branch
×
UNCOV
935
        @indent -= 1
×
936

UNCOV
937
        if node.else_branch
×
938
          emit("else")
×
UNCOV
939
          @indent += 1
×
UNCOV
940
          visit(node.else_branch)
×
UNCOV
941
          @indent -= 1
×
942
        end
943

UNCOV
944
        emit("end")
×
945
      end
946

947
      def visit_raw_code(node)
1✔
UNCOV
948
        node.code.each_line do |line|
×
UNCOV
949
          emit(line.rstrip)
×
950
        end
951
      end
952

953
      private
1✔
954

955
      def emit(text)
1✔
956
        @output << (("  " * @indent) + text)
3✔
957
      end
958

959
      def emit_comment(text)
1✔
960
        emit("# #{text}")
1✔
961
      end
962

963
      def generate_expression(node)
1✔
UNCOV
964
        case node
×
965
        when Literal
UNCOV
966
          node.value.inspect
×
967
        when VariableRef
UNCOV
968
          node.name
×
969
        when MethodCall
UNCOV
970
          args = node.arguments.map { |a| generate_expression(a) }.join(", ")
×
UNCOV
971
          if node.receiver
×
UNCOV
972
            "#{generate_expression(node.receiver)}.#{node.method_name}(#{args})"
×
973
          else
UNCOV
974
            "#{node.method_name}(#{args})"
×
975
          end
976
        when BinaryOp
UNCOV
977
          "(#{generate_expression(node.left)} #{node.operator} #{generate_expression(node.right)})"
×
978
        when UnaryOp
UNCOV
979
          "#{node.operator}#{generate_expression(node.operand)}"
×
980
        else
UNCOV
981
          node.to_s
×
982
        end
983
      end
984
    end
985

986
    #==========================================================================
987
    # RBS Generator - Converts IR to RBS type definitions
988
    #==========================================================================
989

990
    class RBSGenerator < Visitor
1✔
991
      attr_reader :output
1✔
992

993
      def initialize(enable_inference: true)
1✔
994
        @output = []
64✔
995
        @indent = 0
64✔
996
        @enable_inference = enable_inference
64✔
997
        @inferrer = TRuby::ASTTypeInferrer.new if enable_inference
64✔
998
        @class_env = nil # 현재 클래스의 타입 환경
64✔
999
      end
1000

1001
      def generate(program)
1✔
1002
        @output = []
64✔
1003
        visit(program)
64✔
1004
        @output.join("\n")
64✔
1005
      end
1006

1007
      def visit_program(node)
1✔
1008
        node.declarations.each do |decl|
64✔
1009
          visit(decl)
50✔
1010
          @output << ""
50✔
1011
        end
1012
      end
1013

1014
      def visit_type_alias(node)
1✔
1015
        emit("type #{node.name} = #{node.definition.to_rbs}")
2✔
1016
      end
1017

1018
      def visit_interface(node)
1✔
1019
        emit("interface _#{node.name}")
2✔
1020
        @indent += 1
2✔
1021

1022
        node.members.each do |member|
2✔
1023
          visit(member)
4✔
1024
        end
1025

1026
        @indent -= 1
2✔
1027
        emit("end")
2✔
1028
      end
1029

1030
      def visit_interface_member(node)
1✔
1031
        emit("def #{node.name}: #{node.type_signature.to_rbs}")
4✔
1032
      end
1033

1034
      def visit_method_def(node)
1✔
1035
        params = node.params.map do |param|
51✔
1036
          type = param.type_annotation&.to_rbs || "untyped"
35✔
1037
          "#{param.name}: #{type}"
35✔
1038
        end.join(", ")
1039

1040
        # 반환 타입: 명시적 타입 > 추론된 타입 > untyped
1041
        return_type = node.return_type&.to_rbs
51✔
1042

1043
        # initialize 메서드는 특별 처리: 명시적 타입이 없으면 void
1044
        # Ruby에서 initialize는 생성자이며, 실제 인스턴스 생성은 Class.new가 담당
1045
        if node.name == "initialize" && return_type.nil?
51✔
1046
          return_type = "void"
1✔
1047
        elsif return_type.nil? && @enable_inference && @inferrer && node.body
50✔
1048
          # 명시적 반환 타입이 없으면 추론 시도
1049
          inferred = @inferrer.infer_method_return_type(node, @class_env)
14✔
1050
          return_type = inferred if inferred && inferred != "untyped"
14✔
1051
        end
1052

1053
        return_type ||= "untyped"
51✔
1054
        emit("def #{node.name}: (#{params}) -> #{return_type}")
51✔
1055
      end
1056

1057
      def visit_class_decl(node)
1✔
1058
        emit("class #{node.name}")
15✔
1059
        @indent += 1
15✔
1060

1061
        # 클래스 타입 환경 생성
1062
        @class_env = TRuby::TypeEnv.new if @enable_inference
15✔
1063

1064
        # 인스턴스 변수 타입 등록
1065
        (node.instance_vars || []).each do |ivar|
15✔
1066
          if @class_env && ivar.type_annotation
6✔
1067
            @class_env.define_instance_var("@#{ivar.name}", ivar.type_annotation.to_rbs)
6✔
1068
          end
1069

1070
          # Emit instance variables first
1071
          visit_instance_variable(ivar)
6✔
1072
        end
1073

1074
        # Add blank line between ivars and methods if both exist
1075
        @output << "" if node.instance_vars&.any? && node.body&.any?
15✔
1076

1077
        # Emit methods
1078
        node.body.each { |member| visit(member) }
35✔
1079

1080
        @class_env = nil
15✔
1081
        @indent -= 1
15✔
1082
        emit("end")
15✔
1083
      end
1084

1085
      def visit_instance_variable(node)
1✔
1086
        type = node.type_annotation&.to_rbs || "untyped"
6✔
1087
        emit("@#{node.name}: #{type}")
6✔
1088
      end
1089

1090
      private
1✔
1091

1092
      def emit(text)
1✔
1093
        @output << (("  " * @indent) + text)
97✔
1094
      end
1095
    end
1096

1097
    #==========================================================================
1098
    # Optimization Passes
1099
    #==========================================================================
1100

1101
    module Passes
1✔
1102
      # Base class for optimization passes
1103
      class Pass
1✔
1104
        attr_reader :name, :changes_made
1✔
1105

1106
        def initialize(name)
1✔
1107
          @name = name
388✔
1108
          @changes_made = 0
388✔
1109
        end
1110

1111
        def run(program)
1✔
1112
          @changes_made = 0
308✔
1113
          transform(program)
308✔
1114
          { program: program, changes: @changes_made }
308✔
1115
        end
1116

1117
        def transform(node)
1✔
UNCOV
1118
          raise NotImplementedError
×
1119
        end
1120
      end
1121

1122
      # Dead code elimination
1123
      class DeadCodeElimination < Pass
1✔
1124
        def initialize
1✔
1125
          super("dead_code_elimination")
97✔
1126
        end
1127

1128
        def transform(node)
1✔
1129
          case node
211✔
1130
          when Program
1131
            node.declarations = node.declarations.map { |d| transform(d) }.compact
140✔
1132
          when Block
1133
            node.statements = eliminate_dead_statements(node.statements)
35✔
1134
            node.statements.each { |stmt| transform(stmt) }
71✔
1135
          when MethodDef
1136
            transform(node.body) if node.body
41✔
1137
          end
1138

1139
          node
211✔
1140
        end
1141

1142
        private
1✔
1143

1144
        def eliminate_dead_statements(statements)
1✔
1145
          result = []
35✔
1146
          found_return = false
35✔
1147

1148
          statements.each do |stmt|
35✔
1149
            if found_return
38✔
1150
              @changes_made += 1
2✔
1151
              next
2✔
1152
            end
1153

1154
            result << stmt
36✔
1155
            found_return = true if stmt.is_a?(Return)
36✔
1156
          end
1157

1158
          result
35✔
1159
        end
1160
      end
1161

1162
      # Constant folding
1163
      class ConstantFolding < Pass
1✔
1164
        def initialize
1✔
1165
          super("constant_folding")
99✔
1166
        end
1167

1168
        def transform(node)
1✔
1169
          case node
181✔
1170
          when Program
1171
            node.declarations.each { |d| transform(d) }
144✔
1172
          when MethodDef
1173
            transform(node.body) if node.body
43✔
1174
          when Block
1175
            node.statements = node.statements.map { |s| fold_constants(s) }
75✔
1176
          when BinaryOp
UNCOV
1177
            fold_binary_op(node)
×
1178
          end
1179

1180
          node
181✔
1181
        end
1182

1183
        private
1✔
1184

1185
        def fold_constants(node)
1✔
1186
          case node
87✔
1187
          when BinaryOp
1188
            fold_binary_op(node)
22✔
1189
          when Assignment
UNCOV
1190
            node.value = fold_constants(node.value)
×
UNCOV
1191
            node
×
1192
          when Return
1193
            node.value = fold_constants(node.value) if node.value
5✔
1194
            node
5✔
1195
          else
1196
            node
60✔
1197
          end
1198
        end
1199

1200
        def fold_binary_op(node)
1✔
1201
          return node unless node.is_a?(BinaryOp)
22✔
1202

1203
          left = fold_constants(node.left)
22✔
1204
          right = fold_constants(node.right)
22✔
1205

1206
          if left.is_a?(Literal) && right.is_a?(Literal)
22✔
1207
            result = evaluate_op(node.operator, left.value, right.value)
4✔
1208
            if result
4✔
1209
              @changes_made += 1
3✔
1210
              return Literal.new(value: result, literal_type: result.class.to_s.downcase.to_sym)
3✔
1211
            end
1212
          end
1213

1214
          node.left = left
19✔
1215
          node.right = right
19✔
1216
          node
19✔
1217
        end
1218

1219
        def evaluate_op(op, left, right)
1✔
1220
          return nil unless left.is_a?(Numeric) && right.is_a?(Numeric)
4✔
1221

1222
          case op
4✔
1223
          when "+" then left + right
2✔
UNCOV
1224
          when "-" then left - right
×
1225
          when "*" then left * right
1✔
1226
          when "/" then right.zero? ? nil : left / right
1✔
UNCOV
1227
          when "%" then right.zero? ? nil : left % right
×
UNCOV
1228
          when "**" then left**right
×
1229
          end
1230
        rescue StandardError
UNCOV
1231
          nil
×
1232
        end
1233
      end
1234

1235
      # Type annotation cleanup
1236
      class TypeAnnotationCleanup < Pass
1✔
1237
        def initialize
1✔
1238
          super("type_annotation_cleanup")
96✔
1239
        end
1240

1241
        def transform(node)
1✔
1242
          case node
138✔
1243
          when Program
1244
            node.declarations.each { |d| transform(d) }
138✔
1245
          when MethodDef
1246
            # Remove redundant type annotations
1247
            node.params.each do |param|
40✔
1248
              if param.type_annotation && redundant_annotation?(param)
33✔
1249
                param.type_annotation = nil
×
UNCOV
1250
                @changes_made += 1
×
1251
              end
1252
            end
1253
          end
1254

1255
          node
138✔
1256
        end
1257

1258
        private
1✔
1259

1260
        def redundant_annotation?(_param)
1✔
1261
          # Consider annotation redundant if it matches the default/inferred type
1262
          false
30✔
1263
        end
1264
      end
1265

1266
      # Unused declaration removal
1267
      class UnusedDeclarationRemoval < Pass
1✔
1268
        def initialize
1✔
1269
          super("unused_declaration_removal")
96✔
1270
        end
1271

1272
        def transform(node)
1✔
1273
          return node unless node.is_a?(Program)
76✔
1274

1275
          used_types = collect_used_types(node)
76✔
1276

1277
          node.declarations = node.declarations.select do |decl|
76✔
1278
            case decl
62✔
1279
            when TypeAlias
1280
              if used_types.include?(decl.name)
4✔
1281
                true
4✔
1282
              else
UNCOV
1283
                @changes_made += 1
×
UNCOV
1284
                false
×
1285
              end
1286
            else
1287
              true
58✔
1288
            end
1289
          end
1290

1291
          node
76✔
1292
        end
1293

1294
        private
1✔
1295

1296
        def collect_used_types(program)
1✔
1297
          used = Set.new
76✔
1298

1299
          program.declarations.each do |decl|
76✔
1300
            case decl
62✔
1301
            when MethodDef
1302
              collect_types_from_method(decl, used)
40✔
1303
            when Interface
1304
              decl.members.each do |member|
3✔
1305
                collect_types_from_type(member.type_signature, used)
10✔
1306
              end
1307
            end
1308
          end
1309

1310
          used
76✔
1311
        end
1312

1313
        def collect_types_from_method(method, used)
1✔
1314
          method.params.each do |param|
40✔
1315
            collect_types_from_type(param.type_annotation, used) if param.type_annotation
33✔
1316
          end
1317
          collect_types_from_type(method.return_type, used) if method.return_type
40✔
1318
        end
1319

1320
        def collect_types_from_type(type_node, used)
1✔
1321
          case type_node
79✔
1322
          when SimpleType
1323
            used.add(type_node.name)
76✔
1324
          when GenericType
1325
            used.add(type_node.base)
1✔
1326
            type_node.type_args.each { |arg| collect_types_from_type(arg, used) }
2✔
1327
          when UnionType, IntersectionType
1328
            type_node.types.each { |t| collect_types_from_type(t, used) }
6✔
1329
          when NullableType
UNCOV
1330
            collect_types_from_type(type_node.inner_type, used)
×
1331
          when FunctionType
UNCOV
1332
            type_node.param_types.each { |t| collect_types_from_type(t, used) }
×
UNCOV
1333
            collect_types_from_type(type_node.return_type, used)
×
1334
          end
1335
        end
1336
      end
1337
    end
1338

1339
    #==========================================================================
1340
    # Optimizer - Runs optimization passes
1341
    #==========================================================================
1342

1343
    class Optimizer
1✔
1344
      DEFAULT_PASSES = [
1345
        Passes::DeadCodeElimination,
1✔
1346
        Passes::ConstantFolding,
1347
        Passes::TypeAnnotationCleanup,
1348
        Passes::UnusedDeclarationRemoval,
1349
      ].freeze
1350

1351
      attr_reader :passes, :stats
1✔
1352

1353
      def initialize(passes: DEFAULT_PASSES)
1✔
1354
        @passes = passes.map(&:new)
96✔
1355
        @stats = {}
96✔
1356
      end
1357

1358
      def optimize(program, max_iterations: 10)
1✔
1359
        @stats = { iterations: 0, total_changes: 0, pass_stats: {} }
75✔
1360

1361
        max_iterations.times do |i|
75✔
1362
          @stats[:iterations] = i + 1
76✔
1363
          changes_this_iteration = 0
76✔
1364

1365
          @passes.each do |pass|
76✔
1366
            result = pass.run(program)
304✔
1367
            program = result[:program]
304✔
1368
            changes_this_iteration += result[:changes]
304✔
1369

1370
            @stats[:pass_stats][pass.name] ||= 0
304✔
1371
            @stats[:pass_stats][pass.name] += result[:changes]
304✔
1372
          end
1373

1374
          @stats[:total_changes] += changes_this_iteration
76✔
1375
          break if changes_this_iteration.zero?
76✔
1376
        end
1377

1378
        { program: program, stats: @stats }
75✔
1379
      end
1380
    end
1381
  end
1382
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc