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

type-ruby / t-ruby / 20560974963

28 Dec 2025 11:21PM UTC coverage: 79.076% (+1.7%) from 77.331%
20560974963

push

github

web-flow
refactor: migrate parser from regex to token-based parser combinator (#29)

* refactor: migrate parser from regex to token-based parser combinator

- Replace monolithic parser_combinator.rb (2833 lines) with modular architecture
- Add Scanner for tokenization with regex literal support
- Create IR::InterpolatedString for string interpolation parsing
- Fix type inference for interpolated strings (returns String)
- Add TRuby::ParseError for unified error handling
- Organize parsers into primitives/, combinators/, and token/ directories
- Each file contains exactly one class (snake_case filename matches PascalCase class)

* fix: enhance parser to support ternary, splat args, and statement expressions

- Add ternary operator (? :) parsing in ExpressionParser
- Support double splat (**opts) and single splat (*args) in method calls
- Support keyword arguments (name: value) in method calls
- Allow case/if/unless/begin as assignment right-hand side values
- Improve generic type compatibility (Array[untyped] with Array[T])

Fixes type inference errors in keyword_args samples.

* style: fix RuboCop violations and adjust metrics limits

* fix: require set for Ruby 3.1 compatibility

1849 of 2098 new or added lines in 53 files covered. (88.13%)

6 existing lines in 2 files now uncovered.

6644 of 8402 relevant lines covered (79.08%)

908.09 hits per line

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

86.52
/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
34,600✔
11
        @type_info = nil
34,600✔
12
        @metadata = {}
34,600✔
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)
281✔
34
        @declarations = declarations
281✔
35
        @source_file = source_file
281✔
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)
97✔
49
        @name = name
97✔
50
        @definition = definition
97✔
51
        @type_params = type_params
97✔
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)
54✔
61
        @name = name
54✔
62
        @members = members
54✔
63
        @extends = extends
54✔
64
        @type_params = type_params
54✔
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)
103✔
78
        @name = name
103✔
79
        @type_signature = type_signature
103✔
80
        @optional = optional
103✔
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)
27✔
90
        @name = name
27✔
91
        @superclass = superclass
27✔
92
        @implements = implements
27✔
93
        @type_params = type_params
27✔
94
        @body = body
27✔
95
        @instance_vars = instance_vars
27✔
96
      end
97

98
      def children
1✔
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)
12✔
109
        @name = name
12✔
110
        @type_annotation = type_annotation
12✔
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✔
119
        super(**opts)
2✔
120
        @name = name
2✔
121
        @body = body
2✔
122
      end
123

124
      def children
1✔
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,692✔
135
        @name = name
3,692✔
136
        @params = params
3,692✔
137
        @return_type = return_type
3,692✔
138
        @body = body
3,692✔
139
        @visibility = visibility
3,692✔
140
        @type_params = type_params
3,692✔
141
      end
142

143
      def children
1✔
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, :interface_ref
1✔
151

152
      # kind: :required, :optional, :rest, :keyrest, :block, :keyword
153
      # :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
154
      # :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
155
      # interface_ref - interface 참조 타입 (예: }: UserParams 부분)
156
      def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
1✔
157
        super(**opts)
3,650✔
158
        @name = name
3,650✔
159
        @type_annotation = type_annotation
3,650✔
160
        @default_value = default_value
3,650✔
161
        @kind = kind
3,650✔
162
        @interface_ref = interface_ref
3,650✔
163
      end
164
    end
165

166
    # Block of statements
167
    class Block < Node
1✔
168
      attr_accessor :statements
1✔
169

170
      def initialize(statements: [], **opts)
1✔
171
        super(**opts)
3,690✔
172
        @statements = statements
3,690✔
173
      end
174

175
      def children
1✔
176
        @statements
×
177
      end
178
    end
179

180
    # Variable assignment
181
    class Assignment < Node
1✔
182
      attr_accessor :target, :value, :type_annotation
1✔
183

184
      def initialize(target:, value:, type_annotation: nil, **opts)
1✔
185
        super(**opts)
29✔
186
        @target = target
29✔
187
        @value = value
29✔
188
        @type_annotation = type_annotation
29✔
189
      end
190

191
      def children
1✔
192
        [@value]
×
193
      end
194
    end
195

196
    # Variable reference
197
    class VariableRef < Node
1✔
198
      attr_accessor :name, :scope
1✔
199

200
      # scope: :local, :instance, :class, :global
201
      def initialize(name:, scope: :local, **opts)
1✔
202
        super(**opts)
3,713✔
203
        @name = name
3,713✔
204
        @scope = scope
3,713✔
205
      end
206
    end
207

208
    # Method call
209
    class MethodCall < Node
1✔
210
      attr_accessor :receiver, :method_name, :arguments, :block, :type_args
1✔
211

212
      def initialize(method_name:, receiver: nil, arguments: [], block: nil, type_args: [], **opts)
1✔
213
        super(**opts)
3,527✔
214
        @receiver = receiver
3,527✔
215
        @method_name = method_name
3,527✔
216
        @arguments = arguments
3,527✔
217
        @block = block
3,527✔
218
        @type_args = type_args
3,527✔
219
      end
220

221
      def children
1✔
222
        ([@receiver, @block] + @arguments).compact
×
223
      end
224
    end
225

226
    # Literal values
227
    class Literal < Node
