konpeito 0.1.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 +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- metadata +267 -0
|
@@ -0,0 +1,1621 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require_relative "annotation_parser"
|
|
6
|
+
|
|
7
|
+
module Konpeito
|
|
8
|
+
module TypeChecker
|
|
9
|
+
# Loads and manages RBS type definitions
|
|
10
|
+
class RBSLoader
|
|
11
|
+
attr_reader :environment, :native_classes, :native_modules, :boxed_classes, :cfunc_methods, :ffi_libraries,
|
|
12
|
+
:extern_classes, :simd_classes, :jvm_classes
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@environment = nil
|
|
16
|
+
@loaded = false
|
|
17
|
+
@native_classes = {} # class_name -> NativeClassType
|
|
18
|
+
@native_modules = {} # module_name -> NativeModuleType
|
|
19
|
+
@boxed_classes = {} # class_name -> true (explicitly boxed classes)
|
|
20
|
+
@cfunc_methods = {} # "ClassName.method_name" -> CFuncType
|
|
21
|
+
@ffi_libraries = {} # class/module_name -> library_name (e.g., :LibM -> "libm")
|
|
22
|
+
@extern_classes = {} # class_name -> ExternClassType (external C struct wrappers)
|
|
23
|
+
@simd_classes = {} # class_name -> SIMDClassType (SIMD vector classes)
|
|
24
|
+
@jvm_classes = {} # "Java::Util::ArrayList" -> { jvm_internal_name:, methods:, static_methods: }
|
|
25
|
+
@rbs_file_contents = {} # path -> content
|
|
26
|
+
@temp_dirs = [] # Temp directories for single RBS files
|
|
27
|
+
@definition_builder = nil # Cached DefinitionBuilder
|
|
28
|
+
@definition_cache = {} # [type_name, singleton?] -> Definition
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Cleanup temp directories created for single RBS file loading
|
|
32
|
+
def cleanup
|
|
33
|
+
@temp_dirs.each do |dir|
|
|
34
|
+
FileUtils.remove_entry(dir) if File.exist?(dir)
|
|
35
|
+
rescue StandardError
|
|
36
|
+
# Ignore cleanup errors
|
|
37
|
+
end
|
|
38
|
+
@temp_dirs.clear
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Load RBS definitions from standard library and optional paths
|
|
42
|
+
# @param rbs_paths [Array<String>] User-specified RBS file paths
|
|
43
|
+
# @param stdlib_libraries [Array<String>] Stdlib library names to load (e.g., ["json", "fileutils"])
|
|
44
|
+
# @param inline_rbs_content [String, nil] RBS content generated from inline annotations
|
|
45
|
+
def load(rbs_paths: [], stdlib_libraries: [], inline_rbs_content: nil)
|
|
46
|
+
cleanup # Clean up any temp dirs from previous load
|
|
47
|
+
loader = RBS::EnvironmentLoader.new
|
|
48
|
+
|
|
49
|
+
# Load stdlib libraries first (only if RBS definitions exist)
|
|
50
|
+
stdlib_libraries.each do |lib_name|
|
|
51
|
+
if stdlib_rbs_available?(lib_name)
|
|
52
|
+
loader.add(library: lib_name)
|
|
53
|
+
else
|
|
54
|
+
warn "Warning: No RBS types available for stdlib '#{lib_name}'"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Add user-specified paths and store file contents for annotation parsing
|
|
59
|
+
rbs_paths.each do |path|
|
|
60
|
+
if File.directory?(path)
|
|
61
|
+
loader.add(path: Pathname(path))
|
|
62
|
+
Dir.glob(File.join(path, "**/*.rbs")).each do |file|
|
|
63
|
+
@rbs_file_contents[file] = File.read(file)
|
|
64
|
+
end
|
|
65
|
+
elsif File.file?(path)
|
|
66
|
+
# For single files, create a unique temp directory to avoid loading
|
|
67
|
+
# other RBS files from the same directory (which could cause
|
|
68
|
+
# DuplicatedMethodDefinitionError)
|
|
69
|
+
content = File.read(path)
|
|
70
|
+
@rbs_file_contents[path] = content
|
|
71
|
+
|
|
72
|
+
temp_dir = Dir.mktmpdir("konpeito_rbs_")
|
|
73
|
+
@temp_dirs << temp_dir
|
|
74
|
+
temp_file = File.join(temp_dir, File.basename(path))
|
|
75
|
+
File.write(temp_file, content)
|
|
76
|
+
loader.add(path: Pathname(temp_dir))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Add inline RBS content if provided
|
|
81
|
+
if inline_rbs_content && !inline_rbs_content.strip.empty?
|
|
82
|
+
@rbs_file_contents["(inline)"] = inline_rbs_content
|
|
83
|
+
|
|
84
|
+
temp_dir = Dir.mktmpdir("konpeito_inline_rbs_")
|
|
85
|
+
@temp_dirs << temp_dir
|
|
86
|
+
temp_file = File.join(temp_dir, "inline.rbs")
|
|
87
|
+
File.write(temp_file, inline_rbs_content)
|
|
88
|
+
loader.add(path: Pathname(temp_dir))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
@environment = RBS::Environment.from_loader(loader).resolve_type_names
|
|
93
|
+
rescue RBS::EnvironmentLoader::UnknownLibraryError => e
|
|
94
|
+
# If stdlib loading fails, retry without it and emit warning
|
|
95
|
+
warn "Warning: #{e.message}"
|
|
96
|
+
# Create a fresh loader without the problematic stdlib libraries
|
|
97
|
+
loader = RBS::EnvironmentLoader.new
|
|
98
|
+
rbs_paths.each do |path|
|
|
99
|
+
if File.directory?(path)
|
|
100
|
+
loader.add(path: Pathname(path))
|
|
101
|
+
elsif File.file?(path)
|
|
102
|
+
# Re-add temp directory
|
|
103
|
+
temp_dir = @temp_dirs.find { |d| File.exist?(File.join(d, File.basename(path))) }
|
|
104
|
+
loader.add(path: Pathname(temp_dir)) if temp_dir
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
@environment = RBS::Environment.from_loader(loader).resolve_type_names
|
|
108
|
+
end
|
|
109
|
+
invalidate_definition_cache!
|
|
110
|
+
@loaded = true
|
|
111
|
+
|
|
112
|
+
# Parse declarations from RBS AST (unified parsing)
|
|
113
|
+
parse_declarations
|
|
114
|
+
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def loaded?
|
|
119
|
+
@loaded
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if a class is a NativeClass
|
|
123
|
+
def native_class?(class_name)
|
|
124
|
+
@native_classes.key?(class_name.to_sym)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Get NativeClassType for a class (returns nil if not native)
|
|
128
|
+
def native_class_type(class_name)
|
|
129
|
+
@native_classes[class_name.to_sym]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Check if a module is a NativeModule
|
|
133
|
+
def native_module?(module_name)
|
|
134
|
+
@native_modules.key?(module_name.to_sym)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Get NativeModuleType for a module (returns nil if not native)
|
|
138
|
+
def native_module_type(module_name)
|
|
139
|
+
@native_modules[module_name.to_sym]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check if a class is explicitly boxed (VALUE-based, for CRuby interop)
|
|
143
|
+
def boxed_class?(class_name)
|
|
144
|
+
@boxed_classes.key?(class_name.to_sym)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get CFuncType for a method (returns nil if not a cfunc)
|
|
148
|
+
# @param class_name [Symbol, String] The class/module name
|
|
149
|
+
# @param method_name [Symbol, String] The method name
|
|
150
|
+
# @param singleton [Boolean] Whether this is a singleton (class) method
|
|
151
|
+
# @return [Types::CFuncType, nil] The cfunc type if found
|
|
152
|
+
def cfunc_method(class_name, method_name, singleton: false)
|
|
153
|
+
key = singleton ? :"#{class_name}.#{method_name}" : :"#{class_name}##{method_name}"
|
|
154
|
+
@cfunc_methods[key]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Check if a method is a cfunc
|
|
158
|
+
def cfunc_method?(class_name, method_name, singleton: false)
|
|
159
|
+
!cfunc_method(class_name, method_name, singleton: singleton).nil?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get the FFI library name for a class/module
|
|
163
|
+
# @param name [Symbol, String] The class or module name
|
|
164
|
+
# @return [String, nil] The library name (e.g., "libm") or nil
|
|
165
|
+
def ffi_library(name)
|
|
166
|
+
@ffi_libraries[name.to_sym]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Check if a class/module has an %a{ffi} annotation
|
|
170
|
+
def ffi_library?(name)
|
|
171
|
+
!ffi_library(name).nil?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Get all FFI libraries used (for linking)
|
|
175
|
+
# @return [Array<String>] Unique list of library names
|
|
176
|
+
def all_ffi_libraries
|
|
177
|
+
# Combine FFI libraries from cfunc modules and extern classes
|
|
178
|
+
libs = @ffi_libraries.values.dup
|
|
179
|
+
@extern_classes.each_value { |ext| libs << ext.ffi_library }
|
|
180
|
+
libs.uniq
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Check if a class is an extern class (wrapping external C struct)
|
|
184
|
+
def extern_class?(class_name)
|
|
185
|
+
@extern_classes.key?(class_name.to_sym)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get ExternClassType for a class (returns nil if not extern)
|
|
189
|
+
def extern_class_type(class_name)
|
|
190
|
+
@extern_classes[class_name.to_sym]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Check if a class is a SIMD class (vector operations)
|
|
194
|
+
def simd_class?(class_name)
|
|
195
|
+
@simd_classes.key?(class_name.to_sym)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Get SIMDClassType for a class (returns nil if not simd)
|
|
199
|
+
def simd_class_type(class_name)
|
|
200
|
+
@simd_classes[class_name.to_sym]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get class definition by name
|
|
204
|
+
def class_definition(name)
|
|
205
|
+
ensure_loaded!
|
|
206
|
+
type_name = parse_type_name(name)
|
|
207
|
+
environment.class_decls[type_name]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Check if a type exists in the environment
|
|
211
|
+
def type_exists?(name)
|
|
212
|
+
ensure_loaded!
|
|
213
|
+
type_name = parse_type_name(name)
|
|
214
|
+
environment.class_decls.key?(type_name)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Parse a type string into an RBS type
|
|
218
|
+
def parse_type(type_string)
|
|
219
|
+
ensure_loaded!
|
|
220
|
+
RBS::Parser.parse_type(type_string)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Get instance methods for a class
|
|
224
|
+
def instance_methods(class_name)
|
|
225
|
+
ensure_loaded!
|
|
226
|
+
|
|
227
|
+
type_name = parse_type_name(class_name)
|
|
228
|
+
decl = environment.class_decls[type_name]
|
|
229
|
+
return [] unless decl
|
|
230
|
+
|
|
231
|
+
definition = build_definition(type_name, singleton: false)
|
|
232
|
+
return [] unless definition
|
|
233
|
+
|
|
234
|
+
definition.methods.keys
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Get method type for a class method
|
|
238
|
+
def method_type(class_name, method_name, singleton: false)
|
|
239
|
+
ensure_loaded!
|
|
240
|
+
|
|
241
|
+
type_name = parse_type_name(class_name)
|
|
242
|
+
decl = environment.class_decls[type_name]
|
|
243
|
+
return nil unless decl
|
|
244
|
+
|
|
245
|
+
definition = build_definition(type_name, singleton: singleton)
|
|
246
|
+
return nil unless definition
|
|
247
|
+
|
|
248
|
+
method = definition.methods[method_name.to_sym]
|
|
249
|
+
return nil unless method
|
|
250
|
+
|
|
251
|
+
method.defs.map(&:type)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Directly look up method type from module/class declaration without building full definition.
|
|
255
|
+
# This avoids expensive DefinitionBuilder and is faster for simple lookups.
|
|
256
|
+
# Falls back to the full method_type if not found (to handle inheritance, mix-ins, etc.)
|
|
257
|
+
def method_type_direct(class_name, method_name, singleton: false)
|
|
258
|
+
ensure_loaded!
|
|
259
|
+
|
|
260
|
+
type_name = parse_type_name(class_name)
|
|
261
|
+
decl_entry = environment.class_decls[type_name]
|
|
262
|
+
return method_type(class_name, method_name, singleton: singleton) unless decl_entry
|
|
263
|
+
|
|
264
|
+
# Search in all declarations (handles reopened classes)
|
|
265
|
+
decl_entry.decls.each do |d|
|
|
266
|
+
decl = d.decl
|
|
267
|
+
next unless decl.respond_to?(:members)
|
|
268
|
+
|
|
269
|
+
decl.members.each do |member|
|
|
270
|
+
case member
|
|
271
|
+
when RBS::AST::Members::MethodDefinition
|
|
272
|
+
# Check if this is the method we're looking for
|
|
273
|
+
is_singleton = member.kind == :singleton
|
|
274
|
+
next unless is_singleton == singleton && member.name == method_name.to_sym
|
|
275
|
+
|
|
276
|
+
# Return the method types
|
|
277
|
+
return member.overloads.map(&:method_type)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Fall back to full definition builder (handles inheritance)
|
|
283
|
+
method_type(class_name, method_name, singleton: singleton)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Alias for backward compatibility
|
|
287
|
+
alias direct_method_type method_type_direct
|
|
288
|
+
|
|
289
|
+
# Look up a constant's type
|
|
290
|
+
def constant_type(constant_name)
|
|
291
|
+
ensure_loaded!
|
|
292
|
+
|
|
293
|
+
type_name = parse_type_name(constant_name)
|
|
294
|
+
environment.constant_decls[type_name]&.decl&.type
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Look up a global variable's type
|
|
298
|
+
def global_variable_type(name)
|
|
299
|
+
ensure_loaded!
|
|
300
|
+
|
|
301
|
+
environment.global_decls[name.to_sym]&.decl&.type
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Get superclass of a class
|
|
305
|
+
def superclass(class_name)
|
|
306
|
+
ensure_loaded!
|
|
307
|
+
|
|
308
|
+
type_name = parse_type_name(class_name)
|
|
309
|
+
decl_entry = environment.class_decls[type_name]
|
|
310
|
+
return nil unless decl_entry
|
|
311
|
+
|
|
312
|
+
# Get the first declaration that has a superclass
|
|
313
|
+
decl_entry.decls.each do |d|
|
|
314
|
+
decl = d.decl
|
|
315
|
+
return decl.super_class.name.name.to_s if decl.respond_to?(:super_class) && decl.super_class
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
nil
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Get all instance variables (with types) for a class
|
|
322
|
+
def instance_variables(class_name)
|
|
323
|
+
ensure_loaded!
|
|
324
|
+
|
|
325
|
+
type_name = parse_type_name(class_name)
|
|
326
|
+
decl_entry = environment.class_decls[type_name]
|
|
327
|
+
return {} unless decl_entry
|
|
328
|
+
|
|
329
|
+
ivars = {}
|
|
330
|
+
|
|
331
|
+
decl_entry.decls.each do |d|
|
|
332
|
+
decl = d.decl
|
|
333
|
+
next unless decl.respond_to?(:members)
|
|
334
|
+
|
|
335
|
+
decl.members.each do |member|
|
|
336
|
+
case member
|
|
337
|
+
when RBS::AST::Members::InstanceVariable
|
|
338
|
+
ivars[member.name] = member.type
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
ivars
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Get all methods (with types) for a class or module
|
|
347
|
+
def all_methods(class_name, singleton: false)
|
|
348
|
+
ensure_loaded!
|
|
349
|
+
|
|
350
|
+
type_name = parse_type_name(class_name)
|
|
351
|
+
decl = environment.class_decls[type_name]
|
|
352
|
+
return {} unless decl
|
|
353
|
+
|
|
354
|
+
builder = RBS::DefinitionBuilder.new(env: environment)
|
|
355
|
+
|
|
356
|
+
definition = if singleton
|
|
357
|
+
builder.build_singleton(type_name)
|
|
358
|
+
else
|
|
359
|
+
builder.build_instance(type_name)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
definition.methods.transform_values do |method|
|
|
363
|
+
method.defs.map(&:type)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Get methods defined directly on a class (not inherited)
|
|
368
|
+
def own_methods(class_name, singleton: false)
|
|
369
|
+
ensure_loaded!
|
|
370
|
+
|
|
371
|
+
type_name = parse_type_name(class_name)
|
|
372
|
+
decl_entry = environment.class_decls[type_name]
|
|
373
|
+
return {} unless decl_entry
|
|
374
|
+
|
|
375
|
+
methods = {}
|
|
376
|
+
|
|
377
|
+
decl_entry.decls.each do |d|
|
|
378
|
+
decl = d.decl
|
|
379
|
+
next unless decl.respond_to?(:members)
|
|
380
|
+
|
|
381
|
+
decl.members.each do |member|
|
|
382
|
+
case member
|
|
383
|
+
when RBS::AST::Members::MethodDefinition
|
|
384
|
+
is_singleton = member.kind == :singleton
|
|
385
|
+
next unless is_singleton == singleton
|
|
386
|
+
|
|
387
|
+
methods[member.name] = member.overloads.map(&:method_type)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
methods
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Convert a type to its string representation
|
|
396
|
+
def type_to_string(type)
|
|
397
|
+
type.to_s
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Check if one RBS type is a subtype of another
|
|
401
|
+
# @param sub_type [RBS::Types::t] The potential subtype
|
|
402
|
+
# @param super_type [RBS::Types::t] The potential supertype
|
|
403
|
+
# @return [Boolean] true if sub_type is a subtype of super_type
|
|
404
|
+
def subtype?(sub_type, super_type)
|
|
405
|
+
ensure_loaded!
|
|
406
|
+
|
|
407
|
+
# Any (untyped) accepts all types
|
|
408
|
+
return true if super_type.is_a?(RBS::Types::Bases::Any)
|
|
409
|
+
|
|
410
|
+
# Same type
|
|
411
|
+
return true if sub_type == super_type
|
|
412
|
+
|
|
413
|
+
# ClassInstance comparison
|
|
414
|
+
if sub_type.is_a?(RBS::Types::ClassInstance) && super_type.is_a?(RBS::Types::ClassInstance)
|
|
415
|
+
return check_class_subtype(sub_type.name, super_type.name)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
false
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
private
|
|
422
|
+
|
|
423
|
+
# Check if one class is a subtype of another (based on inheritance)
|
|
424
|
+
def check_class_subtype(sub_name, super_name)
|
|
425
|
+
return true if sub_name == super_name
|
|
426
|
+
|
|
427
|
+
# Get the class declaration
|
|
428
|
+
decl_entry = environment.class_decls[sub_name]
|
|
429
|
+
return false unless decl_entry
|
|
430
|
+
|
|
431
|
+
# Check superclass chain
|
|
432
|
+
decl_entry.decls.each do |d|
|
|
433
|
+
decl = d.decl
|
|
434
|
+
if decl.respond_to?(:super_class) && decl.super_class
|
|
435
|
+
parent_name = decl.super_class.name
|
|
436
|
+
return true if parent_name == super_name
|
|
437
|
+
return true if check_class_subtype(parent_name, super_name)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
false
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
public
|
|
445
|
+
|
|
446
|
+
# Check if stdlib RBS types are available for a library
|
|
447
|
+
def stdlib_rbs_available?(lib_name)
|
|
448
|
+
# Try to find the library in RBS's built-in paths
|
|
449
|
+
begin
|
|
450
|
+
test_loader = RBS::EnvironmentLoader.new
|
|
451
|
+
test_loader.add(library: lib_name)
|
|
452
|
+
true
|
|
453
|
+
rescue RBS::EnvironmentLoader::UnknownLibraryError
|
|
454
|
+
false
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def ensure_loaded!
|
|
459
|
+
raise Error, "RBS environment not loaded. Call #load first." unless @loaded
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Cached DefinitionBuilder (one per environment)
|
|
463
|
+
def definition_builder
|
|
464
|
+
@definition_builder ||= RBS::DefinitionBuilder.new(env: environment)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Build and cache a class/module definition
|
|
468
|
+
def build_definition(type_name, singleton: false)
|
|
469
|
+
cache_key = [type_name, singleton]
|
|
470
|
+
return @definition_cache[cache_key] if @definition_cache.key?(cache_key)
|
|
471
|
+
|
|
472
|
+
definition = begin
|
|
473
|
+
if singleton
|
|
474
|
+
definition_builder.build_singleton(type_name)
|
|
475
|
+
else
|
|
476
|
+
definition_builder.build_instance(type_name)
|
|
477
|
+
end
|
|
478
|
+
rescue RBS::NoTypeFoundError
|
|
479
|
+
nil
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@definition_cache[cache_key] = definition
|
|
483
|
+
definition
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Invalidate definition caches (call when environment changes)
|
|
487
|
+
def invalidate_definition_cache!
|
|
488
|
+
@definition_builder = nil
|
|
489
|
+
@definition_cache = {}
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def parse_type_name(name)
|
|
493
|
+
name_str = name.to_s
|
|
494
|
+
if name_str.include?("::")
|
|
495
|
+
# Nested class: "Ractor::Port" → name: :Port, namespace: ::Ractor::
|
|
496
|
+
parts = name_str.split("::")
|
|
497
|
+
short_name = parts.pop.to_sym
|
|
498
|
+
namespace = RBS::Namespace.new(path: parts.map(&:to_sym), absolute: true)
|
|
499
|
+
RBS::TypeName.new(namespace: namespace, name: short_name)
|
|
500
|
+
else
|
|
501
|
+
RBS::TypeName.new(namespace: RBS::Namespace.root, name: name.to_sym)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# ============================================================
|
|
506
|
+
# Unified Declaration Parsing (using RBS AST with %a{} annotations)
|
|
507
|
+
# ============================================================
|
|
508
|
+
|
|
509
|
+
# Parse all declarations from RBS file contents
|
|
510
|
+
def parse_declarations
|
|
511
|
+
@rbs_file_contents.each_value do |content|
|
|
512
|
+
parse_declarations_from_content(content)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Validate inheritance (superclasses must exist)
|
|
516
|
+
validate_native_inheritance
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Parse declarations from RBS content using RBS AST
|
|
520
|
+
# Recognizes %a{} annotations for:
|
|
521
|
+
# - %a{native} - Native struct class
|
|
522
|
+
# - %a{native: vtable} - Native with dynamic dispatch
|
|
523
|
+
# - %a{extern} - External C struct wrapper (requires %a{ffi})
|
|
524
|
+
# - %a{boxed} - VALUE-based class
|
|
525
|
+
# - %a{struct} - Value type (pass-by-value)
|
|
526
|
+
# - %a{simd} - SIMD vector class
|
|
527
|
+
# - %a{ffi: "lib"} - FFI library for class/module
|
|
528
|
+
# - %a{cfunc: "name"} - C function binding for method
|
|
529
|
+
# - %a{cfunc} - C function (use method name)
|
|
530
|
+
def parse_declarations_from_content(content)
|
|
531
|
+
buffer = RBS::Buffer.new(name: "(inline)", content: content)
|
|
532
|
+
_, _, declarations = RBS::Parser.parse_signature(buffer)
|
|
533
|
+
|
|
534
|
+
declarations.each do |decl|
|
|
535
|
+
case decl
|
|
536
|
+
when RBS::AST::Declarations::Class
|
|
537
|
+
parse_class_declaration(decl)
|
|
538
|
+
when RBS::AST::Declarations::Module
|
|
539
|
+
parse_module_declaration(decl)
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
rescue RBS::ParsingError => e
|
|
543
|
+
warn "Warning: RBS parsing error: #{e.message}"
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Parse a class declaration
|
|
547
|
+
def parse_class_declaration(decl)
|
|
548
|
+
class_name = decl.name.name
|
|
549
|
+
|
|
550
|
+
# Check for Java:: namespace prefix (JVM interop class)
|
|
551
|
+
full_name = decl.name.to_s.delete_prefix("::")
|
|
552
|
+
if full_name.start_with?("Java::")
|
|
553
|
+
parse_jvm_class(decl, full_name)
|
|
554
|
+
return
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
annotations = AnnotationParser.parse_all(decl.annotations)
|
|
558
|
+
|
|
559
|
+
# Skip built-in types
|
|
560
|
+
if builtin_type?(class_name)
|
|
561
|
+
return
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Check for %a{ffi: "lib"} at class level
|
|
565
|
+
ffi_ann = AnnotationParser.find(annotations, :ffi)
|
|
566
|
+
@ffi_libraries[class_name] = ffi_ann[:library] if ffi_ann
|
|
567
|
+
|
|
568
|
+
# Check for %a{boxed}
|
|
569
|
+
if AnnotationParser.has?(annotations, :boxed)
|
|
570
|
+
@boxed_classes[class_name] = true
|
|
571
|
+
return
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Check for %a{extern} (requires ffi)
|
|
575
|
+
if AnnotationParser.has?(annotations, :extern)
|
|
576
|
+
parse_extern_class(decl, ffi_ann)
|
|
577
|
+
return
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
# Check for %a{simd}
|
|
581
|
+
if AnnotationParser.has?(annotations, :simd)
|
|
582
|
+
parse_simd_class(decl)
|
|
583
|
+
return
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# Check for %a{struct} (value type)
|
|
587
|
+
is_struct = AnnotationParser.has?(annotations, :struct)
|
|
588
|
+
|
|
589
|
+
# Check for %a{native} with options
|
|
590
|
+
native_ann = AnnotationParser.find(annotations, :native)
|
|
591
|
+
use_vtable = native_ann&.dig(:vtable) || false
|
|
592
|
+
|
|
593
|
+
# Parse fields and methods
|
|
594
|
+
fields = {}
|
|
595
|
+
methods = {}
|
|
596
|
+
superclass_name = decl.super_class&.name&.name
|
|
597
|
+
|
|
598
|
+
decl.members.each do |member|
|
|
599
|
+
case member
|
|
600
|
+
when RBS::AST::Members::InstanceVariable
|
|
601
|
+
# Strip @ prefix from instance variable name (e.g., :@x -> :x)
|
|
602
|
+
field_name = member.name.to_s.delete_prefix("@").to_sym
|
|
603
|
+
native_type = convert_rbs_type_to_native_field(member.type)
|
|
604
|
+
fields[field_name] = native_type if native_type
|
|
605
|
+
when RBS::AST::Members::MethodDefinition
|
|
606
|
+
# Parse method annotations for cfunc
|
|
607
|
+
method_annotations = AnnotationParser.parse_all(member.annotations)
|
|
608
|
+
cfunc_ann = AnnotationParser.find(method_annotations, :cfunc)
|
|
609
|
+
|
|
610
|
+
if cfunc_ann
|
|
611
|
+
parse_cfunc_method(class_name, member, cfunc_ann)
|
|
612
|
+
else
|
|
613
|
+
# Skip field accessors
|
|
614
|
+
field_base = member.name.to_s.chomp("=").to_sym
|
|
615
|
+
is_accessor = fields.key?(field_base) ||
|
|
616
|
+
(superclass_name && will_inherit_field?(superclass_name, field_base))
|
|
617
|
+
next if is_accessor
|
|
618
|
+
next if member.kind == :singleton # Skip constructors
|
|
619
|
+
|
|
620
|
+
method_type = parse_native_method(member, class_name)
|
|
621
|
+
methods[member.name] = method_type if method_type
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Create NativeClassType if has fields or methods
|
|
627
|
+
return if fields.empty? && methods.empty?
|
|
628
|
+
|
|
629
|
+
native_type = Types::NativeClassType.new(
|
|
630
|
+
class_name,
|
|
631
|
+
fields,
|
|
632
|
+
methods,
|
|
633
|
+
superclass: superclass_name,
|
|
634
|
+
vtable: use_vtable,
|
|
635
|
+
is_value_type: is_struct
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# Validate value type constraints
|
|
639
|
+
if is_struct
|
|
640
|
+
valid, error_msg = native_type.valid_value_type?
|
|
641
|
+
unless valid
|
|
642
|
+
warn "Warning: %a{struct} #{class_name} is invalid: #{error_msg}"
|
|
643
|
+
native_type.is_value_type = false
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
@native_classes[class_name] = native_type
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Parse a module declaration
|
|
651
|
+
def parse_module_declaration(decl)
|
|
652
|
+
module_name = decl.name.name
|
|
653
|
+
annotations = AnnotationParser.parse_all(decl.annotations)
|
|
654
|
+
|
|
655
|
+
# Check for %a{ffi: "lib"}
|
|
656
|
+
ffi_ann = AnnotationParser.find(annotations, :ffi)
|
|
657
|
+
@ffi_libraries[module_name] = ffi_ann[:library] if ffi_ann
|
|
658
|
+
|
|
659
|
+
# Check for %a{jvm_static: "..."} module (maps to external Java class)
|
|
660
|
+
jvm_static_ann = AnnotationParser.find(annotations, :jvm_static)
|
|
661
|
+
if jvm_static_ann
|
|
662
|
+
parse_jvm_static_module(decl, jvm_static_ann[:java_class])
|
|
663
|
+
return
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Check for %a{native} module
|
|
667
|
+
if AnnotationParser.has?(annotations, :native)
|
|
668
|
+
parse_native_module(decl)
|
|
669
|
+
return
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
# Parse methods for cfunc annotations
|
|
673
|
+
decl.members.each do |member|
|
|
674
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
|
675
|
+
|
|
676
|
+
method_annotations = AnnotationParser.parse_all(member.annotations)
|
|
677
|
+
cfunc_ann = AnnotationParser.find(method_annotations, :cfunc)
|
|
678
|
+
parse_cfunc_method(module_name, member, cfunc_ann) if cfunc_ann
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# Parse a native module (with %a{native})
|
|
683
|
+
def parse_native_module(decl)
|
|
684
|
+
module_name = decl.name.name
|
|
685
|
+
methods = {}
|
|
686
|
+
|
|
687
|
+
decl.members.each do |member|
|
|
688
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
|
689
|
+
|
|
690
|
+
method_type = parse_native_method(member, module_name)
|
|
691
|
+
methods[member.name] = method_type if method_type
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
return if methods.empty?
|
|
695
|
+
|
|
696
|
+
@native_modules[module_name] = Types::NativeModuleType.new(module_name, methods)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# Parse an extern class (with %a{extern})
|
|
700
|
+
def parse_extern_class(decl, ffi_ann)
|
|
701
|
+
class_name = decl.name.name
|
|
702
|
+
|
|
703
|
+
unless ffi_ann
|
|
704
|
+
warn "Warning: %a{extern} class #{class_name} requires %a{ffi} annotation, skipping"
|
|
705
|
+
return
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
extern_methods = {}
|
|
709
|
+
|
|
710
|
+
decl.members.each do |member|
|
|
711
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
|
712
|
+
|
|
713
|
+
overload = member.overloads.first
|
|
714
|
+
next unless overload
|
|
715
|
+
|
|
716
|
+
func_type = overload.method_type.type
|
|
717
|
+
return_type_str = rbs_type_to_string(func_type.return_type)
|
|
718
|
+
param_types = parse_extern_params(func_type)
|
|
719
|
+
return_type = parse_extern_return_type(return_type_str, class_name)
|
|
720
|
+
|
|
721
|
+
is_singleton = member.kind == :singleton
|
|
722
|
+
is_constructor = is_singleton && (return_type == :ptr || return_type_str == class_name.to_s)
|
|
723
|
+
|
|
724
|
+
# Instance methods receive opaque pointer as first param
|
|
725
|
+
unless is_singleton
|
|
726
|
+
param_types = [:ptr] + param_types
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
extern_methods[member.name] = Types::ExternMethodType.new(
|
|
730
|
+
member.name.to_s,
|
|
731
|
+
param_types,
|
|
732
|
+
is_constructor ? :ptr : return_type,
|
|
733
|
+
is_constructor: is_constructor
|
|
734
|
+
)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
@extern_classes[class_name] = Types::ExternClassType.new(
|
|
738
|
+
class_name,
|
|
739
|
+
ffi_ann[:library],
|
|
740
|
+
extern_methods
|
|
741
|
+
)
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# Parse a SIMD class (with %a{simd})
|
|
745
|
+
def parse_simd_class(decl)
|
|
746
|
+
class_name = decl.name.name
|
|
747
|
+
field_names = []
|
|
748
|
+
simd_methods = {}
|
|
749
|
+
|
|
750
|
+
decl.members.each do |member|
|
|
751
|
+
case member
|
|
752
|
+
when RBS::AST::Members::InstanceVariable
|
|
753
|
+
# Strip @ prefix from instance variable name
|
|
754
|
+
field_name = member.name.to_s.delete_prefix("@").to_sym
|
|
755
|
+
type_str = rbs_type_to_string(member.type)
|
|
756
|
+
unless type_str == "Float"
|
|
757
|
+
warn "Warning: %a{simd} class #{class_name} field '#{field_name}' must be Float, got #{type_str}"
|
|
758
|
+
next
|
|
759
|
+
end
|
|
760
|
+
field_names << field_name
|
|
761
|
+
when RBS::AST::Members::MethodDefinition
|
|
762
|
+
# Skip field accessors
|
|
763
|
+
field_base = member.name.to_s.chomp("=").to_sym
|
|
764
|
+
next if field_names.include?(field_base)
|
|
765
|
+
next if member.kind == :singleton
|
|
766
|
+
|
|
767
|
+
method_type = parse_simd_method(member, class_name)
|
|
768
|
+
simd_methods[member.name] = method_type if method_type
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Validate SIMD field count
|
|
773
|
+
unless Types::SIMDClassType::ALLOWED_WIDTHS.include?(field_names.size)
|
|
774
|
+
warn "Warning: %a{simd} class #{class_name} must have #{Types::SIMDClassType::ALLOWED_WIDTHS.join('/')} Float fields, got #{field_names.size}"
|
|
775
|
+
return
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
@simd_classes[class_name] = Types::SIMDClassType.new(class_name, field_names, simd_methods)
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
# Parse a JVM interop class (Java:: namespace prefix)
|
|
782
|
+
# e.g., class Java::Util::ArrayList → java/util/ArrayList
|
|
783
|
+
def parse_jvm_class(decl, full_name)
|
|
784
|
+
# Convert Java::Util::ArrayList → java/util/ArrayList
|
|
785
|
+
# All segments form the Java class path; package segments are lowercased
|
|
786
|
+
segments = full_name.split("::")
|
|
787
|
+
jvm_segments = segments.each_with_index.map do |seg, i|
|
|
788
|
+
i < segments.size - 1 ? seg[0].downcase + seg[1..] : seg
|
|
789
|
+
end
|
|
790
|
+
jvm_internal_name = jvm_segments.join("/")
|
|
791
|
+
|
|
792
|
+
methods = {}
|
|
793
|
+
static_methods = {}
|
|
794
|
+
constructor_params = nil
|
|
795
|
+
|
|
796
|
+
decl.members.each do |member|
|
|
797
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
|
798
|
+
|
|
799
|
+
overload = member.overloads.first
|
|
800
|
+
next unless overload
|
|
801
|
+
|
|
802
|
+
func_type = overload.method_type.type
|
|
803
|
+
param_types = func_type.required_positionals.map { |p| jvm_rbs_type_to_tag(p.type) }
|
|
804
|
+
return_type = jvm_rbs_type_to_tag(func_type.return_type)
|
|
805
|
+
|
|
806
|
+
method_name = member.name.to_s
|
|
807
|
+
|
|
808
|
+
if member.kind == :singleton
|
|
809
|
+
if method_name == "new"
|
|
810
|
+
# Constructor: store param types for <init> descriptor
|
|
811
|
+
constructor_params = param_types
|
|
812
|
+
# Return type of constructor is the class itself
|
|
813
|
+
return_type = :value
|
|
814
|
+
else
|
|
815
|
+
static_methods[method_name] = { params: param_types, return: return_type }
|
|
816
|
+
end
|
|
817
|
+
else
|
|
818
|
+
# Check if return type is the same class (for method chaining)
|
|
819
|
+
# Use full qualified name from RBS type for Java:: classes
|
|
820
|
+
ret_type_obj = func_type.return_type
|
|
821
|
+
if ret_type_obj.is_a?(RBS::Types::ClassInstance)
|
|
822
|
+
ret_full_name = ret_type_obj.name.to_s
|
|
823
|
+
else
|
|
824
|
+
ret_full_name = rbs_type_to_string(ret_type_obj)
|
|
825
|
+
end
|
|
826
|
+
return_class = (ret_full_name == full_name) ? full_name : nil
|
|
827
|
+
|
|
828
|
+
methods[method_name] = {
|
|
829
|
+
params: param_types,
|
|
830
|
+
return: return_type,
|
|
831
|
+
return_class: return_class
|
|
832
|
+
}
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
@jvm_classes[full_name] = {
|
|
837
|
+
jvm_internal_name: jvm_internal_name,
|
|
838
|
+
methods: methods,
|
|
839
|
+
static_methods: static_methods,
|
|
840
|
+
constructor_params: constructor_params
|
|
841
|
+
}
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
# Parse a module with %a{jvm_static: "java/class/Name"} annotation.
|
|
845
|
+
# Maps a Ruby module to an external Java class's static methods.
|
|
846
|
+
def parse_jvm_static_module(decl, java_class)
|
|
847
|
+
module_name = decl.name.to_s.delete_prefix("::")
|
|
848
|
+
static_methods = {}
|
|
849
|
+
|
|
850
|
+
decl.members.each do |member|
|
|
851
|
+
next unless member.is_a?(RBS::AST::Members::MethodDefinition)
|
|
852
|
+
next unless member.kind == :singleton # def self.xxx only
|
|
853
|
+
|
|
854
|
+
overload = member.overloads.first
|
|
855
|
+
next unless overload
|
|
856
|
+
|
|
857
|
+
func_type = overload.method_type.type
|
|
858
|
+
param_types = func_type.required_positionals.map { |p| jvm_rbs_type_to_tag(p.type) }
|
|
859
|
+
return_type = jvm_rbs_type_to_tag(func_type.return_type)
|
|
860
|
+
|
|
861
|
+
method_name = member.name.to_s
|
|
862
|
+
java_method_name = snake_to_camel(method_name)
|
|
863
|
+
|
|
864
|
+
method_info = { params: param_types, return: return_type, java_name: java_method_name }
|
|
865
|
+
|
|
866
|
+
# Check for %a{callback: "..." descriptor: "..."} annotation on method
|
|
867
|
+
method_annotations = AnnotationParser.parse_all(member.annotations)
|
|
868
|
+
callback_ann = AnnotationParser.find(method_annotations, :callback)
|
|
869
|
+
if callback_ann
|
|
870
|
+
cb_info = { interface: callback_ann[:interface] }
|
|
871
|
+
if callback_ann[:descriptor]
|
|
872
|
+
cb_info[:descriptor] = callback_ann[:descriptor]
|
|
873
|
+
cb_info[:param_types] = parse_callback_descriptor_params(callback_ann[:descriptor])
|
|
874
|
+
cb_info[:return_type] = parse_callback_descriptor_return(callback_ann[:descriptor])
|
|
875
|
+
end
|
|
876
|
+
method_info[:block_callback] = cb_info
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
static_methods[method_name] = method_info
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
@jvm_classes[module_name] = {
|
|
883
|
+
jvm_internal_name: java_class,
|
|
884
|
+
methods: {},
|
|
885
|
+
static_methods: static_methods,
|
|
886
|
+
constructor_params: nil,
|
|
887
|
+
jvm_static_module: true
|
|
888
|
+
}
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
# Convert snake_case to camelCase (e.g., "set_background" -> "setBackground")
|
|
892
|
+
def snake_to_camel(name)
|
|
893
|
+
parts = name.split("_")
|
|
894
|
+
parts[0] + parts[1..].map(&:capitalize).join
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Convert camelCase to snake_case (e.g., "setBackground" -> "set_background")
|
|
898
|
+
def camel_to_snake(name)
|
|
899
|
+
name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
# Register Java:: references found by AST pre-scan.
|
|
903
|
+
# Introspects the classpath for the referenced classes and auto-registers them
|
|
904
|
+
# in @jvm_classes so that load_classpath_types can supplement their methods.
|
|
905
|
+
def register_java_references(java_refs, classpath)
|
|
906
|
+
refs = java_refs[:refs]
|
|
907
|
+
aliases = java_refs[:aliases]
|
|
908
|
+
|
|
909
|
+
# Skip classes already registered via RBS
|
|
910
|
+
new_refs = refs.reject { |ruby_name, _| @jvm_classes.key?(ruby_name) }
|
|
911
|
+
return if new_refs.empty?
|
|
912
|
+
|
|
913
|
+
# Introspect the classpath for referenced classes
|
|
914
|
+
target_classes = new_refs.values
|
|
915
|
+
introspect_json = run_introspector(classpath, target_classes)
|
|
916
|
+
return unless introspect_json
|
|
917
|
+
|
|
918
|
+
# Build SAM interface map from all introspected classes
|
|
919
|
+
sam_interfaces = build_sam_interface_map(introspect_json)
|
|
920
|
+
|
|
921
|
+
# Register each class in @jvm_classes
|
|
922
|
+
new_refs.each do |ruby_name, jvm_internal|
|
|
923
|
+
class_data = introspect_json.dig("classes", jvm_internal)
|
|
924
|
+
next unless class_data
|
|
925
|
+
|
|
926
|
+
register_introspected_class(ruby_name, jvm_internal, class_data, sam_interfaces)
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Register aliases: KCanvas → same data as Java::Konpeito::Canvas::Canvas
|
|
930
|
+
aliases.each do |alias_name, java_path|
|
|
931
|
+
next if @jvm_classes.key?(alias_name)
|
|
932
|
+
|
|
933
|
+
source = @jvm_classes[java_path]
|
|
934
|
+
@jvm_classes[alias_name] = source.dup if source
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# Load type information from classpath JAR/class files.
|
|
939
|
+
# Supplements @jvm_classes entries that were registered from RBS but may lack
|
|
940
|
+
# method signatures. RBS-defined methods take priority; missing ones are filled
|
|
941
|
+
# from classpath introspection.
|
|
942
|
+
def load_classpath_types(classpath)
|
|
943
|
+
return unless classpath
|
|
944
|
+
|
|
945
|
+
target_classes = @jvm_classes.values.map { |info| info[:jvm_internal_name] }.compact
|
|
946
|
+
return if target_classes.empty?
|
|
947
|
+
|
|
948
|
+
introspect_json = run_introspector(classpath, target_classes)
|
|
949
|
+
return unless introspect_json
|
|
950
|
+
|
|
951
|
+
merge_introspected_types(introspect_json)
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
# Parse param types from a JVM method descriptor (e.g., "(DD)V" -> [:double, :double])
|
|
955
|
+
def parse_callback_descriptor_params(descriptor)
|
|
956
|
+
return [] unless descriptor =~ /\A\(([^)]*)\)/
|
|
957
|
+
|
|
958
|
+
params_str = ::Regexp.last_match(1)
|
|
959
|
+
types = []
|
|
960
|
+
i = 0
|
|
961
|
+
while i < params_str.length
|
|
962
|
+
case params_str[i]
|
|
963
|
+
when "I" then types << :i64; i += 1
|
|
964
|
+
when "J" then types << :i64; i += 1
|
|
965
|
+
when "D" then types << :double; i += 1
|
|
966
|
+
when "F" then types << :double; i += 1
|
|
967
|
+
when "Z" then types << :i8; i += 1
|
|
968
|
+
when "L"
|
|
969
|
+
# Object type: skip to ;
|
|
970
|
+
semi = params_str.index(";", i)
|
|
971
|
+
class_name = params_str[(i + 1)...semi]
|
|
972
|
+
types << if class_name == "java/lang/String"
|
|
973
|
+
:string
|
|
974
|
+
else
|
|
975
|
+
:value
|
|
976
|
+
end
|
|
977
|
+
i = semi + 1
|
|
978
|
+
else
|
|
979
|
+
types << :value; i += 1
|
|
980
|
+
end
|
|
981
|
+
end
|
|
982
|
+
types
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
# Parse return type from a JVM method descriptor (e.g., "(DD)V" -> :void)
|
|
986
|
+
def parse_callback_descriptor_return(descriptor)
|
|
987
|
+
return :void unless descriptor =~ /\)(.+)\z/
|
|
988
|
+
|
|
989
|
+
ret = ::Regexp.last_match(1)
|
|
990
|
+
case ret
|
|
991
|
+
when "V" then :void
|
|
992
|
+
when "I", "J" then :i64
|
|
993
|
+
when "D", "F" then :double
|
|
994
|
+
when "Z" then :i8
|
|
995
|
+
when "Ljava/lang/String;" then :string
|
|
996
|
+
else :value
|
|
997
|
+
end
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
# Convert RBS type to JVM interop type tag
|
|
1001
|
+
# Returns a symbol tag or a string for Java class references (e.g., "java/util/ArrayList")
|
|
1002
|
+
def jvm_rbs_type_to_tag(type)
|
|
1003
|
+
# For ClassInstance types, check full qualified name for Java:: prefix
|
|
1004
|
+
if type.is_a?(RBS::Types::ClassInstance)
|
|
1005
|
+
full_name = type.name.to_s
|
|
1006
|
+
if full_name.start_with?("Java::")
|
|
1007
|
+
# Return the JVM internal name as a string tag (e.g., "java/lang/StringBuilder")
|
|
1008
|
+
segments = full_name.split("::")
|
|
1009
|
+
jvm_segs = segments.each_with_index.map { |s, i| i < segments.size - 1 ? s[0].downcase + s[1..] : s }
|
|
1010
|
+
return jvm_segs.join("/")
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
type_str = rbs_type_to_string(type)
|
|
1015
|
+
case type_str
|
|
1016
|
+
when "Integer" then :i64
|
|
1017
|
+
when "Float" then :double
|
|
1018
|
+
when "String" then :string
|
|
1019
|
+
when "Bool", "bool" then :i8
|
|
1020
|
+
when "void" then :void
|
|
1021
|
+
else :value # Object, untyped, etc.
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
# Check if a class name is a JVM interop class
|
|
1026
|
+
def jvm_class?(name)
|
|
1027
|
+
@jvm_classes.key?(name.to_s)
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# Run the KonpeitoAssembler --introspect subprocess to extract type info from classpath.
|
|
1031
|
+
# Returns parsed JSON hash or nil on failure.
|
|
1032
|
+
def run_introspector(classpath, target_classes)
|
|
1033
|
+
asm_jar = File.expand_path("../../../tools/konpeito-asm/konpeito-asm.jar", __dir__)
|
|
1034
|
+
return nil unless File.exist?(asm_jar)
|
|
1035
|
+
|
|
1036
|
+
java_home = ENV["JAVA_HOME"] || Platform.default_java_home
|
|
1037
|
+
java_cmd = File.join(java_home, "bin", "java")
|
|
1038
|
+
unless File.exist?(java_cmd)
|
|
1039
|
+
java_cmd = Platform.find_executable("java") || "java"
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
request = { "classpath" => classpath, "classes" => target_classes }
|
|
1043
|
+
request_json = JSON.generate(request)
|
|
1044
|
+
|
|
1045
|
+
cmd = [java_cmd, "-jar", asm_jar, "--introspect"]
|
|
1046
|
+
output = nil
|
|
1047
|
+
IO.popen(cmd, "r+", err: File::NULL) do |io|
|
|
1048
|
+
io.write(request_json)
|
|
1049
|
+
io.close_write
|
|
1050
|
+
output = io.read
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
return nil unless $?.success? && output && !output.strip.empty?
|
|
1054
|
+
|
|
1055
|
+
JSON.parse(output)
|
|
1056
|
+
rescue StandardError => e
|
|
1057
|
+
$stderr.puts "[DEBUG] run_introspector error: #{e.class}: #{e.message}" if ENV["KONPEITO_DEBUG"]
|
|
1058
|
+
nil
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
# Merge introspected class info into @jvm_classes.
|
|
1062
|
+
# RBS-defined methods take priority; classpath fills in missing ones.
|
|
1063
|
+
def merge_introspected_types(introspect_data)
|
|
1064
|
+
classes_data = introspect_data["classes"]
|
|
1065
|
+
return unless classes_data
|
|
1066
|
+
|
|
1067
|
+
# Build SAM interface map from inner classes:
|
|
1068
|
+
# { "konpeito/canvas/KCanvas$MouseCallback" => { method: "call", descriptor: "(DD)V" } }
|
|
1069
|
+
sam_interfaces = {}
|
|
1070
|
+
classes_data.each_value do |class_info|
|
|
1071
|
+
inner_classes = class_info["inner_classes"] || {}
|
|
1072
|
+
inner_classes.each do |inner_name, inner_info|
|
|
1073
|
+
next unless inner_info["is_interface"]
|
|
1074
|
+
|
|
1075
|
+
abstract_methods = inner_info["abstract_methods"] || {}
|
|
1076
|
+
if abstract_methods.size == 1
|
|
1077
|
+
method_name, method_info = abstract_methods.first
|
|
1078
|
+
sam_interfaces[inner_name] = {
|
|
1079
|
+
method: method_name,
|
|
1080
|
+
descriptor: method_info["descriptor"]
|
|
1081
|
+
}
|
|
1082
|
+
end
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
# Merge into each @jvm_classes entry
|
|
1087
|
+
@jvm_classes.each do |ruby_name, jvm_info|
|
|
1088
|
+
jvm_internal = jvm_info[:jvm_internal_name]
|
|
1089
|
+
class_data = classes_data[jvm_internal]
|
|
1090
|
+
next unless class_data
|
|
1091
|
+
|
|
1092
|
+
existing_static = jvm_info[:static_methods] || {}
|
|
1093
|
+
introspected_static = class_data["static_methods"] || {}
|
|
1094
|
+
|
|
1095
|
+
introspected_static.each do |java_method_name, method_data|
|
|
1096
|
+
ruby_method_name = camel_to_snake(java_method_name)
|
|
1097
|
+
|
|
1098
|
+
# Skip if already defined in RBS
|
|
1099
|
+
next if existing_static.key?(ruby_method_name)
|
|
1100
|
+
|
|
1101
|
+
descriptor = method_data["descriptor"]
|
|
1102
|
+
param_types = parse_callback_descriptor_params(descriptor)
|
|
1103
|
+
return_type = parse_callback_descriptor_return(descriptor)
|
|
1104
|
+
|
|
1105
|
+
method_info = {
|
|
1106
|
+
params: param_types,
|
|
1107
|
+
return: return_type,
|
|
1108
|
+
java_name: java_method_name
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
# Auto-detect SAM callback parameters:
|
|
1112
|
+
# If a parameter type is a SAM interface, convert to block_callback
|
|
1113
|
+
# and remove the SAM parameter from the param list
|
|
1114
|
+
raw_param_types = parse_descriptor_raw_params(descriptor)
|
|
1115
|
+
sam_param_idx = nil
|
|
1116
|
+
raw_param_types.each_with_index do |raw_type, idx|
|
|
1117
|
+
if raw_type.is_a?(String) && sam_interfaces.key?(raw_type)
|
|
1118
|
+
sam_param_idx = idx
|
|
1119
|
+
break
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
if sam_param_idx
|
|
1124
|
+
sam_info = sam_interfaces[raw_param_types[sam_param_idx]]
|
|
1125
|
+
method_info[:block_callback] = {
|
|
1126
|
+
interface: raw_param_types[sam_param_idx],
|
|
1127
|
+
descriptor: sam_info[:descriptor],
|
|
1128
|
+
param_types: parse_callback_descriptor_params(sam_info[:descriptor]),
|
|
1129
|
+
return_type: parse_callback_descriptor_return(sam_info[:descriptor])
|
|
1130
|
+
}
|
|
1131
|
+
# Remove the SAM parameter from the param list
|
|
1132
|
+
method_info[:params].delete_at(sam_param_idx)
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
existing_static[ruby_method_name] = method_info
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
jvm_info[:static_methods] = existing_static
|
|
1139
|
+
|
|
1140
|
+
# Also merge instance methods
|
|
1141
|
+
existing_methods = jvm_info[:methods] || {}
|
|
1142
|
+
introspected_methods = class_data["methods"] || {}
|
|
1143
|
+
|
|
1144
|
+
introspected_methods.each do |java_method_name, method_data|
|
|
1145
|
+
ruby_method_name = camel_to_snake(java_method_name)
|
|
1146
|
+
next if existing_methods.key?(ruby_method_name)
|
|
1147
|
+
|
|
1148
|
+
descriptor = method_data["descriptor"]
|
|
1149
|
+
param_types = parse_callback_descriptor_params(descriptor)
|
|
1150
|
+
return_type = parse_callback_descriptor_return(descriptor)
|
|
1151
|
+
|
|
1152
|
+
existing_methods[ruby_method_name] = {
|
|
1153
|
+
params: param_types,
|
|
1154
|
+
return: return_type
|
|
1155
|
+
}
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
jvm_info[:methods] = existing_methods
|
|
1159
|
+
|
|
1160
|
+
# Merge constructor if not already defined
|
|
1161
|
+
if jvm_info[:constructor_params].nil? && class_data["constructor"]
|
|
1162
|
+
ctor_desc = class_data["constructor"]["descriptor"]
|
|
1163
|
+
jvm_info[:constructor_params] = parse_callback_descriptor_params(ctor_desc) if ctor_desc
|
|
1164
|
+
end
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
# Build SAM (Single Abstract Method) interface map from introspection data.
|
|
1169
|
+
# Returns { "pkg/Class$Interface" => { method: "call", descriptor: "(DD)V" } }
|
|
1170
|
+
def build_sam_interface_map(introspect_data)
|
|
1171
|
+
sam_interfaces = {}
|
|
1172
|
+
classes_data = introspect_data["classes"] || {}
|
|
1173
|
+
classes_data.each_value do |class_info|
|
|
1174
|
+
inner_classes = class_info["inner_classes"] || {}
|
|
1175
|
+
inner_classes.each do |inner_name, inner_info|
|
|
1176
|
+
next unless inner_info["is_interface"]
|
|
1177
|
+
|
|
1178
|
+
abstract_methods = inner_info["abstract_methods"] || {}
|
|
1179
|
+
if abstract_methods.size == 1
|
|
1180
|
+
method_name, method_info = abstract_methods.first
|
|
1181
|
+
sam_interfaces[inner_name] = {
|
|
1182
|
+
method: method_name,
|
|
1183
|
+
descriptor: method_info["descriptor"]
|
|
1184
|
+
}
|
|
1185
|
+
end
|
|
1186
|
+
end
|
|
1187
|
+
end
|
|
1188
|
+
sam_interfaces
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
# Register one introspected class in @jvm_classes (auto-registered without RBS).
|
|
1192
|
+
def register_introspected_class(ruby_name, jvm_internal, class_data, sam_interfaces)
|
|
1193
|
+
static_methods = {}
|
|
1194
|
+
(class_data["static_methods"] || {}).each do |java_name, info|
|
|
1195
|
+
ruby_method = camel_to_snake(java_name)
|
|
1196
|
+
descriptor = info["descriptor"]
|
|
1197
|
+
method_info = {
|
|
1198
|
+
params: parse_callback_descriptor_params(descriptor),
|
|
1199
|
+
return: parse_callback_descriptor_return(descriptor),
|
|
1200
|
+
java_name: java_name
|
|
1201
|
+
}
|
|
1202
|
+
# Auto-detect SAM callback parameters
|
|
1203
|
+
detect_sam_callback!(method_info, descriptor, sam_interfaces)
|
|
1204
|
+
static_methods[ruby_method] = method_info
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1207
|
+
methods = {}
|
|
1208
|
+
(class_data["methods"] || {}).each do |java_name, info|
|
|
1209
|
+
ruby_method = camel_to_snake(java_name)
|
|
1210
|
+
descriptor = info["descriptor"]
|
|
1211
|
+
method_info = {
|
|
1212
|
+
params: parse_callback_descriptor_params(descriptor),
|
|
1213
|
+
return: parse_callback_descriptor_return(descriptor),
|
|
1214
|
+
java_name: java_name
|
|
1215
|
+
}
|
|
1216
|
+
# Auto-detect SAM callback on instance methods too
|
|
1217
|
+
detect_sam_callback!(method_info, descriptor, sam_interfaces)
|
|
1218
|
+
methods[ruby_method] = method_info
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
constructor_params = nil
|
|
1222
|
+
if class_data["constructor"]
|
|
1223
|
+
ctor_desc = class_data["constructor"]["descriptor"]
|
|
1224
|
+
constructor_params = parse_callback_descriptor_params(ctor_desc) if ctor_desc
|
|
1225
|
+
end
|
|
1226
|
+
|
|
1227
|
+
@jvm_classes[ruby_name] = {
|
|
1228
|
+
jvm_internal_name: jvm_internal,
|
|
1229
|
+
methods: methods,
|
|
1230
|
+
static_methods: static_methods,
|
|
1231
|
+
constructor_params: constructor_params,
|
|
1232
|
+
auto_registered: true
|
|
1233
|
+
}
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
# Detect SAM callback parameters in a method and update method_info in-place.
|
|
1237
|
+
def detect_sam_callback!(method_info, descriptor, sam_interfaces)
|
|
1238
|
+
raw_param_types = parse_descriptor_raw_params(descriptor)
|
|
1239
|
+
sam_param_idx = nil
|
|
1240
|
+
raw_param_types.each_with_index do |raw_type, idx|
|
|
1241
|
+
if raw_type.is_a?(String) && sam_interfaces.key?(raw_type)
|
|
1242
|
+
sam_param_idx = idx
|
|
1243
|
+
break
|
|
1244
|
+
end
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
return unless sam_param_idx
|
|
1248
|
+
|
|
1249
|
+
sam_info = sam_interfaces[raw_param_types[sam_param_idx]]
|
|
1250
|
+
method_info[:block_callback] = {
|
|
1251
|
+
interface: raw_param_types[sam_param_idx],
|
|
1252
|
+
descriptor: sam_info[:descriptor],
|
|
1253
|
+
param_types: parse_callback_descriptor_params(sam_info[:descriptor]),
|
|
1254
|
+
return_type: parse_callback_descriptor_return(sam_info[:descriptor])
|
|
1255
|
+
}
|
|
1256
|
+
# Remove the SAM parameter from the param list
|
|
1257
|
+
method_info[:params].delete_at(sam_param_idx)
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
# Parse raw parameter types from descriptor, returning class names as strings
|
|
1261
|
+
# for L...; types (needed for SAM interface detection).
|
|
1262
|
+
# e.g., "(Lkonpeito/canvas/KCanvas$MouseCallback;)V" -> ["konpeito/canvas/KCanvas$MouseCallback"]
|
|
1263
|
+
def parse_descriptor_raw_params(descriptor)
|
|
1264
|
+
return [] unless descriptor =~ /\A\(([^)]*)\)/
|
|
1265
|
+
|
|
1266
|
+
params_str = ::Regexp.last_match(1)
|
|
1267
|
+
types = []
|
|
1268
|
+
i = 0
|
|
1269
|
+
while i < params_str.length
|
|
1270
|
+
case params_str[i]
|
|
1271
|
+
when "I", "J", "D", "F", "Z", "B", "C", "S"
|
|
1272
|
+
types << params_str[i]
|
|
1273
|
+
i += 1
|
|
1274
|
+
when "L"
|
|
1275
|
+
semi = params_str.index(";", i)
|
|
1276
|
+
class_name = params_str[(i + 1)...semi]
|
|
1277
|
+
types << class_name
|
|
1278
|
+
i = semi + 1
|
|
1279
|
+
when "["
|
|
1280
|
+
# Array type: skip dimension prefix
|
|
1281
|
+
types << :array
|
|
1282
|
+
i += 1
|
|
1283
|
+
# Skip element type
|
|
1284
|
+
if params_str[i] == "L"
|
|
1285
|
+
semi = params_str.index(";", i)
|
|
1286
|
+
i = semi + 1
|
|
1287
|
+
else
|
|
1288
|
+
i += 1
|
|
1289
|
+
end
|
|
1290
|
+
else
|
|
1291
|
+
types << :unknown
|
|
1292
|
+
i += 1
|
|
1293
|
+
end
|
|
1294
|
+
end
|
|
1295
|
+
types
|
|
1296
|
+
end
|
|
1297
|
+
|
|
1298
|
+
# Parse a cfunc method (with %a{cfunc} or %a{cfunc: "name"})
|
|
1299
|
+
def parse_cfunc_method(context_name, member, cfunc_ann)
|
|
1300
|
+
overload = member.overloads.first
|
|
1301
|
+
return unless overload
|
|
1302
|
+
|
|
1303
|
+
func_type = overload.method_type.type
|
|
1304
|
+
c_func_name = cfunc_ann[:c_name] || member.name.to_s
|
|
1305
|
+
|
|
1306
|
+
param_types = func_type.required_positionals.map do |param|
|
|
1307
|
+
parse_cfunc_type(rbs_type_to_string(param.type))
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
return_type = parse_cfunc_type(rbs_type_to_string(func_type.return_type))
|
|
1311
|
+
|
|
1312
|
+
cfunc_type = Types::CFuncType.new(c_func_name, param_types, return_type)
|
|
1313
|
+
singleton = member.kind == :singleton
|
|
1314
|
+
key = singleton ? :"#{context_name}.#{member.name}" : :"#{context_name}##{member.name}"
|
|
1315
|
+
@cfunc_methods[key] = cfunc_type
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
# Parse a native method from RBS AST
|
|
1319
|
+
def parse_native_method(member, class_name)
|
|
1320
|
+
overload = member.overloads.first
|
|
1321
|
+
return nil unless overload
|
|
1322
|
+
|
|
1323
|
+
func_type = overload.method_type.type
|
|
1324
|
+
param_types = []
|
|
1325
|
+
param_names = []
|
|
1326
|
+
|
|
1327
|
+
func_type.required_positionals.each do |param|
|
|
1328
|
+
param_types << convert_to_native_return_type(rbs_type_to_string(param.type), class_name)
|
|
1329
|
+
param_names << (param.name || :"arg#{param_names.size}")
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
return_type = convert_to_native_return_type(rbs_type_to_string(func_type.return_type), class_name)
|
|
1333
|
+
|
|
1334
|
+
Types::NativeMethodType.new(param_types, return_type, param_names: param_names)
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
# Parse a SIMD method from RBS AST
|
|
1338
|
+
def parse_simd_method(member, class_name)
|
|
1339
|
+
overload = member.overloads.first
|
|
1340
|
+
return nil unless overload
|
|
1341
|
+
|
|
1342
|
+
func_type = overload.method_type.type
|
|
1343
|
+
param_types = []
|
|
1344
|
+
param_names = []
|
|
1345
|
+
|
|
1346
|
+
func_type.required_positionals.each do |param|
|
|
1347
|
+
type_str = rbs_type_to_string(param.type)
|
|
1348
|
+
param_types << parse_simd_type(type_str, class_name)
|
|
1349
|
+
param_names << (param.name || :"arg#{param_names.size}")
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
return_type = parse_simd_type(rbs_type_to_string(func_type.return_type), class_name)
|
|
1353
|
+
|
|
1354
|
+
Types::NativeMethodType.new(param_types, return_type, param_names: param_names)
|
|
1355
|
+
end
|
|
1356
|
+
|
|
1357
|
+
# Parse extern method parameters
|
|
1358
|
+
def parse_extern_params(func_type)
|
|
1359
|
+
func_type.required_positionals.map do |param|
|
|
1360
|
+
parse_extern_type(rbs_type_to_string(param.type))
|
|
1361
|
+
end
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
# ============================================================
|
|
1365
|
+
# Type Conversion Helpers
|
|
1366
|
+
# ============================================================
|
|
1367
|
+
|
|
1368
|
+
# Convert RBS type to string representation
|
|
1369
|
+
def rbs_type_to_string(type)
|
|
1370
|
+
case type
|
|
1371
|
+
when RBS::Types::ClassInstance
|
|
1372
|
+
base_name = type.name.name.to_s
|
|
1373
|
+
if type.args.empty?
|
|
1374
|
+
base_name
|
|
1375
|
+
else
|
|
1376
|
+
# Handle generic types: NativeHash[String, Integer] -> "NativeHash[String, Integer]"
|
|
1377
|
+
args_str = type.args.map { |a| rbs_type_to_string(a) }.join(", ")
|
|
1378
|
+
"#{base_name}[#{args_str}]"
|
|
1379
|
+
end
|
|
1380
|
+
when RBS::Types::Bases::Bool
|
|
1381
|
+
"Bool"
|
|
1382
|
+
when RBS::Types::Bases::Void
|
|
1383
|
+
"void"
|
|
1384
|
+
when RBS::Types::Bases::Nil
|
|
1385
|
+
"nil"
|
|
1386
|
+
when RBS::Types::Optional
|
|
1387
|
+
rbs_type_to_string(type.type) + "?"
|
|
1388
|
+
when RBS::Types::Literal
|
|
1389
|
+
# Handle literal types (e.g., 4 in StaticArray[Float, 4])
|
|
1390
|
+
type.literal.to_s
|
|
1391
|
+
else
|
|
1392
|
+
type.to_s
|
|
1393
|
+
end
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
# Convert RBS type to native field type
|
|
1397
|
+
def convert_rbs_type_to_native_field(type)
|
|
1398
|
+
type_str = rbs_type_to_string(type)
|
|
1399
|
+
is_optional = type.is_a?(RBS::Types::Optional)
|
|
1400
|
+
base_type_str = type_str.chomp("?")
|
|
1401
|
+
|
|
1402
|
+
convert_to_native_field_type(base_type_str, is_optional)
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
# Convert RBS type string to native field type
|
|
1406
|
+
def convert_to_native_field_type(type_str, is_optional = false)
|
|
1407
|
+
case type_str
|
|
1408
|
+
when "Float", "Float64" then :Float64
|
|
1409
|
+
when "Integer", "Int64" then :Int64
|
|
1410
|
+
when "Bool", "bool" then :Bool
|
|
1411
|
+
when "String" then :String
|
|
1412
|
+
when "Array" then :Array
|
|
1413
|
+
when "Hash" then :Hash
|
|
1414
|
+
when "Object", "untyped" then :Object
|
|
1415
|
+
else
|
|
1416
|
+
# Could be a NativeClass type
|
|
1417
|
+
if is_optional
|
|
1418
|
+
# Optional NativeClass = reference (stored as VALUE), can be nil
|
|
1419
|
+
{ ref: type_str.to_sym }
|
|
1420
|
+
else
|
|
1421
|
+
# Non-optional = embedded (stored as struct)
|
|
1422
|
+
type_str.to_sym
|
|
1423
|
+
end
|
|
1424
|
+
end
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
# Convert RBS type string to native method return/param type
|
|
1428
|
+
def convert_to_native_return_type(type_str, class_name)
|
|
1429
|
+
case type_str
|
|
1430
|
+
when "Float", "Float64" then :Float64
|
|
1431
|
+
when "Integer", "Int64" then :Int64
|
|
1432
|
+
when "void", "Void", "nil" then :Void
|
|
1433
|
+
when class_name.to_s then :Self
|
|
1434
|
+
else
|
|
1435
|
+
type_str.to_sym
|
|
1436
|
+
end
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1439
|
+
# Convert type string to cfunc type symbol
|
|
1440
|
+
def parse_cfunc_type(type_str)
|
|
1441
|
+
case type_str.strip
|
|
1442
|
+
when "Float" then :Float
|
|
1443
|
+
when "Integer" then :Integer
|
|
1444
|
+
when "String" then :String
|
|
1445
|
+
when "Bool", "bool" then :Bool
|
|
1446
|
+
when "void", "nil" then :void
|
|
1447
|
+
else type_str.to_sym
|
|
1448
|
+
end
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
# Convert type string to extern type symbol
|
|
1452
|
+
def parse_extern_type(type_str)
|
|
1453
|
+
case type_str.strip
|
|
1454
|
+
when "Float" then :Float
|
|
1455
|
+
when "Integer" then :Integer
|
|
1456
|
+
when "String" then :String
|
|
1457
|
+
when "Bool", "bool" then :Bool
|
|
1458
|
+
when "Array" then :Array
|
|
1459
|
+
when "Hash" then :Hash
|
|
1460
|
+
when "void", "nil" then :void
|
|
1461
|
+
else :ptr # Unknown types are treated as opaque pointers
|
|
1462
|
+
end
|
|
1463
|
+
end
|
|
1464
|
+
|
|
1465
|
+
# Parse extern class return type
|
|
1466
|
+
def parse_extern_return_type(type_str, class_name)
|
|
1467
|
+
case type_str
|
|
1468
|
+
when class_name.to_s then :ptr # Return Self = return opaque pointer
|
|
1469
|
+
when "void", "nil" then :void
|
|
1470
|
+
else parse_extern_type(type_str)
|
|
1471
|
+
end
|
|
1472
|
+
end
|
|
1473
|
+
|
|
1474
|
+
# Convert type string to SIMD type symbol
|
|
1475
|
+
def parse_simd_type(type_str, class_name)
|
|
1476
|
+
case type_str
|
|
1477
|
+
when class_name.to_s then :Self
|
|
1478
|
+
when "Float", "Float64" then :Float64
|
|
1479
|
+
when "void", "nil" then :Void
|
|
1480
|
+
else type_str.to_sym
|
|
1481
|
+
end
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
# Check if class is a built-in type that should be skipped
|
|
1485
|
+
def builtin_type?(class_name)
|
|
1486
|
+
name_str = class_name.to_s
|
|
1487
|
+
# Extract base name (before '[' if generic)
|
|
1488
|
+
base_name = name_str.split("[").first
|
|
1489
|
+
|
|
1490
|
+
# Check for generic built-in types
|
|
1491
|
+
%w[NativeArray NativeHash StaticArray Slice ByteBuffer ByteSlice StringBuffer NativeString].include?(base_name) ||
|
|
1492
|
+
# Legacy class name encoding patterns (backward compatibility)
|
|
1493
|
+
name_str.match?(/\AStaticArray\d+(Float|Int)\z/) ||
|
|
1494
|
+
name_str.match?(/\ASlice(Int64|Float64)\z/) ||
|
|
1495
|
+
name_str.match?(/\ANativeHash(String|Symbol|Integer)(Integer|Float|Bool|String|Object|Array|Hash|\w+)\z/)
|
|
1496
|
+
end
|
|
1497
|
+
|
|
1498
|
+
# Parse generic native type from RBS ClassInstance
|
|
1499
|
+
# Returns hash with type info, or nil if not a generic native type
|
|
1500
|
+
def parse_generic_native_type(type)
|
|
1501
|
+
return nil unless type.is_a?(RBS::Types::ClassInstance)
|
|
1502
|
+
return nil if type.args.empty?
|
|
1503
|
+
|
|
1504
|
+
base_name = type.name.name.to_s
|
|
1505
|
+
args = type.args
|
|
1506
|
+
|
|
1507
|
+
case base_name
|
|
1508
|
+
when "NativeHash"
|
|
1509
|
+
return nil unless args.size == 2
|
|
1510
|
+
{
|
|
1511
|
+
type: :native_hash,
|
|
1512
|
+
key_type: resolve_generic_type_arg(args[0]),
|
|
1513
|
+
value_type: resolve_generic_type_arg(args[1])
|
|
1514
|
+
}
|
|
1515
|
+
when "NativeArray"
|
|
1516
|
+
return nil unless args.size == 1
|
|
1517
|
+
{
|
|
1518
|
+
type: :native_array,
|
|
1519
|
+
element_type: resolve_generic_type_arg(args[0])
|
|
1520
|
+
}
|
|
1521
|
+
when "StaticArray"
|
|
1522
|
+
return nil unless args.size == 2
|
|
1523
|
+
{
|
|
1524
|
+
type: :static_array,
|
|
1525
|
+
element_type: resolve_generic_type_arg(args[0]),
|
|
1526
|
+
size: resolve_literal_arg(args[1])
|
|
1527
|
+
}
|
|
1528
|
+
when "Slice"
|
|
1529
|
+
return nil unless args.size == 1
|
|
1530
|
+
{
|
|
1531
|
+
type: :slice,
|
|
1532
|
+
element_type: resolve_generic_type_arg(args[0])
|
|
1533
|
+
}
|
|
1534
|
+
else
|
|
1535
|
+
nil
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
# Resolve a generic type argument to internal type symbol
|
|
1540
|
+
def resolve_generic_type_arg(arg)
|
|
1541
|
+
case arg
|
|
1542
|
+
when RBS::Types::ClassInstance
|
|
1543
|
+
name = arg.name.name.to_s
|
|
1544
|
+
case name
|
|
1545
|
+
when "Integer", "Int64" then :Integer
|
|
1546
|
+
when "Float", "Float64" then :Float
|
|
1547
|
+
when "Bool", "bool" then :Bool
|
|
1548
|
+
when "String" then :String
|
|
1549
|
+
when "Symbol" then :Symbol
|
|
1550
|
+
when "Array" then :Array
|
|
1551
|
+
when "Hash" then :Hash
|
|
1552
|
+
when "Object" then :Object
|
|
1553
|
+
else
|
|
1554
|
+
# Could be a NativeClass type
|
|
1555
|
+
name.to_sym
|
|
1556
|
+
end
|
|
1557
|
+
when RBS::Types::Bases::Bool
|
|
1558
|
+
:Bool
|
|
1559
|
+
else
|
|
1560
|
+
arg.to_s.to_sym
|
|
1561
|
+
end
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
# Resolve a literal argument (for StaticArray size)
|
|
1565
|
+
def resolve_literal_arg(arg)
|
|
1566
|
+
case arg
|
|
1567
|
+
when RBS::Types::Literal
|
|
1568
|
+
arg.literal
|
|
1569
|
+
when RBS::Types::ClassInstance
|
|
1570
|
+
# Might be a constant reference, try to parse as integer
|
|
1571
|
+
arg.name.name.to_s.to_i
|
|
1572
|
+
else
|
|
1573
|
+
arg.to_s.to_i
|
|
1574
|
+
end
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1577
|
+
# Check if a superclass has a field (for accessor detection)
|
|
1578
|
+
def will_inherit_field?(superclass_name, field_name)
|
|
1579
|
+
parent = @native_classes[superclass_name]
|
|
1580
|
+
return false unless parent
|
|
1581
|
+
return true if parent.fields.key?(field_name)
|
|
1582
|
+
return false unless parent.superclass
|
|
1583
|
+
|
|
1584
|
+
will_inherit_field?(parent.superclass, field_name)
|
|
1585
|
+
end
|
|
1586
|
+
|
|
1587
|
+
# Validate that all superclasses and embedded types exist
|
|
1588
|
+
def validate_native_inheritance
|
|
1589
|
+
@native_classes.each do |class_name, class_type|
|
|
1590
|
+
# Set registry for embedded type resolution
|
|
1591
|
+
class_type.native_class_registry = @native_classes
|
|
1592
|
+
|
|
1593
|
+
# Validate superclass
|
|
1594
|
+
if class_type.superclass && !@native_classes.key?(class_type.superclass)
|
|
1595
|
+
raise Error, "NativeClass #{class_name} inherits from unknown class #{class_type.superclass}"
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
# Validate embedded and reference NativeClass field types
|
|
1599
|
+
class_type.fields.each do |field_name, field_type|
|
|
1600
|
+
next if Types::NativeClassType::ALLOWED_PRIMITIVE_TYPES.include?(field_type)
|
|
1601
|
+
next if Types::NativeClassType::RUBY_OBJECT_TYPES.include?(field_type)
|
|
1602
|
+
|
|
1603
|
+
# Check for reference types (Hash with :ref key)
|
|
1604
|
+
if field_type.is_a?(Hash) && field_type[:ref]
|
|
1605
|
+
ref_class = field_type[:ref]
|
|
1606
|
+
unless @native_classes.key?(ref_class)
|
|
1607
|
+
raise Error, "NativeClass #{class_name} has field '#{field_name}' referencing unknown type #{ref_class}"
|
|
1608
|
+
end
|
|
1609
|
+
next
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
# Embedded NativeClass
|
|
1613
|
+
unless @native_classes.key?(field_type)
|
|
1614
|
+
raise Error, "NativeClass #{class_name} has field '#{field_name}' with unknown type #{field_type}"
|
|
1615
|
+
end
|
|
1616
|
+
end
|
|
1617
|
+
end
|
|
1618
|
+
end
|
|
1619
|
+
end
|
|
1620
|
+
end
|
|
1621
|
+
end
|