konpeito 0.1.0

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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,565 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types"
4
+
5
+ module Konpeito
6
+ module TypeChecker
7
+ # Type inference engine
8
+ # Traverses Prism AST and infers types for expressions
9
+ class Inferrer
10
+ attr_reader :rbs_loader, :errors
11
+
12
+ def initialize(rbs_loader)
13
+ @rbs_loader = rbs_loader
14
+ @errors = []
15
+ @scopes = [{}] # Stack of variable -> type mappings
16
+ @current_class = nil # Current class context
17
+ @instance_var_types = {} # class_name -> { ivar_name -> type }
18
+ end
19
+
20
+ # Infer the type of an AST node
21
+ def infer(node)
22
+ return Types::UNTYPED unless node
23
+
24
+ method_name = :"infer_#{node_type(node)}"
25
+ if respond_to?(method_name, true)
26
+ send(method_name, node)
27
+ else
28
+ # Unknown node type, return untyped
29
+ Types::UNTYPED
30
+ end
31
+ end
32
+
33
+ # Get the inferred type for a local variable
34
+ def variable_type(name)
35
+ @scopes.reverse_each do |scope|
36
+ return scope[name.to_sym] if scope.key?(name.to_sym)
37
+ end
38
+ nil
39
+ end
40
+
41
+ # Set the type for a local variable in current scope
42
+ def set_variable_type(name, type)
43
+ @scopes.last[name.to_sym] = type
44
+ end
45
+
46
+ private
47
+
48
+ def node_type(node)
49
+ node.class.name.split("::").last.sub(/Node$/, "").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
50
+ end
51
+
52
+ def with_scope
53
+ @scopes.push({})
54
+ result = yield
55
+ @scopes.pop
56
+ result
57
+ end
58
+
59
+ # Literal types
60
+ def infer_integer(node)
61
+ Types::INTEGER
62
+ end
63
+
64
+ def infer_float(node)
65
+ Types::FLOAT
66
+ end
67
+
68
+ def infer_string(node)
69
+ Types::STRING
70
+ end
71
+
72
+ def infer_symbol(node)
73
+ Types::SYMBOL
74
+ end
75
+
76
+ def infer_true(node)
77
+ Types::TRUE_CLASS
78
+ end
79
+
80
+ def infer_false(node)
81
+ Types::FALSE_CLASS
82
+ end
83
+
84
+ def infer_nil(node)
85
+ Types::NIL
86
+ end
87
+
88
+ def infer_interpolated_string(node)
89
+ Types::STRING
90
+ end
91
+
92
+ def infer_interpolated_symbol(node)
93
+ Types::SYMBOL
94
+ end
95
+
96
+ # Array literal
97
+ def infer_array(node)
98
+ if node.elements.empty?
99
+ Types.array(Types::UNTYPED)
100
+ else
101
+ element_types = node.elements.map { |e| infer(e) }.uniq
102
+ element_type = if element_types.size == 1
103
+ element_types.first
104
+ else
105
+ Types.union(*element_types)
106
+ end
107
+ Types.array(element_type)
108
+ end
109
+ end
110
+
111
+ # Hash literal
112
+ def infer_hash(node)
113
+ if node.elements.empty?
114
+ Types.hash_type(Types::UNTYPED, Types::UNTYPED)
115
+ else
116
+ key_types = []
117
+ value_types = []
118
+
119
+ node.elements.each do |element|
120
+ if element.is_a?(Prism::AssocNode)
121
+ key_types << infer(element.key)
122
+ value_types << infer(element.value)
123
+ end
124
+ end
125
+
126
+ key_type = key_types.uniq.size == 1 ? key_types.first : Types.union(*key_types.uniq)
127
+ value_type = value_types.uniq.size == 1 ? value_types.first : Types.union(*value_types.uniq)
128
+
129
+ Types.hash_type(key_type, value_type)
130
+ end
131
+ end
132
+
133
+ # Range literal
134
+ def infer_range(node)
135
+ Types::ClassInstance.new(:Range, [infer(node.left)])
136
+ end
137
+
138
+ # Variable access
139
+ def infer_local_variable_read(node)
140
+ variable_type(node.name) || Types::UNTYPED
141
+ end
142
+
143
+ def infer_local_variable_write(node)
144
+ value_type = infer(node.value)
145
+ set_variable_type(node.name, value_type)
146
+ value_type
147
+ end
148
+
149
+ def infer_local_variable_and_write(node)
150
+ existing = variable_type(node.name) || Types::UNTYPED
151
+ value_type = infer(node.value)
152
+ # Result is union of nil (if existing was falsy) and value
153
+ Types.union(existing, value_type)
154
+ end
155
+
156
+ def infer_local_variable_or_write(node)
157
+ existing = variable_type(node.name) || Types::UNTYPED
158
+ value_type = infer(node.value)
159
+ set_variable_type(node.name, Types.union(existing, value_type))
160
+ Types.union(existing, value_type)
161
+ end
162
+
163
+ def infer_instance_variable_read(node)
164
+ return Types::UNTYPED unless @current_class
165
+
166
+ ivar_name = node.name.to_s
167
+
168
+ # Check cached instance variable types for this class
169
+ if @instance_var_types[@current_class]&.key?(ivar_name)
170
+ return @instance_var_types[@current_class][ivar_name]
171
+ end
172
+
173
+ # Check NativeClass field definitions from RBS
174
+ if @rbs_loader&.native_class?(@current_class)
175
+ native_type = @rbs_loader.native_class_type(@current_class)
176
+ field_name = ivar_name.delete_prefix("@").to_sym
177
+ if native_type.fields.key?(field_name)
178
+ return native_field_to_type(native_type.fields[field_name])
179
+ end
180
+ end
181
+
182
+ Types::UNTYPED
183
+ end
184
+
185
+ def infer_instance_variable_write(node)
186
+ value_type = infer(node.value)
187
+
188
+ # Track the type if we're in a class context
189
+ if @current_class
190
+ @instance_var_types[@current_class] ||= {}
191
+ ivar_name = node.name.to_s
192
+ existing_type = @instance_var_types[@current_class][ivar_name]
193
+ @instance_var_types[@current_class][ivar_name] =
194
+ existing_type ? Types.union(existing_type, value_type) : value_type
195
+ end
196
+
197
+ value_type
198
+ end
199
+
200
+ # Convert NativeClass field type to internal Type
201
+ def native_field_to_type(field_type)
202
+ case field_type
203
+ when :i64 then Types::ClassInstance.new(:Integer)
204
+ when :double then Types::ClassInstance.new(:Float)
205
+ when :bool then Types::BOOL
206
+ when :value then Types::UNTYPED # Could be any VALUE
207
+ else Types::UNTYPED
208
+ end
209
+ end
210
+
211
+ def infer_class_variable_read(_node)
212
+ Types::UNTYPED
213
+ end
214
+
215
+ def infer_class_variable_write(node)
216
+ infer(node.value)
217
+ end
218
+
219
+ def infer_constant_read(node)
220
+ name = node.name.to_sym
221
+ if rbs_loader.type_exists?(name)
222
+ # It's a class/module constant, return its singleton type
223
+ Types::ClassInstance.new(name)
224
+ else
225
+ Types::UNTYPED
226
+ end
227
+ end
228
+
229
+ # Control flow
230
+ def infer_if(node)
231
+ infer(node.predicate)
232
+
233
+ then_type = node.statements ? infer_statements(node.statements) : Types::NIL
234
+ else_type = node.subsequent ? infer(node.subsequent) : Types::NIL
235
+
236
+ Types.union(then_type, else_type)
237
+ end
238
+
239
+ def infer_unless(node)
240
+ infer(node.predicate)
241
+
242
+ then_type = node.statements ? infer_statements(node.statements) : Types::NIL
243
+ else_type = node.else_clause ? infer(node.else_clause) : Types::NIL
244
+
245
+ Types.union(then_type, else_type)
246
+ end
247
+
248
+ def infer_else(node)
249
+ node.statements ? infer_statements(node.statements) : Types::NIL
250
+ end
251
+
252
+ def infer_while(node)
253
+ infer(node.predicate)
254
+ infer_statements(node.statements) if node.statements
255
+ Types::NIL
256
+ end
257
+
258
+ def infer_until(node)
259
+ infer(node.predicate)
260
+ infer_statements(node.statements) if node.statements
261
+ Types::NIL
262
+ end
263
+
264
+ def infer_case(node)
265
+ infer(node.predicate) if node.predicate
266
+
267
+ branch_types = []
268
+ node.conditions.each do |condition|
269
+ branch_types << infer(condition)
270
+ end
271
+
272
+ if node.else_clause
273
+ branch_types << infer(node.else_clause)
274
+ else
275
+ branch_types << Types::NIL
276
+ end
277
+
278
+ Types.union(*branch_types)
279
+ end
280
+
281
+ def infer_when(node)
282
+ node.statements ? infer_statements(node.statements) : Types::NIL
283
+ end
284
+
285
+ # Method call
286
+ def infer_call(node)
287
+ receiver_type = node.receiver ? infer(node.receiver) : self_type
288
+
289
+ # Try to get method return type from RBS
290
+ if receiver_type.is_a?(Types::ClassInstance)
291
+ method_types = rbs_loader.method_type(receiver_type.name, node.name)
292
+ if method_types && !method_types.empty?
293
+ return_type = rbs_type_to_internal(method_types.first.type.return_type)
294
+ return return_type
295
+ end
296
+ end
297
+
298
+ # Special cases for common methods
299
+ case node.name
300
+ when :+, :-, :*, :/, :%
301
+ infer_arithmetic(node, receiver_type)
302
+ when :==, :!=, :<, :>, :<=, :>=
303
+ Types::BOOL
304
+ when :to_s, :inspect
305
+ Types::STRING
306
+ when :to_i
307
+ Types::INTEGER
308
+ when :to_f
309
+ Types::FLOAT
310
+ when :to_a
311
+ Types.array(Types::UNTYPED)
312
+ when :to_h
313
+ Types.hash_type(Types::UNTYPED, Types::UNTYPED)
314
+ when :map, :collect
315
+ if receiver_type.is_a?(Types::ClassInstance) && receiver_type.name == :Array
316
+ Types.array(Types::UNTYPED) # Need block return type
317
+ else
318
+ Types::UNTYPED
319
+ end
320
+ when :select, :filter, :reject
321
+ receiver_type
322
+ when :first, :last
323
+ if receiver_type.is_a?(Types::ClassInstance) && receiver_type.name == :Array
324
+ if receiver_type.type_args.any?
325
+ Types.optional(receiver_type.type_args.first)
326
+ else
327
+ Types.optional(Types::UNTYPED)
328
+ end
329
+ else
330
+ Types::UNTYPED
331
+ end
332
+ else
333
+ Types::UNTYPED
334
+ end
335
+ end
336
+
337
+ def infer_arithmetic(node, receiver_type)
338
+ return Types::UNTYPED unless receiver_type.is_a?(Types::ClassInstance)
339
+
340
+ case receiver_type.name
341
+ when :Integer
342
+ arg_type = node.arguments&.arguments&.first ? infer(node.arguments.arguments.first) : Types::UNTYPED
343
+ if arg_type.is_a?(Types::ClassInstance) && arg_type.name == :Float
344
+ Types::FLOAT
345
+ else
346
+ Types::INTEGER
347
+ end
348
+ when :Float
349
+ Types::FLOAT
350
+ when :String
351
+ node.name == :+ ? Types::STRING : Types::UNTYPED
352
+ else
353
+ Types::UNTYPED
354
+ end
355
+ end
356
+
357
+ # Method definition
358
+ def infer_def(node)
359
+ with_scope do
360
+ # Add parameters to scope
361
+ if node.parameters
362
+ infer_parameters(node.parameters)
363
+ end
364
+
365
+ # Infer body type
366
+ if node.body
367
+ infer(node.body)
368
+ else
369
+ Types::NIL
370
+ end
371
+ end
372
+
373
+ Types::SYMBOL # def returns method name as symbol
374
+ end
375
+
376
+ def infer_parameters(node)
377
+ node.requireds&.each do |param|
378
+ set_variable_type(param.name, Types::UNTYPED) if param.respond_to?(:name)
379
+ end
380
+
381
+ node.optionals&.each do |param|
382
+ if param.respond_to?(:name)
383
+ value_type = param.respond_to?(:value) ? infer(param.value) : Types::UNTYPED
384
+ set_variable_type(param.name, value_type)
385
+ end
386
+ end
387
+
388
+ if node.rest && node.rest.respond_to?(:name) && node.rest.name
389
+ set_variable_type(node.rest.name, Types.array(Types::UNTYPED))
390
+ end
391
+
392
+ node.keywords&.each do |param|
393
+ if param.respond_to?(:name)
394
+ value_type = param.respond_to?(:value) && param.value ? infer(param.value) : Types::UNTYPED
395
+ set_variable_type(param.name, value_type)
396
+ end
397
+ end
398
+
399
+ if node.keyword_rest && node.keyword_rest.respond_to?(:name) && node.keyword_rest.name
400
+ set_variable_type(node.keyword_rest.name, Types.hash_type(Types::SYMBOL, Types::UNTYPED))
401
+ end
402
+
403
+ if node.block && node.block.respond_to?(:name) && node.block.name
404
+ set_variable_type(node.block.name, Types::UNTYPED)
405
+ end
406
+ end
407
+
408
+ # Class/Module definition
409
+ def infer_class(node)
410
+ # Extract class name
411
+ class_name = extract_constant_name(node.constant_path)
412
+ old_class = @current_class
413
+ @current_class = class_name
414
+
415
+ # Load instance variable types from RBS if available
416
+ load_ivar_types_from_rbs(class_name) if @rbs_loader&.loaded?
417
+
418
+ with_scope do
419
+ infer(node.body) if node.body
420
+ end
421
+
422
+ @current_class = old_class
423
+ Types::NIL
424
+ end
425
+
426
+ # Extract class/module name from constant path
427
+ def extract_constant_name(node)
428
+ case node
429
+ when Prism::ConstantReadNode
430
+ node.name.to_s
431
+ when Prism::ConstantPathNode
432
+ parts = []
433
+ current = node
434
+ while current.is_a?(Prism::ConstantPathNode)
435
+ parts.unshift(current.name.to_s) if current.respond_to?(:name)
436
+ current = current.parent
437
+ end
438
+ parts.unshift(current.name.to_s) if current.respond_to?(:name)
439
+ parts.join("::")
440
+ else
441
+ "Unknown"
442
+ end
443
+ end
444
+
445
+ # Load instance variable types from RBS class definition
446
+ def load_ivar_types_from_rbs(class_name)
447
+ @instance_var_types[class_name] ||= {}
448
+ # RBS instance variable types are loaded on-demand when accessed
449
+ end
450
+
451
+ def infer_module(node)
452
+ with_scope do
453
+ infer(node.body) if node.body
454
+ end
455
+ Types::NIL
456
+ end
457
+
458
+ # Blocks
459
+ def infer_block(node)
460
+ with_scope do
461
+ if node.parameters
462
+ # Add block parameters to scope
463
+ node.parameters.parameters&.requireds&.each do |param|
464
+ set_variable_type(param.name, Types::UNTYPED) if param.respond_to?(:name)
465
+ end
466
+ end
467
+
468
+ node.body ? infer(node.body) : Types::NIL
469
+ end
470
+ end
471
+
472
+ def infer_lambda(node)
473
+ # Lambda is a Proc
474
+ Types::ClassInstance.new(:Proc)
475
+ end
476
+
477
+ # Statements
478
+ def infer_program(node)
479
+ infer_statements(node.statements)
480
+ end
481
+
482
+ def infer_statements(node)
483
+ return Types::NIL unless node&.body&.any?
484
+ node.body.map { |stmt| infer(stmt) }.last
485
+ end
486
+
487
+ def infer_begin(node)
488
+ result_type = node.statements ? infer_statements(node.statements) : Types::NIL
489
+
490
+ if node.rescue_clause
491
+ rescue_type = infer(node.rescue_clause)
492
+ result_type = Types.union(result_type, rescue_type)
493
+ end
494
+
495
+ if node.ensure_clause
496
+ infer(node.ensure_clause)
497
+ end
498
+
499
+ result_type
500
+ end
501
+
502
+ def infer_rescue(node)
503
+ node.statements ? infer_statements(node.statements) : Types::NIL
504
+ end
505
+
506
+ def infer_ensure(node)
507
+ node.statements ? infer_statements(node.statements) : Types::NIL
508
+ end
509
+
510
+ def infer_return(node)
511
+ if node.arguments
512
+ types = node.arguments.arguments.map { |arg| infer(arg) }
513
+ types.size == 1 ? types.first : Types::Tuple.new(types)
514
+ else
515
+ Types::NIL
516
+ end
517
+ end
518
+
519
+ def infer_yield(node)
520
+ Types::UNTYPED # Depends on block
521
+ end
522
+
523
+ def infer_break(node)
524
+ Types::BOTTOM
525
+ end
526
+
527
+ def infer_next(node)
528
+ Types::BOTTOM
529
+ end
530
+
531
+ def infer_parentheses(node)
532
+ node.body ? infer(node.body) : Types::NIL
533
+ end
534
+
535
+ # Helpers
536
+ def self_type
537
+ @current_class ? Types::ClassInstance.new(@current_class.to_sym) : Types::UNTYPED
538
+ end
539
+
540
+ def rbs_type_to_internal(rbs_type)
541
+ case rbs_type
542
+ when RBS::Types::ClassInstance
543
+ name = rbs_type.name.name
544
+ args = rbs_type.args.map { |a| rbs_type_to_internal(a) }
545
+ Types::ClassInstance.new(name, args)
546
+ when RBS::Types::Bases::Void, RBS::Types::Bases::Nil
547
+ Types::NIL
548
+ when RBS::Types::Bases::Any
549
+ Types::UNTYPED
550
+ when RBS::Types::Bases::Bool
551
+ Types::BOOL
552
+ when RBS::Types::Union
553
+ types = rbs_type.types.map { |t| rbs_type_to_internal(t) }
554
+ Types.union(*types)
555
+ when RBS::Types::Optional
556
+ Types.optional(rbs_type_to_internal(rbs_type.type))
557
+ when RBS::Types::Literal
558
+ Types::Literal.new(rbs_type.literal)
559
+ else
560
+ Types::UNTYPED
561
+ end
562
+ end
563
+ end
564
+ end
565
+ end