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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- 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
|