t-ruby 0.0.38 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +21 -17
- data/README.md +3 -4
- data/lib/t_ruby/ast_type_inferrer.rb +511 -0
- data/lib/t_ruby/body_parser.rb +561 -0
- data/lib/t_ruby/cli.rb +3 -0
- data/lib/t_ruby/compiler.rb +152 -3
- data/lib/t_ruby/config.rb +7 -0
- data/lib/t_ruby/ir.rb +90 -6
- data/lib/t_ruby/parser.rb +192 -7
- data/lib/t_ruby/type_checker.rb +17 -14
- data/lib/t_ruby/type_env.rb +127 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +26 -21
- data/lib/t_ruby.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64bc7ea0ab44e83d6580d07227fee508ae3b5e53bc0e6b19b8164857eaf31376
|
|
4
|
+
data.tar.gz: 01da8585df97030974563634d8212058a3d3325ca9bc05188ae12ccf1cdc6770
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0bf9cd9518368e5ef256dd1851285fbbb78404b16c837346ecb286bfde37d2b8f8f5738782629efb5507fb7419eaea197726ae85f1ccb9481c09abc26d80a7f
|
|
7
|
+
data.tar.gz: 67de07897cec9ef86c5df0798c4f2d44e1d54da0d8bfe799386eb544c28a4c0d5595f17dcc0c0bbc4236a44c17e76f1283fc75a9cd4452cee40f34daa86ef609
|
data/LICENSE
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
BSD 2-Clause License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025
|
|
3
|
+
Copyright (c) 2025 Yonghyun Kim and Type-Ruby contributors
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
19
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
20
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
21
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
22
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
23
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
24
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
25
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
CHANGED
|
@@ -255,10 +255,9 @@ watch:
|
|
|
255
255
|
## Links
|
|
256
256
|
|
|
257
257
|
**IDE Support**
|
|
258
|
-
- [VS Code Extension (and Cursor)](
|
|
259
|
-
- [JetBrains Plugin](
|
|
260
|
-
- [Vim
|
|
261
|
-
- [Neovim Setup](./docs/neovim/en/getting-started.md)
|
|
258
|
+
- [VS Code Extension (and Cursor)](https://github.com/type-ruby/t-ruby-vscode)
|
|
259
|
+
- [JetBrains Plugin](https://github.com/type-ruby/t-ruby-jetbrains)
|
|
260
|
+
- [Vim / Neovim](https://github.com/type-ruby/t-ruby-vim)
|
|
262
261
|
|
|
263
262
|
**Guides**
|
|
264
263
|
- [Syntax Highlighting](./docs/syntax-highlighting/en/guide.md)
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# ASTTypeInferrer - TypeScript 스타일 정적 타입 추론 엔진
|
|
5
|
+
# IR 노드를 순회하면서 타입을 추론하고 캐싱
|
|
6
|
+
class ASTTypeInferrer
|
|
7
|
+
# 리터럴 타입 매핑
|
|
8
|
+
LITERAL_TYPE_MAP = {
|
|
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
|
|
21
|
+
COMPARISON_OPS = %w[== != < > <= >= <=>].freeze
|
|
22
|
+
LOGICAL_OPS = %w[&& ||].freeze
|
|
23
|
+
|
|
24
|
+
# 내장 메서드 반환 타입
|
|
25
|
+
BUILTIN_METHODS = {
|
|
26
|
+
# String 메서드
|
|
27
|
+
%w[String upcase] => "String",
|
|
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
|
|
144
|
+
|
|
145
|
+
def initialize
|
|
146
|
+
@type_cache = {} # 노드 → 타입 캐시 (TypeScript의 지연 평가)
|
|
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)
|
|
154
|
+
# 캐시 확인 (지연 평가)
|
|
155
|
+
cache_key = node.object_id
|
|
156
|
+
return @type_cache[cache_key] if @type_cache.key?(cache_key)
|
|
157
|
+
|
|
158
|
+
type = case node
|
|
159
|
+
when IR::Literal
|
|
160
|
+
infer_literal(node)
|
|
161
|
+
when IR::VariableRef
|
|
162
|
+
infer_variable_ref(node, env)
|
|
163
|
+
when IR::BinaryOp
|
|
164
|
+
infer_binary_op(node, env)
|
|
165
|
+
when IR::UnaryOp
|
|
166
|
+
infer_unary_op(node, env)
|
|
167
|
+
when IR::MethodCall
|
|
168
|
+
infer_method_call(node, env)
|
|
169
|
+
when IR::ArrayLiteral
|
|
170
|
+
infer_array_literal(node, env)
|
|
171
|
+
when IR::HashLiteral
|
|
172
|
+
infer_hash_literal(node, env)
|
|
173
|
+
when IR::Assignment
|
|
174
|
+
infer_assignment(node, env)
|
|
175
|
+
when IR::Conditional
|
|
176
|
+
infer_conditional(node, env)
|
|
177
|
+
when IR::Block
|
|
178
|
+
infer_block(node, env)
|
|
179
|
+
when IR::Return
|
|
180
|
+
infer_return(node, env)
|
|
181
|
+
when IR::RawCode
|
|
182
|
+
"untyped"
|
|
183
|
+
else
|
|
184
|
+
"untyped"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
@type_cache[cache_key] = type
|
|
188
|
+
type
|
|
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)
|
|
196
|
+
return nil unless method_node.body
|
|
197
|
+
|
|
198
|
+
# 메서드 스코프 생성
|
|
199
|
+
env = TypeEnv.new(class_env)
|
|
200
|
+
|
|
201
|
+
# 파라미터 타입 등록
|
|
202
|
+
method_node.params.each do |param|
|
|
203
|
+
param_type = param.type_annotation&.to_rbs || "untyped"
|
|
204
|
+
env.define(param.name, param_type)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 본문에서 반환 타입 수집
|
|
208
|
+
return_types, terminated = collect_return_types(method_node.body, env)
|
|
209
|
+
|
|
210
|
+
# 암묵적 반환값 추론 (마지막 표현식) - 종료되지 않은 경우만
|
|
211
|
+
unless terminated
|
|
212
|
+
implicit_return = infer_implicit_return(method_node.body, env)
|
|
213
|
+
return_types << implicit_return if implicit_return
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# 타입 통합
|
|
217
|
+
unify_types(return_types)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
# 리터럴 타입 추론
|
|
223
|
+
def infer_literal(node)
|
|
224
|
+
LITERAL_TYPE_MAP[node.literal_type] || "untyped"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# 변수 참조 타입 추론
|
|
228
|
+
def infer_variable_ref(node, env)
|
|
229
|
+
# 상수(클래스명)는 그 자체가 타입 (예: MyClass.new 호출 시)
|
|
230
|
+
if node.scope == :constant || node.name.match?(/^[A-Z]/)
|
|
231
|
+
return node.name
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
env.lookup(node.name) || "untyped"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# 이항 연산자 타입 추론
|
|
238
|
+
def infer_binary_op(node, env)
|
|
239
|
+
left_type = infer_expression(node.left, env)
|
|
240
|
+
right_type = infer_expression(node.right, env)
|
|
241
|
+
op = node.operator
|
|
242
|
+
|
|
243
|
+
# 비교 연산자는 항상 bool
|
|
244
|
+
return "bool" if COMPARISON_OPS.include?(op)
|
|
245
|
+
|
|
246
|
+
# 논리 연산자
|
|
247
|
+
if op == "&&"
|
|
248
|
+
# && 는 falsy면 왼쪽, truthy면 오른쪽 반환
|
|
249
|
+
return right_type # 단순화: 오른쪽 타입 반환
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
if op == "||"
|
|
253
|
+
# || 는 truthy면 왼쪽, falsy면 오른쪽 반환
|
|
254
|
+
return union_type(left_type, right_type)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# 산술 연산자
|
|
258
|
+
if ARITHMETIC_OPS.include?(op)
|
|
259
|
+
return infer_arithmetic_result(left_type, right_type, op)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
"untyped"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# 산술 연산 결과 타입 추론
|
|
266
|
+
def infer_arithmetic_result(left_type, right_type, op)
|
|
267
|
+
left_base = base_type(left_type)
|
|
268
|
+
right_base = base_type(right_type)
|
|
269
|
+
|
|
270
|
+
# 문자열 연결
|
|
271
|
+
if op == "+" && (left_base == "String" || right_base == "String")
|
|
272
|
+
return "String"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# 숫자 연산
|
|
276
|
+
if numeric_type?(left_base) && numeric_type?(right_base)
|
|
277
|
+
# Float가 하나라도 있으면 Float
|
|
278
|
+
return "Float" if left_base == "Float" || right_base == "Float"
|
|
279
|
+
|
|
280
|
+
return "Integer"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# 배열 연결
|
|
284
|
+
if op == "+" && left_base.start_with?("Array")
|
|
285
|
+
return left_type
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
"untyped"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# 단항 연산자 타입 추론
|
|
292
|
+
def infer_unary_op(node, env)
|
|
293
|
+
operand_type = infer_expression(node.operand, env)
|
|
294
|
+
|
|
295
|
+
case node.operator
|
|
296
|
+
when "!"
|
|
297
|
+
"bool"
|
|
298
|
+
when "-"
|
|
299
|
+
operand_type
|
|
300
|
+
else
|
|
301
|
+
"untyped"
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# 메서드 호출 타입 추론
|
|
306
|
+
def infer_method_call(node, env)
|
|
307
|
+
# receiver 타입 추론
|
|
308
|
+
receiver_type = if node.receiver
|
|
309
|
+
infer_expression(node.receiver, env)
|
|
310
|
+
else
|
|
311
|
+
"Object"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
receiver_base = base_type(receiver_type)
|
|
315
|
+
|
|
316
|
+
# 내장 메서드 조회
|
|
317
|
+
method_key = [receiver_base, node.method_name]
|
|
318
|
+
if BUILTIN_METHODS.key?(method_key)
|
|
319
|
+
result = BUILTIN_METHODS[method_key]
|
|
320
|
+
|
|
321
|
+
# self 반환인 경우 receiver 타입 반환
|
|
322
|
+
return receiver_type if result == "self"
|
|
323
|
+
|
|
324
|
+
return result
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Object 메서드 fallback
|
|
328
|
+
object_key = ["Object", node.method_name]
|
|
329
|
+
if BUILTIN_METHODS.key?(object_key)
|
|
330
|
+
result = BUILTIN_METHODS[object_key]
|
|
331
|
+
return receiver_type if result == "self"
|
|
332
|
+
|
|
333
|
+
return result
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# new 메서드는 클래스 인스턴스 반환
|
|
337
|
+
if node.method_name == "new" && receiver_base.match?(/^[A-Z]/)
|
|
338
|
+
return receiver_base
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
"untyped"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# 배열 리터럴 타입 추론
|
|
345
|
+
def infer_array_literal(node, env)
|
|
346
|
+
return "Array[untyped]" if node.elements.empty?
|
|
347
|
+
|
|
348
|
+
element_types = node.elements.map { |e| infer_expression(e, env) }
|
|
349
|
+
unified = unify_types(element_types)
|
|
350
|
+
|
|
351
|
+
"Array[#{unified}]"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# 해시 리터럴 타입 추론
|
|
355
|
+
def infer_hash_literal(node, env)
|
|
356
|
+
return "Hash[untyped, untyped]" if node.pairs.empty?
|
|
357
|
+
|
|
358
|
+
key_types = node.pairs.map { |p| infer_expression(p.key, env) }
|
|
359
|
+
value_types = node.pairs.map { |p| infer_expression(p.value, env) }
|
|
360
|
+
|
|
361
|
+
key_type = unify_types(key_types)
|
|
362
|
+
value_type = unify_types(value_types)
|
|
363
|
+
|
|
364
|
+
"Hash[#{key_type}, #{value_type}]"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# 대입 타입 추론 (변수 타입 업데이트 및 우변 타입 반환)
|
|
368
|
+
def infer_assignment(node, env)
|
|
369
|
+
value_type = infer_expression(node.value, env)
|
|
370
|
+
|
|
371
|
+
# 변수 타입 등록
|
|
372
|
+
target = node.target
|
|
373
|
+
if target.start_with?("@") && !target.start_with?("@@")
|
|
374
|
+
env.define_instance_var(target, value_type)
|
|
375
|
+
elsif target.start_with?("@@")
|
|
376
|
+
env.define_class_var(target, value_type)
|
|
377
|
+
else
|
|
378
|
+
env.define(target, value_type)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
value_type
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# 조건문 타입 추론 (then/else 브랜치 통합)
|
|
385
|
+
def infer_conditional(node, env)
|
|
386
|
+
then_type = infer_expression(node.then_branch, env) if node.then_branch
|
|
387
|
+
else_type = infer_expression(node.else_branch, env) if node.else_branch
|
|
388
|
+
|
|
389
|
+
types = [then_type, else_type].compact
|
|
390
|
+
return "nil" if types.empty?
|
|
391
|
+
|
|
392
|
+
unify_types(types)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# 블록 타입 추론 (마지막 문장의 타입)
|
|
396
|
+
def infer_block(node, env)
|
|
397
|
+
return "nil" if node.statements.empty?
|
|
398
|
+
|
|
399
|
+
# 마지막 문장 타입 반환 (Ruby의 암묵적 반환)
|
|
400
|
+
last_stmt = node.statements.last
|
|
401
|
+
infer_expression(last_stmt, env)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# return 문 타입 추론
|
|
405
|
+
def infer_return(node, env)
|
|
406
|
+
return "nil" unless node.value
|
|
407
|
+
|
|
408
|
+
infer_expression(node.value, env)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# 본문에서 모든 return 타입 수집
|
|
412
|
+
# @return [Array<(Array<String>, Boolean)>] [수집된 타입들, 종료 여부]
|
|
413
|
+
def collect_return_types(body, env)
|
|
414
|
+
types = []
|
|
415
|
+
|
|
416
|
+
terminated = collect_returns_recursive(body, env, types)
|
|
417
|
+
|
|
418
|
+
[types, terminated]
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# @return [Boolean] true if this node terminates (contains unconditional return)
|
|
422
|
+
def collect_returns_recursive(node, env, types)
|
|
423
|
+
case node
|
|
424
|
+
when IR::Return
|
|
425
|
+
type = node.value ? infer_expression(node.value, env) : "nil"
|
|
426
|
+
types << type
|
|
427
|
+
true # return은 항상 실행 흐름 종료
|
|
428
|
+
when IR::Block
|
|
429
|
+
node.statements.each do |stmt|
|
|
430
|
+
terminated = collect_returns_recursive(stmt, env, types)
|
|
431
|
+
return true if terminated # return 이후 코드는 unreachable
|
|
432
|
+
end
|
|
433
|
+
false
|
|
434
|
+
when IR::Conditional
|
|
435
|
+
then_terminated = node.then_branch ? collect_returns_recursive(node.then_branch, env, types) : false
|
|
436
|
+
else_terminated = node.else_branch ? collect_returns_recursive(node.else_branch, env, types) : false
|
|
437
|
+
# 모든 분기가 종료되어야 조건문 전체가 종료됨
|
|
438
|
+
then_terminated && else_terminated
|
|
439
|
+
else
|
|
440
|
+
false
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# 암묵적 반환값 추론 (마지막 표현식)
|
|
445
|
+
def infer_implicit_return(body, env)
|
|
446
|
+
case body
|
|
447
|
+
when IR::Block
|
|
448
|
+
return nil if body.statements.empty?
|
|
449
|
+
|
|
450
|
+
last_stmt = body.statements.last
|
|
451
|
+
|
|
452
|
+
# return 문이면 이미 수집됨
|
|
453
|
+
return nil if last_stmt.is_a?(IR::Return)
|
|
454
|
+
|
|
455
|
+
infer_expression(last_stmt, env)
|
|
456
|
+
else
|
|
457
|
+
infer_expression(body, env)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# 타입 통합 (여러 타입을 하나로)
|
|
462
|
+
def unify_types(types)
|
|
463
|
+
types = types.compact.uniq
|
|
464
|
+
|
|
465
|
+
return "nil" if types.empty?
|
|
466
|
+
return types.first if types.length == 1
|
|
467
|
+
|
|
468
|
+
# nil과 다른 타입이 있으면 nullable
|
|
469
|
+
if types.include?("nil") && types.length == 2
|
|
470
|
+
other = types.find { |t| t != "nil" }
|
|
471
|
+
return "#{other}?" if other
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# 동일 기본 타입은 통합
|
|
475
|
+
base_types = types.map { |t| base_type(t) }.uniq
|
|
476
|
+
return types.first if base_types.length == 1
|
|
477
|
+
|
|
478
|
+
# Union 타입 생성
|
|
479
|
+
types.join(" | ")
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Union 타입 생성
|
|
483
|
+
def union_type(type1, type2)
|
|
484
|
+
return type2 if type1 == type2
|
|
485
|
+
return type2 if type1 == "nil"
|
|
486
|
+
return type1 if type2 == "nil"
|
|
487
|
+
|
|
488
|
+
"#{type1} | #{type2}"
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# 기본 타입 추출 (Generic에서)
|
|
492
|
+
def base_type(type)
|
|
493
|
+
return "untyped" if type.nil?
|
|
494
|
+
|
|
495
|
+
type_str = type.is_a?(String) ? type : type.to_rbs
|
|
496
|
+
|
|
497
|
+
# Array[X] → Array
|
|
498
|
+
return ::Regexp.last_match(1) if type_str =~ /^(\w+)\[/
|
|
499
|
+
|
|
500
|
+
# Nullable X? → X
|
|
501
|
+
return type_str[0..-2] if type_str.end_with?("?")
|
|
502
|
+
|
|
503
|
+
type_str
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# 숫자 타입인지 확인
|
|
507
|
+
def numeric_type?(type)
|
|
508
|
+
%w[Integer Float Numeric].include?(type)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|