type-guessr 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -0
  3. data/exe/type-guessr +30 -0
  4. data/lib/ruby_lsp/type_guessr/addon.rb +20 -45
  5. data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +352 -0
  6. data/lib/ruby_lsp/type_guessr/constants.rb +39 -0
  7. data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +27 -22
  8. data/lib/ruby_lsp/type_guessr/debug_server.rb +20 -17
  9. data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
  10. data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
  11. data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
  12. data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
  13. data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
  14. data/lib/ruby_lsp/type_guessr/hover.rb +129 -261
  15. data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
  16. data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +613 -277
  17. data/lib/ruby_lsp/type_guessr/type_inferrer.rb +8 -105
  18. data/lib/type-guessr.rb +3 -11
  19. data/lib/type_guessr/core/cache/gem_dependency_resolver.rb +113 -0
  20. data/lib/type_guessr/core/cache/gem_signature_cache.rb +98 -0
  21. data/lib/type_guessr/core/cache/gem_signature_extractor.rb +87 -0
  22. data/lib/type_guessr/core/cache.rb +5 -0
  23. data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +19 -34
  24. data/lib/type_guessr/core/converter/call_converter.rb +161 -0
  25. data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
  26. data/lib/type_guessr/core/converter/context.rb +144 -0
  27. data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
  28. data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
  29. data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
  30. data/lib/type_guessr/core/converter/prism_converter.rb +154 -1613
  31. data/lib/type_guessr/core/converter/rbs_converter.rb +35 -14
  32. data/lib/type_guessr/core/converter/registration.rb +100 -0
  33. data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
  34. data/lib/type_guessr/core/converter.rb +4 -0
  35. data/lib/type_guessr/core/index/location_index.rb +32 -0
  36. data/lib/type_guessr/core/index.rb +3 -0
  37. data/lib/type_guessr/core/inference/resolver.rb +516 -349
  38. data/lib/type_guessr/core/inference.rb +4 -0
  39. data/lib/type_guessr/core/ir/nodes.rb +362 -103
  40. data/lib/type_guessr/core/ir.rb +3 -0
  41. data/lib/type_guessr/core/logger.rb +6 -13
  42. data/lib/type_guessr/core/node_context_helper.rb +126 -0
  43. data/lib/type_guessr/core/node_key_generator.rb +31 -0
  44. data/lib/type_guessr/core/registry/class_variable_registry.rb +63 -0
  45. data/lib/type_guessr/core/registry/instance_variable_registry.rb +84 -0
  46. data/lib/type_guessr/core/registry/method_registry.rb +65 -38
  47. data/lib/type_guessr/core/registry/signature_registry.rb +543 -0
  48. data/lib/type_guessr/core/registry.rb +6 -0
  49. data/lib/type_guessr/core/signature_builder.rb +39 -0
  50. data/lib/type_guessr/core/type_serializer.rb +96 -0
  51. data/lib/type_guessr/core/type_simplifier.rb +15 -12
  52. data/lib/type_guessr/core/types.rb +250 -32
  53. data/lib/type_guessr/core.rb +29 -0
  54. data/lib/type_guessr/mcp/file_watcher.rb +87 -0
  55. data/lib/type_guessr/mcp/server.rb +463 -0
  56. data/lib/type_guessr/mcp/standalone_runtime.rb +213 -0
  57. data/lib/type_guessr/version.rb +1 -1
  58. metadata +57 -8
  59. data/lib/type_guessr/core/rbs_provider.rb +0 -304
  60. data/lib/type_guessr/core/registry/variable_registry.rb +0 -87
  61. data/lib/type_guessr/core/signature_provider.rb +0 -101