1✔
228
      attr_accessor :value, :literal_type
1✔
229

230
      def initialize(value:, literal_type:, **opts)
1✔
231
        super(**opts)
239✔
232
        @value = value
239✔
233
        @literal_type = literal_type
239✔
234
      end
235
    end
236

237
    # Interpolated string (string with #{...} expressions)
238
    class InterpolatedString < Node
1✔
239
      attr_accessor :parts
1✔
240

241
      def initialize(parts: [], **opts)
1✔
242
        super(**opts)
16✔
243
        @parts = parts
16✔
244
      end
245

246
      def children
1✔
NEW
247
        @parts
×
248
      end
249
    end
250

251
    # Array literal
252
    class ArrayLiteral < Node
1✔
253
      attr_accessor :elements, :element_type
1✔
254

255
      def initialize(elements: [], element_type: nil, **opts)
1✔
256
        super(**opts)
9✔
257
        @elements = elements
9✔
258
        @element_type = element_type
9✔
259
      end
260

261
      def children
1✔
262
        @elements
×
263
      end
264
    end
265

266
    # Hash literal
267
    class HashLiteral < Node
1✔
268
      attr_accessor :pairs, :key_type, :value_type
1✔
269

270
      def initialize(pairs: [], key_type: nil, value_type: nil, **opts)
1✔
271
        super(**opts)
6✔
272
        @pairs = pairs
6✔
273
        @key_type = key_type
6✔
274
        @value_type = value_type
6✔
275
      end
276
    end
277

278
    # Hash pair (key => value)
279
    class HashPair < Node
1✔
280
      attr_accessor :key, :value
1✔
281

282
      def initialize(key:, value:, **opts)
1✔
283
        super(**opts)
8✔
284
        @key = key
8✔
285
        @value = value
8✔
286
      end
287

288
      def children
1✔
289
        [@key, @value]
×
290
      end
291
    end
292

293
    # Conditional (if/unless)
294
    class Conditional < Node
1✔
295
      attr_accessor :condition, :then_branch, :else_branch, :kind
1✔
296

297
      # kind: :if, :unless, :ternary
298
      def initialize(condition:, then_branch:, else_branch: nil, kind: :if, **opts)
1✔
299
        super(**opts)
15✔
300
        @condition = condition
15✔
301
        @then_branch = then_branch
15✔
302
        @else_branch = else_branch
15✔
303
        @kind = kind
15✔
304
      end
305

306
      def children
1✔
307
        [@condition, @then_branch, @else_branch].compact
×
308
      end
309
    end
310

311
    # Case/when expression
312
    class CaseExpr < Node
1✔
313
      attr_accessor :subject, :when_clauses, :else_clause
1✔
314

315
      def initialize(subject: nil, when_clauses: [], else_clause: nil, **opts)
1✔
316
        super(**opts)
1✔
317
        @subject = subject
1✔
318
        @when_clauses = when_clauses
1✔
319
        @else_clause = else_clause
1✔
320
      end
321

322
      def children
1✔
323
        ([@subject, @else_clause] + @when_clauses).compact
×
324
      end
325
    end
326

327
    # When clause
328
    class WhenClause < Node
1✔
329
      attr_accessor :patterns, :body
1✔
330

331
      def initialize(patterns:, body:, **opts)
1✔
332
        super(**opts)
2✔
333
        @patterns = patterns
2✔
334
        @body = body
2✔
335
      end
336

337
      def children
1✔
338
        [@body] + @patterns
×
339
      end
340
    end
341

342
    # Loop constructs
343
    class Loop < Node
1✔
344
      attr_accessor :kind, :condition, :body
1✔
345

346
      # kind: :while, :until, :loop
347
      def initialize(kind:, body:, condition: nil, **opts)
1✔
348
        super(**opts)
3✔
349
        @kind = kind
3✔
350
        @condition = condition
3✔
351
        @body = body
3✔
352
      end
353

354
      def children
1✔
355
        [@condition, @body].compact
×
356
      end
357
    end
358

359
    # For loop / each iteration
360
    class ForLoop < Node
1✔
361
      attr_accessor :variable, :iterable, :body
1✔
362

363
      def initialize(variable:, iterable:, body:, **opts)
1✔
364
        super(**opts)
×
365
        @variable = variable
×
366
        @iterable = iterable
×
367
        @body = body
×
368
      end
369

370
      def children
1✔
371
        [@iterable, @body]
×
372
      end
373
    end
374

375
    # Return statement
376
    class Return < Node
1✔
377
      attr_accessor :value
1✔
378

379
      def initialize(value: nil, **opts)
1✔
380
        super(**opts)
24✔
381
        @value = value
24✔
382
      end
383

384
      def children
1✔
385
        [@value].compact
×
386
      end
387
    end
388

389
    # Binary operation
390
    class BinaryOp < Node
1✔
391
      attr_accessor :operator, :left, :right
1✔
392

393
      def initialize(operator:, left:, right:, **opts)
1✔
394
        super(**opts)
75✔
395
        @operator = operator
75✔
396
        @left = left
75✔
397
        @right = right
75✔
398
      end
399

400
      def children
1✔
401
        [@left, @right]
×
402
      end
403
    end
404

405
    # Unary operation
406
    class UnaryOp < Node
1✔
407
      attr_accessor :operator, :operand
1✔
408

409
      def initialize(operator:, operand:, **opts)
