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

type-ruby / t-ruby / 20477536068

24 Dec 2025 03:38AM UTC coverage: 75.7% (+0.02%) from 75.685%
20477536068

Pull #12

github

web-flow
Merge 07f3087db into 067a7f88c
Pull Request #12: fix: support Unicode method names in parser and compiler

50 of 51 new or added lines in 4 files covered. (98.04%)

43 existing lines in 2 files now uncovered.

4978 of 6576 relevant lines covered (75.7%)

1182.32 hits per line

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

72.62
/lib/t_ruby/ast_type_inferrer.rb
1
# frozen_string_literal: true
2

3
module TRuby
1✔
4
  # ASTTypeInferrer - TypeScript 스타일 정적 타입 추론 엔진
5
  # IR 노드를 순회하면서 타입을 추론하고 캐싱
6
  class ASTTypeInferrer
1✔
7
    # 리터럴 타입 매핑
8
    LITERAL_TYPE_MAP = {
1✔
9
      string: "String",
10
      integer: "Integer",
11
      float: "Float",
12
      boolean: "bool",
13
      symbol: "Symbol",
14
      nil: "nil",
15
      array: "Array[untyped]",
16
      hash: "Hash[untyped, untyped]",
17
    }.freeze
18

19
    # 산술 연산자 규칙 (피연산자 타입 → 결과 타입)
20
    ARITHMETIC_OPS = %w[+ - * / % **].freeze
1✔
21
    COMPARISON_OPS = %w[== != < > <= >= <=>].freeze
1✔
22
    LOGICAL_OPS = %w[&& ||].freeze
1✔
23

24
    # 내장 메서드 반환 타입
