konpeito 0.1.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d7f18a84d8cfece0c51becca2067ab474195205b4d5ce09b7a4e197b129851a
4
- data.tar.gz: ca0f8a113919d8ca6b28e8109fd2ed031979de67e695c9aaad9f659b425dab89
3
+ metadata.gz: b559f8a6de1d213b466939fb5fbde0a797a6808a7ce664f28d55ed0496696dde
4
+ data.tar.gz: 6809762994527a50fcf96c1644f24cde5f2324b668e1dc646bf32077ea679af0
5
5
  SHA512:
6
- metadata.gz: 62a1bd26cf53ddc918f1129772574021154079e492c33f48437290179fd71bdba65ca78c95890f16c224cf58930fbc879040c595580eb73a1d7e6868f0460cd9
7
- data.tar.gz: 295d6ac004cda8c7823ad711dca5015a95c78302fd0f3c2da0796cb30ef0c638a13bac53f0c8ae439fc50556bb1262e6a304facdd4dfaa3c6b425f1d3842f85a
6
+ metadata.gz: c8c43cca2f5e3812ee706a9b2b2c833ad9036d98e997b4481ce701d0edfa98b03a967f935c2b6aec7b8b2dfb19d27225b0e7a6f5e091ecc993a0786cec3cc2d7
7
+ data.tar.gz: 27acd8361e403febf7fd4e45b5ab832f1c767deedf329b8e164c8b62f7f1aa127c0ea75eeccaee7d5ef6767ea7f45c787baa526461bfa6dbf6ba034f90815bff
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to Konpeito will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.3] - 2026-02-18
9
+
10
+ ### Fixed
11
+ - JVM backend: resolve class method descriptor mismatch when called from instance methods (pre-register singleton method descriptors)
12
+ - Documentation: correct JWM and Skija GitHub URLs in README
13
+
14
+ ### Added
15
+ - Documentation: Castella UI calculator demo with Style composition
16
+
17
+ ## [0.1.2] - 2026-02-17
18
+
19
+ ### Fixed
20
+ - Castella UI: include padding and respect FIXED sizes in Column/Row measure
21
+ - Castella UI: use lighter accent variant for normal button hover across all themes
22
+ - JVM backend: preserve subclass type through inherited self-returning method chains
23
+
24
+ ### Changed
25
+ - Documentation: reorganize Castella UI section in README with DSL / Reactive State / Style Composition subheadings
26
+ - Documentation: add Style Composition example and screenshot to README
27
+ - Documentation: remove filler spacers from counter demos (EXPANDING default makes them unnecessary)
28
+ - Documentation: update counter screenshot
29
+
8
30
  ## [0.1.1] - 2026-02-16
9
31
 
10
32
  ### Fixed
@@ -86,5 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
86
108
  - `%a{extern}` - external C struct wrappers
87
109
  - `%a{simd}` - SIMD vectorization
88
110
 
111
+ [0.1.3]: https://github.com/i2y/konpeito/compare/v0.1.2...v0.1.3
112
+ [0.1.2]: https://github.com/i2y/konpeito/compare/v0.1.1...v0.1.2
89
113
  [0.1.1]: https://github.com/i2y/konpeito/compare/v0.1.0...v0.1.1
90
114
  [0.1.0]: https://github.com/i2y/konpeito/releases/tag/v0.1.0
data/README.md CHANGED
@@ -135,43 +135,238 @@ The JVM backend supports seamless Java interop — call Java libraries directly
135
135
 
136
136
  ## Castella UI
137
137
 