1✔
410
        super(**opts)
1✔
411
        @operator = operator
1✔
412
        @operand = operand
1✔
413
      end
414

415
      def children
1✔
416
        [@operand]
×
417
      end
418
    end
419

420
    # Type cast / assertion
421
    class TypeCast < Node
1✔
422
      attr_accessor :expression, :target_type, :kind
1✔
423

424
      # kind: :as, :assert
425
      def initialize(expression:, target_type:, kind: :as, **opts)
1✔
426
        super(**opts)
×
427
        @expression = expression
×
428
        @target_type = target_type
×
429
        @kind = kind
×
430
      end
431

432
      def children
1✔
433
        [@expression]
×
434
      end
435
    end
436

437
    # Type guard (is_a?, respond_to?)
438
    class TypeGuard < Node
1✔
439
      attr_accessor :expression, :type_check, :narrowed_type
1✔
440

441
      def initialize(expression:, type_check:, narrowed_type: nil, **opts)
1✔
442
        super(**opts)
×
443
        @expression = expression
×
444
        @type_check = type_check
×
445
        @narrowed_type = narrowed_type
×
446
      end
447

448
      def children
1✔
449
        [@expression]
×
450
      end
451
    end
452

453
    # Lambda/Proc definition
454
    class Lambda < Node
1✔
455
      attr_accessor :params, :body, :return_type
1✔
456

457
      def initialize(body:, params: [], return_type: nil, **opts)
1✔
458
        super(**opts)
×
459
        @params = params
×
460
        @body = body
×
461
        @return_type = return_type
×
462
      end
463

464
      def children
1✔
465
        [@body]
×
466
      end
467
    end
468

469
    # Begin/rescue/ensure block
470
    class BeginBlock < Node
1✔
471
      attr_accessor :body, :rescue_clauses, :else_clause, :ensure_clause
1✔
472

473
      def initialize(body:, rescue_clauses: [], else_clause: nil, ensure_clause: nil, **opts)
1✔
474
        super(**opts)
3✔
475
        @body = body
3✔
476
        @rescue_clauses = rescue_clauses
3✔
477
        @else_clause = else_clause
3✔
478
        @ensure_clause = ensure_clause
3✔
479
      end
480

481
      def children
1✔
482
        [@body, @else_clause, @ensure_clause].compact + @rescue_clauses
×
483
      end
484
    end
485

486
    # Rescue clause
487
    class RescueClause < Node
1✔
488
      attr_accessor :exception_types, :variable, :body
1✔
489

490
      def initialize(body:, exception_types: [], variable: nil, **opts)
1✔
491
        super(**opts)
2✔
492
        @exception_types = exception_types
2✔
493
        @variable = variable
2✔
494
        @body = body
2✔
495
      end
496

497
      def children
1✔
498
        [@body]
×
499
      end
500
    end
501

502
    # Raw Ruby code (for passthrough)
503
    class RawCode < Node
1✔
504
      attr_accessor :code
1✔
505

506
      def initialize(code:, **opts)
1✔
UNCOV
507
        super(**opts)
×
UNCOV
508
        @code = code
×
509
      end
510
    end
511

512
    #==========================================================================
513
    # Type Representation Nodes
514
    #==========================================================================
515

516
    # Base type node
517
    class TypeNode < Node
1✔
518
      def to_rbs
1✔
519
        raise NotImplementedError
×
520
      end
521

522
      def to_trb
1✔
523
        raise NotImplementedError
×
524
      end
525
    end
526

527
    # Simple type (String, Integer, etc.)
528
    class SimpleType < TypeNode
1✔
529
      attr_accessor :name
1✔
530

531
      def initialize(name:, **opts)
1✔
532
        super(**opts)
15,131✔
533
        @name = name
15,131✔
534
      end
535

536
      def to_rbs
1✔
537
        @name
281✔
538
      end
539

540
      def to_trb
1✔
541
        @name
14✔
542
      end
543
    end
544

545
    # Generic type (Array<String>, Map<K, V>)
546
    class GenericType < TypeNode
1✔
547
      attr_accessor :base, :type_args
1✔
548

549
      def initialize(base:, type_args: [], **opts)
1✔
550
        super(**opts)
100✔
551
        @base = base
100✔
552
        @type_args = type_args
100✔
553
      end
554

555
      def to_rbs
1✔
556
        "#{@base}[#{@type_args.map(&:to_rbs).join(", ")}]"
32✔
557
      end
558

559
      def to_trb
1✔
560
        "#{@base}<#{@type_args.map(&:to_trb).join(", ")}>"
2✔
561
      end
562
    end
563

564
    # Union type (String | Integer | nil)
565
    class UnionType < TypeNode
1✔
566
      attr_accessor :types
1✔
567

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

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

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

582
    # Intersection type (Readable & Writable)
583
    class IntersectionType < TypeNode
1✔
584
      attr_accessor :types
1✔
585

586
      def initialize(types: [], **opts)
1✔
587
        super(**opts)
6✔
588
        @types = types
6✔
589
      end
590

591
      def to_rbs
1✔
592
        @types.map(&:to_rbs).join(" & ")
1✔
593
      end
594

595
      def to_trb
1✔
596
        @types.map(&:to_trb).join(" & ")
1✔
597
      end
598
    end
599

600
    # Function/Proc type ((String, Integer) -> Boolean)
601
    class FunctionType < TypeNode