@@ -0,0 +1,543 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require_relative "../types"
5
+ require_relative "../logger"
6
+ require_relative "../converter"
7
+ require_relative "../type_serializer"
8
+
9
+ module TypeGuessr
10
+ module Core
11
+ module Registry
12
+ # Preloads stdlib RBS signatures and provides O(1) hash lookup
13
+ # Singleton to ensure RBS is loaded only once across all usages
14
+ class SignatureRegistry
15
+ class << self
16
+ attr_accessor :instance
17
+ end
18
+
19
+ # Represents a method signature entry from RBS
20
+ # Handles overload resolution and type conversion
21
+ class MethodEntry
22
+ # @param method_types [Array<RBS::MethodType>] RBS method type definitions
23
+ # @param converter [Converter::RBSConverter] RBS to internal type converter
24
+ def initialize(method_types, converter)
25
+ @method_types = method_types
26
+ @converter = converter
27
+ end
28
+
29
+ # Get the return type with overload resolution
30
+ # @param arg_types [Array<Types::Type>] argument types for overload matching
31
+ # @return [Types::Type] the return type
32
+ def return_type(arg_types = [])
33
+ best_match = find_best_overload(arg_types)
34
+ @converter.convert(best_match.type.return_type)
35
+ end
36
+
37
+ # Get block parameter types for this method
38
+ # @return [Array<Types::Type>] array of block parameter types (empty if no block)
39
+ def block_param_types
40
+ return @block_param_types if defined?(@block_param_types)
41
+
42
+ @block_param_types = compute_block_param_types
43
+ end
44
+
45
+ # Get method-level type parameter names from the best matching overload
46
+ # @param arg_types [Array<Types::Type>] argument types for overload matching
47
+ # @return [Array<Symbol>] type parameter names (e.g., [:U], [:X], [])
48
+ def type_params(arg_types = [])
49
+ find_best_overload(arg_types).type_params.map(&:name)
50
+ end
51
+
52
+ # Get the type variable used as the block return type
53
+ # Returns the method-level type variable that appears in the block's return type,
54
+ # allowing Resolver to substitute it with the actual block body type.
55
+ # @param arg_types [Array<Types::Type>] argument types for overload matching
56
+ # @return [Symbol, nil] type variable name (e.g., :U, :X) or nil if no block/no type var
57
+ def block_return_type_var(arg_types = [])
58
+ best = find_best_overload(arg_types)
59
+ return nil unless best.block
60
+
61
+ method_type_param_names = best.type_params.to_set(&:name)
62
+ return nil if method_type_param_names.empty?
63
+
64
+ extract_type_var_from_return(best.block.type.return_type, method_type_param_names)
65
+ end
66
+
67
+ # Get raw method signatures for display
68
+ # @return [Array<RBS::MethodType>] raw RBS method types
69
+ def signatures
70
+ @method_types
71
+ end
72
+
73
+ # Get formatted signature strings for display
74
+ # @return [Array<String>] human-readable method signatures
75
+ def signature_strings
76
+ @method_types.map(&:to_s)
77
+ end
78
+
79
+ private def compute_block_param_types
80
+ # Find the signature with a block
81
+ sig_with_block = @method_types.find(&:block)
82
+ return [] unless sig_with_block
83
+
84
+ extract_block_param_types(sig_with_block)
85
+ end
86
+
87
+ private def extract_block_param_types(method_type)
88
+ return [] unless method_type.block
89
+
90
+ block_func = method_type.block.type
91
+ return [] unless block_func.is_a?(RBS::Types::Function)
92
+
93
+ block_func.required_positionals.flat_map do |param|
94
+ # Handle Tuple types (e.g., [K, V] in Hash#each) by flattening
95
+ if param.type.is_a?(RBS::Types::Tuple)
96
+ param.type.types.map { |t| @converter.convert(t) }
97
+ else
98
+ [@converter.convert(param.type)]
99
+ end
100
+ end
101
+ end
102
+
103
+ # Extract a method-level type variable from the block return type
104
+ # Walks the RBS type tree to find a Variable whose name is in the method's type_params.
105
+ # Handles Union types like `nil | false | U` (filter_map).
106
+ # @param rbs_type [RBS::Types::t] the block return type from RBS
107
+ # @param method_type_param_names [Set<Symbol>] method-level type param names
108
+ # @return [Symbol, nil] the type variable name, or nil if not found
109
+ private def extract_type_var_from_return(rbs_type, method_type_param_names)
110
+ case rbs_type
111
+ when RBS::Types::Variable
112
+ method_type_param_names.include?(rbs_type.name) ? rbs_type.name : nil
113
+ when RBS::Types::Union
114
+ # Search union members for a type variable (e.g., nil | false | U)
115
+ rbs_type.types.each do |t|
116
+ found = extract_type_var_from_return(t, method_type_param_names)
117
+ return found if found
118
+ end
119
+ nil
120
+ end
121
+ end
122
+
123
+ # Find the best matching overload for given argument types
124
+ # @param arg_types [Array<Types::Type>] argument types
125
+ # @return [RBS::MethodType] best matching method type (first if no match)
126
+ private def find_best_overload(arg_types)
127
+ return @method_types.first if arg_types.empty?
128
+
129
+ # Score each overload
130
+ scored = @method_types.map do |method_type|
131
+ score = calculate_overload_score(method_type, arg_types)
132
+ [method_type, score]
133
+ end
134
+
135
+ # Return best scoring overload, or first if all scores are 0
136
+ best = scored.max_by { |_mt, score| score }
137
+ best[1].positive? ? best[0] : @method_types.first
138
+ end
139
+
140
+ # Calculate match score for an overload
141
+ private def calculate_overload_score(method_type, arg_types)
142
+ func = method_type.type
143
+ return 0 unless func.is_a?(RBS::Types::Function)
144
+
145
+ required = func.required_positionals
146
+ optional = func.optional_positionals
147
+ rest = func.rest_positionals
148
+
149
+ # Check argument count
150
+ min_args = required.size
151
+ max_args = rest ? Float::INFINITY : required.size + optional.size
152
+ return 0 unless arg_types.size.between?(min_args, max_args)
153
+
154
+ # Score each argument match
155
+ score = 0
156
+ arg_types.each_with_index do |arg_type, i|
157
+ param = if i < required.size
158
+ required[i]
159
+ elsif i < required.size + optional.size
160
+ optional[i - required.size]
161
+ else
162
+ rest
163
+ end
164
+
165
+ break unless param
166
+
167
+ score += type_match_score(arg_type, param.type)
168
+ end
169
+
170
+ score
171
+ end
172
+
173
+ # Calculate match score between our type and RBS parameter type
174
+ private def type_match_score(our_type, rbs_type)
175
+ case rbs_type
176
+ when RBS::Types::ClassInstance
177
+ class_name = rbs_type.name.to_s.delete_prefix("::")
178
+ return 2 if types_match_class?(our_type, class_name)
179
+
180
+ 0
181
+ when RBS::Types::Union
182
+ max_score = rbs_type.types.map { |t| type_match_score(our_type, t) }.max || 0
183
+ max_score.positive? ? 1 : 0
184
+ else
185
+ # Unknown RBS type - give weak match to avoid penalizing
186
+ 1
187
+ end
188
+ end
189
+
190
+ private def types_match_class?(our_type, class_name)
191
+ case our_type
192
+ when Types::ClassInstance
193
+ our_type.name == class_name
194
+ when Types::ArrayType, Types::TupleType
195
+ class_name == "Array"
196
+ when Types::HashShape
197
+ class_name == "Hash"
198
+ else
199
+ false
200
+ end
201
+ end
202
+ end
203
+
204
+ # Represents a cached gem method entry (pre-inferred, no RBS)
205
+ # Implements the same interface as MethodEntry for transparent lookup
206
+ class GemMethodEntry
207
+ attr_reader :params
208
+
209
+ def initialize(return_type, params = [], skip_stdlib_rbs: false)
210
+ @return_type = return_type
211
+ @params = params
212
+ @skip_stdlib_rbs = skip_stdlib_rbs
213
+ end
214
+
215
+ def skip_stdlib_rbs? = @skip_stdlib_rbs
216
+
217
+ def return_type(_arg_types = [])
218
+ @return_type
219
+ end
220
+
221
+ def block_param_types
222
+ []
223
+ end
224
+
225
+ def type_params(_arg_types = [])
226
+ []
227
+ end
228
+
229
+ def block_return_type_var(_arg_types = [])
230
+ nil
231
+ end
232
+
233
+ def signatures
234
+ []
235
+ end
236
+
237
+ # Get formatted signature strings for display
238
+ # @return [Array<String>] human-readable method signatures
239
+ def signature_strings
240
+ param_str = @params.join(", ")
241
+ ["(#{param_str}) -> #{@return_type}"]
242
+ end
243
+ end
244
+
245
+ attr_accessor :code_index
246
+ attr_writer :on_demand_inferrer # ->(class_name, method_name, kind) { ... }
247
+
248
+ def initialize(code_index: nil)
249
+ @code_index = code_index
250
+ @instance_methods = {} # { "String" => { "upcase" => MethodEntry } }
251
+ @class_methods = {} # { "File" => { "read" => MethodEntry } }
252
+ @converter = Converter::RBSConverter.new
253
+ @preloaded = false
254
+ @on_demand_inferrer = nil
255
+ end
256
+
257
+ # Preload stdlib RBS signatures
258
+ # @return [self]
259
+ def preload
260
+ return self if @preloaded
261
+
262
+ load_stdlib_rbs
263
+ @preloaded = true
264
+ self
265
+ end
266
+
267
+ # Check if preloading is complete
268
+ # @return [Boolean]
269
+ def preloaded?
270
+ @preloaded
271
+ end
272
+
273
+ # Look up instance method entry
274
+ # Falls back to ancestor chain traversal when code_index is available
275
+ # @param class_name [String] the class name
276
+ # @param method_name [String] the method name
277
+ # @return [MethodEntry, nil] method entry or nil if not found
278
+ def lookup(class_name, method_name)
279
+ result = @instance_methods.dig(class_name, method_name)
280
+ return result if result
281
+
282
+ return nil unless @code_index
283
+
284
+ @code_index.ancestors_of(class_name).each do |ancestor|
285
+ next if ancestor == class_name
286
+
287
+ result = @instance_methods.dig(ancestor, method_name)
288
+ return result if result
289
+ end
290
+ nil
291
+ end
292
+
293
+ # Look up class method entry
294
+ # Falls back to ancestor chain traversal when code_index is available
295
+ # @param class_name [String] the class name
296
+ # @param method_name [String] the method name
297
+ # @return [MethodEntry, nil] method entry or nil if not found
298
+ def lookup_class_method(class_name, method_name)
299
+ result = @class_methods.dig(class_name, method_name)
300
+ return result if result
301
+ return nil unless @code_index
302
+
303
+ @code_index.ancestors_of(class_name).each do |ancestor|
304
+ next if ancestor == class_name
305
+
306
+ result = @class_methods.dig(ancestor, method_name)
307
+ return result if result
308
+ end
309
+ nil
310
+ end
311
+
312
+ # Get method return type (convenience method matching old SignatureProvider API)
313
+ # Triggers on-demand inference if the entry has Unguessed return type.
314
+ # @param class_name [String] the class name
315
+ # @param method_name [String] the method name
316
+ # @param arg_types [Array<Types::Type>] argument types for overload matching
317
+ # @return [Types::Type] the return type (Unknown if not found)
318
+ def get_method_return_type(class_name, method_name, arg_types = [])
319
+ entry = lookup(class_name, method_name)
320
+ return Types::Unknown.instance unless entry
321
+
322
+ result = entry.return_type(arg_types)
323
+ if result.is_a?(Types::Unguessed)
324
+ try_on_demand_inference(class_name, method_name, :instance)
325
+ entry = lookup(class_name, method_name)
326
+ return Types::Unknown.instance unless entry
327
+
328
+ result = entry.return_type(arg_types)
329
+ return Types::Unknown.instance if result.is_a?(Types::Unguessed)
330
+ end
331
+ result
332
+ end
333
+
334
+ # Get class method return type (convenience method matching old SignatureProvider API)
335
+ # Triggers on-demand inference if the entry has Unguessed return type.
336
+ # @param class_name [String] the class name
337
+ # @param method_name [String] the method name
338
+ # @param arg_types [Array<Types::Type>] argument types for overload matching
339
+ # @return [Types::Type] the return type (Unknown if not found)
340
+ def get_class_method_return_type(class_name, method_name, arg_types = [])
341
+ entry = lookup_class_method(class_name, method_name)
342
+ return Types::Unknown.instance unless entry
343
+
344
+ result = entry.return_type(arg_types)
345
+ if result.is_a?(Types::Unguessed)
346
+ try_on_demand_inference(class_name, method_name, :class)
347
+ entry = lookup_class_method(class_name, method_name)
348
+ return Types::Unknown.instance unless entry
349
+
350
+ result = entry.return_type(arg_types)
351
+ return Types::Unknown.instance if result.is_a?(Types::Unguessed)
352
+ end
353
+ result
354
+ end
355
+
356
+ # Get block parameter types for a method
357
+ # @param class_name [String] the receiver class name
358
+ # @param method_name [String] the method name
359
+ # @return [Array<Types::Type>] array of block parameter types (empty if no block)
360
+ def get_block_param_types(class_name, method_name)
361
+ entry = lookup(class_name, method_name)
362
+ return [] unless entry
363
+
364
+ entry.block_param_types
365
+ end
366
+
367
+ # Get method signatures for hover display
368
+ # @param class_name [String] the class name
369
+ # @param method_name [String] the method name
370
+ # @return [Array<Signature>] wrapped signatures for compatibility
371
+ def get_method_signatures(class_name, method_name)
372
+ entry = lookup(class_name, method_name)
373
+ return [] unless entry
374
+
375
+ entry.signatures.map { |mt| Signature.new(mt) }
376
+ end
377
+
378
+ # Get class method signatures for hover display
379
+ # @param class_name [String] the class name
380
+ # @param method_name [String] the method name
381
+ # @return [Array<Signature>] wrapped signatures for compatibility
382
+ def get_class_method_signatures(class_name, method_name)
383
+ entry = lookup_class_method(class_name, method_name)
384
+ return [] unless entry
385
+
386
+ entry.signatures.map { |mt| Signature.new(mt) }
387
+ end
388
+
389
+ # Register a single gem instance method
390
+ # Skips if RBS entry already exists (RBS takes priority).
391
+ # force: true overwrites existing GemMethodEntry (but never MethodEntry/RBS).
392
+ def register_gem_method(class_name, method_name, return_type, params = [], force: false)
393
+ @instance_methods[class_name] ||= {}
394
+
395
+ existing = @instance_methods[class_name][method_name]
396
+ if existing
397
+ return if existing.is_a?(MethodEntry)
398
+ return unless force
399
+ end
400
+
401
+ @instance_methods[class_name][method_name] = GemMethodEntry.new(return_type, params, skip_stdlib_rbs: force)
402
+ end
403
+
404
+ # Register a single gem class method
405
+ # Skips if RBS entry already exists (RBS takes priority).
406
+ # force: true overwrites existing GemMethodEntry (but never MethodEntry/RBS).
407
+ def register_gem_class_method(class_name, method_name, return_type, params = [], force: false)
408
+ @class_methods[class_name] ||= {}
409
+
410
+ existing = @class_methods[class_name][method_name]
411
+ if existing
412
+ return if existing.is_a?(MethodEntry)
413
+ return unless force
414
+ end
415
+
416
+ @class_methods[class_name][method_name] = GemMethodEntry.new(return_type, params, skip_stdlib_rbs: force)
417
+ end
418
+
419
+ # Bulk load gem cache data into the registry
420
+ # @param cache_data [Hash] { class_name => { method_name => { "return_type" => ..., "params" => [...] } } }
421
+ # @param kind [Symbol] :instance or :class
422
+ def load_gem_cache(cache_data, kind: :instance)
423
+ target = kind == :class ? @class_methods : @instance_methods
424
+
425
+ cache_data.each do |class_name, methods|
426
+ target[class_name] ||= {}
427
+ methods.each do |method_name, entry_data|
428
+ next if target[class_name].key?(method_name) # RBS priority
429
+
430
+ return_type = TypeSerializer.deserialize(entry_data["return_type"])
431
+ params = (entry_data["params"] || []).map do |p|
432
+ Types::ParamSignature.new(
433
+ name: p["name"].to_sym,
434
+ kind: p["kind"].to_sym,
435
+ type: TypeSerializer.deserialize(p["type"])
436
+ )
437
+ end
438
+ target[class_name][method_name] = GemMethodEntry.new(return_type, params)
439
+ end
440
+ end
441
+ end
442
+
443
+ # Replace Unguessed GemMethodEntry entries with inferred results.
444
+ # Only replaces entries that are GemMethodEntry with Unguessed return type.
445
+ # @param cache_data [Hash] { class_name => { method_name => { "return_type" => ..., "params" => [...] } } }
446
+ # @param kind [Symbol] :instance or :class
447
+ def replace_unguessed_entries(cache_data, kind: :instance)
448
+ target = kind == :class ? @class_methods : @instance_methods
449
+
450
+ cache_data.each do |class_name, methods|
451
+ next unless target[class_name]
452
+
453
+ methods.each do |method_name, entry_data|
454
+ existing = target[class_name][method_name]
455
+ next unless existing.is_a?(GemMethodEntry)
456
+ next unless existing.return_type.is_a?(Types::Unguessed)
457
+
458
+ return_type = TypeSerializer.deserialize(entry_data["return_type"])
459
+ params = (entry_data["params"] || []).map do |p|
460
+ Types::ParamSignature.new(
461
+ name: p["name"].to_sym,
462
+ kind: p["kind"].to_sym,
463
+ type: TypeSerializer.deserialize(p["type"])
464
+ )
465
+ end
466
+ target[class_name][method_name] = GemMethodEntry.new(return_type, params)
467
+ end
468
+ end
469
+ end
470
+
471
+ # Wrapper for RBS method type (for compatibility with existing code)
472
+ class Signature
473
+ attr_reader :method_type
474
+
475
+ def initialize(method_type)
476
+ @method_type = method_type
477
+ end
478
+ end
479
+
480
+ private def try_on_demand_inference(class_name, method_name, kind)
481
+ return unless @on_demand_inferrer
482
+
483
+ @on_demand_inferrer.call(class_name, method_name, kind)
484
+ end
485
+
486
+ private def load_stdlib_rbs
487
+ loader = RBS::EnvironmentLoader.new
488
+ env = RBS::Environment.from_loader(loader).resolve_type_names
489
+ builder = RBS::DefinitionBuilder.new(env: env)
490
+
491
+ # Collect type parameter names for generic classes (e.g., "Set" => [:A])
492
+ # This lets RBSConverter build ClassInstance with named type_params
493
+ class_type_params = {}
494
+ env.class_decls.each do |type_name, entry|
495
+ params = entry.type_params.map(&:name)
496
+ class_type_params[type_name.to_s.delete_prefix("::")] = params if params.any?
497
+ end
498
+ @converter = Converter::RBSConverter.new(class_type_params)
499
+
500
+ env.class_decls.each_key do |type_name|
501
+ load_class_definitions(type_name, builder)
502
+ end
503
+
504
+ env.class_alias_decls.each_value do |entry|
505
+ alias_name = entry.decl.new_name.to_s.delete_prefix("::")
506
+ original_name = entry.decl.old_name.to_s.delete_prefix("::")
507
+ @instance_methods[alias_name] ||= @instance_methods[original_name] if @instance_methods[original_name]
508
+ @class_methods[alias_name] ||= @class_methods[original_name] if @class_methods[original_name]
509
+ end
510
+ rescue StandardError => e
511
+ Logger.error("Failed to preload RBS environment", e)
512
+ end
513
+
514
+ private def load_class_definitions(type_name, builder)
515
+ class_name = type_name.to_s.delete_prefix("::")
516
+
517
+ # Load instance methods
518
+ begin
519
+ definition = builder.build_instance(type_name)
520
+ @instance_methods[class_name] = build_method_entries(definition)
521
+ rescue RBS::NoTypeFoundError, RBS::NoSuperclassFoundError, RBS::NoMixinFoundError
522
+ # Skip classes with missing dependencies
523
+ end
524
+
525
+ # Load class methods (singleton)
526
+ begin
527
+ definition = builder.build_singleton(type_name)
528
+ @class_methods[class_name] = build_method_entries(definition)
529
+ rescue RBS::NoTypeFoundError, RBS::NoSuperclassFoundError, RBS::NoMixinFoundError
530
+ # Skip classes with missing dependencies
531
+ end
532
+ end
533
+
534
+ private def build_method_entries(definition)
535
+ # RBS methods hash uses Symbol keys, but we use String keys for lookup
536
+ definition.methods.to_h do |method_name, method_def|
537
+ [method_name.to_s, MethodEntry.new(method_def.method_types, @converter)]
538
+ end
539
+ end
540
+ end
541
+ end
542
+ end
543
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry/method_registry"
4
+ require_relative "registry/instance_variable_registry"
5
+ require_relative "registry/class_variable_registry"
6
+ require_relative "registry/signature_registry"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types"
4
+
5
+ module TypeGuessr
6
+ module Core
7
+ # Builds MethodSignature from DefNode using Resolver for type inference.
8
+ # Extracts the param formatting + type inference logic that was previously
9
+ # embedded in the LSP hover layer, making it reusable across contexts.
10
+ class SignatureBuilder
11
+ def initialize(resolver)
12
+ @resolver = resolver
13
+ end
14
+
15
+ # @param def_node [IR::DefNode] Method definition node
16
+ # @return [Types::MethodSignature] Structured method signature
17
+ def build_from_def_node(def_node)
18
+ params = build_param_signatures(def_node.params)
19
+ return_type = @resolver.infer(def_node).type
20
+ Types::MethodSignature.new(params, return_type)
21
+ end
22
+
23
+ private def build_param_signatures(param_nodes)
24
+ return [] if param_nodes.nil? || param_nodes.empty?
25
+
26
+ param_nodes.map { |p| build_param_signature(p) }
27
+ end
28
+
29
+ private def build_param_signature(param_node)
30
+ type = @resolver.infer(param_node).type
31
+ Types::ParamSignature.new(
32
+ name: param_node.name,
33
+ kind: param_node.kind,
34
+ type: type
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types"
4
+
5
+ module TypeGuessr
6
+ module Core
7
+ # Converts Type objects to/from JSON-compatible Hashes.
8
+ # Used by GemSignatureCache for persisting inferred method signatures.
9
+ module TypeSerializer
10
+ module_function def serialize(type)
11
+ case type
12
+ when Types::Unguessed
13
+ { "_type" => "Unguessed" }
14
+ when Types::Unknown
15
+ { "_type" => "Unknown" }
16
+ when Types::ClassInstance
17
+ h = { "_type" => "ClassInstance", "name" => type.name }
18
+ h["type_params"] = type.type_params.to_h { |k, v| [k.to_s, serialize(v)] } if type.type_params&.any?
19
+ h
20
+ when Types::SingletonType
21
+ { "_type" => "SingletonType", "name" => type.name }
22
+ when Types::TupleType
23
+ { "_type" => "TupleType", "element_types" => type.element_types.map { |t| serialize(t) } }
24
+ when Types::ArrayType
25
+ { "_type" => "ArrayType", "element_type" => serialize(type.element_type) }
26
+ when Types::HashShape
27
+ { "_type" => "HashShape", "fields" => type.fields.transform_keys(&:to_s).transform_values { |v| serialize(v) } }
28
+ when Types::HashType
29
+ { "_type" => "HashType", "key_type" => serialize(type.key_type), "value_type" => serialize(type.value_type) }
30
+ when Types::RangeType
31
+ { "_type" => "RangeType", "element_type" => serialize(type.element_type) }
32
+ when Types::Union
33
+ { "_type" => "Union", "types" => type.types.map { |t| serialize(t) } }
34
+ when Types::TypeVariable
35
+ { "_type" => "TypeVariable", "name" => type.name.to_s }
36
+ when Types::SelfType
37
+ { "_type" => "SelfType" }
38
+ when Types::ForwardingArgs
39
+ { "_type" => "ForwardingArgs" }
40
+ when Types::MethodSignature
41
+ { "_type" => "MethodSignature",
42
+ "return_type" => serialize(type.return_type),
43
+ "params" => type.params.map { |p| serialize_param(p) } }
44
+ end
45
+ end
46
+
47
+ # Deserialize a Hash back to a Type object
48
+ # @param hash [Hash] JSON-compatible hash with "_type" discriminator
49
+ # @return [Types::Type] The deserialized type
50
+ # @raise [ArgumentError] if "_type" is unknown
51
+ module_function def deserialize(hash)
52
+ case hash["_type"]
53
+ when "Unguessed" then Types::Unguessed.instance
54
+ when "Unknown" then Types::Unknown.instance
55
+ when "ClassInstance"
56
+ type_params = hash["type_params"]&.to_h { |k, v| [k.to_sym, deserialize(v)] }
57
+ Types::ClassInstance.for(hash["name"], type_params)
58
+ when "SingletonType" then Types::SingletonType.new(hash["name"])
59
+ when "ArrayType" then Types::ArrayType.new(deserialize(hash["element_type"]))
60
+ when "TupleType" then Types::TupleType.new(hash["element_types"].map { |t| deserialize(t) })
61
+ when "HashType" then Types::HashType.new(deserialize(hash["key_type"]), deserialize(hash["value_type"]))
62
+ when "RangeType" then Types::RangeType.new(deserialize(hash["element_type"]))
63
+ when "HashShape" then Types::HashShape.new(hash["fields"].to_h { |k, v| [k.to_sym, deserialize(v)] })
64
+ when "Union" then Types::Union.new(hash["types"].map { |t| deserialize(t) })
65
+ when "TypeVariable" then Types::TypeVariable.new(hash["name"].to_sym)
66
+ when "SelfType" then Types::SelfType.instance
67
+ when "ForwardingArgs" then Types::ForwardingArgs.instance
68
+ when "MethodSignature" then deserialize_method_signature(hash)
69
+ else
70
+ raise ArgumentError, "Unknown type: #{hash["_type"]}"
71
+ end
72
+ end
73
+
74
+ # @param param [Types::ParamSignature]
75
+ # @return [Hash]
76
+ module_function def serialize_param(param)
77
+ { "name" => param.name.to_s, "kind" => param.kind.to_s, "type" => serialize(param.type) }
78
+ end
79
+
80
+ # @param hash [Hash]
81
+ # @return [Types::MethodSignature]
82
+ module_function def deserialize_method_signature(hash)
83
+ params = hash["params"].map do |p|
84
+ Types::ParamSignature.new(
85
+ name: p["name"].to_sym,
86
+ kind: p["kind"].to_sym,
87
+ type: deserialize(p["type"])
88
+ )
89
+ end
90
+ Types::MethodSignature.new(params, deserialize(hash["return_type"]))
91
+ end
92
+
93
+ private_class_method :serialize_param, :deserialize_method_signature
94
+ end
95
+ end
96
+ end