25
    BUILTIN_METHODS = {
26
      # String 메서드
27
      %w[String upcase] => "String",
1✔
28
      %w[String downcase] => "String",
29
      %w[String capitalize] => "String",
30
      %w[String reverse] => "String",
31
      %w[String strip] => "String",
32
      %w[String chomp] => "String",
33
      %w[String chop] => "String",
34
      %w[String gsub] => "String",
35
      %w[String sub] => "String",
36
      %w[String tr] => "String",
37
      %w[String to_s] => "String",
38
      %w[String to_str] => "String",
39
      %w[String to_sym] => "Symbol",
40
      %w[String to_i] => "Integer",
41
      %w[String to_f] => "Float",
42
      %w[String length] => "Integer",
43
      %w[String size] => "Integer",
44
      %w[String bytesize] => "Integer",
45
      %w[String empty?] => "bool",
46
      %w[String include?] => "bool",
47
      %w[String start_with?] => "bool",
48
      %w[String end_with?] => "bool",
49
      %w[String match?] => "bool",
50
      %w[String split] => "Array[String]",
51
      %w[String chars] => "Array[String]",
52
      %w[String bytes] => "Array[Integer]",
53
      %w[String lines] => "Array[String]",
54

55
      # Integer 메서드
56
      %w[Integer to_s] => "String",
57
      %w[Integer to_i] => "Integer",
58
      %w[Integer to_f] => "Float",
59
      %w[Integer abs] => "Integer",
60
      %w[Integer even?] => "bool",
61
      %w[Integer odd?] => "bool",
62
      %w[Integer zero?] => "bool",
63
      %w[Integer positive?] => "bool",
64
      %w[Integer negative?] => "bool",
65
      %w[Integer times] => "Integer",
66
      %w[Integer upto] => "Enumerator[Integer]",
67
      %w[Integer downto] => "Enumerator[Integer]",
68

69
      # Float 메서드
70
      %w[Float to_s] => "String",
71
      %w[Float to_i] => "Integer",
72
      %w[Float to_f] => "Float",
73
      %w[Float abs] => "Float",
74
      %w[Float ceil] => "Integer",
75
      %w[Float floor] => "Integer",
76
      %w[Float round] => "Integer",
77
      %w[Float truncate] => "Integer",
78
      %w[Float nan?] => "bool",
79
      %w[Float infinite?] => "Integer?",
80
      %w[Float finite?] => "bool",
81
      %w[Float zero?] => "bool",
82
      %w[Float positive?] => "bool",
83
      %w[Float negative?] => "bool",
84

85
      # Array 메서드
86
      %w[Array length] => "Integer",
87
      %w[Array size] => "Integer",
88
      %w[Array count] => "Integer",
89
      %w[Array empty?] => "bool",
90
      %w[Array any?] => "bool",
91
      %w[Array all?] => "bool",
92
      %w[Array none?] => "bool",
93
      %w[Array include?] => "bool",
94
      %w[Array reverse] => "Array[untyped]",
95
      %w[Array sort] => "Array[untyped]",
96
      %w[Array uniq] => "Array[untyped]",
97
      %w[Array compact] => "Array[untyped]",
98
      %w[Array flatten] => "Array[untyped]",
99
      %w[Array join] => "String",
100
      %w[Array to_s] => "String",
101
      %w[Array to_a] => "Array[untyped]",
102

103
      # Hash 메서드
104
      %w[Hash length] => "Integer",
105
      %w[Hash size] => "Integer",
106
      %w[Hash empty?] => "bool",
107
      %w[Hash key?] => "bool",
108
      %w[Hash has_key?] => "bool",
109
      %w[Hash value?] => "bool",
110
      %w[Hash has_value?] => "bool",
111
      %w[Hash include?] => "bool",
112
      %w[Hash keys] => "Array[untyped]",
113
      %w[Hash values] => "Array[untyped]",
114
      %w[Hash to_s] => "String",
115
      %w[Hash to_a] => "Array[untyped]",
116
      %w[Hash to_h] => "Hash[untyped, untyped]",
117

118
      # Object 메서드 (모든 타입에 적용)
119
      %w[Object to_s] => "String",
120
      %w[Object inspect] => "String",
121
      %w[Object class] => "Class",
122
      %w[Object is_a?] => "bool",
123
      %w[Object kind_of?] => "bool",
124
      %w[Object instance_of?] => "bool",
125
      %w[Object respond_to?] => "bool",
126
      %w[Object nil?] => "bool",
127
      %w[Object frozen?] => "bool",
128
      %w[Object dup] => "untyped",
129
      %w[Object clone] => "untyped",
130
      %w[Object freeze] => "self",
131
      %w[Object tap] => "self",
132
      %w[Object then] => "untyped",
133
      %w[Object yield_self] => "untyped",
134

135
      # Symbol 메서드
136
      %w[Symbol to_s] => "String",
137
      %w[Symbol to_sym] => "Symbol",
138
      %w[Symbol length] => "Integer",
139
      %w[Symbol size] => "Integer",
140
      %w[Symbol empty?] => "bool",
141
    }.freeze
142

143
    attr_reader :type_cache
1✔
144

145
    def initialize
1✔
146
      @type_cache = {} # 노드 → 타입 캐시 (TypeScript의 지연 평가)
92✔
147
    end
148

149
    # 표현식 타입 추론
150
    # @param node [IR::Node] IR 노드
151
    # @param env [TypeEnv] 타입 환경
152
    # @return [String, IR::TypeNode, nil] 추론된 타입
153
    def infer_expression(node, env)
1✔
154
      # 캐시 확인 (지연 평가)
155
      cache_key = node.object_id
68✔
156
      return @type_cache[cache_key] if @type_cache.key?(cache_key)
68✔
157

158
      type = case node
68✔
159
             when IR::Literal
160
               infer_literal(node)
37✔
161
             when IR::VariableRef
162
               infer_variable_ref(node, env)
10✔
163
             when IR::BinaryOp
164
               infer_binary_op(node, env)
6✔
165
             when IR::UnaryOp
166
               infer_unary_op(node, env)
×
167
             when IR::MethodCall
168
               infer_method_call(node, env)
7✔
169
             when IR::ArrayLiteral
170
               infer_array_literal(node, env)
3✔
171
             when IR::HashLiteral
172
               infer_hash_literal(node, env)