1✔
602
      attr_accessor :param_types, :return_type
1✔
603

604
      def initialize(return_type:, param_types: [], **opts)
1✔
605
        super(**opts)
7✔
606
        @param_types = param_types
7✔
607
        @return_type = return_type
7✔
608
      end
609

610
      def to_rbs
1✔
611
        params = @param_types.map(&:to_rbs).join(", ")
1✔
612
        "^(#{params}) -> #{@return_type.to_rbs}"
1✔
613
      end
614

615
      def to_trb
1✔
616
        params = @param_types.map(&:to_trb).join(", ")
1✔
617
        "(#{params}) -> #{@return_type.to_trb}"
1✔
618
      end
619
    end
620

621
    # Tuple type ([String, Integer, Boolean])
622
    class TupleType < TypeNode
1✔
623
      attr_accessor :element_types
1✔
624

625
      def initialize(element_types: [], **opts)
1✔
626
        super(**opts)
2✔
627
        @element_types = element_types
2✔
628
      end
629

630
      def to_rbs
1✔
631
        "[#{@element_types.map(&:to_rbs).join(", ")}]"
1✔
632
      end
633

634
      def to_trb
1✔
635
        "[#{@element_types.map(&:to_trb).join(", ")}]"
1✔
636
      end
637
    end
638

639
    # Nullable type (String?)
640
    class NullableType < TypeNode
1✔
641
      attr_accessor :inner_type
1✔
642

643
      def initialize(inner_type:, **opts)
1✔
644
        super(**opts)
5✔
645
        @inner_type = inner_type
5✔
646
      end
647

648
      def to_rbs
1✔
649
        "#{@inner_type.to_rbs}?"
1✔
650
      end
651

652
      def to_trb
1✔
653
        "#{@inner_type.to_trb}?"
1✔
654
      end
655
    end
656

657
    # Literal type (literal value as type)
658
    class LiteralType < TypeNode
1✔
659
      attr_accessor :value
1✔
660

661
      def initialize(value:, **opts)
1✔
662
        super(**opts)
×
663
        @value = value
×
664
      end
665

666
      def to_rbs
1✔
667
        @value.inspect
×
668
      end
669

670
      def to_trb
1✔
671
        @value.inspect
×
672
      end
673
    end
674

675
    #==========================================================================
676
    # Visitor Pattern
677
    #==========================================================================
678

679
    class Visitor
1✔
680
      def visit(node)
1✔
681
        method_name = "visit_#{node.class.name.split("::").last.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, "")}"
210✔
682
        if respond_to?(method_name)
210✔
683
          send(method_name, node)
209✔
684
        else
685
          visit_default(node)
1✔
686
        end
687
      end
688

689
      def visit_default(node)
1✔
690
        node.children.each { |child| visit(child) }
2✔
691
      end
692

693
      def visit_children(node)
1✔
694
        node.children.each { |child| visit(child) }
×
695
      end
696
    end
697

698
    #==========================================================================
699
    # IR Builder - Converts parsed AST to IR
700
    #==========================================================================
701

702
    class Builder
1✔
703
      def initialize
1✔
704
        @type_registry = {}
266✔
705
      end
706

707
      # Build IR from parser output
708
      def build(parse_result, source: nil)
1✔
709
        # Build type aliases
710
        declarations = (parse_result[:type_aliases] || []).map do |alias_info|
266✔
711
          build_type_alias(alias_info)
85✔
712
        end
713

714
        # Build interfaces
715
        (parse_result[:interfaces] || []).each do |interface_info|
266✔
716
          declarations << build_interface(interface_info)
51✔
717
        end
718

719
        # Build classes
720
        (parse_result[:classes] || []).each do |class_info|
266✔
721
          declarations << build_class(class_info)
22✔
722
        end
723

724
        # Build functions/methods
725
        (parse_result[:functions] || []).each do |func_info|
266✔
726
          declarations << build_method(func_info)
3,626✔
727
        end
728

729
        Program.new(declarations: declarations, source_file: source)
266✔
730
      end
731

732
      # Build from source code
733
      def build_from_source(source)
1✔
734
        parser = Parser.new(source)
1✔
735
        result = parser.parse
1✔
736
        build(result, source: source)
1✔
737
      end
738

739
      private
1✔
740

741
      def build_type_alias(info)
1✔
742
        TypeAlias.new(
85✔
743
          name: info[:name],
744
          definition: parse_type(info[:definition])
745
        )
746
      end
747

748
      def build_interface(info)
1✔
749
        members = (info[:members] || []).map do |member|
51✔
750
          InterfaceMember.new(
99✔
751
            name: member[:name],
752
            type_signature: parse_type(member[:type])
753
          )
754
        end
755

756
        Interface.new(
51✔
757
          name: info[:name],
758
          members: members
759
        )
760
      end
761

762
      def build_class(info)
1✔
763
        # Build methods
764
        methods = (info[:methods] || []).map do |method_info|
22✔
765
          build_method(method_info)
30✔
766
        end
767

768
        # Build instance variables
769
        instance_vars = (info[:instance_vars] || []).map do |ivar|
22✔
770
          InstanceVariable.new(
8✔
771
            name: ivar[:name],
772
            type_annotation: ivar[:type] ? parse_type(ivar[:type]) : nil
8✔
773
          )
774
        end
775

