konpeito 0.2.2 → 0.2.3

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/konpeito/codegen/builtin_methods.rb +4 -2
  4. data/lib/konpeito/codegen/cruby_backend.rb +122 -10
  5. data/lib/konpeito/codegen/inliner.rb +9 -3
  6. data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
  7. data/lib/konpeito/codegen/llvm_generator.rb +334 -45
  8. data/lib/konpeito/codegen/monomorphizer.rb +14 -2
  9. data/lib/konpeito/hir/builder.rb +150 -20
  10. data/lib/konpeito/hir/nodes.rb +16 -0
  11. data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
  12. data/lib/konpeito/type_checker/types.rb +6 -6
  13. data/lib/konpeito/type_checker/unification.rb +8 -1
  14. data/lib/konpeito/version.rb +1 -1
  15. data/tools/konpeito-asm/build.sh +1 -0
  16. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  17. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  18. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
  19. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  20. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
  21. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
  22. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  23. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  24. data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
  25. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
  26. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
  27. data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
  28. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
  29. data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
  30. data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
  31. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
  32. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
  33. metadata +7 -1
@@ -500,6 +500,19 @@ module Konpeito
500
500
  generate_attr_reader_method(attr_name, class_def)
501
501
  generate_attr_writer_method(attr_name, class_def)
502
502
  end
503
+ elsif child.node_type == :constant_write
504
+ # Constant assignment in class body (e.g., PI = 3)
505
+ const_name = child.node.name.to_s
506
+ value_node = visit_literal_value(child.children.first)
507
+ class_def.body_constants << [const_name, value_node]
508
+ elsif child.node_type == :class_variable_write
509
+ # Class variable initialization in class body (e.g., @@count = 0)
510
+ cvar_name = child.node.name.to_s
511
+ value_node = visit_literal_value(child.children.first)
512
+ class_def.body_class_vars << [cvar_name, value_node]
513
+ # Also track class variable for the class
514
+ @class_vars[name] ||= Set.new
515
+ @class_vars[name] << cvar_name
503
516
  else
504
517
  visit(child)
505
518
  end
@@ -807,7 +820,11 @@ module Konpeito
807
820
  elsif child.node_type == :constant_write
808
821
  # Handle constant assignment within module
809
822
  const_name = child.node.name.to_s
810
- module_constants[const_name] = visit(child)
823
+ module_constants[const_name] = visit_literal_value(child.children.first)
824
+ elsif child.node_type == :class_variable_write
825
+ # Handle class variable initialization within module
826
+ # (modules can have class variables too)
827
+ visit(child)
811
828
  else
812
829
  visit(child)
813
830
  end
@@ -2116,6 +2133,42 @@ module Konpeito
2116
2133
  block = visit_block_def(block_child)
2117
2134
  end
2118
2135
 
2136
+ # Handle &blk block argument reference (e.g., arr.map(&blk))
2137
+ unless block
2138
+ block_arg_child = typed_node.children.find { |c| c.node_type == :block_argument }
2139
+ if block_arg_child
2140
+ blk_node = block_arg_child.node
2141
+ if blk_node.respond_to?(:expression) && blk_node.expression
2142
+ blk_name = blk_node.expression.name.to_s
2143
+ # Create a wrapper BlockDef: { |__block_arg_param| blk.call(__block_arg_param) }
2144
+ # Load blk from captures inside the block body (not from outer scope)
2145
+ param_name = "__block_arg_param"
2146
+ param = Param.new(name: param_name, type: TypeChecker::Types::UNTYPED)
2147
+ param_var = LocalVar.new(name: param_name, type: TypeChecker::Types::UNTYPED)
2148
+ param_load = LoadLocal.new(var: param_var, type: TypeChecker::Types::UNTYPED, result_var: new_temp_var)
2149
+ blk_local_var = LocalVar.new(name: blk_name, type: TypeChecker::Types::UNTYPED)
2150
+ blk_load = LoadLocal.new(var: blk_local_var, type: TypeChecker::Types::UNTYPED, result_var: new_temp_var)
2151
+ call_inst = Call.new(
2152
+ receiver: blk_load,
2153
+ method_name: "call",
2154
+ args: [param_load],
2155
+ type: TypeChecker::Types::UNTYPED,
2156
+ result_var: new_temp_var
2157
+ )
2158
+ # Wrap in a BasicBlock (BlockDef.body expects Array[BasicBlock])
2159
+ bb = BasicBlock.new(label: "block_arg_body")
2160
+ bb.add_instruction(blk_load)
2161
+ bb.add_instruction(param_load)
2162
+ bb.add_instruction(call_inst)
2163
+ block = BlockDef.new(
2164
+ params: [param],
2165
+ body: [bb],
2166
+ captures: [Capture.new(name: blk_name, type: TypeChecker::Types::UNTYPED)]
2167
+ )
2168
+ end
2169
+ end
2170
+ end
2171
+
2119
2172
  result_var = new_temp_var
