konpeito 0.9.1 → 0.10.0
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 +21 -0
- data/lib/konpeito/cli/doctor_command.rb +9 -0
- data/lib/konpeito/codegen/monomorphizer.rb +114 -5
- data/lib/konpeito/codegen/mruby_backend.rb +14 -0
- data/lib/konpeito/codegen/mruby_helpers.c +30 -0
- data/lib/konpeito/hir/builder.rb +5 -0
- data/lib/konpeito/hir/nodes.rb +2 -0
- data/lib/konpeito/platform.rb +41 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +18 -1
- data/lib/konpeito/type_checker/rbs_loader.rb +7 -1
- 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: 945682bc2b93e30dbea1e57f3d05b1613c780b8301d1b66fe6b763191727ca64
|
|
4
|
+
data.tar.gz: 38f644805520a6885c5d583f499fbec84eca513d48c9ca0a7350e469821a8587
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92ad6de982c1d40d9fd821899a32fdde41b5aa3d3bfc618f02feff51f9e8a26ff61b24173f54ef4a0781035ec75898cdb9e2dafec584d06d5f94e8b60f166e1f
|
|
7
|
+
data.tar.gz: 477c494b53513b16f99de04a91410cdea9d8a30c64b5e14d8f436cae1d14fc48323948c9d42bf4791bdfa3e77e7cffca6e856fe25c9d5a39f60b523134a3f27c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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.10.0] - 2026-03-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **mruby 4 RC2 support**: Version detection (`mruby_version`, `mruby_major_version`
|
|
12
|
+
in Platform), C-level compatibility guards (`KONPEITO_MRUBY4` macro,
|
|
13
|
+
`_Static_assert(sizeof(mrb_value)==8)`, `MRB_NO_BOXING` error), runtime version
|
|
14
|
+
check in MRubyBackend, mruby version display in `konpeito doctor`, and CI matrix
|
|
15
|
+
for mruby 3.x / 4.x
|
|
16
|
+
- **User-defined generics**: RBS `class Stack[T]` type parameters extracted and
|
|
17
|
+
propagated through HIR ClassDef, HM inferrer (fresh TypeVars on `.new`), and
|
|
18
|
+
monomorphizer (class-level specialization generating `Stack_Integer` etc.)
|
|
19
|
+
- **Cosmos demoscene demo** (`examples/mruby_cosmos/`): Procedural graphics showcase
|
|
20
|
+
with sine lookup table, starfield, orbital particles, glowing nebula, rotating
|
|
21
|
+
wireframes, pulse rings, aurora wave, and CRT scanlines
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- mruby version requirement updated from "3.x" to "3.x or 4.x" in docs
|
|
25
|
+
- Monomorphizer `RBS_TYPE_PARAMS` check replaced with dynamic `unresolved_type_param?`
|
|
26
|
+
that includes user-defined class type parameters
|
|
27
|
+
|
|
8
28
|
## [0.9.1] - 2026-03-16
|
|
9
29
|
|
|
10
30
|
### Fixed
|
|
@@ -411,6 +431,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
411
431
|
- `%a{extern}` - external C struct wrappers
|
|
412
432
|
- `%a{simd}` - SIMD vectorization
|
|
413
433
|
|
|
434
|
+
[0.10.0]: https://github.com/i2y/konpeito/compare/v0.9.1...v0.10.0
|
|
414
435
|
[0.9.1]: https://github.com/i2y/konpeito/compare/v0.9.0...v0.9.1
|
|
415
436
|
[0.9.0]: https://github.com/i2y/konpeito/compare/v0.8.0...v0.9.0
|
|
416
437
|
[0.8.0]: https://github.com/i2y/konpeito/compare/v0.7.1...v0.8.0
|
|
@@ -189,6 +189,15 @@ module Konpeito
|
|
|
189
189
|
hint: "Install: #{Platform.mruby_install_hint}"
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
# mruby version
|
|
193
|
+
mruby_ver = Platform.mruby_version
|
|
194
|
+
checks << {
|
|
195
|
+
name: "mruby version",
|
|
196
|
+
detail: mruby_ver || "unknown",
|
|
197
|
+
status: mruby_ver ? :ok : :warning,
|
|
198
|
+
hint: "Supported: mruby 3.x or 4.x"
|
|
199
|
+
}
|
|
200
|
+
|
|
192
201
|
# mruby headers
|
|
193
202
|
cflags = Platform.mruby_cflags
|
|
194
203
|
inc_dir = cflags.split.find { |f| f.start_with?("-I") }&.sub(/^-I/, "")
|
|
@@ -14,7 +14,7 @@ module Konpeito
|
|
|
14
14
|
# identity("hello") # generates identity_String
|
|
15
15
|
#
|
|
16
16
|
class Monomorphizer
|
|
17
|
-
attr_reader :specializations, :call_sites, :union_dispatches
|
|
17
|
+
attr_reader :specializations, :call_sites, :union_dispatches, :class_specializations
|
|
18
18
|
|
|
19
19
|
def initialize(hir_program, type_info)
|
|
20
20
|
@hir_program = hir_program
|
|
@@ -24,6 +24,7 @@ module Konpeito
|
|
|
24
24
|
@union_call_sites = [] # Calls requiring runtime dispatch
|
|
25
25
|
@union_dispatches = {} # { [func_name, original_types] => dispatch_info }
|
|
26
26
|
@generated_functions = {}
|
|
27
|
+
@class_specializations = {} # { [class_name, type_args_strs] => specialized_class_name }
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Analyze the HIR program to find monomorphization opportunities
|
|
@@ -34,6 +35,9 @@ module Konpeito
|
|
|
34
35
|
|
|
35
36
|
# Determine which specializations to generate
|
|
36
37
|
determine_specializations
|
|
38
|
+
|
|
39
|
+
# Collect and generate class-level specializations for generic classes
|
|
40
|
+
analyze_class_specializations
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
# Apply monomorphization transformations
|
|
@@ -41,6 +45,9 @@ module Konpeito
|
|
|
41
45
|
# Generate specialized function copies
|
|
42
46
|
generate_specialized_functions
|
|
43
47
|
|
|
48
|
+
# Generate specialized class definitions
|
|
49
|
+
generate_specialized_classes
|
|
50
|
+
|
|
44
51
|
# Rewrite call sites to use specialized functions
|
|
45
52
|
rewrite_call_sites
|
|
46
53
|
end
|
|
@@ -197,7 +204,7 @@ module Konpeito
|
|
|
197
204
|
next if types.any? { |t| t == TypeChecker::Types::UNTYPED || t.is_a?(TypeChecker::Types::Untyped) }
|
|
198
205
|
next if skip_functions.include?(func_name.to_s)
|
|
199
206
|
# Skip if any type is an unresolved RBS type parameter (Elem, K, V, etc.)
|
|
200
|
-
next if types.any? { |t| t.is_a?(TypeChecker::Types::ClassInstance) &&
|
|
207
|
+
next if types.any? { |t| t.is_a?(TypeChecker::Types::ClassInstance) && unresolved_type_param?(t.name) }
|
|
201
208
|
|
|
202
209
|
type_suffix = types.map { |t| type_to_suffix(t) }.join("_")
|
|
203
210
|
specialized = "#{func_name}_#{type_suffix}"
|
|
@@ -329,15 +336,27 @@ module Konpeito
|
|
|
329
336
|
end
|
|
330
337
|
|
|
331
338
|
# RBS type parameter names that should not be used as monomorphized suffixes
|
|
332
|
-
# These are unresolved generic type variables, not concrete Ruby classes
|
|
333
|
-
|
|
339
|
+
# These are unresolved generic type variables, not concrete Ruby classes.
|
|
340
|
+
# User-defined type params (from RBS class declarations) are also checked dynamically.
|
|
341
|
+
BUILTIN_RBS_TYPE_PARAMS = Set.new(%w[Elem K V U T S R E A B C D N M].map(&:to_sym)).freeze
|
|
342
|
+
|
|
343
|
+
def unresolved_type_param?(type_name)
|
|
344
|
+
return true if BUILTIN_RBS_TYPE_PARAMS.include?(type_name)
|
|
345
|
+
# Check user-defined class type params
|
|
346
|
+
if @type_info.respond_to?(:class_type_params)
|
|
347
|
+
@type_info.class_type_params.each_value do |param_map|
|
|
348
|
+
return true if param_map.key?(type_name)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
false
|
|
352
|
+
end
|
|
334
353
|
|
|
335
354
|
def type_to_suffix(type)
|
|
336
355
|
case type
|
|
337
356
|
when TypeChecker::Types::ClassInstance
|
|
338
357
|
# If the type name is an unresolved RBS type parameter (Elem, K, V, etc.),
|
|
339
358
|
# treat it as untyped to avoid generating rb_const_get("Elem") at runtime
|
|
340
|
-
if
|
|
359
|
+
if unresolved_type_param?(type.name)
|
|
341
360
|
"Any"
|
|
342
361
|
else
|
|
343
362
|
type.name.to_s
|
|
@@ -422,6 +441,96 @@ module Konpeito
|
|
|
422
441
|
term
|
|
423
442
|
end
|
|
424
443
|
|
|
444
|
+
# Analyze generic classes and collect concrete type instantiations from .new call sites
|
|
445
|
+
def analyze_class_specializations
|
|
446
|
+
generic_classes = @hir_program.classes.select { |c| c.type_params && !c.type_params.empty? }
|
|
447
|
+
return if generic_classes.empty?
|
|
448
|
+
|
|
449
|
+
generic_names = generic_classes.map(&:name).to_set
|
|
450
|
+
|
|
451
|
+
# Scan all functions for ClassName.new calls that produce typed ClassInstance
|
|
452
|
+
@hir_program.functions.each do |func|
|
|
453
|
+
func.body.each do |block|
|
|
454
|
+
block.instructions.each do |inst|
|
|
455
|
+
next unless inst.is_a?(HIR::Call) && inst.method_name.to_s == "new"
|
|
456
|
+
next unless inst.type.is_a?(TypeChecker::Types::ClassInstance)
|
|
457
|
+
next unless generic_names.include?(inst.type.name.to_s)
|
|
458
|
+
|
|
459
|
+
type_args = inst.type.type_args
|
|
460
|
+
next if type_args.empty?
|
|
461
|
+
|
|
462
|
+
# Resolve type args through unifier
|
|
463
|
+
resolved_args = type_args.map { |ta| @type_info.unifier.apply(ta) }
|
|
464
|
+
# Skip if any arg is still unresolved
|
|
465
|
+
next if resolved_args.any? { |ta| ta.is_a?(TypeChecker::TypeVar) || ta == TypeChecker::Types::UNTYPED }
|
|
466
|
+
|
|
467
|
+
class_name = inst.type.name.to_s
|
|
468
|
+
arg_strs = resolved_args.map(&:to_s)
|
|
469
|
+
key = [class_name, arg_strs]
|
|
470
|
+
next if @class_specializations.key?(key)
|
|
471
|
+
|
|
472
|
+
suffix = resolved_args.map { |t| type_to_suffix(t) }.join("_")
|
|
473
|
+
@class_specializations[key] = "#{class_name}_#{suffix}"
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Generate specialized ClassDef and methods for each class instantiation
|
|
480
|
+
def generate_specialized_classes
|
|
481
|
+
@class_specializations.each do |(class_name, type_arg_strs), specialized_name|
|
|
482
|
+
original_class = @hir_program.classes.find { |c| c.name == class_name }
|
|
483
|
+
next unless original_class
|
|
484
|
+
|
|
485
|
+
# Build substitution: type_param_name → concrete type string
|
|
486
|
+
substitution = {}
|
|
487
|
+
original_class.type_params.each_with_index do |tp, i|
|
|
488
|
+
substitution[tp.to_s] = type_arg_strs[i]
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Create specialized ClassDef with concrete ivar types
|
|
492
|
+
specialized_class = HIR::ClassDef.new(
|
|
493
|
+
name: specialized_name,
|
|
494
|
+
superclass: original_class.superclass,
|
|
495
|
+
method_names: original_class.method_names.dup,
|
|
496
|
+
instance_vars: original_class.instance_vars.dup,
|
|
497
|
+
included_modules: original_class.included_modules.dup
|
|
498
|
+
)
|
|
499
|
+
specialized_class.type_params = [] # Concrete — no type params
|
|
500
|
+
|
|
501
|
+
# Substitute ivar types
|
|
502
|
+
original_class.instance_var_types.each do |ivar_name, field_tag|
|
|
503
|
+
specialized_class.instance_var_types[ivar_name] =
|
|
504
|
+
if substitution.key?(field_tag.to_s)
|
|
505
|
+
substitution[field_tag.to_s].to_sym
|
|
506
|
+
else
|
|
507
|
+
field_tag
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
@hir_program.classes << specialized_class
|
|
512
|
+
|
|
513
|
+
# Copy and rename methods
|
|
514
|
+
original_class.method_names.each do |method_name|
|
|
515
|
+
original_func = @hir_program.functions.find do |f|
|
|
516
|
+
f.owner_class == class_name && f.name.to_s == method_name
|
|
517
|
+
end
|
|
518
|
+
next unless original_func
|
|
519
|
+
|
|
520
|
+
specialized_body = deep_clone_blocks(original_func.body)
|
|
521
|
+
specialized_func = HIR::Function.new(
|
|
522
|
+
name: method_name,
|
|
523
|
+
params: original_func.params.dup,
|
|
524
|
+
body: specialized_body,
|
|
525
|
+
return_type: original_func.return_type,
|
|
526
|
+
is_instance_method: original_func.is_instance_method,
|
|
527
|
+
owner_class: specialized_name
|
|
528
|
+
)
|
|
529
|
+
@hir_program.functions << specialized_func
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
425
534
|
def rewrite_call_sites
|
|
426
535
|
# Track which calls have already been processed (for Union calls that appear multiple times)
|
|
427
536
|
processed_calls = Set.new
|
|
@@ -31,7 +31,21 @@ module Konpeito
|
|
|
31
31
|
!!@cross_target
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# Check mruby version compatibility. Raises for unsupported versions, warns for future versions.
|
|
35
|
+
def check_mruby_compatibility
|
|
36
|
+
major = Platform.mruby_major_version
|
|
37
|
+
return unless major # unknown version — skip check
|
|
38
|
+
|
|
39
|
+
if major < 3
|
|
40
|
+
raise "mruby #{Platform.mruby_version} is not supported. Konpeito requires mruby 3.x or 4.x."
|
|
41
|
+
elsif major > 4
|
|
42
|
+
warn "Warning: mruby #{Platform.mruby_version} is newer than tested. Konpeito is tested with mruby 3.x and 4.x."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
34
46
|
def generate
|
|
47
|
+
check_mruby_compatibility unless cross_compiling?
|
|
48
|
+
|
|
35
49
|
ir_file = "#{output_base}.ll"
|
|
36
50
|
obj_file = "#{output_base}.o"
|
|
37
51
|
init_c_file = "#{output_base}_mruby_init.c"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
#include <mruby.h>
|
|
16
16
|
#include <mruby/value.h>
|
|
17
|
+
#include <mruby/version.h>
|
|
17
18
|
#include <mruby/class.h>
|
|
18
19
|
#include <mruby/data.h>
|
|
19
20
|
#include <mruby/string.h>
|
|
@@ -29,6 +30,26 @@
|
|
|
29
30
|
#include <stdarg.h>
|
|
30
31
|
#include <stdint.h>
|
|
31
32
|
|
|
33
|
+
/* ================================================================
|
|
34
|
+
* mruby version compatibility
|
|
35
|
+
* ================================================================ */
|
|
36
|
+
|
|
37
|
+
/* Detect mruby 4.x */
|
|
38
|
+
#if defined(MRUBY_RELEASE_MAJOR) && MRUBY_RELEASE_MAJOR >= 4
|
|
39
|
+
#define KONPEITO_MRUBY4 1
|
|
40
|
+
#else
|
|
41
|
+
#define KONPEITO_MRUBY4 0
|
|
42
|
+
#endif
|
|
43
|
+
|
|
44
|
+
/* Reject boxing modes where mrb_value is not 64-bit.
|
|
45
|
+
* Konpeito LLVM IR treats mrb_value as i64 — this must hold. */
|
|
46
|
+
#ifdef MRB_NO_BOXING
|
|
47
|
+
#error "Konpeito requires mrb_value to be 64-bit (NaN-boxing or word-boxing). MRB_NO_BOXING is not supported."
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
_Static_assert(sizeof(mrb_value) == sizeof(uint64_t),
|
|
51
|
+
"Konpeito requires sizeof(mrb_value) == 8 (64-bit)");
|
|
52
|
+
|
|
32
53
|
/* ================================================================
|
|
33
54
|
* Global mrb_state pointer (set by main() in generated init code)
|
|
34
55
|
* All CRuby-compatible wrappers below use this to access mruby.
|
|
@@ -40,6 +61,15 @@ mrb_state *konpeito_get_mrb_state(void) {
|
|
|
40
61
|
return konpeito_mrb_state;
|
|
41
62
|
}
|
|
42
63
|
|
|
64
|
+
/* Return the mruby version description string */
|
|
65
|
+
const char *konpeito_mruby_version(void) {
|
|
66
|
+
#ifdef MRUBY_DESCRIPTION
|
|
67
|
+
return MRUBY_DESCRIPTION;
|
|
68
|
+
#else
|
|
69
|
+
return "mruby (unknown version)";
|
|
70
|
+
#endif
|
|
71
|
+
}
|
|
72
|
+
|
|
43
73
|
/* ================================================================
|
|
44
74
|
* Block stack for rb_yield / rb_block_given_p / rb_block_proc
|
|
45
75
|
*
|
data/lib/konpeito/hir/builder.rb
CHANGED
|
@@ -450,6 +450,11 @@ module Konpeito
|
|
|
450
450
|
)
|
|
451
451
|
# Mark as reopened if it's a known Ruby core class
|
|
452
452
|
class_def.reopened = true if RUBY_CORE_CLASSES.include?(name)
|
|
453
|
+
# Propagate type_params from RBS (e.g., class Stack[T])
|
|
454
|
+
if @rbs_loader&.respond_to?(:user_class_type_params)
|
|
455
|
+
tp = @rbs_loader.user_class_type_params[name]
|
|
456
|
+
class_def.type_params = tp if tp
|
|
457
|
+
end
|
|
453
458
|
end
|
|
454
459
|
|
|
455
460
|
# Visit body within class context
|
data/lib/konpeito/hir/nodes.rb
CHANGED
|
@@ -65,6 +65,7 @@ module Konpeito
|
|
|
65
65
|
attr_accessor :instance_var_types # HM-inferred ivar types: { "name" => :Integer, "age" => :String, ... }
|
|
66
66
|
attr_accessor :body_constants # Array of [name, value_node] for constants defined in class body
|
|
67
67
|
attr_accessor :body_class_vars # Array of [name, value_node] for class variables initialized in class body
|
|
68
|
+
attr_accessor :type_params # Array of type parameter names (e.g., [:T] for class Stack[T])
|
|
68
69
|
|
|
69
70
|
def initialize(name:, superclass: nil, method_names: [], instance_vars: [], included_modules: [], extended_modules: [], prepended_modules: [])
|
|
70
71
|
super(type: TypeChecker::Types::NIL)
|
|
@@ -83,6 +84,7 @@ module Konpeito
|
|
|
83
84
|
@instance_var_types = {}
|
|
84
85
|
@body_constants = []
|
|
85
86
|
@body_class_vars = []
|
|
87
|
+
@type_params = []
|
|
86
88
|
end
|
|
87
89
|
end
|
|
88
90
|
|
data/lib/konpeito/platform.rb
CHANGED
|
@@ -145,6 +145,47 @@ module Konpeito
|
|
|
145
145
|
File.exist?(File.join(inc_dir, "mruby.h"))
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
# Get mruby version string (e.g., "3.4.0" or "4.0.0-rc2")
|
|
149
|
+
def self.mruby_version
|
|
150
|
+
# Try mruby-config --version first (mruby 4.x supports this)
|
|
151
|
+
config = find_mruby_config
|
|
152
|
+
if config
|
|
153
|
+
version = `#{config} --version 2>/dev/null`.strip
|
|
154
|
+
# Only accept if it looks like a version string (not help text)
|
|
155
|
+
return version if version.match?(/\A\d+\.\d+/)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Fallback: parse mruby/version.h
|
|
159
|
+
version_h = find_mruby_version_header
|
|
160
|
+
if version_h
|
|
161
|
+
content = File.read(version_h)
|
|
162
|
+
major = content[/MRUBY_RELEASE_MAJOR\s+(\d+)/, 1]
|
|
163
|
+
minor = content[/MRUBY_RELEASE_MINOR\s+(\d+)/, 1]
|
|
164
|
+
patch = content[/MRUBY_RELEASE_TEENY\s+(\d+)/, 1]
|
|
165
|
+
return "#{major}.#{minor}.#{patch}" if major
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Find mruby/version.h from include paths
|
|
172
|
+
def self.find_mruby_version_header
|
|
173
|
+
cflags = mruby_cflags
|
|
174
|
+
cflags.split.select { |f| f.start_with?("-I") }.each do |flag|
|
|
175
|
+
inc_dir = flag.sub(/^-I/, "")
|
|
176
|
+
path = File.join(inc_dir, "mruby", "version.h")
|
|
177
|
+
return path if File.exist?(path)
|
|
178
|
+
end
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Get mruby major version number (e.g., 3 or 4)
|
|
183
|
+
def self.mruby_major_version
|
|
184
|
+
version = mruby_version
|
|
185
|
+
return nil unless version
|
|
186
|
+
version.split(".").first.to_i
|
|
187
|
+
end
|
|
188
|
+
|
|
148
189
|
# Find zig compiler (used for cross-compilation)
|
|
149
190
|
def self.find_zig
|
|
150
191
|
find_executable("zig")
|
|
@@ -9,7 +9,7 @@ module Konpeito
|
|
|
9
9
|
# Uses Algorithm W with constraint generation and unification
|
|
10
10
|
class HMInferrer
|
|
11
11
|
attr_reader :errors, :unifier, :node_types, :diagnostics, :ivar_types, :inference_errors,
|
|
12
|
-
:unresolved_type_warnings
|
|
12
|
+
:unresolved_type_warnings, :class_type_params
|
|
13
13
|
|
|
14
14
|
def initialize(rbs_loader = nil, file_path: nil, source: nil)
|
|
15
15
|
@rbs_loader = rbs_loader
|
|
@@ -35,6 +35,7 @@ module Konpeito
|
|
|
35
35
|
@keyword_param_vars = {} # func_key => { :param_name => TypeVar }
|
|
36
36
|
@unresolved_type_warnings = [] # Warnings for types that survived inference
|
|
37
37
|
@polymorphic_methods = {} # qualified_key => TypeScheme for instance methods
|
|
38
|
+
@class_type_params = {} # class_name => { :T => TypeVar, ... } for generic classes
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
# Main entry: infer types for a program AST
|
|
@@ -903,6 +904,22 @@ module Konpeito
|
|
|
903
904
|
if init_type
|
|
904
905
|
unify_call_args(init_type, arg_types)
|
|
905
906
|
end
|
|
907
|
+
|
|
908
|
+
# For generic classes (e.g., class Stack[T] in RBS), create fresh TypeVars
|
|
909
|
+
# so that Stack.new → Stack[T'] where T' is unified from usage
|
|
910
|
+
class_name_str = receiver_type.name.to_s
|
|
911
|
+
rbs_type_params = @rbs_loader&.respond_to?(:user_class_type_params) &&
|
|
912
|
+
@rbs_loader.user_class_type_params[class_name_str]
|
|
913
|
+
if rbs_type_params && !rbs_type_params.empty?
|
|
914
|
+
fresh_vars = rbs_type_params.map { TypeVar.new }
|
|
915
|
+
# Store mapping so instance method resolution can build substitution
|
|
916
|
+
@class_type_params[class_name_str] ||= {}
|
|
917
|
+
rbs_type_params.each_with_index do |tp_name, i|
|
|
918
|
+
@class_type_params[class_name_str][tp_name] = fresh_vars[i]
|
|
919
|
+
end
|
|
920
|
+
return Types::ClassInstance.new(receiver_type.name, fresh_vars)
|
|
921
|
+
end
|
|
922
|
+
|
|
906
923
|
return Types::ClassInstance.new(receiver_type.name)
|
|
907
924
|
end
|
|
908
925
|
|
|
@@ -9,7 +9,7 @@ module Konpeito
|
|
|
9
9
|
# Loads and manages RBS type definitions
|
|
10
10
|
class RBSLoader
|
|
11
11
|
attr_reader :environment, :native_classes, :native_modules, :boxed_classes, :cfunc_methods, :ffi_libraries,
|
|
12
|
-
:extern_classes, :simd_classes, :jvm_classes
|
|
12
|
+
:extern_classes, :simd_classes, :jvm_classes, :user_class_type_params
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
15
|
@environment = nil
|
|
@@ -22,6 +22,7 @@ module Konpeito
|
|
|
22
22
|
@extern_classes = {} # class_name -> ExternClassType (external C struct wrappers)
|
|
23
23
|
@simd_classes = {} # class_name -> SIMDClassType (SIMD vector classes)
|
|
24
24
|
@jvm_classes = {} # "Java::Util::ArrayList" -> { jvm_internal_name:, methods:, static_methods: }
|
|
25
|
+
@user_class_type_params = {} # "Stack" => [:T], "Pair" => [:A, :B]
|
|
25
26
|
@rbs_file_contents = {} # path -> content
|
|
26
27
|
@temp_dirs = [] # Temp directories for single RBS files
|
|
27
28
|
@definition_builder = nil # Cached DefinitionBuilder
|
|
@@ -574,6 +575,11 @@ module Konpeito
|
|
|
574
575
|
return
|
|
575
576
|
end
|
|
576
577
|
|
|
578
|
+
# Extract type parameters (e.g., class Stack[T] → [:T])
|
|
579
|
+
if decl.type_params && !decl.type_params.empty?
|
|
580
|
+
@user_class_type_params[class_name.to_s] = decl.type_params.map { |tp| tp.name.to_sym }
|
|
581
|
+
end
|
|
582
|
+
|
|
577
583
|
annotations = AnnotationParser.parse_all(decl.annotations)
|
|
578
584
|
|
|
579
585
|
# Skip built-in types
|
data/lib/konpeito/version.rb
CHANGED