konpeito 0.2.4 → 0.3.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 +4 -4
- data/.rubocop.yml +645 -0
- data/CHANGELOG.md +37 -0
- data/Justfile +107 -0
- data/README.md +143 -43
- data/konpeito.gemspec +3 -2
- data/lib/konpeito/cli/build_command.rb +21 -3
- data/lib/konpeito/cli/completion_command.rb +298 -0
- data/lib/konpeito/cli/deps_command.rb +129 -21
- data/lib/konpeito/cli/fmt_command.rb +24 -132
- data/lib/konpeito/cli/run_command.rb +29 -3
- data/lib/konpeito/cli.rb +45 -14
- data/lib/konpeito/codegen/builtin_methods.rb +16 -0
- data/lib/konpeito/codegen/cruby_backend.rb +76 -6
- data/lib/konpeito/codegen/jvm_generator.rb +100 -9
- data/lib/konpeito/codegen/llvm_generator.rb +907 -195
- data/lib/konpeito/dependency_resolver.rb +32 -9
- data/lib/konpeito/hir/builder.rb +369 -57
- data/lib/konpeito/hir/nodes.rb +25 -5
- data/lib/konpeito/type_checker/rbs_loader.rb +3 -2
- data/lib/konpeito/ui/app.rb +1 -1
- data/lib/konpeito/version.rb +1 -1
- data/lib/konpeito.rb +0 -7
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +32 -0
- metadata +6 -23
- data/lib/konpeito/cli/lsp_command.rb +0 -40
- data/lib/konpeito/formatter/formatter.rb +0 -1214
- data/lib/konpeito/lsp/document_manager.rb +0 -820
- data/lib/konpeito/lsp/server.rb +0 -183
- data/lib/konpeito/lsp/transport.rb +0 -38
- data/test_native_array.rb +0 -172
- data/test_native_array_class.rb +0 -197
- data/test_native_class.rb +0 -151
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Konpeito
|
|
4
|
-
#
|
|
5
|
-
|
|
4
|
+
# Native extension file extensions to check when scanning gem directories
|
|
5
|
+
NATIVE_EXTENSIONS = %w[.bundle .so .dll .dylib].freeze
|
|
6
|
+
|
|
7
|
+
# Known standard library names that can be loaded at runtime via rb_require.
|
|
8
|
+
# This list acts as a fast path; dynamic gem detection via Gem.path covers the rest.
|
|
6
9
|
KNOWN_STDLIB_LIBRARIES = %w[
|
|
7
|
-
json fileutils find pathname tempfile timeout uri yaml
|
|
10
|
+
json fileutils find pathname tempfile timeout uri yaml tmpdir
|
|
8
11
|
digest openssl socket stringio csv date time set
|
|
9
12
|
net/http net/https net/ftp net/smtp net/pop net/imap
|
|
10
13
|
securerandom base64 benchmark erb logger optparse
|
|
@@ -184,9 +187,9 @@ module Konpeito
|
|
|
184
187
|
@cache_manager.add_dependency(path, dep_path)
|
|
185
188
|
end
|
|
186
189
|
resolve_file(dep_path)
|
|
187
|
-
elsif req[:type] == :require &&
|
|
188
|
-
#
|
|
189
|
-
log " Detected
|
|
190
|
+
elsif req[:type] == :require && runtime_loadable?(req[:name])
|
|
191
|
+
# Stdlib or installed gem — will be loaded at runtime via rb_require
|
|
192
|
+
log " Detected runtime require: #{req[:name]}"
|
|
190
193
|
@stdlib_requires << req[:name]
|
|
191
194
|
elsif req[:type] == :require_relative
|
|
192
195
|
# Check if this points to a native extension (.bundle/.so/.dll)
|
|
@@ -207,10 +210,10 @@ module Konpeito
|
|
|
207
210
|
)
|
|
208
211
|
end
|
|
209
212
|
else
|
|
210
|
-
# Unknown require
|
|
213
|
+
# Unknown require that cannot be resolved statically or found in gems.
|
|
211
214
|
raise DependencyError.new(
|
|
212
215
|
"Cannot resolve require: #{req[:name]}. " \
|
|
213
|
-
"If this is a gem,
|
|
216
|
+
"If this is a gem, ensure it is installed (`gem install #{req[:name]}`).",
|
|
214
217
|
from_file: path,
|
|
215
218
|
line: req[:line],
|
|
216
219
|
missing_file: req[:name]
|
|
@@ -283,7 +286,27 @@ module Konpeito
|
|
|
283
286
|
%w[.bundle .so .dll .dylib].any? { |ext| File.exist?("#{base}#{ext}") }
|
|
284
287
|
end
|
|
285
288
|
|
|
286
|
-
# Check
|
|
289
|
+
# Check whether a require target can be loaded at runtime (stdlib or installed gem).
|
|
290
|
+
# This replaces the hardcoded KNOWN_STDLIB_LIBRARIES approach with dynamic detection.
|
|
291
|
+
def runtime_loadable?(name)
|
|
292
|
+
# Fast path: known stdlib names (avoids gem lookup overhead for common stdlib)
|
|
293
|
+
return true if stdlib_library?(name)
|
|
294
|
+
|
|
295
|
+
# Dynamic gem detection: scan all GEM_PATH directories for a matching lib file.
|
|
296
|
+
# Using Gem.path (not Gem.find_files) because bundler restricts Gem.find_files
|
|
297
|
+
# to only activated gems, while Gem.path covers all installed gem homes.
|
|
298
|
+
name_rb = name.end_with?(".rb") ? name : "#{name}.rb"
|
|
299
|
+
Gem.path.any? do |gem_home|
|
|
300
|
+
Dir.glob(File.join(gem_home, "gems", "*", "lib")).any? do |lib_dir|
|
|
301
|
+
File.exist?(File.join(lib_dir, name_rb)) ||
|
|
302
|
+
NATIVE_EXTENSIONS.any? { |ext| File.exist?(File.join(lib_dir, "#{name}#{ext}")) }
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
rescue LoadError, NameError
|
|
306
|
+
false
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Check if a name is a known stdlib library (fast path, kept for backwards compat)
|
|
287
310
|
def stdlib_library?(name)
|
|
288
311
|
# Normalize: convert dashes to underscores for matching
|
|
289
312
|
normalized = name.tr("-", "_")
|
data/lib/konpeito/hir/builder.rb
CHANGED
|
@@ -134,6 +134,10 @@ module Konpeito
|
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
def visit_program(typed_node)
|
|
137
|
+
# Scan top-level statements for include/extend/prepend before visiting
|
|
138
|
+
# These need to be executed in the Init function before class definitions
|
|
139
|
+
scan_toplevel_includes(typed_node.children.first)
|
|
140
|
+
|
|
137
141
|
# Create a main function for top-level code
|
|
138
142
|
main_func = Function.new(
|
|
139
143
|
name: "__main__",
|
|
@@ -541,6 +545,26 @@ module Konpeito
|
|
|
541
545
|
NilLit.new
|
|
542
546
|
end
|
|
543
547
|
|
|
548
|
+
# Scan top-level statements for include/extend/prepend calls
|
|
549
|
+
# These must be executed in Init before class definitions so that
|
|
550
|
+
# constants from included modules are available for superclass resolution.
|
|
551
|
+
def scan_toplevel_includes(statements_node)
|
|
552
|
+
return unless statements_node&.children
|
|
553
|
+
|
|
554
|
+
statements_node.children.each do |child|
|
|
555
|
+
if include_statement?(child)
|
|
556
|
+
module_name = extract_include_module_name(child)
|
|
557
|
+
@program.toplevel_includes << [:include, module_name] if module_name
|
|
558
|
+
elsif extend_statement?(child)
|
|
559
|
+
module_name = extract_include_module_name(child)
|
|
560
|
+
@program.toplevel_includes << [:extend, module_name] if module_name
|
|
561
|
+
elsif prepend_statement?(child)
|
|
562
|
+
module_name = extract_include_module_name(child)
|
|
563
|
+
@program.toplevel_includes << [:prepend, module_name] if module_name
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
544
568
|
# Check if a node is an include statement
|
|
545
569
|
def include_statement?(typed_node)
|
|
546
570
|
return false unless typed_node.node_type == :call
|
|
@@ -781,7 +805,11 @@ module Konpeito
|
|
|
781
805
|
|
|
782
806
|
method_names = []
|
|
783
807
|
singleton_method_names = []
|
|
808
|
+
module_function_method_names = []
|
|
809
|
+
module_private_method_names = Set.new
|
|
784
810
|
module_constants = {}
|
|
811
|
+
# Track current visibility state within this module body
|
|
812
|
+
module_visibility = :public
|
|
785
813
|
|
|
786
814
|
# Visit body within module context
|
|
787
815
|
old_module = @current_module
|
|
@@ -802,6 +830,12 @@ module Konpeito
|
|
|
802
830
|
func.is_instance_method = false if func && func.name.to_s == method_name
|
|
803
831
|
else
|
|
804
832
|
method_names << method_name
|
|
833
|
+
case module_visibility
|
|
834
|
+
when :module_function
|
|
835
|
+
module_function_method_names << method_name
|
|
836
|
+
when :private
|
|
837
|
+
module_private_method_names << method_name
|
|
838
|
+
end
|
|
805
839
|
end
|
|
806
840
|
elsif child.node_type == :singleton_class
|
|
807
841
|
# class << self in module - collect singleton methods
|
|
@@ -818,13 +852,29 @@ module Konpeito
|
|
|
818
852
|
end
|
|
819
853
|
end
|
|
820
854
|
elsif child.node_type == :constant_write
|
|
821
|
-
# Handle constant assignment within module
|
|
855
|
+
# Handle constant assignment within module.
|
|
856
|
+
# Collect literal value for C Init optimization (simple constants only).
|
|
857
|
+
# Also emit StoreConstant into __main__ for non-literal values (Mutex.new,
|
|
858
|
+
# {}, File.join(...), etc.) so they get initialized at runtime via rb_const_set.
|
|
859
|
+
# Simple literals are already set in C Init, so we skip them here to avoid
|
|
860
|
+
# "already initialized constant" warnings.
|
|
822
861
|
const_name = child.node.name.to_s
|
|
823
|
-
|
|
862
|
+
lit_val = visit_literal_value(child.children.first)
|
|
863
|
+
module_constants[const_name] = lit_val
|
|
864
|
+
# Only emit for non-literal values (literals handled by C Init)
|
|
865
|
+
unless lit_val.is_a?(HIR::IntegerLit) || lit_val.is_a?(HIR::FloatLit) ||
|
|
866
|
+
lit_val.is_a?(HIR::StringLit) || lit_val.is_a?(HIR::SymbolLit) ||
|
|
867
|
+
lit_val.is_a?(HIR::BoolLit) || lit_val.is_a?(HIR::NilLit)
|
|
868
|
+
visit(child)
|
|
869
|
+
end
|
|
824
870
|
elsif child.node_type == :class_variable_write
|
|
825
871
|
# Handle class variable initialization within module
|
|
826
872
|
# (modules can have class variables too)
|
|
827
873
|
visit(child)
|
|
874
|
+
elsif module_visibility_modifier?(child)
|
|
875
|
+
# module_function / private / public / protected as bare calls — compilation
|
|
876
|
+
# directive only, must NOT emit a runtime rb_funcallv call into __main__.
|
|
877
|
+
module_visibility = extract_module_visibility(child)
|
|
828
878
|
else
|
|
829
879
|
visit(child)
|
|
830
880
|
end
|
|
@@ -834,17 +884,45 @@ module Konpeito
|
|
|
834
884
|
|
|
835
885
|
@current_module = old_module
|
|
836
886
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
887
|
+
# Merge into existing ModuleDef if this module was already opened (multi-file projects)
|
|
888
|
+
existing_module_def = @program.modules.find { |m| m.name == name }
|
|
889
|
+
if existing_module_def
|
|
890
|
+
existing_module_def.methods.concat(method_names)
|
|
891
|
+
existing_module_def.singleton_methods.concat(singleton_method_names)
|
|
892
|
+
existing_module_def.module_function_methods.concat(module_function_method_names)
|
|
893
|
+
existing_module_def.private_methods.merge(module_private_method_names)
|
|
894
|
+
existing_module_def.constants.merge!(module_constants)
|
|
895
|
+
else
|
|
896
|
+
module_def = ModuleDef.new(
|
|
897
|
+
name: name,
|
|
898
|
+
methods: method_names,
|
|
899
|
+
singleton_methods: singleton_method_names,
|
|
900
|
+
constants: module_constants
|
|
901
|
+
)
|
|
902
|
+
module_def.module_function_methods.concat(module_function_method_names)
|
|
903
|
+
module_def.private_methods.merge(module_private_method_names)
|
|
904
|
+
@program.modules << module_def
|
|
905
|
+
end
|
|
845
906
|
NilLit.new
|
|
846
907
|
end
|
|
847
908
|
|
|
909
|
+
# Check if a node is a bare visibility modifier inside a module body
|
|
910
|
+
# (module_function, private, public, protected with no arguments and no receiver)
|
|
911
|
+
def module_visibility_modifier?(typed_node)
|
|
912
|
+
return false unless typed_node.node_type == :call
|
|
913
|
+
return false unless typed_node.node.receiver.nil?
|
|
914
|
+
name = typed_node.node.name.to_s
|
|
915
|
+
return false unless %w[module_function private public protected].include?(name)
|
|
916
|
+
# Must be bare (no arguments) or have only symbol arguments (per-method form)
|
|
917
|
+
true
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
# Extract the visibility symbol from a visibility modifier call node
|
|
921
|
+
def extract_module_visibility(typed_node)
|
|
922
|
+
name = typed_node.node.name.to_s
|
|
923
|
+
name == "module_function" ? :module_function : name.to_sym
|
|
924
|
+
end
|
|
925
|
+
|
|
848
926
|
# Check if a def node is a singleton method (def self.method)
|
|
849
927
|
def singleton_method?(typed_node)
|
|
850
928
|
return false unless typed_node.node_type == :def
|
|
@@ -2098,6 +2176,7 @@ module Konpeito
|
|
|
2098
2176
|
# Get arguments and keyword arguments
|
|
2099
2177
|
args = []
|
|
2100
2178
|
keyword_args = {}
|
|
2179
|
+
keyword_splat = nil
|
|
2101
2180
|
args_child = typed_node.children.find { |c| c.node_type == :arguments }
|
|
2102
2181
|
if args_child
|
|
2103
2182
|
args_child.children.each do |arg|
|
|
@@ -2117,6 +2196,15 @@ module Konpeito
|
|
|
2117
2196
|
# Fallback: create typed node for the value
|
|
2118
2197
|
keyword_args[key_name] = visit_node_directly(elem.value)
|
|
2119
2198
|
end
|
|
2199
|
+
elsif elem.is_a?(Prism::AssocSplatNode) && elem.value
|
|
2200
|
+
# **hash at call site — capture the splatted hash expression
|
|
2201
|
+
typed_assoc = arg.children[ei]
|
|
2202
|
+
splat_expr = if typed_assoc && typed_assoc.children && !typed_assoc.children.empty?
|
|
2203
|
+
visit(typed_assoc.children.first)
|
|
2204
|
+
else
|
|
2205
|
+
visit_node_directly(elem.value)
|
|
2206
|
+
end
|
|
2207
|
+
keyword_splat = splat_expr
|
|
2120
2208
|
end
|
|
2121
2209
|
end
|
|
2122
2210
|
elsif arg.node.is_a?(Prism::SplatNode)
|
|
@@ -2211,6 +2299,7 @@ module Konpeito
|
|
|
2211
2299
|
args: args,
|
|
2212
2300
|
block: block,
|
|
2213
2301
|
keyword_args: keyword_args,
|
|
2302
|
+
keyword_splat: keyword_splat,
|
|
2214
2303
|
type: typed_node.type,
|
|
2215
2304
|
result_var: result_var,
|
|
2216
2305
|
safe_navigation: is_safe_nav
|
|
@@ -5990,13 +6079,84 @@ module Konpeito
|
|
|
5990
6079
|
|
|
5991
6080
|
# Detect variables from outer scope that are used in the block
|
|
5992
6081
|
def detect_captured_variables(outer_vars)
|
|
5993
|
-
#
|
|
5994
|
-
#
|
|
5995
|
-
|
|
6082
|
+
# Only capture outer variables that are actually referenced (read or written)
|
|
6083
|
+
# in the block body or any nested block bodies within it.
|
|
6084
|
+
referenced = Set.new
|
|
6085
|
+
block_body = @current_function.body
|
|
6086
|
+
scan_block_body_for_captures(block_body, outer_vars, referenced)
|
|
6087
|
+
referenced.map do |name|
|
|
5996
6088
|
Capture.new(name: name, type: outer_vars[name].type)
|
|
5997
6089
|
end
|
|
5998
6090
|
end
|
|
5999
6091
|
|
|
6092
|
+
# Recursively scan basic blocks for variable references to outer scope,
|
|
6093
|
+
# including nested block definitions (e.g., mutex.synchronize { ... })
|
|
6094
|
+
def scan_block_body_for_captures(blocks, outer_vars, referenced)
|
|
6095
|
+
blocks.each do |bb|
|
|
6096
|
+
bb.instructions.each do |inst|
|
|
6097
|
+
scan_instruction_for_captures(inst, outer_vars, referenced)
|
|
6098
|
+
end
|
|
6099
|
+
# Also check terminator for variable references
|
|
6100
|
+
if bb.terminator
|
|
6101
|
+
scan_instruction_for_captures(bb.terminator, outer_vars, referenced)
|
|
6102
|
+
end
|
|
6103
|
+
end
|
|
6104
|
+
end
|
|
6105
|
+
|
|
6106
|
+
# Recursively scan a single HIR instruction (and its sub-expressions) for captures
|
|
6107
|
+
def scan_instruction_for_captures(node, outer_vars, referenced)
|
|
6108
|
+
return unless node.is_a?(Instruction)
|
|
6109
|
+
case node
|
|
6110
|
+
when LoadLocal
|
|
6111
|
+
referenced << node.var.name if outer_vars.key?(node.var.name)
|
|
6112
|
+
when StoreLocal
|
|
6113
|
+
referenced << node.var.name if outer_vars.key?(node.var.name)
|
|
6114
|
+
scan_instruction_for_captures(node.value, outer_vars, referenced) if node.value.is_a?(Instruction)
|
|
6115
|
+
when Call
|
|
6116
|
+
scan_instruction_for_captures(node.receiver, outer_vars, referenced) if node.receiver
|
|
6117
|
+
node.args.each { |a| scan_instruction_for_captures(a, outer_vars, referenced) }
|
|
6118
|
+
if node.block && node.block.respond_to?(:body) && node.block.body
|
|
6119
|
+
scan_block_body_for_captures(node.block.body, outer_vars, referenced)
|
|
6120
|
+
end
|
|
6121
|
+
when ThreadNew, ProcNew, FiberNew, RactorNew
|
|
6122
|
+
if node.respond_to?(:block_def) && node.block_def
|
|
6123
|
+
scan_block_body_for_captures(node.block_def.body, outer_vars, referenced)
|
|
6124
|
+
end
|
|
6125
|
+
else
|
|
6126
|
+
# Generic fallback: scan sub-expressions and nested block bodies
|
|
6127
|
+
# for instruction types like MutexSynchronize, BeginRescue, etc.
|
|
6128
|
+
if node.respond_to?(:block_def) && node.block_def
|
|
6129
|
+
scan_block_body_for_captures(node.block_def.body, outer_vars, referenced)
|
|
6130
|
+
end
|
|
6131
|
+
# Scan known sub-expression attributes that may reference outer variables
|
|
6132
|
+
[:mutex, :cv, :queue, :value, :receiver, :ractor, :port, :max_size, :key].each do |attr|
|
|
6133
|
+
if node.respond_to?(attr)
|
|
6134
|
+
sub = node.send(attr)
|
|
6135
|
+
scan_instruction_for_captures(sub, outer_vars, referenced) if sub.is_a?(Instruction)
|
|
6136
|
+
end
|
|
6137
|
+
end
|
|
6138
|
+
# Scan args array if present
|
|
6139
|
+
if node.respond_to?(:args) && node.args.is_a?(Array)
|
|
6140
|
+
node.args.each { |a| scan_instruction_for_captures(a, outer_vars, referenced) }
|
|
6141
|
+
end
|
|
6142
|
+
# Scan nested HIR blocks (BeginRescue try/rescue/else/ensure blocks)
|
|
6143
|
+
if node.respond_to?(:try_hir_blocks) && node.try_hir_blocks
|
|
6144
|
+
scan_block_body_for_captures(node.try_hir_blocks, outer_vars, referenced)
|
|
6145
|
+
end
|
|
6146
|
+
if node.respond_to?(:rescue_clauses) && node.rescue_clauses
|
|
6147
|
+
node.rescue_clauses.each do |clause|
|
|
6148
|
+
scan_block_body_for_captures(clause.body, outer_vars, referenced) if clause.respond_to?(:body) && clause.body
|
|
6149
|
+
end
|
|
6150
|
+
end
|
|
6151
|
+
if node.respond_to?(:else_blocks) && node.else_blocks
|
|
6152
|
+
scan_block_body_for_captures(node.else_blocks, outer_vars, referenced)
|
|
6153
|
+
end
|
|
6154
|
+
if node.respond_to?(:ensure_blocks) && node.ensure_blocks
|
|
6155
|
+
scan_block_body_for_captures(node.ensure_blocks, outer_vars, referenced)
|
|
6156
|
+
end
|
|
6157
|
+
end
|
|
6158
|
+
end
|
|
6159
|
+
|
|
6000
6160
|
def build_block_params(params_node)
|
|
6001
6161
|
result = []
|
|
6002
6162
|
params_node.parameters&.requireds&.each do |param|
|
|
@@ -6624,14 +6784,56 @@ module Konpeito
|
|
|
6624
6784
|
ensure_child = typed_node.children.find { |c| c.node_type == :ensure }
|
|
6625
6785
|
|
|
6626
6786
|
result_var = new_temp_var
|
|
6787
|
+
has_rescue = rescue_child != nil
|
|
6627
6788
|
|
|
6628
|
-
#
|
|
6789
|
+
# When rescue clauses are present, collect the try body into a separate HIR block
|
|
6790
|
+
# list using BlockBodyCollector so that control flow (if/else etc.) inside the try
|
|
6791
|
+
# body does NOT bleed into the outer function's basic block graph.
|
|
6792
|
+
# This prevents invalid phi-node predecessors in the rescue try callback.
|
|
6793
|
+
try_hir_blocks = nil
|
|
6629
6794
|
try_instructions = []
|
|
6630
|
-
|
|
6795
|
+
|
|
6796
|
+
if has_rescue && statements_child
|
|
6797
|
+
saved_function = @current_function
|
|
6798
|
+
saved_current_blk = @current_block
|
|
6799
|
+
|
|
6800
|
+
@block_counter += 1
|
|
6801
|
+
try_entry = BasicBlock.new(label: "rescue_try_entry_#{@block_counter}")
|
|
6802
|
+
try_hir_blocks = [try_entry]
|
|
6803
|
+
@current_function = BlockBodyCollector.new(try_hir_blocks)
|
|
6804
|
+
set_current_block(try_entry)
|
|
6805
|
+
|
|
6631
6806
|
statements_child.children.each do |stmt|
|
|
6632
6807
|
inst = visit(stmt)
|
|
6633
6808
|
try_instructions << inst if inst
|
|
6634
6809
|
end
|
|
6810
|
+
|
|
6811
|
+
# Close the last open block with a Return
|
|
6812
|
+
unless @current_block.terminator
|
|
6813
|
+
last_val = try_instructions.last
|
|
6814
|
+
@current_block.set_terminator(Return.new(value: last_val || NilLit.new))
|
|
6815
|
+
end
|
|
6816
|
+
|
|
6817
|
+
@current_function = saved_function
|
|
6818
|
+
set_current_block(saved_current_blk)
|
|
6819
|
+
elsif statements_child
|
|
6820
|
+
# No rescue clauses: inline (original behaviour)
|
|
6821
|
+
# Capture ALL emitted instructions (not just return values) because
|
|
6822
|
+
# visit() for assignment nodes emits StoreLocal but returns the value.
|
|
6823
|
+
# Guard: visit() may change @current_block (if/else, while, etc.),
|
|
6824
|
+
# in which case fall back to original behavior.
|
|
6825
|
+
statements_child.children.each do |stmt|
|
|
6826
|
+
saved_block = @current_block
|
|
6827
|
+
before_idx = @current_block.instructions.size
|
|
6828
|
+
inst = visit(stmt)
|
|
6829
|
+
if @current_block.equal?(saved_block) && @current_block.instructions.size >= before_idx
|
|
6830
|
+
@current_block.instructions[before_idx..]&.each do |emitted|
|
|
6831
|
+
try_instructions << emitted
|
|
6832
|
+
end
|
|
6833
|
+
else
|
|
6834
|
+
try_instructions << inst if inst
|
|
6835
|
+
end
|
|
6836
|
+
end
|
|
6635
6837
|
end
|
|
6636
6838
|
|
|
6637
6839
|
# Record instruction count after try body - everything emitted after this
|
|
@@ -6644,26 +6846,43 @@ module Konpeito
|
|
|
6644
6846
|
rescue_clauses = build_rescue_clauses(rescue_child)
|
|
6645
6847
|
end
|
|
6646
6848
|
|
|
6647
|
-
# Collect else instructions
|
|
6849
|
+
# Collect else instructions - capture ALL emitted instructions
|
|
6850
|
+
# (visit() for assignments emits StoreLocal but returns the value)
|
|
6648
6851
|
else_instructions = []
|
|
6649
6852
|
if else_child
|
|
6650
6853
|
else_statements = else_child.children.find { |c| c.node_type == :statements }
|
|
6651
6854
|
if else_statements
|
|
6652
6855
|
else_statements.children.each do |stmt|
|
|
6856
|
+
saved_block = @current_block
|
|
6857
|
+
before_idx = @current_block.instructions.size
|
|
6653
6858
|
inst = visit(stmt)
|
|
6654
|
-
|
|
6859
|
+
if @current_block.equal?(saved_block) && @current_block.instructions.size >= before_idx
|
|
6860
|
+
@current_block.instructions[before_idx..]&.each do |emitted|
|
|
6861
|
+
else_instructions << emitted
|
|
6862
|
+
end
|
|
6863
|
+
else
|
|
6864
|
+
else_instructions << inst if inst
|
|
6865
|
+
end
|
|
6655
6866
|
end
|
|
6656
6867
|
end
|
|
6657
6868
|
end
|
|
6658
6869
|
|
|
6659
|
-
# Collect ensure instructions
|
|
6870
|
+
# Collect ensure instructions - capture ALL emitted instructions
|
|
6660
6871
|
ensure_instructions = []
|
|
6661
6872
|
if ensure_child
|
|
6662
6873
|
ensure_statements = ensure_child.children.find { |c| c.node_type == :statements }
|
|
6663
6874
|
if ensure_statements
|
|
6664
6875
|
ensure_statements.children.each do |stmt|
|
|
6876
|
+
saved_block = @current_block
|
|
6877
|
+
before_idx = @current_block.instructions.size
|
|
6665
6878
|
inst = visit(stmt)
|
|
6666
|
-
|
|
6879
|
+
if @current_block.equal?(saved_block) && @current_block.instructions.size >= before_idx
|
|
6880
|
+
@current_block.instructions[before_idx..]&.each do |emitted|
|
|
6881
|
+
ensure_instructions << emitted
|
|
6882
|
+
end
|
|
6883
|
+
else
|
|
6884
|
+
ensure_instructions << inst if inst
|
|
6885
|
+
end
|
|
6667
6886
|
end
|
|
6668
6887
|
end
|
|
6669
6888
|
end
|
|
@@ -6671,12 +6890,13 @@ module Konpeito
|
|
|
6671
6890
|
# Collect ALL instruction object_ids emitted during rescue/else/ensure visitation
|
|
6672
6891
|
# This includes sub-expression instructions (e.g. StringLit for literal args)
|
|
6673
6892
|
non_try_ids = Set.new
|
|
6674
|
-
@current_block.instructions[non_try_start_idx..].each do |i|
|
|
6893
|
+
(@current_block.instructions[non_try_start_idx..] || []).each do |i|
|
|
6675
6894
|
non_try_ids << i.object_id
|
|
6676
6895
|
end
|
|
6677
6896
|
|
|
6678
6897
|
inst = BeginRescue.new(
|
|
6679
6898
|
try_blocks: try_instructions,
|
|
6899
|
+
try_hir_blocks: try_hir_blocks,
|
|
6680
6900
|
rescue_clauses: rescue_clauses,
|
|
6681
6901
|
else_blocks: else_instructions,
|
|
6682
6902
|
ensure_blocks: ensure_instructions,
|
|
@@ -6710,13 +6930,23 @@ module Konpeito
|
|
|
6710
6930
|
exception_var = current.node.reference.name.to_s
|
|
6711
6931
|
end
|
|
6712
6932
|
|
|
6713
|
-
# Collect body instructions
|
|
6933
|
+
# Collect body instructions - capture ALL emitted instructions
|
|
6934
|
+
# (visit() for assignments emits StoreLocal but returns the value)
|
|
6935
|
+
# Guard: visit() may change @current_block (if/else, while, etc.)
|
|
6714
6936
|
body_instructions = []
|
|
6715
6937
|
statements_child = current.children.find { |c| c.node_type == :statements }
|
|
6716
6938
|
if statements_child
|
|
6717
6939
|
statements_child.children.each do |stmt|
|
|
6940
|
+
saved_block = @current_block
|
|
6941
|
+
before_idx = @current_block.instructions.size
|
|
6718
6942
|
inst = visit(stmt)
|
|
6719
|
-
|
|
6943
|
+
if @current_block.equal?(saved_block) && @current_block.instructions.size >= before_idx
|
|
6944
|
+
@current_block.instructions[before_idx..]&.each do |emitted|
|
|
6945
|
+
body_instructions << emitted
|
|
6946
|
+
end
|
|
6947
|
+
else
|
|
6948
|
+
body_instructions << inst if inst
|
|
6949
|
+
end
|
|
6720
6950
|
end
|
|
6721
6951
|
end
|
|
6722
6952
|
|
|
@@ -6735,7 +6965,10 @@ module Konpeito
|
|
|
6735
6965
|
end
|
|
6736
6966
|
|
|
6737
6967
|
def visit_case(typed_node)
|
|
6738
|
-
# Handle case/when/else statements
|
|
6968
|
+
# Handle case/when/else statements by creating proper HIR basic blocks,
|
|
6969
|
+
# like visit_if does. This avoids the CaseStatement-embedding approach which
|
|
6970
|
+
# caused when-body instructions to execute unconditionally in the entry block.
|
|
6971
|
+
#
|
|
6739
6972
|
# case x
|
|
6740
6973
|
# when 1 then "one"
|
|
6741
6974
|
# when 2, 3 then "small"
|
|
@@ -6753,62 +6986,141 @@ module Konpeito
|
|
|
6753
6986
|
end
|
|
6754
6987
|
end
|
|
6755
6988
|
|
|
6756
|
-
#
|
|
6757
|
-
|
|
6989
|
+
# Create merge block
|
|
6990
|
+
merge_block = new_block("case_merge")
|
|
6991
|
+
incoming = {} # { hir_block_label => result_hir_node }
|
|
6992
|
+
|
|
6993
|
+
when_nodes = typed_node.children.select { |c| c.node_type == :when }
|
|
6994
|
+
has_else = typed_node.children.any? { |c| c.node_type == :else }
|
|
6995
|
+
|
|
6996
|
+
when_nodes.each_with_index do |when_child, i|
|
|
6997
|
+
# Extract conditions and body statements
|
|
6998
|
+
conditions = []
|
|
6999
|
+
body_stmts = nil
|
|
7000
|
+
when_child.children.each do |child|
|
|
7001
|
+
if child.node_type == :statements
|
|
7002
|
+
body_stmts = child
|
|
7003
|
+
else
|
|
7004
|
+
# Evaluate condition in the current check block
|
|
7005
|
+
cond = visit(child)
|
|
7006
|
+
conditions << cond
|
|
7007
|
+
end
|
|
7008
|
+
end
|
|
7009
|
+
|
|
7010
|
+
body_block = new_block("when_body")
|
|
7011
|
+
is_last_when = (i == when_nodes.size - 1)
|
|
7012
|
+
next_block = new_block(is_last_when && !has_else ? "when_else" : "when_check")
|
|
6758
7013
|
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
7014
|
+
# Generate condition checks with OR logic for multiple conditions
|
|
7015
|
+
if predicate.nil?
|
|
7016
|
+
# No predicate: treat conditions as boolean (truthy) directly
|
|
7017
|
+
if conditions.empty?
|
|
7018
|
+
@current_block.set_terminator(Jump.new(target: body_block.label))
|
|
7019
|
+
else
|
|
7020
|
+
# OR chain: if any condition is truthy, execute body
|
|
7021
|
+
remaining = conditions.dup
|
|
7022
|
+
last_cond = remaining.pop
|
|
7023
|
+
remaining.each do |cond|
|
|
7024
|
+
or_check_block = new_block("when_or_check")
|
|
7025
|
+
@current_block.set_terminator(Branch.new(
|
|
7026
|
+
condition: cond,
|
|
7027
|
+
then_block: body_block.label,
|
|
7028
|
+
else_block: or_check_block.label
|
|
7029
|
+
))
|
|
7030
|
+
set_current_block(or_check_block)
|
|
7031
|
+
end
|
|
7032
|
+
@current_block.set_terminator(Branch.new(
|
|
7033
|
+
condition: last_cond,
|
|
7034
|
+
then_block: body_block.label,
|
|
7035
|
+
else_block: next_block.label
|
|
7036
|
+
))
|
|
7037
|
+
end
|
|
7038
|
+
else
|
|
7039
|
+
# With predicate: use === comparison
|
|
7040
|
+
if conditions.empty?
|
|
7041
|
+
@current_block.set_terminator(Jump.new(target: body_block.label))
|
|
7042
|
+
else
|
|
7043
|
+
remaining = conditions.dup
|
|
7044
|
+
last_cond = remaining.pop
|
|
7045
|
+
remaining.each do |cond|
|
|
7046
|
+
check_var = new_temp_var
|
|
7047
|
+
check = CaseEqualityCheck.new(condition: cond, predicate: predicate, result_var: check_var)
|
|
7048
|
+
emit(check)
|
|
7049
|
+
or_check_block = new_block("when_or_check")
|
|
7050
|
+
@current_block.set_terminator(Branch.new(
|
|
7051
|
+
condition: check,
|
|
7052
|
+
then_block: body_block.label,
|
|
7053
|
+
else_block: or_check_block.label
|
|
7054
|
+
))
|
|
7055
|
+
set_current_block(or_check_block)
|
|
7056
|
+
end
|
|
7057
|
+
check_var = new_temp_var
|
|
7058
|
+
check = CaseEqualityCheck.new(condition: last_cond, predicate: predicate, result_var: check_var)
|
|
7059
|
+
emit(check)
|
|
7060
|
+
@current_block.set_terminator(Branch.new(
|
|
7061
|
+
condition: check,
|
|
7062
|
+
then_block: body_block.label,
|
|
7063
|
+
else_block: next_block.label
|
|
7064
|
+
))
|
|
7065
|
+
end
|
|
7066
|
+
end
|
|
7067
|
+
|
|
7068
|
+
# Generate body in body_block
|
|
7069
|
+
set_current_block(body_block)
|
|
7070
|
+
body_result = NilLit.new
|
|
7071
|
+
if body_stmts
|
|
7072
|
+
body_stmts.children.each do |stmt|
|
|
7073
|
+
result = visit(stmt)
|
|
7074
|
+
body_result = result if result
|
|
7075
|
+
end
|
|
7076
|
+
end
|
|
7077
|
+
body_exit = @current_block
|
|
7078
|
+
unless @current_block.terminator
|
|
7079
|
+
@current_block.set_terminator(Jump.new(target: merge_block.label))
|
|
7080
|
+
end
|
|
7081
|
+
incoming[body_exit.label] = body_result
|
|
7082
|
+
|
|
7083
|
+
# Move to next check block
|
|
7084
|
+
set_current_block(next_block)
|
|
6764
7085
|
end
|
|
6765
7086
|
|
|
6766
|
-
#
|
|
6767
|
-
else_body = nil
|
|
7087
|
+
# Else body (in the last check/else block = @current_block)
|
|
6768
7088
|
else_child = typed_node.children.find { |c| c.node_type == :else }
|
|
7089
|
+
else_result = NilLit.new
|
|
6769
7090
|
if else_child
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
else_body << inst if inst
|
|
7091
|
+
else_stmts = else_child.children.find { |c| c.node_type == :statements }
|
|
7092
|
+
if else_stmts
|
|
7093
|
+
else_stmts.children.each do |stmt|
|
|
7094
|
+
result = visit(stmt)
|
|
7095
|
+
else_result = result if result
|
|
6776
7096
|
end
|
|
6777
7097
|
end
|
|
6778
7098
|
end
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
@current_block.instructions[sub_start_idx..].each do |i|
|
|
6783
|
-
sub_ids << i.object_id
|
|
7099
|
+
else_exit = @current_block
|
|
7100
|
+
unless @current_block.terminator
|
|
7101
|
+
@current_block.set_terminator(Jump.new(target: merge_block.label))
|
|
6784
7102
|
end
|
|
7103
|
+
incoming[else_exit.label] = else_result
|
|
6785
7104
|
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
result_var: result_var
|
|
6792
|
-
)
|
|
6793
|
-
inst.sub_instruction_ids = sub_ids
|
|
6794
|
-
emit(inst)
|
|
6795
|
-
inst
|
|
7105
|
+
# Merge block with phi node
|
|
7106
|
+
set_current_block(merge_block)
|
|
7107
|
+
phi = Phi.new(incoming: incoming, type: typed_node.type, result_var: result_var)
|
|
7108
|
+
emit(phi)
|
|
7109
|
+
phi
|
|
6796
7110
|
end
|
|
6797
7111
|
|
|
6798
7112
|
def build_when_clause(when_typed_node)
|
|
6799
|
-
# Extract conditions and body from when clause
|
|
7113
|
+
# Extract conditions and body from when clause (kept for backward compatibility)
|
|
6800
7114
|
conditions = []
|
|
6801
7115
|
body = []
|
|
6802
7116
|
|
|
6803
7117
|
when_typed_node.children.each do |child|
|
|
6804
7118
|
if child.node_type == :statements
|
|
6805
|
-
# This is the body
|
|
6806
7119
|
child.children.each do |stmt|
|
|
6807
7120
|
inst = visit(stmt)
|
|
6808
7121
|
body << inst if inst
|
|
6809
7122
|
end
|
|
6810
7123
|
else
|
|
6811
|
-
# This is a condition
|
|
6812
7124
|
conditions << visit(child)
|
|
6813
7125
|
end
|
|
6814
7126
|
end
|
|
@@ -7470,7 +7782,7 @@ module Konpeito
|
|
|
7470
7782
|
|
|
7471
7783
|
def new_temp_var
|
|
7472
7784
|
@var_counter += 1
|
|
7473
|
-
"
|
|
7785
|
+
"__t#{@var_counter}"
|
|
7474
7786
|
end
|
|
7475
7787
|
end
|
|
7476
7788
|
end
|