138
- Castella is a reactive GUI framework that runs on the JVM backend, powered by [JWM](https://github.com/nicenote/jwm) + [Skija](https://github.com/nicenote/skija) for cross-platform rendering. Based on a port of [Castella for Python](https://github.com/i2y/castella).
138
+ A reactive GUI framework for the JVM backend, powered by [JWM](https://github.com/HumbleUI/JWM) + [Skija](https://github.com/HumbleUI/Skija).
139
+
140
+ ### DSL
141
+
142
+ Ruby's block syntax becomes a UI DSL — `column`, `row`, `text`, `button` etc. nest naturally with keyword arguments. A plain Ruby method is a reusable component.
143
+
144
+ <p align="center">
145
+ <img src="docs/screenshots/dashboard.png" alt="Analytics Dashboard" width="720" />
146
+ </p>
147
+
148
+ ```ruby
149
+ def view
150
+ column(padding: 20.0, spacing: 16.0) {
151
+ row(spacing: 12.0) {
152
+ text("Analytics Dashboard", font_size: 26.0, bold: true)
153
+ spacer
154
+ button("Refresh", width: 90.0) {}
155
+ }.fixed_height(40.0)
156
+
157
+ # KPI Cards — extract a method, and it's a reusable component
158
+ row(spacing: 12.0) {
159
+ kpi_card("Revenue", "$48,250", "+12.5%", $theme.accent)
160
+ kpi_card("Users", "3,842", "+8.1%", $theme.success)
161
+ kpi_card("Orders", "1,205", "-2.3%", $theme.error)
162
+ }
163
+
164
+ # Charts, tables, and layouts compose with blocks
165
+ row(spacing: 12.0) {
166
+ column(expanding_width: true, bg_color: $theme.bg_primary, border_radius: 10.0, padding: 14.0) {
167
+ bar_chart(labels, data, ["Revenue", "Costs"]).title("Monthly Overview").fixed_height(220.0)
168
+ }
169
+ column(expanding_width: true, bg_color: $theme.bg_primary, border_radius: 10.0, padding: 14.0) {
170
+ data_table(headers, widths, rows).fixed_height(200.0)
171
+ }
172
+ }
173
+ }
174
+ end
175
+
176
+ # A Ruby method is a reusable component
177
+ def kpi_card(label, value, change, color)
178
+ column(spacing: 6.0, bg_color: $theme.bg_primary, border_radius: 10.0, padding: 16.0, expanding_width: true) {
179
+ text(label, font_size: 12.0, color: $theme.text_secondary)
180
+ text(value, font_size: 24.0, bold: true)
181
+ text(change, font_size: 13.0, color: color)
182
+ }
183
+ end
184
+ ```
185
+
186
+ ### Reactive State
187
+
188
+ `state(0)` creates an observable value, and the UI re-renders automatically when it changes:
189
+
190
+ <p align="center">
191
+ <img src="docs/screenshots/counter.png" alt="Counter App" width="320" />
192
+ </p>
139
193
 
140
194
  ```ruby
141
- class CounterApp < Component
195
+ class Counter < Component
142
196
  def initialize
143
197
  super
144
198
  @count = state(0)
145
199
  end
146
200
 
147
201
  def view
148
- label = "Count: " + @count.value.to_s
149
- Column(
150
- Text(label).font_size(32),
151
- Row(
152
- Button(" - ").on_click { @count -= 1 },
153
- Button(" + ").on_click { @count += 1 }
154
- )
155
- )
202
+ column(padding: 16.0, spacing: 8.0) {
203
+ text "Count: #{@count}", font_size: 32.0, align: :center
204
+ row(spacing: 8.0) {
205
+ button(" - ") { @count -= 1 }
206
+ button(" + ") { @count += 1 }
207
+ }
208
+ }
156
209
  end
157
210
  end
158
211
  ```
159
212
 
160
- A block-based DSL is also available:
213
+ An OOP-style API (`Column(...)`, `Row(...)`) is also available. Available widgets: `Text`, `Button`, `TextInput`, `MultilineText`, `Column`, `Row`, `Image`, `Checkbox`, `Slider`, `ProgressBar`, `Tabs`, `DataTable`, `TreeView`, `BarChart`, `LineChart`, `PieChart`, `Markdown`, and more.
214
+
215
+ ### Style Composition
216
+
217
+ Styles are first-class objects — store them in variables and compose with `+`:
218
+
219
+ <p align="center">
220
+ <img src="docs/screenshots/style_composition.png" alt="Style Composition" width="480" />
221
+ </p>
161
222
 
162
223
  ```ruby
163
- def view
164
- column(padding: 16.0) do
165
- text "Counter: #{@count}", font_size: 24.0, color: 0xFFC0CAF5
166
- row(spacing: 8.0) do
167
- button("-", width: 60.0) { @count -= 1 }
168
- button("+", width: 60.0) { @count += 1 }
224
+ card = Style.new.bg_color($theme.bg_primary).border_radius(10.0).padding(16.0)
225
+ green_card = card + Style.new.border_color($theme.success)
226
+
227
+ column(spacing: 12.0) {
228
+ container(card) { text "Default card" }
229
+ container(green_card) { text "Green border variant" }
230
+ }
231
+ ```
232
+
233
+ ### Calculator Demo
234
+
235
+ A port of the original Python Castella calculator. Flex layout, button kinds (`KIND_DANGER`, `KIND_WARNING`, `KIND_SUCCESS`), reactive `state()`, and class methods called from instance method callbacks — all in ~130 lines:
236
+
237
+ <p align="center">
238
+ <img src="docs/screenshots/calc.png" alt="Calculator" width="320" />
239
+ </p>
240
+
241
+ ```ruby
242
+ class Calc < Component
243
+ def initialize
244
+ super
245
+ @display = state("0")
246
+ @lhs = 0.0
247
+ @current_op = ""
248
+ @is_refresh = true
249
+ end
250
+
251
+ # --- Calculator logic ---
252
+
253
+ def press_number(label)
254
+ if @display.value == "0" || @is_refresh
255
+ @display.set(label)
256
+ else
257
+ @display.set(@display.value + label)
258
+ end
259
+ @is_refresh = false
260
+ end
261
+
262
+ def press_dot
263
+ if @is_refresh
264
+ @display.set("0.")
265
+ @is_refresh = false
266
+ return
267
+ end
268
+ if !@display.value.include?(".")
269
+ @display.set(@display.value + ".")
169
270
  end
170
271
  end
272
+
273
+ def all_clear
274
+ @display.set("0")
275
+ @lhs = 0.0
276
+ @current_op = ""
277
+ @is_refresh = true
278
+ end
279
+
280
+ def self.calc(lhs, op, rhs)
281
+ if op == "+"
282
+ lhs + rhs
283
+ elsif op == "-"
284
+ lhs - rhs
285
+ elsif op == "\u00D7"
286
+ lhs * rhs
287
+ elsif op == "\u00F7"
288
+ if rhs == 0.0
289
+ 0.0
290
+ else
291
+ lhs / rhs
292
+ end
293
+ else
294
+ rhs
295
+ end
296
+ end
297
+
298
+ def self.format_result(val)
299
+ if val == val.to_i.to_f
300
+ val.to_i.to_s
301
+ else
302
+ val.to_s
303
+ end
304
+ end
305
+
306
+ def press_operator(new_op)
307
+ rhs = @display.value.to_f
308
+ if @current_op != ""
309
+ result = Calc.calc(@lhs, @current_op, rhs)
310
+ @display.set(Calc.format_result(result))
311
+ @lhs = result
312
+ else
313
+ @lhs = rhs
314
+ end
315
+ if new_op == "="
316
+ @current_op = ""
317
+ else
318
+ @current_op = new_op
319
+ end
320
+ @is_refresh = true
321
+ end
322
+
323
+ # --- View ---
324
+
325
+ def view
326
+ grid = Style.new.spacing(4.0)
327
+ btn = Style.new.font_size(32.0)
328
+ op = btn + Style.new.kind(KIND_WARNING)
329
+ ac = btn + Style.new.kind(KIND_DANGER).flex(3)
330
+ eq = btn + Style.new.kind(KIND_SUCCESS)
331
+ wide = btn + Style.new.flex(2)
332
+
333
+ column(spacing: 4.0, padding: 4.0) {
334
+ text @display.value, font_size: 48.0, align: :right, kind: KIND_INFO, height: 72.0
335
+ row(grid) {
336
+ button("AC", ac) { all_clear }
337
+ button("\u00F7", op) { press_operator("\u00F7") }
338
+ }
339
+ row(grid) {
340
+ button("7", btn) { press_number("7") }
341
+ button("8", btn) { press_number("8") }
342
+ button("9", btn) { press_number("9") }
343
+ button("\u00D7", op) { press_operator("\u00D7") }
344
+ }
345
+ row(grid) {
346
+ button("4", btn) { press_number("4") }
347
+ button("5", btn) { press_number("5") }
348
+ button("6", btn) { press_number("6") }
349
+ button("-", op) { press_operator("-") }
350
+ }
351
+ row(grid) {
352
+ button("1", btn) { press_number("1") }
353
+ button("2", btn) { press_number("2") }
354
+ button("3", btn) { press_number("3") }
355
+ button("+", op) { press_operator("+") }
356
+ }
357
+ row(grid) {
358
+ button("0", wide) { press_number("0") }
359
+ button(".", btn) { press_dot }
360
+ button("=", eq) { press_operator("=") }
361
+ }
362
+ }
363
+ end
171
364
  end
172
- ```
173
365
 
174
- Available widgets: `Text`, `Button`, `TextInput`, `MultilineText`, `Column`, `Row`, `Image`, `Checkbox`, `Slider`, `ProgressBar`, `Tabs`, `DataTable`, `TreeView`, `BarChart`, `Markdown`, and more.
366
+ frame = JWMFrame.new("Castella Calculator", 320, 480)
367
+ app = App.new(frame, Calc.new)
368
+ app.run
369
+ ```
175
370
 
176
371
  ## Performance
177
372
 
@@ -3148,6 +3148,22 @@ module Konpeito
3148
3148
  ensure_slot(result_var, ret_type)
3149
3149
  instructions << store_instruction(result_var, ret_type)
3150
3150
  @variable_types[result_var] = ret_type
3151
+
3152
+ # Propagate class type so subsequent method chains resolve the receiver.
3153
+ # Without this, `Button("Animate!").kind(1)` fails because the receiver
3154
+ # class of `.kind(1)` is unknown after the static call to `Button()`.
3155
+ if ret_type.is_a?(Symbol) && ret_type.to_s.start_with?("class:")
3156
+ cls = ret_type.to_s.sub("class:", "")
3157
+ @variable_class_types[result_var] = cls if @class_info.key?(cls)
3158
+ end
3159
+ # Also check HM-inferred type from the call instruction
3160
+ if !@variable_class_types[result_var] && inst.respond_to?(:type) && inst.type
3161
+ call_type = inst.type
3162
+ call_type = call_type.prune if call_type.respond_to?(:prune)
3163
+ if call_type.is_a?(TypeChecker::Types::ClassInstance) && @class_info.key?(call_type.name.to_s)
3164
+ @variable_class_types[result_var] = call_type.name.to_s
3165
+ end
3166
+ end
3151
3167
  end
3152
3168
  end
3153
3169
 
@@ -3262,6 +3278,24 @@ module Konpeito
3262
3278
  instructions << { "op" => "invokestatic", "owner" => "java/lang/Boolean",
3263
3279
  "name" => "valueOf", "descriptor" => "(Z)Ljava/lang/Boolean;" }
3264
3280
  type = :value
3281
+ # Unbox Object to match expected primitive return type (static methods only).
3282
+ # Instance method descriptors are always normalized to Object return, so
3283
+ # unboxing would be incorrect — the value might not be the expected wrapper type.
3284
+ elsif !@generating_instance_method && @current_function_return_type == :double && type == :value
3285
+ instructions << { "op" => "checkcast", "type" => "java/lang/Double" }
3286
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Double",
3287
+ "name" => "doubleValue", "descriptor" => "()D" }
3288
+ type = :double
3289
+ elsif !@generating_instance_method && @current_function_return_type == :i64 && type == :value
3290
+ instructions << { "op" => "checkcast", "type" => "java/lang/Long" }
3291
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Long",
3292
+ "name" => "longValue", "descriptor" => "()J" }
3293
+ type = :i64
3294
+ elsif !@generating_instance_method && @current_function_return_type == :i8 && type == :value
3295
+ instructions << { "op" => "checkcast", "type" => "java/lang/Boolean" }
3296
+ instructions << { "op" => "invokevirtual", "owner" => "java/lang/Boolean",
3297
+ "name" => "booleanValue", "descriptor" => "()Z" }
3298
+ type = :i8
3265
3299
  end
3266
3300
 
3267
3301
  instructions << case type
@@ -4013,6 +4047,33 @@ module Konpeito
4013
4047
 
4014
4048
  @method_descriptors[key] = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4015
4049
  end
4050
+
4051
+ # Pre-register singleton (class) method descriptors
4052
+ (class_def.singleton_methods || []).each do |method_name|
4053
+ key = "#{class_name}.#{method_name}"
4054
+ next if @method_descriptors.key?(key)
4055
+
4056
+ func = find_class_singleton_method(class_name, method_name.to_s)
4057
+ next unless func
4058
+
4059
+ rbs_param_types = resolve_rbs_param_types(class_name, method_name.to_s, true)
4060
+ params_desc = func.params.each_with_index.map do |p, i|
4061
+ rbs_t = rbs_param_types && i < rbs_param_types.size ? rbs_param_types[i] : nil
4062
+ t = (rbs_t && rbs_t != :value) ? rbs_t : param_type(p)
4063
+ t = :value if t == :void
4064
+ type_to_descriptor(t)
4065
+ end.join
4066
+
4067
+ rbs_ret = resolve_rbs_return_type(class_name, method_name.to_s, true)
4068
+ if rbs_ret && rbs_ret != :value
4069
+ ret_type = rbs_ret
4070
+ else
4071
+ frt = function_return_type(func)
4072
+ ret_type = (frt == :void) ? :value : frt
4073
+ end
4074
+
4075
+ @method_descriptors[key] = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4076
+ end
4016
4077
  end
4017
4078
 
4018
4079
  def resolve_class_fields(class_def)
@@ -4391,9 +4452,17 @@ module Konpeito
4391
4452
  @current_generating_func_name = func.name.to_s
4392
4453
  reset_function_state(func)
4393
4454
 
4394
- # Pre-determine return type so generate_return knows if method returns Object
4395
- pre_ret = function_return_type(func)
4396
- @current_function_return_type = (pre_ret == :void) ? :value : pre_ret
4455
+ # Pre-determine return type so generate_return knows if method returns Object.
4456
+ # If a descriptor was pre-registered (from RBS), use its return type to ensure
4457
+ # the generated return instruction matches the pre-registered descriptor.
4458
+ pre_registered = @method_descriptors["#{class_def.name}.#{func.name}"]
4459
+ if pre_registered
4460
+ pre_ret = parse_descriptor_return_type(pre_registered)
4461
+ else
4462
+ pre_ret = function_return_type(func)
4463
+ pre_ret = :value if pre_ret == :void
4464
+ end
4465
+ @current_function_return_type = pre_ret
4397
4466
 
4398
4467
  # Try to resolve param types from RBS if HIR types are unresolved
4399
4468
  rbs_param_types = resolve_rbs_param_types(class_def.name.to_s, func.name.to_s, true)
@@ -4419,9 +4488,36 @@ module Konpeito
4419
4488
  instructions << default_return(ret_type)
4420
4489
  end
4421
4490
 
4422
- # Build descriptor from actual param types (which may have been corrected from RBS)
4423
- params_desc = func.params.map { |p| type_to_descriptor(@variable_types[p.name.to_s] || :value) }.join
4424
- descriptor = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4491
+ # Build descriptor: if pre-registered, use that descriptor directly for consistency.
4492
+ # The pre-registered descriptor was computed from param_type/RBS before code gen,
4493
+ # matching what the call site will use. The code-gen-derived descriptor may differ
4494
+ # because detect_return_type_from_instructions can miss returns in branching code.
4495
+ if pre_registered
4496
+ descriptor = pre_registered
4497
+ # Ensure final return instruction matches the pre-registered return type
4498
+ pre_ret_type = parse_descriptor_return_type(pre_registered)
4499
+ if pre_ret_type != :void && pre_ret_type != :value
4500
+ # Replace trailing void return with typed default return if needed
4501
+ if instructions.last && instructions.last["op"] == "return"
4502
+ instructions.pop
4503
+ case pre_ret_type
4504
+ when :i64
4505
+ instructions << { "op" => "lconst_0" }
4506
+ instructions << { "op" => "lreturn" }
4507
+ when :double
4508
+ instructions << { "op" => "dconst_0" }
4509
+ instructions << { "op" => "dreturn" }
4510
+ when :i8
4511
+ instructions << { "op" => "iconst_0" }
4512
+ instructions << { "op" => "ireturn" }
4513
+ end
4514
+ end
4515
+ end
4516
+ else
4517
+ # Build descriptor from actual param types (which may have been corrected from RBS)
4518
+ params_desc = func.params.map { |p| type_to_descriptor(@variable_types[p.name.to_s] || :value) }.join
4519
+ descriptor = "(#{params_desc})#{type_to_descriptor(ret_type)}"
4520
+ end
4425
4521
 
4426
4522
  # When renaming for conflict avoidance, use prefixed JVM name
4427
4523
  jvm_name = if rename_prefix
@@ -5114,8 +5210,16 @@ module Konpeito
5114
5210
  # checkcast when return type is a user class (descriptor returns Object but actual is specific class)
5115
5211
  if ret_type == :value
5116
5212
  rbs_class = resolve_rbs_return_class_name(owner_class, method_name)
5117
- # Also check inferred return class from function_return_type (self-returning methods)
5118
- rbs_class ||= inferred_return_class
5213
+ # When function returns self (SelfRef), preserve the receiver's actual class type.
5214
+ # RBS annotations declare the defining class (e.g., Widget#fixed_height → Widget)
5215
+ # but `self` at runtime is the receiver's class (e.g., Button). Without this,
5216
+ # method chains like Button(...).fixed_height(36.0).on_click { ... } break because
5217
+ # the receiver type narrows to Widget and on_click (defined on Button) is not found.
5218
+ if inferred_return_class
5219
+ rbs_class = inferred_return_class
5220
+ else
5221
+ rbs_class ||= inferred_return_class
5222
+ end
5119
5223
  if rbs_class && @class_info.key?(rbs_class)
5120
5224
  instructions << { "op" => "checkcast", "type" => @class_info[rbs_class][:jvm_name] }
5121
5225
  @variable_class_types[result_var] = rbs_class
@@ -5372,6 +5476,10 @@ module Konpeito
5372
5476
 
5373
5477
  return [] unless target_func
5374
5478
 
5479
+ # Look up the registered descriptor first to determine expected param types
5480
+ registered = @method_descriptors["#{class_name}.#{method_name}"]
5481
+ registered_param_types = registered ? parse_descriptor_param_types(registered) : nil
5482
+
5375
5483
  # Resolve param types: prefer RBS types over HIR types (which may be unresolved TypeVars)
5376
5484
  rbs_param_types = resolve_rbs_param_types(class_name, method_name, true)
5377
5485
 
@@ -5392,18 +5500,17 @@ module Konpeito
5392
5500
  end
5393
5501
  arg_types << :hash
5394
5502
  elsif i < args.size
5395
- param_t = param_type(param)
5396
- # Fallback to RBS type if param type is unresolved
5397
- if param_t == :value && rbs_param_types && i < rbs_param_types.size && rbs_param_types[i] != :value
5398
- # RBS resolved what HM couldn't — undo typevar fallback
5399
- @typevar_fallback_count -= 1 if @typevar_fallback_count > 0
5400
- param_t = rbs_param_types[i]
5401
- end
5402
- # Also try inferring from arg itself
5403
- if param_t == :value
5404
- arg_var = extract_var_name(args[i])
5405
- param_t = arg_var ? (@variable_types[arg_var] || :value) : (infer_type_from_hir(args[i]) || :value)
5406
- end
5503
+ # Use registered descriptor's param type if available (most accurate)
5504
+ param_t = if registered_param_types && i < registered_param_types.size
5505
+ registered_param_types[i]
5506
+ else
5507
+ pt = param_type(param)
5508
+ if pt == :value && rbs_param_types && i < rbs_param_types.size && rbs_param_types[i] != :value
5509
+ rbs_param_types[i]
5510
+ else
5511
+ pt
5512
+ end
5513
+ end
5407
5514
  instructions.concat(load_value(args[i], param_t))
5408
5515
  arg_types << param_t
5409
5516
  else
@@ -5415,7 +5522,6 @@ module Konpeito
5415
5522
  end
5416
5523
 
5417
5524
  # Build descriptor — prefer registered descriptor for consistency
5418
- registered = @method_descriptors["#{class_name}.#{method_name}"]
5419
5525
  if registered
5420
5526
  descriptor = registered
5421
5527
  ret_type = parse_descriptor_return_type(registered)
@@ -1429,6 +1429,10 @@ module Konpeito
1429
1429
  # Score how well an argument type matches an RBS parameter type
1430
1430
  def overload_match_score(arg_type, rbs_param_type)
1431
1431
  case rbs_param_type
1432
+ when RBS::Types::Alias
1433
+ # Resolve common aliases: ::int -> Integer, ::string -> String, etc.
1434
+ resolved = resolve_rbs_alias(rbs_param_type)
1435
+ return overload_match_score(arg_type, resolved) if resolved
1432
1436
  when RBS::Types::ClassInstance
1433
1437
  param_name = rbs_param_type.name.name
1434
1438
  if arg_type.is_a?(Types::ClassInstance)
@@ -1444,6 +1448,24 @@ module Konpeito
1444
1448
  0
1445
1449
  end
1446
1450
 
1451
+ # Resolve RBS type aliases to their underlying ClassInstance types
1452
+ def resolve_rbs_alias(alias_type)
1453
+ alias_name = alias_type.name.name
1454
+ class_name = case alias_name
1455
+ when :int then :Integer
1456
+ when :string then :String
1457
+ when :float then :Float
1458
+ when :bool then :Bool
1459
+ when :boolish then :Bool
1460
+ end
1461
+ return nil unless class_name
1462
+ RBS::Types::ClassInstance.new(
1463
+ name: RBS::TypeName.new(name: class_name, namespace: RBS::Namespace.root),
1464
+ args: [],
1465
+ location: alias_type.location
1466
+ )
1467
+ end
1468
+
1447
1469
  def numeric_compatible?(arg_name, param_name)
1448
1470
  numerics = %i[Integer Float Rational Complex Numeric]
1449
1471
  numerics.include?(arg_name) && numerics.include?(param_name)
@@ -186,6 +186,9 @@ module Konpeito
186
186
  elsif singleton_value_compatible?(t1, t2)
187
187
  # ClassSingleton (value constant like EXPANDING) is compatible with its base type.
188
188
  # e.g., singleton(EXPANDING) ↔ Integer when EXPANDING = 2
189
+ elsif literal_compatible?(t1, t2)
190
+ # Literals are subtypes of their base type (e.g., Literal(1) <: Integer, Literal("x") <: String)
191
+ # Unions of same-type literals are also compatible (e.g., -1 | 0 | 1 <: Integer)
189
192
  else
190
193
  raise UnificationError.new(t1, t2)
191
194
  end
@@ -207,6 +210,38 @@ module Konpeito
207
210
  (t2.is_a?(Types::ClassSingleton) && t1.is_a?(Types::ClassInstance))
208
211
  end
209
212
 
213
+ # Check if a literal type (or union of literals) is compatible with its base class type.
214
+ # e.g., Literal(1) <: Integer, Literal("hello") <: String, Literal(1.0) <: Float
215
+ # Union(Literal(-1), Literal(0), Literal(1)) <: Integer
216
+ def literal_compatible?(t1, t2)
217
+ (literal_subtype_of_class?(t1, t2)) || (literal_subtype_of_class?(t2, t1))
218
+ end
219
+
220
+ def literal_subtype_of_class?(literal_side, class_side)
221
+ return false unless class_side.is_a?(Types::ClassInstance)
222
+
223
+ if literal_side.is_a?(Types::Literal)
224
+ literal_base_type_name(literal_side.value) == class_side.name
225
+ elsif literal_side.is_a?(Types::Union)
226
+ literal_side.types.all? do |sub|
227
+ sub.is_a?(Types::Literal) && literal_base_type_name(sub.value) == class_side.name
228
+ end
229
+ else
230
+ false
231
+ end
232
+ end
233
+
234
+ # Map a Ruby literal value to its base type name
235
+ def literal_base_type_name(value)
236
+ case value
237
+ when Integer then :Integer
238
+ when Float then :Float
239
+ when String then :String
240
+ when Symbol then :Symbol
241
+ when true, false then :Bool
242
+ end
243
+ end
244
+
210
245
  # Check if `from` can be widened to `to` (Java/Kotlin-style widening primitive conversion).
211
246
  # Integer → Float is safe (no precision loss for typical values).
212
247
  # Float → Integer is NOT supported (requires explicit conversion).