×
173
             when IR::Assignment
174
               infer_assignment(node, env)
2✔
175
             when IR::Conditional
176
               infer_conditional(node, env)
×
177
             when IR::Block
178
               infer_block(node, env)
2✔
179
             when IR::Return
180
               infer_return(node, env)
×
181
             when IR::RawCode
182
               "untyped"
1✔
183
             else
184
               "untyped"
×
185
             end
186

187
      @type_cache[cache_key] = type
68✔
188
      type
68✔
189
    end
190

191
    # 메서드 반환 타입 추론
192
    # @param method_node [IR::MethodDef] 메서드 정의 IR
193
    # @param class_env [TypeEnv, nil] 클래스 타입 환경
194
    # @return [String, IR::TypeNode, nil] 추론된 반환 타입
195
    def infer_method_return_type(method_node, class_env = nil)
1✔
196
      return nil unless method_node.body
18✔
197

198
      # 메서드 스코프 생성
199
      env = TypeEnv.new(class_env)
17✔
200

201
      # 파라미터 타입 등록
202
      method_node.params.each do |param|
17✔
203
        param_type = param.type_annotation&.to_rbs || "untyped"
6✔
204
        env.define(param.name, param_type)
6✔
205
      end
206

207
      # 본문에서 반환 타입 수집
208
      return_types = collect_return_types(method_node.body, env)
17✔
209

210
      # 암묵적 반환값 추론 (마지막 표현식)
211
      implicit_return = infer_implicit_return(method_node.body, env)
17✔
212
      return_types << implicit_return if implicit_return
17✔
213

214
      # 타입 통합
215
      unify_types(return_types)
17✔
216
    end
217

218
    private
1✔
219

220
    # 리터럴 타입 추론
221
    def infer_literal(node)
1✔
222
      LITERAL_TYPE_MAP[node.literal_type] || "untyped"
37✔
223
    end
224

225
    # 변수 참조 타입 추론
226
    def infer_variable_ref(node, env)
1✔
227
      # 상수(클래스명)는 그 자체가 타입 (예: MyClass.new 호출 시)
228
      if node.scope == :constant || node.name.match?(/^[A-Z]/)
10✔
229
        return node.name
1✔
230
      end
231

232
      env.lookup(node.name) || "untyped"
9✔
233
    end
234

235
    # 이항 연산자 타입 추론
236
    def infer_binary_op(node, env)
1✔
237
      left_type = infer_expression(node.left, env)
6✔
238
      right_type = infer_expression(node.right, env)
6✔
239
      op = node.operator
6✔
240

241
      # 비교 연산자는 항상 bool
242
      return "bool" if COMPARISON_OPS.include?(op)
6✔
243

244
      # 논리 연산자
245
      if op == "&&"
5✔
246
        # && 는 falsy면 왼쪽, truthy면 오른쪽 반환
UNCOV
247
        return right_type # 단순화: 오른쪽 타입 반환
×
248
      end
249

250
      if op == "||"
5✔
251
        # || 는 truthy면 왼쪽, falsy면 오른쪽 반환
UNCOV
252
        return union_type(left_type, right_type)
×
253
      end
254

255
      # 산술 연산자
256
      if ARITHMETIC_OPS.include?(op)
5✔
257
        return infer_arithmetic_result(left_type, right_type, op)
5✔
258
      end
259

UNCOV
260
      "untyped"
×
261
    end
262

263
    # 산술 연산 결과 타입 추론
264
    def infer_arithmetic_result(left_type, right_type, op)
1✔
265
      left_base = base_type(left_type)
5✔
266
      right_base = base_type(right_type)
5✔
267

268
      # 문자열 연결
269
      if op == "+" && (left_base == "String" || right_base == "String")
5✔
270
        return "String"
2✔
271
      end
272

273
      # 숫자 연산
274
      if numeric_type?(left_base) && numeric_type?(right_base)
3✔
275
        # Float가 하나라도 있으면 Float
276
        return "Float" if left_base == "Float" || right_base == "Float"