2120
2173
  is_safe_nav = node.respond_to?(:safe_navigation?) && node.safe_navigation?
2121
2174
  inst = Call.new(
@@ -6439,25 +6492,60 @@ module Konpeito
6439
6492
  # RHS value
6440
6493
  rhs = visit(typed_node.children.first)
6441
6494
 
6442
- targets = typed_node.node.lefts
6443
- targets.each_with_index do |target, i|
6444
- case target
6445
- when Prism::LocalVariableTargetNode
6495
+ lefts = typed_node.node.lefts
6496
+ rest = typed_node.node.rest
6497
+ rights = typed_node.node.rights
6498
+
6499
+ # Process left targets (before splat)
6500
+ lefts.each_with_index do |target, i|
6501
+ emit_multi_write_target(target, rhs, i)
6502
+ end
6503
+
6504
+ # Process splat target: *rest
6505
+ if rest && rest.is_a?(Prism::SplatNode) && rest.expression
6506
+ target = rest.expression
6507
+ if target.is_a?(Prism::LocalVariableTargetNode)
6446
6508
  name = target.name.to_s
6447
- # Extract element using MultiWriteExtract (rb_ary_entry)
6448
- elem_var = new_temp_var
6449
- elem_inst = MultiWriteExtract.new(array: rhs, index: i, type: TypeChecker::Types::UNTYPED, result_var: elem_var)
6450
- emit(elem_inst)
6509
+ splat_var = new_temp_var
6510
+ end_offset = rights ? rights.length : 0
6511
+ splat_inst = MultiWriteSplat.new(
6512
+ array: rhs, start_index: lefts.length, end_offset: end_offset,
6513
+ type: TypeChecker::Types::UNTYPED, result_var: splat_var
6514
+ )
6515
+ emit(splat_inst)
6451
6516
 
6452
6517
  var = @local_vars[name] ||= LocalVar.new(name: name, type: TypeChecker::Types::UNTYPED)
6453
- store_inst = StoreLocal.new(var: var, value: elem_inst, type: TypeChecker::Types::UNTYPED)
6518
+ store_inst = StoreLocal.new(var: var, value: splat_inst, type: TypeChecker::Types::UNTYPED)
6454
6519
  emit(store_inst)
6455
6520
  end
6456
6521
  end
6457
6522
 
6523
+ # Process right targets (after splat) — use negative indices from end
6524
+ if rights && !rights.empty?
6525
+ rights.each_with_index do |target, i|
6526
+ # Use negative index: rights[0] = arr[-rights.length], rights[1] = arr[-(rights.length-1)], etc.
6527
+ neg_index = -(rights.length - i)
6528
+ emit_multi_write_target(target, rhs, neg_index)
6529
+ end
6530
+ end
6531
+
6458
6532
  rhs
6459
6533
  end
6460
6534
 
6535
+ def emit_multi_write_target(target, rhs, index)
6536
+ case target
6537
+ when Prism::LocalVariableTargetNode
6538
+ name = target.name.to_s
6539
+ elem_var = new_temp_var
6540
+ elem_inst = MultiWriteExtract.new(array: rhs, index: index, type: TypeChecker::Types::UNTYPED, result_var: elem_var)
6541
+ emit(elem_inst)
6542
+
6543
+ var = @local_vars[name] ||= LocalVar.new(name: name, type: TypeChecker::Types::UNTYPED)
6544
+ store_inst = StoreLocal.new(var: var, value: elem_inst, type: TypeChecker::Types::UNTYPED)
6545
+ emit(store_inst)
6546
+ end
6547
+ end
6548
+
6461
6549
  def visit_return(typed_node)
6462
6550
  value = typed_node.children.any? ? visit(typed_node.children.first) : NilLit.new
6463
6551
  @current_block.set_terminator(Return.new(value: value))
@@ -6726,17 +6814,16 @@ module Konpeito
6726
6814
  end
6727
6815
 
6728
6816
  # Build else body
6729
- # Use without_emit to prevent instructions from being added to current block
6817
+ # Use with_emit_to to redirect emitted instructions to the else_body array
6730
6818
  else_body = nil
6731
6819
  else_child = typed_node.children.find { |c| c.node_type == :else }
6732
6820
  if else_child
6733
6821
  else_body = []
6734
6822
  else_statements = else_child.children.find { |c| c.node_type == :statements }
6735
6823
  if else_statements
6736
- without_emit do
6824
+ with_emit_to(else_body) do
6737
6825
  else_statements.children.each do |stmt|
6738
- inst = visit(stmt)
6739
- else_body << inst if inst
6826
+ visit(stmt)
6740
6827
  end
6741
6828
  end
6742
6829
  end
@@ -6804,16 +6891,16 @@ module Konpeito
6804
6891
  end
6805
6892
 
6806
6893
  # Second pass: build body with pattern variables in scope
6807
- # Use without_emit to prevent instructions from being added to current block
6808
- # They should only be stored in the InClause body
6809
- without_emit do
6894
+ # Use with_emit_to to redirect emitted instructions to the body array
6895
+ # instead of the current block. This ensures that StoreLocal instructions
6896
+ # (from variable assignments like `captured = a`) are properly captured.
6897
+ with_emit_to(body) do
6810
6898
  in_typed_node.children.each do |child|
6811
6899
  case child.node_type
6812
6900
  when :statements
6813
6901
  # This is the body
6814
6902
  child.children.each do |stmt|
6815
- inst = visit(stmt)
6816
- body << inst if inst
6903
+ visit(stmt)
6817
6904
  end
6818
6905
  end
6819
6906
  end
@@ -7293,7 +7380,11 @@ module Konpeito
7293
7380
 
7294
7381
  def emit(instruction)
7295
7382
  return if @suppress_emit
7296
- @current_block.add_instruction(instruction) if @current_block
7383
+ if @emit_collector
7384
+ @emit_collector << instruction
7385
+ else
7386
+ @current_block.add_instruction(instruction) if @current_block
7387
+ end
7297
7388
  end
7298
7389
 
7299
7390
  def without_emit
@@ -7304,6 +7395,45 @@ module Konpeito
7304
7395
  result
7305
7396
  end
7306
7397
 
7398
+ # Redirect emitted instructions to the given array instead of the current block.
7399
+ # Used for building case/in clause bodies where we need to capture all emitted
7400
+ # instructions (including StoreLocal from variable assignments) without adding
7401
+ # them to the current basic block.
7402
+ def with_emit_to(collector)
7403
+ old_collector = @emit_collector
7404
+ @emit_collector = collector
7405
+ yield
7406
+ ensure
7407
+ @emit_collector = old_collector
7408
+ end
7409
+
7410
+ # Extract a literal value from a typed node without emitting instructions.
7411
+ # Used for class/module body constant and class variable initializations.
7412
+ # Returns an HIR literal node (IntegerLit, FloatLit, StringLit, etc.) or visits the node.
7413
+ def visit_literal_value(typed_node)
7414
+ return NilLit.new unless typed_node
7415
+
7416
+ case typed_node.node_type
7417
+ when :integer
7418
+ IntegerLit.new(value: typed_node.node.value)
7419
+ when :float
7420
+ FloatLit.new(value: typed_node.node.value)
7421
+ when :string
7422
+ StringLit.new(value: typed_node.node.unescaped)
7423
+ when :symbol
7424
+ SymbolLit.new(value: typed_node.node.value.to_s)
7425
+ when :true
7426
+ BoolLit.new(value: true)
7427
+ when :false
7428
+ BoolLit.new(value: false)
7429
+ when :nil
7430
+ NilLit.new
7431
+ else
7432
+ # For non-literal values, try visiting but suppress emit
7433
+ without_emit { visit(typed_node) }
7434
+ end
7435
+ end
7436
+
7307
7437
  def new_temp_var
7308
7438
  @var_counter += 1
7309
7439
  "t#{@var_counter}"
@@ -59,6 +59,8 @@ module Konpeito
59
59
  attr_accessor :reopened # true if this is reopening an existing class
60
60
  attr_accessor :singleton_methods # Names of class-level methods (def self.xxx or class << self)
61
61
  attr_accessor :instance_var_types # HM-inferred ivar types: { "name" => :Integer, "age" => :String, ... }
62
+ attr_accessor :body_constants # Array of [name, value_node] for constants defined in class body
63
+ attr_accessor :body_class_vars # Array of [name, value_node] for class variables initialized in class body
62
64
 
63
65
  def initialize(name:, superclass: nil, method_names: [], instance_vars: [], included_modules: [], extended_modules: [], prepended_modules: [])
64
66
  super(type: TypeChecker::Types::NIL)
@@ -75,6 +77,8 @@ module Konpeito
75
77
  @reopened = false
76
78
  @singleton_methods = []
77
79
  @instance_var_types = {}
80
+ @body_constants = []
81
+ @body_class_vars = []
78
82
  end
79
83
  end
80
84
 
@@ -483,6 +487,18 @@ module Konpeito
483
487
  end
484
488
  end
485
489
 
490
+ # Multi-write splat extraction: a, *rest = arr → rest = arr[start..end]
491
+ class MultiWriteSplat < Instruction
492
+ attr_reader :array, :start_index, :end_offset
493
+
494
+ def initialize(array:, start_index:, end_offset:, type: TypeChecker::Types::UNTYPED, result_var: nil)
495
+ super(type: type, result_var: result_var)
496
+ @array = array
497
+ @start_index = start_index # Index to start collecting from
498
+ @end_offset = end_offset # Number of elements to exclude from end (for trailing targets)
499
+ end
500
+ end
501
+
486
502
  # Constant lookup
487
503
  class ConstantLookup < Instruction
488
504
  attr_reader :name, :scope
@@ -34,6 +34,7 @@ module Konpeito
34
34
  @deferred_constraints = [] # Deferred method resolution for TypeVar receivers
35
35
  @keyword_param_vars = {} # func_key => { :param_name => TypeVar }
36
36
  @unresolved_type_warnings = [] # Warnings for types that survived inference
37
+ @polymorphic_methods = {} # qualified_key => TypeScheme for instance methods
37
38
  end
38
39
 
39
40
  # Main entry: infer types for a program AST
@@ -309,9 +310,27 @@ module Konpeito
309
310
  @function_types[node.name.to_sym] = func_type
310
311
 
311
312
  if @in_class_collect
312
- # Class methods: monomorphic binding — call-site unification flows into method body
313
- # This allows argument types (e.g., Array from call site) to be visible in method body
314
- bind_scheme(node.name, TypeScheme.new([], func_type))
313
+ is_class_method = node.receiver.is_a?(Prism::SelfNode) rescue false
314
+ is_initialize = node.name.to_sym == :initialize
315
+ if is_class_method || is_initialize
316
+ # Class methods and initialize: monomorphic binding — call-site unification
317
+ # flows into method body. This allows argument types (e.g., Array from call
318
+ # site) to be visible in method body.
319
+ bind_scheme(node.name, TypeScheme.new([], func_type))
320
+ else
321
+ # Instance methods: polymorphic binding — each call site gets fresh type
322
+ # variables. This allows methods like []= to accept different types at
323
+ # different call sites (e.g., c["name"] = "Alice"; c["age"] = 30).
324
+ all_vars = collect_type_vars(func_type)
325
+ scheme = TypeScheme.new(all_vars, func_type)
326
+ bind_scheme(node.name, scheme)
327
+ # Mark this method as polymorphic for receiver-based call resolution.
328
+ # The actual scheme will be created lazily at the first call site,
329
+ # after body inference has established internal TypeVar linkages.
330
+ if @collect_class_name && !all_vars.empty?
331
+ @polymorphic_methods[:"#{@collect_class_name}##{node.name}"] = true
332
+ end
333
+ end
315
334
  else
316
335
  # Top-level functions: generalize to type scheme (all type vars are quantified)
317
336
  all_vars = collect_type_vars(func_type)
@@ -782,23 +801,43 @@ module Konpeito
782
801
 
783
802
  # Receiver-based call on user class: look up class-qualified method type
784
803
  # Walk up class hierarchy to find inherited methods
785
- # Call-site unification flows argument types into method body (monomorphic binding)
786
804
  if node.receiver && receiver_type.is_a?(Types::ClassInstance)
787
805
  class_name = receiver_type.name.to_s
788
806
  cls = class_name
789
807
  func_type = nil
808
+ polymorphic_scheme = nil
790
809
  while cls
791
- func_type = @function_types[:"#{cls}##{method_name}"]
810
+ qualified_key = :"#{cls}##{method_name}"
811
+ # Check if this method has polymorphic binding (instance methods)
812
+ polymorphic_scheme = @polymorphic_methods[qualified_key]
813
+ func_type = @function_types[qualified_key]
792
814
  break if func_type.is_a?(FunctionType)
793
815
  cls = @class_parents[cls]
794
816
  end
795
817
  if func_type.is_a?(FunctionType)
796
- # Directly unify — monomorphic binding allows call-site types to flow into method body
797
- unify_call_args(func_type, arg_types)
798
- # Unify keyword args from call site
799
- unify_keyword_args(node, :"#{class_name}##{method_name}")
800
- infer_block_body_without_rbs(node.block) if node.block
801
- return @unifier.apply(func_type.return_type)
818
+ if polymorphic_scheme
819
+ # Polymorphic instance methods: try unification but allow type mismatches.
820
+ # If a type mismatch occurs (e.g., []= called with String then Integer),
821
+ # fall through to dynamic dispatch (UNTYPED return).
822
+ begin
823
+ unify_call_args(func_type, arg_types)
824
+ unify_keyword_args(node, :"#{class_name}##{method_name}")
825
+ infer_block_body_without_rbs(node.block) if node.block
826
+ return @unifier.apply(func_type.return_type)
827
+ rescue UnificationError
828
+ # Type mismatch on polymorphic instance method — fall through to
829
+ # dynamic dispatch. This handles cases like c["name"] = "Alice"
830
+ # followed by c["age"] = 30 where val type differs across calls.
831
+ infer_block_body_without_rbs(node.block) if node.block
832
+ return Types::UNTYPED
833
+ end
834
+ else
835
+ # Monomorphic binding: call-site types flow into method body
836
+ unify_call_args(func_type, arg_types)
837
+ unify_keyword_args(node, :"#{class_name}##{method_name}")
838
+ infer_block_body_without_rbs(node.block) if node.block
839
+ return @unifier.apply(func_type.return_type)
840
+ end
802
841
  end
803
842
  end
804
843
 
@@ -1208,23 +1247,29 @@ module Konpeito
1208
1247
  # Guard against UntypedFunction (which lacks required_positionals)
1209
1248
  return Types::UNTYPED if method_type.type.is_a?(RBS::Types::UntypedFunction)
1210
1249
 
1211
- # Unify argument types with parameter types from RBS
1212
- rbs_params = method_type.type.required_positionals + method_type.type.optional_positionals
1213
- arg_types.each_with_index do |arg_type, i|
1214
- next unless rbs_params[i]
1215
- expected_type = substitute_rbs_type(rbs_params[i].type, substitution)
1216
- @unifier.unify(arg_type, expected_type)
1217
- end
1250
+ begin
1251
+ # Unify argument types with parameter types from RBS
1252
+ rbs_params = method_type.type.required_positionals + method_type.type.optional_positionals
1253
+ arg_types.each_with_index do |arg_type, i|
1254
+ next unless rbs_params[i]
1255
+ expected_type = substitute_rbs_type(rbs_params[i].type, substitution)
1256
+ @unifier.unify(arg_type, expected_type)
1257
+ end
1218
1258
 
1219
- # Convert and return the return type
1220
- return_type = substitute_rbs_type(method_type.type.return_type, substitution)
1259
+ # Convert and return the return type
1260
+ return_type = substitute_rbs_type(method_type.type.return_type, substitution)
1221
1261
 
1222
- # Handle block if present
1223
- if node.block && method_type.block
1224
- infer_block_for_rbs(node.block, method_type.block, substitution)
1225
- end
1262
+ # Handle block if present
1263
+ if node.block && method_type.block
1264
+ infer_block_for_rbs(node.block, method_type.block, substitution)
1265
+ end
1226
1266
 
1227
- @unifier.apply(return_type)
1267
+ @unifier.apply(return_type)
1268
+ rescue UnificationError
1269
+ # RBS type unification failed — fall through to dynamic dispatch
1270
+ infer_block_body_without_rbs(node.block) if node.block
1271
+ nil
1272
+ end
1228
1273
  end
1229
1274
 
1230
1275
  # Build substitution map for singleton method type parameters
@@ -1281,24 +1326,32 @@ module Konpeito
1281
1326
  # Guard against UntypedFunction (which lacks required_positionals)
1282
1327
  return Types::UNTYPED if method_type.type.is_a?(RBS::Types::UntypedFunction)
1283
1328
 
1284
- # Unify argument types with parameter types from RBS
1285
- # This allows us to infer argument types from method signatures
1286
- rbs_params = method_type.type.required_positionals + method_type.type.optional_positionals
1287
- arg_types.each_with_index do |arg_type, i|
1288
- next unless rbs_params[i]
1289
- expected_type = substitute_rbs_type(rbs_params[i].type, substitution)
1290
- @unifier.unify(arg_type, expected_type)
1291
- end
1329
+ begin
1330
+ # Unify argument types with parameter types from RBS
1331
+ # This allows us to infer argument types from method signatures
1332
+ rbs_params = method_type.type.required_positionals + method_type.type.optional_positionals
1333
+ arg_types.each_with_index do |arg_type, i|
1334
+ next unless rbs_params[i]
1335
+ expected_type = substitute_rbs_type(rbs_params[i].type, substitution)
1336
+ @unifier.unify(arg_type, expected_type)
1337
+ end
1292
1338
 
1293
- # Substitute and convert return type
1294
- return_type = substitute_rbs_type(method_type.type.return_type, substitution)
1339
+ # Substitute and convert return type
1340
+ return_type = substitute_rbs_type(method_type.type.return_type, substitution)
1295
1341
 
1296
- # Handle block if present (for methods like map)
1297
- if node.block && method_type.block
1298
- infer_block_for_rbs(node.block, method_type.block, substitution)
1299
- end
1342
+ # Handle block if present (for methods like map)
1343
+ if node.block && method_type.block
1344
+ infer_block_for_rbs(node.block, method_type.block, substitution)
1345
+ end
1300
1346
 
1301
- @unifier.apply(return_type)
1347
+ @unifier.apply(return_type)
1348
+ rescue UnificationError
1349
+ # RBS type unification failed — Ruby stdlib RBS has complex type signatures
1350
+ # (e.g., sort_by block returns Comparable | Array[untyped], flat_map returns Array[U] | U)
1351
+ # that may not unify cleanly with concrete types. Fall through to dynamic dispatch.
1352
+ infer_block_body_without_rbs(node.block) if node.block
1353
+ nil
1354
+ end
1302
1355
  end
1303
1356
 
1304
1357
  # Build substitution map from class type parameters to actual types
@@ -1491,10 +1544,14 @@ module Konpeito
1491
1544
  # The block's return type should unify with the RBS block's return type
1492
1545
  # For example: map's block { (Elem) -> U } means block returns U
1493
1546
 
1547
+ # BlockArgumentNode (&blk) has no parameters or body — skip block inference entirely
1548
+ return Types::UNTYPED if block_node.is_a?(Prism::BlockArgumentNode)
1549
+
1494
1550
  push_env
1495
1551
 
1496
1552
  # Bind block parameters (skip if UntypedFunction)
1497
- if block_node.parameters && rbs_block.type.respond_to?(:required_positionals)
1553
+ # BlockArgumentNode (&blk) does not have a parameters method - skip it
1554
+ if !block_node.is_a?(Prism::BlockArgumentNode) && block_node.respond_to?(:parameters) && block_node.parameters && rbs_block.type.respond_to?(:required_positionals)
1498
1555
  if block_node.parameters.is_a?(Prism::NumberedParametersNode)
1499
1556
  # Numbered block parameters (_1, _2, ...)
1500
1557
  rbs_positionals = rbs_block.type.required_positionals
@@ -1542,6 +1599,8 @@ module Konpeito
1542
1599
  # Infer block body without RBS block type info.
1543
1600
  # Block parameters become fresh TypeVars; captured variables retain outer scope types.
1544
1601
  def infer_block_body_without_rbs(block_node)
1602
+ return if block_node.is_a?(Prism::BlockArgumentNode)
1603
+
1545
1604
  push_env
1546
1605
  if block_node.parameters
1547
1606
  if block_node.parameters.is_a?(Prism::NumberedParametersNode)
@@ -145,13 +145,13 @@ module Konpeito
145
145
 
146
146
  # Basic Ruby class hierarchy
147
147
  CLASS_HIERARCHY = {
148
- Integer: [:Numeric, :Object, :BasicObject],
149
- Float: [:Numeric, :Object, :BasicObject],
150
- Rational: [:Numeric, :Object, :BasicObject],
148
+ Integer: [:Numeric, :Comparable, :Object, :BasicObject],
149
+ Float: [:Numeric, :Comparable, :Object, :BasicObject],
150
+ Rational: [:Numeric, :Comparable, :Object, :BasicObject],
151
151
  Complex: [:Numeric, :Object, :BasicObject],
152
- Numeric: [:Object, :BasicObject],
153
- String: [:Object, :BasicObject],
154
- Symbol: [:Object, :BasicObject],
152
+ Numeric: [:Comparable, :Object, :BasicObject],
153
+ String: [:Comparable, :Object, :BasicObject],
154
+ Symbol: [:Comparable, :Object, :BasicObject],
155
155
  Array: [:Object, :BasicObject],
156
156
  Hash: [:Object, :BasicObject],
157
157
  TrueClass: [:Object, :BasicObject],
@@ -18,8 +18,15 @@ module Konpeito
18
18
  end
19
19
 
20
20
  def to_s
21
+ # Guard against circular type variable references (e.g., TypeVar → Union → TypeVar)
22
+ visiting = Thread.current[:__typevar_visiting] ||= Set.new
23
+ return @name if visiting.include?(@id)
24
+
21
25
  if @instance
22
- @instance.to_s
26
+ visiting.add(@id)
27
+ result = @instance.to_s
28
+ visiting.delete(@id)
29
+ result
23
30
  else
24
31
  @name
25
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Konpeito
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.3"
5
5
  end
@@ -44,6 +44,7 @@ mkdir -p "$BUILD_DIR/konpeito/runtime"
44
44
  "$SCRIPT_DIR/src/konpeito/runtime/KRactor.java" \
45
45
  "$SCRIPT_DIR/src/konpeito/runtime/KRactorPort.java" \
46
46
  "$SCRIPT_DIR/src/konpeito/runtime/KMatchData.java" \
47
+ "$SCRIPT_DIR/src/konpeito/runtime/KFiber.java" \
47
48
  "$SCRIPT_DIR/src/konpeito/runtime/RubyDispatch.java"
48
49
 
49
50
  # Copy runtime classes to a separate directory for JAR bundling
@@ -206,7 +206,12 @@ public class KonpeitoAssembler {
206
206
  emitInstruction(mv, inst, labels);
207
207
  }
208
208
 
209
- mv.visitMaxs(0, 0); // ASM COMPUTE_MAXS handles this
209
+ try {
210
+ mv.visitMaxs(0, 0); // ASM COMPUTE_MAXS handles this
211
+ } catch (Exception e) {
212
+ System.err.println("ASM error in class=" + cw + " method=" + name + " descriptor=" + descriptor);
213
+ throw e;
214
+ }
210
215
  mv.visitEnd();
211
216
  }
212
217
 
@@ -275,6 +280,13 @@ public class KonpeitoAssembler {
275
280
  // --- Arithmetic (int) ---
276
281
  case "iadd" -> mv.visitInsn(Opcodes.IADD);
277
282
  case "isub" -> mv.visitInsn(Opcodes.ISUB);
283
+ case "imul" -> mv.visitInsn(Opcodes.IMUL);
284
+ case "iand" -> mv.visitInsn(Opcodes.IAND);
285
+ case "ior" -> mv.visitInsn(Opcodes.IOR);
286
+
287
+ // --- Short/byte push ---
288
+ case "bipush" -> mv.visitIntInsn(Opcodes.BIPUSH, getInt(inst, "value"));
289
+ case "sipush" -> mv.visitIntInsn(Opcodes.SIPUSH, getInt(inst, "value"));
278
290
 
279
291
  // --- Arithmetic (long) ---
280
292
  case "ladd" -> mv.visitInsn(Opcodes.LADD);
@@ -441,6 +453,10 @@ public class KonpeitoAssembler {
441
453
  case "dastore" -> mv.visitInsn(Opcodes.DASTORE);
442
454
  case "aaload" -> mv.visitInsn(Opcodes.AALOAD);
443
455
  case "aastore" -> mv.visitInsn(Opcodes.AASTORE);
456
+ case "baload" -> mv.visitInsn(Opcodes.BALOAD);
457
+ case "bastore" -> mv.visitInsn(Opcodes.BASTORE);
458
+ case "iaload" -> mv.visitInsn(Opcodes.IALOAD);
459
+ case "iastore" -> mv.visitInsn(Opcodes.IASTORE);
444
460
 
445
461
  // --- Exception handling ---
446
462
  case "athrow" -> mv.visitInsn(Opcodes.ATHROW);