konpeito 0.1.0 → 0.1.2
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 +29 -0
- data/README.md +79 -22
- data/lib/konpeito/codegen/jvm_generator.rb +87 -10
- data/lib/konpeito/hir/builder.rb +6 -4
- 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 +83 -18
- data/lib/konpeito/ui/core.rb +315 -24
- data/lib/konpeito/ui/row.rb +85 -17
- 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
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +3 -0
- 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: 53a3bbb24175ed554f9f560c38e6f8138206da3fb6385f87358e9dfa3eed4f10
|
|
4
|
+
data.tar.gz: 80026dca6352dcb9effa9e7bac8e7ebc58e517ae84cb1f15627935fbb3d5fee4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4233ae433c5730c64da97806275ca0300d80f77e5af8c338cf2185f54205d69b99091447a097506b5d2944dac437396a8a4e04ae9a97a90df60f8a93d60e730d
|
|
7
|
+
data.tar.gz: 911c32b9ac29989060b08fe6fc352074a02038ae20f8b516723e56a263bf602b0ee70206cf41a3bbe682044ab4c7511552fa255ef9d7b137da952dcea271f860
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.2] - 2026-02-17
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Castella UI: include padding and respect FIXED sizes in Column/Row measure
|
|
12
|
+
- Castella UI: use lighter accent variant for normal button hover across all themes
|
|
13
|
+
- JVM backend: preserve subclass type through inherited self-returning method chains
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Documentation: reorganize Castella UI section in README with DSL / Reactive State / Style Composition subheadings
|
|
17
|
+
- Documentation: add Style Composition example and screenshot to README
|
|
18
|
+
- Documentation: remove filler spacers from counter demos (EXPANDING default makes them unnecessary)
|
|
19
|
+
- Documentation: update counter screenshot
|
|
20
|
+
|
|
21
|
+
## [0.1.1] - 2026-02-16
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- JVM backend: detect and resolve field type conflicts in `dedup_inherited_fields` to prevent runtime NPE when parent/child classes use same ivar with different types
|
|
25
|
+
- JVM backend: UI layout fixes for Castella dashboard (`.class` dispatch, `attr_accessor` field access, scrollbar thumb rendering, etc.)
|
|
26
|
+
- Remove duplicate `source_code_uri` from gemspec metadata
|
|
27
|
+
- Remove debug `puts` from data_table widget
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Castella UI: visual properties (`bg_color`, `border_radius`, `border_color`, `border_width`) directly on Column/Row layouts, eliminating Container wrapper boilerplate
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Documentation: note that JVM backend might be more mature than LLVM backend
|
|
34
|
+
|
|
8
35
|
## [0.1.0] - 2026-02-13
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -72,4 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
72
99
|
- `%a{extern}` - external C struct wrappers
|
|
73
100
|
- `%a{simd}` - SIMD vectorization
|
|
74
101
|
|
|
102
|
+
[0.1.2]: https://github.com/i2y/konpeito/compare/v0.1.1...v0.1.2
|
|
103
|
+
[0.1.1]: https://github.com/i2y/konpeito/compare/v0.1.0...v0.1.1
|
|
75
104
|
[0.1.0]: https://github.com/i2y/konpeito/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -135,43 +135,100 @@ 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/nicenote/jwm) + [Skija](https://github.com/nicenote/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>
|
|
139
147
|
|
|
140
148
|
```ruby
|
|
141
|
-
|
|
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>
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
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
|
-
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 }
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
```
|
|
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)
|
|
173
226
|
|
|
174
|
-
|
|
227
|
+
column(spacing: 12.0) {
|
|
228
|
+
container(card) { text "Default card" }
|
|
229
|
+
container(green_card) { text "Green border variant" }
|
|
230
|
+
}
|
|
231
|
+
```
|
|
175
232
|
|
|
176
233
|
## Performance
|
|
177
234
|
|
|
@@ -187,6 +187,10 @@ module Konpeito
|
|
|
187
187
|
# Classes are generated before main class, so we need this early.
|
|
188
188
|
prescan_top_level_constants(hir_program)
|
|
189
189
|
|
|
190
|
+
# Pre-scan: register global variable static fields early so that
|
|
191
|
+
# global variables referenced inside blocks have fields available.
|
|
192
|
+
prescan_global_variables(hir_program)
|
|
193
|
+
|
|
190
194
|
# Generate module interfaces FIRST (before classes that may implement them)
|
|
191
195
|
hir_program.modules.each do |module_def|
|
|
192
196
|
@block_methods = []
|
|
@@ -294,17 +298,43 @@ module Konpeito
|
|
|
294
298
|
}
|
|
295
299
|
end
|
|
296
300
|
|
|
301
|
+
# Yield every instruction in basic_blocks, recursing into block bodies
|
|
302
|
+
# attached to HIR::Call nodes.
|
|
303
|
+
def each_instruction_recursive(basic_blocks, &blk)
|
|
304
|
+
basic_blocks.each do |bb|
|
|
305
|
+
bb.instructions.each do |inst|
|
|
306
|
+
blk.call(inst)
|
|
307
|
+
if inst.is_a?(HIR::Call) && inst.block
|
|
308
|
+
each_instruction_recursive(inst.block.body, &blk)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
297
314
|
# Pre-scan top-level StoreConstant instructions to populate @constant_fields early.
|
|
298
315
|
# This is needed because user classes are generated before the main class, and
|
|
299
316
|
# class constructors may reference top-level constants (e.g. EXPANDING = 1).
|
|
317
|
+
# Recurses into block bodies so constants defined inside lambdas are also found.
|
|
300
318
|
def prescan_top_level_constants(hir_program)
|
|
301
319
|
hir_program.functions.each do |func|
|
|
302
320
|
next if func.owner_class || func.owner_module
|
|
303
|
-
func.body
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
each_instruction_recursive(func.body) do |inst|
|
|
322
|
+
if inst.is_a?(HIR::StoreConstant) && inst.scope.nil?
|
|
323
|
+
@constant_fields << inst.name.to_s
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Pre-scan all functions for global variable access (LoadGlobalVar/StoreGlobalVar)
|
|
330
|
+
# and register the corresponding static fields early. This ensures global variables
|
|
331
|
+
# referenced inside blocks have their fields available during code generation.
|
|
332
|
+
def prescan_global_variables(hir_program)
|
|
333
|
+
hir_program.functions.each do |func|
|
|
334
|
+
each_instruction_recursive(func.body) do |inst|
|
|
335
|
+
if inst.is_a?(HIR::LoadGlobalVar) || inst.is_a?(HIR::StoreGlobalVar)
|
|
336
|
+
field_name = inst.name.sub(/^\$/, "GLOBAL_")
|
|
337
|
+
register_global_field(field_name)
|
|
308
338
|
end
|
|
309
339
|
end
|
|
310
340
|
end
|
|
@@ -3118,6 +3148,22 @@ module Konpeito
|
|
|
3118
3148
|
ensure_slot(result_var, ret_type)
|
|
3119
3149
|
instructions << store_instruction(result_var, ret_type)
|
|
3120
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
|
|
3121
3167
|
end
|
|
3122
3168
|
end
|
|
3123
3169
|
|
|
@@ -3709,6 +3755,11 @@ module Konpeito
|
|
|
3709
3755
|
# Remove fields from child classes that are already declared on a parent class.
|
|
3710
3756
|
# Must run AFTER all classes are registered (register_class_info) since the
|
|
3711
3757
|
# HIR class order may register children before parents.
|
|
3758
|
+
#
|
|
3759
|
+
# Type conflict detection: when a child class assigns a concrete but different type
|
|
3760
|
+
# to a field that the parent typed narrowly (e.g. parent :i64, child :string), widen
|
|
3761
|
+
# the parent's field to :value (Object) to prevent runtime NPE.
|
|
3762
|
+
# If the child type is :value (unresolved/unknown), keep the parent's concrete type.
|
|
3712
3763
|
def dedup_inherited_fields
|
|
3713
3764
|
@class_info.each do |class_name, info|
|
|
3714
3765
|
parent = info[:super_name]
|
|
@@ -3719,6 +3770,16 @@ module Konpeito
|
|
|
3719
3770
|
if parent_info[:fields]
|
|
3720
3771
|
parent_info[:fields].each_key do |fname|
|
|
3721
3772
|
if info[:fields].key?(fname)
|
|
3773
|
+
child_type = info[:fields][fname]
|
|
3774
|
+
parent_type = parent_info[:fields][fname]
|
|
3775
|
+
# Only widen when both types are concrete but different.
|
|
3776
|
+
# If child is :value (unknown), trust the parent's type.
|
|
3777
|
+
# If parent is :value, trust it (already wide enough).
|
|
3778
|
+
if child_type != parent_type && child_type != :value && parent_type != :value
|
|
3779
|
+
warn "[konpeito] JVM field type conflict: #{parent_name}##{fname} is :#{parent_type}, " \
|
|
3780
|
+
"but subclass #{class_name} uses :#{child_type}. Widening to :value (Object)."
|
|
3781
|
+
parent_info[:fields][fname] = :value
|
|
3782
|
+
end
|
|
3722
3783
|
info[:fields].delete(fname)
|
|
3723
3784
|
end
|
|
3724
3785
|
end
|
|
@@ -5069,8 +5130,16 @@ module Konpeito
|
|
|
5069
5130
|
# checkcast when return type is a user class (descriptor returns Object but actual is specific class)
|
|
5070
5131
|
if ret_type == :value
|
|
5071
5132
|
rbs_class = resolve_rbs_return_class_name(owner_class, method_name)
|
|
5072
|
-
#
|
|
5073
|
-
|
|
5133
|
+
# When function returns self (SelfRef), preserve the receiver's actual class type.
|
|
5134
|
+
# RBS annotations declare the defining class (e.g., Widget#fixed_height → Widget)
|
|
5135
|
+
# but `self` at runtime is the receiver's class (e.g., Button). Without this,
|
|
5136
|
+
# method chains like Button(...).fixed_height(36.0).on_click { ... } break because
|
|
5137
|
+
# the receiver type narrows to Widget and on_click (defined on Button) is not found.
|
|
5138
|
+
if inferred_return_class
|
|
5139
|
+
rbs_class = inferred_return_class
|
|
5140
|
+
else
|
|
5141
|
+
rbs_class ||= inferred_return_class
|
|
5142
|
+
end
|
|
5074
5143
|
if rbs_class && @class_info.key?(rbs_class)
|
|
5075
5144
|
instructions << { "op" => "checkcast", "type" => @class_info[rbs_class][:jvm_name] }
|
|
5076
5145
|
@variable_class_types[result_var] = rbs_class
|
|
@@ -5189,8 +5258,13 @@ module Konpeito
|
|
|
5189
5258
|
end
|
|
5190
5259
|
instructions = []
|
|
5191
5260
|
|
|
5192
|
-
# Load self
|
|
5193
|
-
|
|
5261
|
+
# Load self — use @block_self_slot if inside a block that captured self
|
|
5262
|
+
self_slot = @block_self_slot || 0
|
|
5263
|
+
instructions << { "op" => "aload", "var" => self_slot }
|
|
5264
|
+
# If self was captured as Object, checkcast to the expected class
|
|
5265
|
+
if @block_self_slot
|
|
5266
|
+
instructions << { "op" => "checkcast", "type" => jvm_class }
|
|
5267
|
+
end
|
|
5194
5268
|
|
|
5195
5269
|
# Prefer registered descriptor for consistency with method definition
|
|
5196
5270
|
registered = @method_descriptors["#{@current_class_name}##{method_name}"]
|
|
@@ -6478,7 +6552,8 @@ module Konpeito
|
|
|
6478
6552
|
instructions
|
|
6479
6553
|
end
|
|
6480
6554
|
|
|
6481
|
-
# Check if a block body accesses instance variables
|
|
6555
|
+
# Check if a block body accesses instance variables or calls methods on
|
|
6556
|
+
# implicit self (receiver-less calls like `kpi_card(label, value)`).
|
|
6482
6557
|
# Recursively checks nested blocks (e.g., on_click inside a yield block)
|
|
6483
6558
|
# so that outer blocks capture self when inner blocks need it.
|
|
6484
6559
|
def block_needs_self?(block_def)
|
|
@@ -6486,6 +6561,8 @@ module Konpeito
|
|
|
6486
6561
|
bb.instructions.any? { |inst|
|
|
6487
6562
|
if inst.is_a?(HIR::LoadInstanceVar) || inst.is_a?(HIR::StoreInstanceVar)
|
|
6488
6563
|
true
|
|
6564
|
+
elsif inst.is_a?(HIR::Call) && (inst.receiver.nil? || inst.receiver.is_a?(HIR::SelfRef))
|
|
6565
|
+
true
|
|
6489
6566
|
elsif inst.is_a?(HIR::Call) && inst.block
|
|
6490
6567
|
block_needs_self?(inst.block)
|
|
6491
6568
|
else
|
data/lib/konpeito/hir/builder.rb
CHANGED
|
@@ -2072,12 +2072,14 @@ module Konpeito
|
|
|
2072
2072
|
# Check if this is a keyword hash argument
|
|
2073
2073
|
if arg.node.is_a?(Prism::KeywordHashNode)
|
|
2074
2074
|
# Extract keyword arguments from the hash
|
|
2075
|
-
arg.
|
|
2075
|
+
# arg.children are typed AssocNode wrappers; each has children[0]=key, children[1]=value
|
|
2076
|
+
arg.node.elements.each_with_index do |elem, ei|
|
|
2076
2077
|
if elem.is_a?(Prism::AssocNode) && elem.key.is_a?(Prism::SymbolNode)
|
|
2077
2078
|
key_name = elem.key.unescaped.to_sym
|
|
2078
|
-
#
|
|
2079
|
-
|
|
2080
|
-
if
|
|
2079
|
+
# Navigate through the typed AssocNode to get the typed value child
|
|
2080
|
+
typed_assoc = arg.children[ei]
|
|
2081
|
+
if typed_assoc && typed_assoc.children && typed_assoc.children.size >= 2
|
|
2082
|
+
value_typed = typed_assoc.children[1]
|
|
2081
2083
|
keyword_args[key_name] = visit(value_typed)
|
|
2082
2084
|
else
|
|
2083
2085
|
# Fallback: create typed node for the value
|
|
@@ -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).
|
data/lib/konpeito/ui/column.rb
CHANGED
|
@@ -25,6 +25,14 @@ class Column < Layout
|
|
|
25
25
|
#: () -> Column
|
|
26
26
|
def scrollable
|
|
27
27
|
@is_scrollable = true
|
|
28
|
+
# Retroactively downgrade existing EXPANDING children to CONTENT
|
|
29
|
+
i = 0
|
|
30
|
+
while i < @children.length
|
|
31
|
+
if @children[i].get_height_policy == EXPANDING
|
|
32
|
+
@children[i].set_height_policy(CONTENT)
|
|
33
|
+
end
|
|
34
|
+
i = i + 1
|
|
35
|
+
end
|
|
28
36
|
self
|
|
29
37
|
end
|
|
30
38
|
|
|
@@ -60,38 +68,80 @@ class Column < Layout
|
|
|
60
68
|
update
|
|
61
69
|
end
|
|
62
70
|
|
|
71
|
+
# Override add: auto-downgrade EXPANDING height to CONTENT in scrollable Column
|
|
72
|
+
#: (untyped w) -> Column
|
|
73
|
+
def add(w)
|
|
74
|
+
if w == nil
|
|
75
|
+
return self
|
|
76
|
+
end
|
|
77
|
+
if @is_scrollable && w.get_height_policy == EXPANDING
|
|
78
|
+
w.set_height_policy(CONTENT)
|
|
79
|
+
end
|
|
80
|
+
super(w)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
63
84
|
#: (untyped painter) -> Size
|
|
64
85
|
def measure(painter)
|
|
65
86
|
total_h = 0.0
|
|
66
87
|
max_w = 0.0
|
|
67
88
|
i = 0
|
|
68
89
|
while i < @children.length
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
c = @children[i]
|
|
91
|
+
cs = c.measure(painter)
|
|
92
|
+
if c.get_height_policy == FIXED
|
|
93
|
+
child_h = c.get_height
|
|
94
|
+
else
|
|
95
|
+
child_h = cs.height
|
|
96
|
+
end
|
|
97
|
+
total_h = total_h + child_h
|
|
71
98
|
total_h = total_h + @spacing if i > 0
|
|
72
99
|
max_w = cs.width if cs.width > max_w
|
|
73
100
|
i = i + 1
|
|
74
101
|
end
|
|
75
|
-
Size.new(max_w, total_h)
|
|
102
|
+
Size.new(max_w + @pad_left + @pad_right, total_h + @pad_top + @pad_bottom)
|
|
76
103
|
end
|
|
77
104
|
|
|
105
|
+
# Unified layout: two-pass flex distribution + scroll offset.
|
|
106
|
+
# With approach C (auto-downgrade), scrollable containers have no EXPANDING
|
|
107
|
+
# children, so flex distribution is a no-op and content stacks sequentially.
|
|
78
108
|
#: (untyped painter) -> void
|
|
79
109
|
def relocate_children(painter)
|
|
80
|
-
|
|
110
|
+
# Account for padding
|
|
111
|
+
inner_w = @width - @pad_left - @pad_right
|
|
112
|
+
inner_h = @height - @pad_top - @pad_bottom
|
|
113
|
+
if inner_w < 0.0
|
|
114
|
+
inner_w = 0.0
|
|
115
|
+
end
|
|
116
|
+
if inner_h < 0.0
|
|
117
|
+
inner_h = 0.0
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
remaining = inner_h
|
|
81
121
|
expanding_total_flex = 0
|
|
82
122
|
|
|
83
|
-
# First pass: measure FIXED
|
|
123
|
+
# First pass: measure CONTENT/FIXED children, collect EXPANDING flex totals
|
|
84
124
|
i = 0
|
|
85
125
|
while i < @children.length
|
|
86
126
|
c = @children[i]
|
|
87
127
|
if c.get_height_policy != EXPANDING
|
|
88
|
-
# Set width before measure
|
|
89
|
-
if c.get_width_policy
|
|
90
|
-
c.resize_wh(
|
|
128
|
+
# Set width before measure (for word-wrap, centering, etc.)
|
|
129
|
+
if c.get_width_policy != FIXED
|
|
130
|
+
c.resize_wh(inner_w, c.get_height)
|
|
91
131
|
end
|
|
92
132
|
cs = c.measure(painter)
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
# Use explicit height for FIXED, measured height for CONTENT
|
|
134
|
+
if c.get_height_policy == FIXED
|
|
135
|
+
child_h = c.get_height
|
|
136
|
+
else
|
|
137
|
+
child_h = cs.height
|
|
138
|
+
end
|
|
139
|
+
if c.get_width_policy == FIXED
|
|
140
|
+
c.resize_wh(cs.width, child_h)
|
|
141
|
+
else
|
|
142
|
+
c.resize_wh(inner_w, child_h)
|
|
143
|
+
end
|
|
144
|
+
remaining = remaining - child_h
|
|
95
145
|
else
|
|
96
146
|
expanding_total_flex = expanding_total_flex + c.get_flex
|
|
97
147
|
end
|
|
@@ -103,8 +153,8 @@ class Column < Layout
|
|
|
103
153
|
remaining = 0.0
|
|
104
154
|
end
|
|
105
155
|
|
|
106
|
-
# Second pass: distribute remaining space to EXPANDING
|
|
107
|
-
cy = @y
|
|
156
|
+
# Second pass: distribute remaining space to EXPANDING, position all
|
|
157
|
+
cy = @y + @pad_top
|
|
108
158
|
if @is_scrollable
|
|
109
159
|
cy = cy - @scroll_offset
|
|
110
160
|
end
|
|
@@ -117,14 +167,14 @@ class Column < Layout
|
|
|
117
167
|
if expanding_total_flex > 0 && remaining > 0.0
|
|
118
168
|
h = remaining * c.get_flex / expanding_total_flex
|
|
119
169
|
end
|
|
120
|
-
c.resize_wh(
|
|
170
|
+
c.resize_wh(inner_w, h)
|
|
121
171
|
else
|
|
122
|
-
#
|
|
123
|
-
if c.get_width_policy
|
|
124
|
-
c.resize_wh(
|
|
172
|
+
# In a Column, non-FIXED children fill the column width
|
|
173
|
+
if c.get_width_policy != FIXED
|
|
174
|
+
c.resize_wh(inner_w, c.get_height)
|
|
125
175
|
end
|
|
126
176
|
end
|
|
127
|
-
c.move_xy(@x, cy)
|
|
177
|
+
c.move_xy(@x + @pad_left, cy)
|
|
128
178
|
cy = cy + c.get_height + @spacing
|
|
129
179
|
total_content_h = total_content_h + c.get_height
|
|
130
180
|
total_content_h = total_content_h + @spacing if i > 0
|
|
@@ -134,7 +184,7 @@ class Column < Layout
|
|
|
134
184
|
|
|
135
185
|
# Auto-scroll to bottom when pinned
|
|
136
186
|
if @pin_bottom && @is_scrollable
|
|
137
|
-
max_scroll = @content_height -
|
|
187
|
+
max_scroll = @content_height - inner_h
|
|
138
188
|
if max_scroll > 0.0
|
|
139
189
|
@scroll_offset = max_scroll
|
|
140
190
|
end
|
|
@@ -143,9 +193,24 @@ class Column < Layout
|
|
|
143
193
|
|
|
144
194
|
#: (untyped painter, bool completely) -> void
|
|
145
195
|
def redraw(painter, completely)
|
|
196
|
+
saved_bg = $__bg_clear_color
|
|
197
|
+
# When this layout has a custom background and is dirty, we handle clearing
|
|
198
|
+
# ourselves to preserve rounded corners. Clear dirty flag so redraw_children
|
|
199
|
+
# won't overwrite with a solid fill_rect.
|
|
200
|
+
if @custom_bg && is_dirty
|
|
201
|
+
parent_bg = saved_bg
|
|
202
|
+
if parent_bg == nil || parent_bg == 0
|
|
203
|
+
parent_bg = $theme.bg_canvas
|
|
204
|
+
end
|
|
205
|
+
painter.fill_rect(0.0, 0.0, @width, @height, parent_bg)
|
|
206
|
+
set_dirty(false)
|
|
207
|
+
completely = true
|
|
208
|
+
end
|
|
209
|
+
draw_visual_background(painter)
|
|
146
210
|
relocate_children(painter)
|
|
147
211
|
redraw_children(painter, completely)
|
|
148
212
|
draw_scrollbar(painter) if @is_scrollable
|
|
213
|
+
$__bg_clear_color = saved_bg
|
|
149
214
|
end
|
|
150
215
|
|
|
151
216
|
#: (untyped painter) -> void
|