2✔
277

278
        return "Integer"
1✔
279
      end
280

281
      # 배열 연결
282
      if op == "+" && left_base.start_with?("Array")
1✔
UNCOV
283
        return left_type
×
284
      end
285

286
      "untyped"
1✔
287
    end
288

289
    # 단항 연산자 타입 추론
290
    def infer_unary_op(node, env)
1✔
UNCOV
291
      operand_type = infer_expression(node.operand, env)
×
292

293
      case node.operator
×
294
      when "!"
295
        "bool"
×
296
      when "-"
297
        operand_type
×
298
      else
299
        "untyped"
×
300
      end
301
    end
302

303
    # 메서드 호출 타입 추론
304
    def infer_method_call(node, env)
1✔
305
      # receiver 타입 추론
306
      receiver_type = if node.receiver
7✔
307
                        infer_expression(node.receiver, env)
7✔
308
                      else
UNCOV
309
                        "Object"
×
310
                      end
311

312
      receiver_base = base_type(receiver_type)
7✔
313

314
      # 내장 메서드 조회
315
      method_key = [receiver_base, node.method_name]
7✔
316
      if BUILTIN_METHODS.key?(method_key)
7✔
317
        result = BUILTIN_METHODS[method_key]
6✔
318

319
        # self 반환인 경우 receiver 타입 반환
320
        return receiver_type if result == "self"
6✔
321

322
        return result
6✔
323
      end
324

325
      # Object 메서드 fallback
326
      object_key = ["Object", node.method_name]
1✔
327
      if BUILTIN_METHODS.key?(object_key)
1✔
UNCOV
328
        result = BUILTIN_METHODS[object_key]
×
UNCOV
329
        return receiver_type if result == "self"
×
330

331
        return result
×
332
      end
333

334
      # new 메서드는 클래스 인스턴스 반환
335
      if node.method_name == "new" && receiver_base.match?(/^[A-Z]/)
1✔
336
        return receiver_base
1✔
337
      end
338

UNCOV
339
      "untyped"
×
340
    end
341

342
    # 배열 리터럴 타입 추론
343
    def infer_array_literal(node, env)
1✔
344
      return "Array[untyped]" if node.elements.empty?
3✔
345

346
      element_types = node.elements.map { |e| infer_expression(e, env) }
7✔
347
      unified = unify_types(element_types)
2✔
348

349
      "Array[#{unified}]"
2✔
350
    end
351

352
    # 해시 리터럴 타입 추론
353
    def infer_hash_literal(node, env)
1✔
UNCOV
354
      return "Hash[untyped, untyped]" if node.pairs.empty?
×
355

356
      key_types = node.pairs.map { |p| infer_expression(p.key, env) }
×
UNCOV
357
      value_types = node.pairs.map { |p| infer_expression(p.value, env) }
×
358

359
      key_type = unify_types(key_types)
×
UNCOV
360
      value_type = unify_types(value_types)
×
361

362
      "Hash[#{key_type}, #{value_type}]"
×
363
    end
364

365
    # 대입 타입 추론 (변수 타입 업데이트 및 우변 타입 반환)
366
    def infer_assignment(node, env)
1✔
367
      value_type = infer_expression(node.value, env)
2✔
368

369
      # 변수 타입 등록
370
      target = node.target
2✔
371
      if target.start_with?("@") && !target.start_with?("@@")
2✔
372
        env.define_instance_var(target, value_type)
1✔
373
      elsif target.start_with?("@@")
1✔
UNCOV
374
        env.define_class_var(target, value_type)
×
375
      else
376
        env.define(target, value_type)
1✔
377
      end
378

379
      value_type
2✔
380
    end
381

382
    # 조건문 타입 추론 (then/else 브랜치 통합)
383
    def infer_conditional(node, env)
1✔
UNCOV
384
      then_type = infer_expression(node.then_branch, env) if node.then_branch
×
UNCOV
385
      else_type = infer_expression(node.else_branch, env) if node.else_branch
×
386

387
      types = [then_type, else_type].compact