776
        ClassDecl.new(
22✔
777
          name: info[:name],
778
          superclass: info[:superclass],
779
          body: methods,
780
          instance_vars: instance_vars
781
        )
782
      end
783

784
      def build_method(info)
1✔
785
        params = (info[:params] || []).map do |param|
3,656✔
786
          Parameter.new(
3,631✔
787
            name: param[:name],
788
            type_annotation: param[:type] ? parse_type(param[:type]) : nil
3,631✔
789
          )
790
        end
791

792
        # 본문 IR이 있으면 사용 (BodyParser에서 파싱됨)
793
        body = info[:body_ir]
3,656✔
794

795
        MethodDef.new(
3,656✔
796
          name: info[:name],
797
          params: params,
798
          return_type: info[:return_type] ? parse_type(info[:return_type]) : nil,
3,656✔
799
          body: body,
800
          visibility: info[:visibility] || :public
801
        )
802
      end
803

804
      def parse_type(type_str)
1✔
805
        return nil unless type_str
7,584✔
806

807
        type_str = type_str.strip
7,584✔
808

809
        # Union type
810
        if type_str.include?("|")
7,584✔
811
          types = type_str.split("|").map { |t| parse_type(t.strip) }
112✔
812
          return UnionType.new(types: types)
31✔
813
        end
814

815
        # Intersection type
816
        if type_str.include?("&")
7,553✔
817
          types = type_str.split("&").map { |t| parse_type(t.strip) }
6✔
818
          return IntersectionType.new(types: types)
2✔
819
        end
820

821
        # Nullable type
822
        if type_str.end_with?("?")
7,551✔
823
          inner = parse_type(type_str[0..-2])
2✔
824
          return NullableType.new(inner_type: inner)
2✔
825
        end
826

827
        # Generic type
828
        if type_str.include?("<") && type_str.include?(">")
7,549✔
829
          match = type_str.match(/^(\w+)<(.+)>$/)
47✔
830
          if match
47✔
831
            base = match[1]
47✔
832
            args = parse_generic_args(match[2])
47✔
833
            return GenericType.new(base: base, type_args: args)
47✔
834
          end
835
        end
836

837
        # Function type
838
        if type_str.include?("->")
7,502✔
839
          match = type_str.match(/^\((.*)?\)\s*->\s*(.+)$/)
1✔
840
          if match
1✔
841
            param_types = match[1] ? match[1].split(",").map { |t| parse_type(t.strip) } : []
2✔
842
            return_type = parse_type(match[2])
1✔
843
            return FunctionType.new(param_types: param_types, return_type: return_type)
1✔
844
          end
845
        end
846

847
        # Simple type
848
        SimpleType.new(name: type_str)
7,501✔
849
      end
850

851
      def parse_generic_args(args_str)
1✔
852
        args = []
47✔
853
        current = ""
47✔
854
        depth = 0
47✔
855

856
        args_str.each_char do |char|
47✔
857
          case char
300✔
858
          when "<"
859
            depth += 1
3✔
860
            current += char
3✔
861
          when ">"
862
            depth -= 1
3✔
863
            current += char
3✔
864
          when ","
865
            if depth.zero?
8✔
866
              args << parse_type(current.strip)
8✔
867
              current = ""
8✔
868
            else
869
              current += char
×
870
            end
871
          else
872
            current += char
286✔
873
          end
874
        end
875

876
        args << parse_type(current.strip) unless current.empty?
47✔
877
        args
47✔
878
      end
879
    end
880

881
    #==========================================================================
882
    # Code Generator - Converts IR to Ruby code
883
    #==========================================================================
884

885
    class CodeGenerator < Visitor
1✔
886
      attr_reader :output
1✔
887

888
      def initialize
1✔
889
        @output = []
2✔
890
        @indent = 0
2✔
891
      end
892

893
      def generate(program)
1✔
894
        @output = []
2✔
895
        visit(program)
2✔
896
        @output.join("\n")
2✔
897
      end
898

899
      def visit_program(node)
1✔
900
        node.declarations.each do |decl|
2✔
901
          visit(decl)
2✔
902
          @output << ""
2✔
903
        end
904
      end
905

906
      def visit_type_alias(node)
1✔
907
        # Type aliases are erased in Ruby output
908
        emit_comment("type #{node.name} = #{node.definition.to_trb}")
1✔
909
      end
910

911
      def visit_interface(node)
1✔
912
        # Interfaces are erased in Ruby output
913
        emit_comment("interface #{node.name}")
×
914
        node.members.each do |member|
×
915
          emit_comment("  #{member.name}: #{member.type_signature.to_trb}")
×
916
        end
917
        emit_comment("end")
×
918
      end
919

920
      def visit_method_def(node)
1✔
921
        params_str = node.params.map(&:name).join(", ")
1✔
922
        emit("def #{node.name}(#{params_str})")
1✔
923
        @indent += 1
1✔
924

925
        if node.body
1✔
926
          visit(node.body)
×
927
        end
928

929
        @indent -= 1
1✔
930
        emit("end")
1✔
931
      end
932

933
      def visit_block(node)
1✔
934
        node.statements.each { |stmt| visit(stmt) }
×
935
      end
936

937
      def visit_assignment(node)
1✔
938
        emit("#{node.target} = #{generate_expression(node.value)}")
×
939
      end
940

941
      def visit_return(node)
1✔
942
        if node.value
