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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/konpeito/codegen/builtin_methods.rb +4 -2
- data/lib/konpeito/codegen/cruby_backend.rb +122 -10
- data/lib/konpeito/codegen/inliner.rb +9 -3
- data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
- data/lib/konpeito/codegen/llvm_generator.rb +334 -45
- data/lib/konpeito/codegen/monomorphizer.rb +14 -2
- data/lib/konpeito/hir/builder.rb +150 -20
- data/lib/konpeito/hir/nodes.rb +16 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
- data/lib/konpeito/type_checker/types.rb +6 -6
- data/lib/konpeito/type_checker/unification.rb +8 -1
- data/lib/konpeito/version.rb +1 -1
- data/tools/konpeito-asm/build.sh +1 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
- data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
- metadata +7 -1
data/lib/konpeito/hir/builder.rb
CHANGED
|
@@ -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] =
|
|
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
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
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
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
6824
|
+
with_emit_to(else_body) do
|
|
6737
6825
|
else_statements.children.each do |stmt|
|
|
6738
|
-
|
|
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
|
|
6808
|
-
#
|
|
6809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}"
|
data/lib/konpeito/hir/nodes.rb
CHANGED
|
@@ -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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
-
|
|
1220
|
-
|
|
1259
|
+
# Convert and return the return type
|
|
1260
|
+
return_type = substitute_rbs_type(method_type.type.return_type, substitution)
|
|
1221
1261
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
|
|
1294
|
-
|
|
1339
|
+
# Substitute and convert return type
|
|
1340
|
+
return_type = substitute_rbs_type(method_type.type.return_type, substitution)
|
|
1295
1341
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
26
|
+
visiting.add(@id)
|
|
27
|
+
result = @instance.to_s
|
|
28
|
+
visiting.delete(@id)
|
|
29
|
+
result
|
|
23
30
|
else
|
|
24
31
|
@name
|
|
25
32
|
end
|
data/lib/konpeito/version.rb
CHANGED
data/tools/konpeito-asm/build.sh
CHANGED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -206,7 +206,12 @@ public class KonpeitoAssembler {
|
|
|
206
206
|
emitInstruction(mv, inst, labels);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
|
|
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);
|