×
UNCOV
388
      return "nil" if types.empty?
×
389

390
      unify_types(types)
×
391
    end
392

393
    # 블록 타입 추론 (마지막 문장의 타입)
394
    def infer_block(node, env)
1✔
395
      return "nil" if node.statements.empty?
2✔
396

397
      # 마지막 문장 타입 반환 (Ruby의 암묵적 반환)
398
      last_stmt = node.statements.last
1✔
399
      infer_expression(last_stmt, env)
1✔
400
    end
401

402
    # return 문 타입 추론
403
    def infer_return(node, env)
1✔
UNCOV
404
      return "nil" unless node.value
×
405

406
      infer_expression(node.value, env)
×
407
    end
408

409
    # 본문에서 모든 return 타입 수집
410
    def collect_return_types(body, env)
1✔
411
      types = []
17✔
412

413
      collect_returns_recursive(body, env, types)
17✔
414

415
      types
17✔
416
    end
417

418
    def collect_returns_recursive(node, env, types)
1✔
419
      case node
34✔
420
      when IR::Return
421
        type = node.value ? infer_expression(node.value, env) : "nil"
1✔
422
        types << type
1✔
423
      when IR::Block
424
        node.statements.each { |stmt| collect_returns_recursive(stmt, env, types) }
34✔
425
      when IR::Conditional
UNCOV
426
        collect_returns_recursive(node.then_branch, env, types) if node.then_branch
×
NEW
427
        collect_returns_recursive(node.else_branch, env, types) if node.else_branch
×
428
      end
429
    end
430

431
    # 암묵적 반환값 추론 (마지막 표현식)
432
    def infer_implicit_return(body, env)
1✔
433
      case body
17✔
434
      when IR::Block
435
        return nil if body.statements.empty?
17✔
436

437
        last_stmt = body.statements.last
17✔
438

439
        # return 문이면 이미 수집됨
440
        return nil if last_stmt.is_a?(IR::Return)
17✔
441

442
        infer_expression(last_stmt, env)
16✔
443
      else
UNCOV
444
        infer_expression(body, env)
×
445
      end
446
    end
447

448
    # 타입 통합 (여러 타입을 하나로)
449
    def unify_types(types)
1✔
450
      types = types.compact.uniq
19✔
451

452
      return "nil" if types.empty?
19✔
453
      return types.first if types.length == 1
19✔
454

455
      # nil과 다른 타입이 있으면 nullable
UNCOV
456
      if types.include?("nil") && types.length == 2
×
457
        other = types.find { |t| t != "nil" }
×
UNCOV
458
        return "#{other}?" if other
×
459
      end
460

461
      # 동일 기본 타입은 통합
UNCOV
462
      base_types = types.map { |t| base_type(t) }.uniq
×
UNCOV
463
      return types.first if base_types.length == 1
×
464

465
      # Union 타입 생성
UNCOV
466
      types.join(" | ")
×
467
    end
468

469
    # Union 타입 생성
470
    def union_type(type1, type2)
1✔
471
      return type2 if type1 == type2
×
UNCOV
472
      return type2 if type1 == "nil"
×
UNCOV
473
      return type1 if type2 == "nil"
×
474

475
      "#{type1} | #{type2}"
×
476
    end
477

478
    # 기본 타입 추출 (Generic에서)
479
    def base_type(type)
1✔
480
      return "untyped" if type.nil?
17✔
481

482
      type_str = type.is_a?(String) ? type : type.to_rbs
17✔
483

484
      # Array[X] → Array
485
      return ::Regexp.last_match(1) if type_str =~ /^(\w+)\[/
17✔
486

487
      # Nullable X? → X
488
      return type_str[0..-2] if type_str.end_with?("?")
17✔
489

490
      type_str
17✔
491
    end
492

493
    # 숫자 타입인지 확인
494
    def numeric_type?(type)
1✔
495
      %w[Integer Float Numeric].include?(type)
5✔
496
    end
497
  end
498
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

© 2025 Coveralls, Inc