×
943
          emit("return #{generate_expression(node.value)}")
×
944
        else
945
          emit("return")
×
946
        end
947
      end
948

949
      def visit_conditional(node)
1✔
950
        keyword = node.kind == :unless ? "unless" : "if"
×
951
        emit("#{keyword} #{generate_expression(node.condition)}")
×
952
        @indent += 1
×
953
        visit(node.then_branch) if node.then_branch
×
954
        @indent -= 1
×
955

956
        if node.else_branch
×
957
          emit("else")
×
958
          @indent += 1
×
959
          visit(node.else_branch)
×
960
          @indent -= 1
×
961
        end
962

963
        emit("end")
×
964
      end
965

966
      def visit_raw_code(node)
1✔
967
        node.code.each_line do |line|
×
968
          emit(line.rstrip)
×
969
        end
970
      end
971

972
      private
1✔
973

974
      def emit(text)
1✔
975
        @output << (("  " * @indent) + text)
3✔
976
      end
977

978
      def emit_comment(text)
1✔
979
        emit("# #{text}")
1✔
980
      end
981

982
      def generate_expression(node)
1✔
983
        case node
×
984
        when Literal
985
          node.value.inspect
×
986
        when VariableRef
987
          node.name
×
988
        when MethodCall
989
          args = node.arguments.map { |a| generate_expression(a) }.join(", ")
×
990
          if node.receiver
×
991
            "#{generate_expression(node.receiver)}.#{node.method_name}(#{args})"
×
992
          else
993
            "#{node.method_name}(#{args})"
×
994
          end
995
        when BinaryOp
996
          "(#{generate_expression(node.left)} #{node.operator} #{generate_expression(node.right)})"
×
997
        when UnaryOp
998
          "#{node.operator}#{generate_expression(node.operand)}"
×
999
        else
1000
          node.to_s
×
1001
        end
1002
      end
1003
    end
1004

1005
    #==========================================================================
1006
    # RBS Generator - Converts IR to RBS type definitions
1007
    #==========================================================================
1008

1009
    class RBSGenerator < Visitor
1✔
1010
      attr_reader :output
1✔
1011

1012
      def initialize(enable_inference: true)
1✔
1013
        @output = []
89✔
1014
        @indent = 0
89✔
1015
        @enable_inference = enable_inference
89✔
1016
        @inferrer = TRuby::ASTTypeInferrer.new if enable_inference
89✔
1017
        @class_env = nil # 현재 클래스의 타입 환경
89✔
1018
      end
1019

1020
      def generate(program)
1✔
1021
        @output = []
89✔
1022
        visit(program)
89✔
1023
        @output.join("\n")
89✔
1024
      end
1025

1026
      def visit_program(node)
1✔
1027
        node.declarations.each do |decl|
89✔
1028
          visit(decl)
78✔
1029
          @output << ""
78✔
1030
        end
1031
      end
1032

1033
      def visit_type_alias(node)
1✔
1034
        emit("type #{node.name} = #{node.definition.to_rbs}")
2✔
1035
      end
1036

1037
      def visit_interface(node)
1✔
1038
        emit("interface _#{node.name}")
5✔
1039
        @indent += 1
5✔
1040

1041
        node.members.each do |member|
5✔
1042
          visit(member)
11✔
1043
        end
1044

1045
        @indent -= 1
5✔
1046
        emit("end")
5✔
1047
      end
1048

1049
      def visit_interface_member(node)
1✔
1050
        emit("def #{node.name}: #{node.type_signature.to_rbs}")
11✔
1051
      end
1052

1053
      def visit_method_def(node)
1✔
1054
        params = node.params.map do |param|
79✔
1055
          type = param.type_annotation&.to_rbs || "untyped"
69✔
1056
          "#{param.name}: #{type}"
69✔
1057
        end.join(", ")
1058

1059
        # 반환 타입: 명시적 타입 > 추론된 타입 > untyped
1060
        return_type = node.return_type&.to_rbs
79✔
1061

1062
        # initialize 메서드는 특별 처리: 명시적 타입이 없으면 void
1063
        # Ruby에서 initialize는 생성자이며, 실제 인스턴스 생성은 Class.new가 담당
1064
        if node.name == "initialize" && return_type.nil?
79✔
1065
          return_type = "void"
2✔
1066
        elsif return_type.nil? && @enable_inference && @inferrer && node.body
77✔
1067
          # 명시적 반환 타입이 없으면 추론 시도
1068
          inferred = @inferrer.infer_method_return_type(node, @class_env)
14✔
1069
          return_type = inferred if inferred && inferred != "untyped"
14✔
1070
        end
1071

1072
        return_type ||= "untyped"
79✔
1073
        visibility_prefix = format_visibility(node.visibility)
79✔
1074
        emit("#{visibility_prefix}def #{node.name}: (#{params}) -> #{return_type}")
79✔
1075
      end
1076

1077
      def visit_class_decl(node)
1✔
1078
        emit("class #{node.name}")
18✔
1079
        @indent += 1
18✔
1080

1081
        # 클래스 타입 환경 생성
1082
        @class_env = TRuby::TypeEnv.new if @enable_inference
18✔
1083

1084
        # 인스턴스 변수 타입 등록
1085
        (node.instance_vars || []).each do |ivar|
18✔
1086
          if @class_env && ivar.type_annotation
8✔
1087
            @class_env.define_instance_var("@#{ivar.name}", ivar.type_annotation.to_rbs)
