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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +214 -19
- data/lib/konpeito/codegen/jvm_generator.rb +127 -21
- data/lib/konpeito/type_checker/hm_inferrer.rb +22 -0
- data/lib/konpeito/type_checker/unification.rb +35 -0
- data/lib/konpeito/ui/column.rb +45 -10
- data/lib/konpeito/ui/core.rb +270 -24
- data/lib/konpeito/ui/row.rb +54 -12
- data/lib/konpeito/ui/theme.rb +5 -5
- data/lib/konpeito/ui/widgets/button.rb +3 -3
- data/lib/konpeito/ui/widgets/checkbox.rb +0 -2
- data/lib/konpeito/ui/widgets/container.rb +18 -0
- data/lib/konpeito/ui/widgets/image.rb +0 -2
- data/lib/konpeito/ui/widgets/input.rb +0 -2
- data/lib/konpeito/ui/widgets/markdown.rb +0 -2
- data/lib/konpeito/ui/widgets/multiline_text.rb +0 -2
- data/lib/konpeito/ui/widgets/net_image.rb +0 -2
- data/lib/konpeito/ui/widgets/radio_buttons.rb +0 -2
- data/lib/konpeito/ui/widgets/switch.rb +0 -2
- data/lib/konpeito/ui/widgets/text.rb +11 -3
- data/lib/konpeito/ui/widgets/tree.rb +6 -0
- data/lib/konpeito/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b559f8a6de1d213b466939fb5fbde0a797a6808a7ce664f28d55ed0496696dde
|
|
4
|
+
data.tar.gz: 6809762994527a50fcf96c1644f24cde5f2324b668e1dc646bf32077ea679af0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4396
|
-
|
|
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
|
|
4423
|
-
|
|
4424
|
-
descriptor
|
|
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
|
-
#
|
|
5118
|
-
|
|
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
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
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).
|