8✔
1088
          end
1089

1090
          # Emit instance variables first
1091
          visit_instance_variable(ivar)
8✔
1092
        end
1093

1094
        # Add blank line between ivars and methods if both exist
1095
        @output << "" if node.instance_vars&.any? && node.body&.any?
18✔
1096

1097
        # Emit methods
1098
        node.body.each { |member| visit(member) }
44✔
1099

1100
        @class_env = nil
18✔
1101
        @indent -= 1
18✔
1102
        emit("end")
18✔
1103
      end
1104

1105
      def visit_instance_variable(node)
1✔
1106
        type = node.type_annotation&.to_rbs || "untyped"
8✔
1107
        emit("@#{node.name}: #{type}")
8✔
1108
      end
1109

1110
      private
1✔
1111

1112
      def emit(text)
1✔
1113
        @output << (("  " * @indent) + text)
146✔
1114
      end
1115

1116
      def format_visibility(visibility)
1✔
1117
        # RBS only supports private visibility, not protected
1118
        # See: https://github.com/ruby/rbs/issues/579
1119
        case visibility
79✔
1120
        when :private
1121
          "private "
2✔
1122
        else
1123
          ""
77✔
1124
        end
1125
      end
1126
    end
1127

1128
    #==========================================================================
1129
    # Optimization Passes
1130
    #==========================================================================
1131

1132
    module Passes
1✔
1133
      # Base class for optimization passes
1134
      class Pass
1✔
1135
        attr_reader :name, :changes_made
1✔
1136

1137
        def initialize(name)
1✔
1138
          @name = name
508✔
1139
          @changes_made = 0
508✔
1140
        end
1141

1142
        def run(program)
1✔
1143
          @changes_made = 0
416✔
1144
          transform(program)
416✔
1145
          { program: program, changes: @changes_made }
416✔
1146
        end
1147

1148
        def transform(node)
1✔
1149
          raise NotImplementedError
×
1150
        end
1151
      end
1152

1153
      # Dead code elimination
1154
      class DeadCodeElimination < Pass
1✔
1155
        def initialize
1✔
1156
          super("dead_code_elimination")
127✔
1157
        end
1158

1159
        def transform(node)
1✔
1160
          case node
317✔
1161
          when Program
1162
            node.declarations = node.declarations.map { |d| transform(d) }.compact
197✔
1163
          when Block
1164
            node.statements = eliminate_dead_statements(node.statements)
59✔
1165
            node.statements.each { |stmt| transform(stmt) }
120✔
1166
          when MethodDef
1167
            transform(node.body) if node.body
65✔
1168
          end
1169

1170
          node
317✔
1171
        end
1172

1173
        private
1✔
1174

1175
        def eliminate_dead_statements(statements)
1✔
1176
          result = []
59✔
1177
          found_return = false
59✔
1178

1179
          statements.each do |stmt|
59✔
1180
            if found_return
63✔
1181
              @changes_made += 1
2✔
1182
              next
2✔
1183
            end
1184

1185
            result << stmt
61✔
1186
            found_return = true if stmt.is_a?(Return)
61✔
1187
          end
1188

1189
          result
59✔
1190
        end
1191
      end
1192

1193
      # Constant folding
1194
      class ConstantFolding < Pass
1✔
1195
        def initialize
1✔
1196
          super("constant_folding")
129✔
1197
        end
1198

1199
        def transform(node)
1✔
1200
          case node
262✔
1201
          when Program
1202
            node.declarations.each { |d| transform(d) }
201✔
1203
          when MethodDef
1204
            transform(node.body) if node.body
67✔
1205
          when Block
1206
            node.statements = node.statements.map { |s| fold_constants(s) }
124✔
1207
          when BinaryOp
1208
            fold_binary_op(node)
×
1209
          end
1210

1211
          node
262✔
1212
        end
1213

1214
        private
1✔
1215

1216
        def fold_constants(node)
1✔
1217
          case node
107✔
1218
          when BinaryOp
1219
            fold_binary_op(node)
19✔
1220
          when Assignment
1221
            node.value = fold_constants(node.value)
1✔
1222
            node
1✔
1223
          when Return
1224
            node.value = fold_constants(node.value) if node.value
6✔
1225
            node
6✔
1226
          else
1227
            node
81✔
1228
          end
1229
        end
1230

1231
        def fold_binary_op(node)
1✔
1232
          return node unless node.is_a?(BinaryOp)
19✔
1233

1234
          left = fold_constants(node.left)
19✔
1235
          right = fold_constants(node.right)
19✔
1236

1237
          if left.is_a?(Literal) && right.is_a?(Literal)
19✔
1238
            result = evaluate_op(node.operator, left.value, right.value)
4✔
1239
            if result
4✔
1240
              @changes_made += 1
3✔
1241
              return Literal.new(value: result, literal_type: result.class.to_s.downcase.to_sym)
3✔
1242
            end
1243
          end
1244

1245
          node.left = left
16✔
1246
          node.right = right
16✔
1247
          node
16✔
1248
        end
1249

1250
        def evaluate_op(op, left, right)
1✔
1251
          return nil unless left.is_a?(Numeric) && right.is_a?(Numeric)
4✔
1252

1253
          case op
4✔
1254
          when "+" then left + right
2✔
1255
          when "-" then left - right
×
1256
          when "*" then left * right
1✔
1257
          when "/" then right.zero? ? nil : left / right
1✔
1258
          when "%" then right.zero? ? nil : left % right
×
1259
          when "**" then left**right
×
1260
          end
1261
        rescue StandardError
1262
          nil
×
1263
        end
1264
      end
1265

1266
      # Type annotation cleanup
1267
      class TypeAnnotationCleanup < Pass
1✔
1268
        def initialize
1✔
1269
          super("type_annotation_cleanup")
126✔
1270
        end
1271

1272
        def transform(node)
1✔
1273
          case node
195✔
1274
          when Program
1275
            node.declarations.each { |d| transform(d) }
195✔
1276
          when MethodDef
1277
            # Remove redundant type annotations
1278
            node.params.each do |param|
64✔
1279
              if param.type_annotation && redundant_annotation?(param)
62✔
1280
                param.type_annotation = nil
×
1281
                @changes_made += 1
×
1282
              end
1283
            end
1284
          end
1285

1286
          node
195✔
1287
        end
1288

1289
        private
1✔
1290

1291
        def redundant_annotation?(_param)
1✔
1292
          # Consider annotation redundant if it matches the default/inferred type
1293
          false
54✔
1294
        end
1295
      end
1296

1297
      # Unused declaration removal
1298
      class UnusedDeclarationRemoval < Pass
1✔
1299
        def initialize
1✔
1300
          super("unused_declaration_removal")
126✔
1301
        end
1302

1303
        def transform(node)
1✔
1304
          return node unless node.is_a?(Program)
103✔
1305

1306
          used_types = collect_used_types(node)
103✔
1307

1308
          node.declarations = node.declarations.select do |decl|
103✔
1309
            case decl
92✔
1310
            when TypeAlias
1311
              if used_types.include?(decl.name)
4✔
1312
                true
4✔
1313
              else
1314
                @changes_made += 1
×
1315
                false
×
1316
              end
1317
            else
1318
              true
88✔
1319
            end
1320
          end
1321

1322
          node
103✔
1323
        end
1324

1325
        private
1✔
1326

1327
        def collect_used_types(program)
1✔
1328
          used = Set.new
103✔
1329

1330
          program.declarations.each do |decl|
103✔
1331
            case decl
92✔
1332
            when MethodDef
1333
              collect_types_from_method(decl, used)
64✔
1334
            when Interface
1335
              decl.members.each do |member|
6✔
1336
                collect_types_from_type(member.type_signature, used)
17✔
1337
              end
1338
            end
1339
          end
1340

1341
          used
103✔
1342
        end
1343

1344
        def collect_types_from_method(method, used)
1✔
1345
          method.params.each do |param|
64✔
1346
            collect_types_from_type(param.type_annotation, used) if param.type_annotation
62✔
1347
          end
1348
          collect_types_from_type(method.return_type, used) if method.return_type
64✔
1349
        end
1350

1351
        def collect_types_from_type(type_node, used)
1✔
1352
          case type_node
151✔
1353
          when SimpleType
1354
            used.add(type_node.name)
132✔
1355
          when GenericType
1356
            used.add(type_node.base)
17✔
1357
            type_node.type_args.each { |arg| collect_types_from_type(arg, used) }
35✔
1358
          when UnionType, IntersectionType
1359
            type_node.types.each { |t| collect_types_from_type(t, used) }
6✔
1360
          when NullableType
1361
            collect_types_from_type(type_node.inner_type, used)
×
1362
          when FunctionType
1363
            type_node.param_types.each { |t| collect_types_from_type(t, used) }
×
1364
            collect_types_from_type(type_node.return_type, used)
×
1365
          end
1366
        end
1367
      end
1368
    end
1369

1370
    #==========================================================================
1371
    # Optimizer - Runs optimization passes
1372
    #==========================================================================
1373

1374
    class Optimizer
1✔
1375
      DEFAULT_PASSES = [
1376
        Passes::DeadCodeElimination,
1✔
1377
        Passes::ConstantFolding,
1378
        Passes::TypeAnnotationCleanup,
1379
        Passes::UnusedDeclarationRemoval,
1380
      ].freeze
1381

1382
      attr_reader :passes, :stats
1✔
1383

1384
      def initialize(passes: DEFAULT_PASSES)
1✔
1385
        @passes = passes.map(&:new)
126✔
1386
        @stats = {}
126✔
1387
      end
1388

1389
      def optimize(program, max_iterations: 10)
1✔
1390
        @stats = { iterations: 0, total_changes: 0, pass_stats: {} }
102✔
1391

1392
        max_iterations.times do |i|
102✔
1393
          @stats[:iterations] = i + 1
103✔
1394
          changes_this_iteration = 0
103✔
1395

1396
          @passes.each do |pass|
103✔
1397
            result = pass.run(program)
412✔
1398
            program = result[:program]
412✔
1399
            changes_this_iteration += result[:changes]
412✔
1400

1401
            @stats[:pass_stats][pass.name] ||= 0
412✔
1402
            @stats[:pass_stats][pass.name] += result[:changes]
412✔
1403
          end
1404

1405
          @stats[:total_changes] += changes_this_iteration
103✔
1406
          break if changes_this_iteration.zero?
103✔
1407
        end
1408

1409
        { program: program, stats: @stats }
102✔
1410
      end
1411
    end
1412
  end
1413
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