type-guessr 0.0.1

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.
@@ -0,0 +1,425 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module TypeGuessr
6
+ module Core
7
+ module Types
8
+ # Base class for all type representations
9
+ class Type
10
+ def ==(other)
11
+ eql?(other)
12
+ end
13
+
14
+ def eql?(other)
15
+ self.class == other.class
16
+ end
17
+
18
+ def hash
19
+ self.class.hash
20
+ end
21
+
22
+ # Substitute type variables with concrete types
23
+ # @param substitutions [Hash{Symbol => Type}] type variable substitutions
24
+ # @return [Type] the type with substitutions applied (self if no change)
25
+ def substitute(_substitutions)
26
+ self
27
+ end
28
+
29
+ # Get the RBS class name for this type
30
+ # Used for looking up method signatures in RBSProvider
31
+ # @return [String, nil] class name or nil if not applicable
32
+ def rbs_class_name
33
+ nil
34
+ end
35
+
36
+ # Get type variable substitutions for this type
37
+ # Used for substituting type variables in block parameters
38
+ # @return [Hash{Symbol => Type}] type variable substitutions (e.g., { Elem: Integer, K: Symbol, V: String })
39
+ def type_variable_substitutions
40
+ {}
41
+ end
42
+ end
43
+
44
+ # Unknown type - no information available
45
+ class Unknown < Type
46
+ include Singleton
47
+
48
+ def to_s
49
+ "untyped"
50
+ end
51
+ end
52
+
53
+ # ClassInstance - instance of a class
54
+ class ClassInstance < Type
55
+ attr_reader :name
56
+
57
+ def initialize(name)
58
+ super()
59
+ @name = name
60
+ end
61
+
62
+ def eql?(other)
63
+ super && @name == other.name
64
+ end
65
+
66
+ def hash
67
+ [self.class, @name].hash
68
+ end
69
+
70
+ def to_s
71
+ case @name
72
+ when "NilClass" then "nil"
73
+ when "TrueClass" then "true"
74
+ when "FalseClass" then "false"
75
+ else @name
76
+ end
77
+ end
78
+
79
+ def rbs_class_name
80
+ @name
81
+ end
82
+ end
83
+
84
+ # SingletonType - represents the class object itself (singleton class)
85
+ class SingletonType < Type
86
+ attr_reader :name
87
+
88
+ def initialize(name)
89
+ super()
90
+ @name = name
91
+ end
92
+
93
+ def eql?(other)
94
+ super && @name == other.name
95
+ end
96
+
97
+ def hash
98
+ [self.class, @name].hash
99
+ end
100
+
101
+ def to_s
102
+ "singleton(#{@name})"
103
+ end
104
+
105
+ def rbs_class_name
106
+ @name
107
+ end
108
+ end
109
+
110
+ # Union - union of multiple types
111
+ class Union < Type
112
+ attr_reader :types
113
+
114
+ def initialize(types, cutoff: RubyLsp::TypeGuessr::Config.union_cutoff)
115
+ super()
116
+ @types = normalize(types, cutoff)
117
+ end
118
+
119
+ def eql?(other)
120
+ return false unless super
121
+
122
+ # Compare as sets since order doesn't matter
123
+ @types.to_set == other.types.to_set
124
+ end
125
+
126
+ def hash
127
+ [self.class, @types.to_set].hash
128
+ end
129
+
130
+ def to_s
131
+ if bool_type?
132
+ "bool"
133
+ elsif optional_type?
134
+ "?#{non_nil_type}"
135
+ else
136
+ @types.map(&:to_s).sort.join(" | ")
137
+ end
138
+ end
139
+
140
+ def substitute(substitutions)
141
+ new_types = @types.map { |t| t.substitute(substitutions) }
142
+ return self if new_types.zip(@types).all? { |new_t, old_t| new_t.equal?(old_t) }
143
+
144
+ Union.new(new_types)
145
+ end
146
+
147
+ private
148
+
149
+ def bool_type?
150
+ return false unless @types.size == 2
151
+
152
+ has_true = @types.any? { |t| t.is_a?(ClassInstance) && t.name == "TrueClass" }
153
+ has_false = @types.any? { |t| t.is_a?(ClassInstance) && t.name == "FalseClass" }
154
+ has_true && has_false
155
+ end
156
+
157
+ def optional_type?
158
+ @types.size == 2 && @types.any? { |t| nil_type?(t) }
159
+ end
160
+
161
+ def nil_type?(type)
162
+ type.is_a?(ClassInstance) && type.name == "NilClass"
163
+ end
164
+
165
+ def non_nil_type
166
+ @types.find { |t| !nil_type?(t) }
167
+ end
168
+
169
+ def normalize(types, cutoff)
170
+ # Flatten nested unions
171
+ flattened = flatten_unions(types)
172
+
173
+ # Deduplicate
174
+ deduplicated = flattened.uniq
175
+
176
+ # Simplify to Unknown if Unknown is present (T | untyped = untyped)
177
+ filtered = simplify_if_unknown_present(deduplicated)
178
+
179
+ # Apply cutoff
180
+ apply_cutoff(filtered, cutoff)
181
+ end
182
+
183
+ def flatten_unions(types)
184
+ types.flat_map do |type|
185
+ type.is_a?(Union) ? type.types : type
186
+ end
187
+ end
188
+
189
+ def simplify_if_unknown_present(types)
190
+ return types if types.size <= 1
191
+
192
+ has_unknown = types.any? { |t| t.is_a?(Unknown) }
193
+ has_unknown ? [Unknown.instance] : types
194
+ end
195
+
196
+ def apply_cutoff(types, cutoff)
197
+ types.take(cutoff)
198
+ end
199
+ end
200
+
201
+ # ArrayType - array with element type
202
+ class ArrayType < Type
203
+ attr_reader :element_type
204
+
205
+ def initialize(element_type = Unknown.instance)
206
+ super()
207
+ @element_type = element_type
208
+ end
209
+
210
+ def eql?(other)
211
+ super && @element_type == other.element_type
212
+ end
213
+
214
+ def hash
215
+ [self.class, @element_type].hash
216
+ end
217
+
218
+ def to_s
219
+ "Array[#{@element_type}]"
220
+ end
221
+
222
+ def substitute(substitutions)
223
+ new_element = @element_type.substitute(substitutions)
224
+ return self if new_element.equal?(@element_type)
225
+
226
+ ArrayType.new(new_element)
227
+ end
228
+
229
+ def rbs_class_name
230
+ "Array"
231
+ end
232
+
233
+ def type_variable_substitutions
234
+ { Elem: @element_type }
235
+ end
236
+ end
237
+
238
+ # HashType - hash with key and value types
239
+ class HashType < Type
240
+ attr_reader :key_type, :value_type
241
+
242
+ def initialize(key_type = Unknown.instance, value_type = Unknown.instance)
243
+ super()
244
+ @key_type = key_type
245
+ @value_type = value_type
246
+ end
247
+
248
+ def eql?(other)
249
+ super && @key_type == other.key_type && @value_type == other.value_type
250
+ end
251
+
252
+ def hash
253
+ [self.class, @key_type, @value_type].hash
254
+ end
255
+
256
+ def to_s
257
+ "Hash[#{@key_type}, #{@value_type}]"
258
+ end
259
+
260
+ def substitute(substitutions)
261
+ new_key = @key_type.substitute(substitutions)
262
+ new_value = @value_type.substitute(substitutions)
263
+ return self if new_key.equal?(@key_type) && new_value.equal?(@value_type)
264
+
265
+ HashType.new(new_key, new_value)
266
+ end
267
+
268
+ def rbs_class_name
269
+ "Hash"
270
+ end
271
+
272
+ def type_variable_substitutions
273
+ { K: @key_type, V: @value_type }
274
+ end
275
+ end
276
+
277
+ # RangeType - range with element type
278
+ class RangeType < Type
279
+ attr_reader :element_type
280
+
281
+ def initialize(element_type = Unknown.instance)
282
+ super()
283
+ @element_type = element_type
284
+ end
285
+
286
+ def eql?(other)
287
+ super && @element_type == other.element_type
288
+ end
289
+
290
+ def hash
291
+ [self.class, @element_type].hash
292
+ end
293
+
294
+ def to_s
295
+ "Range[#{@element_type}]"
296
+ end
297
+
298
+ def substitute(substitutions)
299
+ new_element = @element_type.substitute(substitutions)
300
+ return self if new_element.equal?(@element_type)
301
+
302
+ RangeType.new(new_element)
303
+ end
304
+
305
+ def rbs_class_name
306
+ "Range"
307
+ end
308
+
309
+ def type_variable_substitutions
310
+ { Elem: @element_type }
311
+ end
312
+ end
313
+
314
+ # HashShape - hash with known field types (Symbol/String keys only)
315
+ class HashShape < Type
316
+ attr_reader :fields
317
+
318
+ def self.new(fields, max_fields: RubyLsp::TypeGuessr::Config.hash_shape_max_fields)
319
+ # Widen to generic Hash when too many fields
320
+ return ClassInstance.new("Hash") if fields.size > max_fields
321
+
322
+ super(fields)
323
+ end
324
+
325
+ def initialize(fields)
326
+ super()
327
+ @fields = fields
328
+ end
329
+
330
+ def eql?(other)
331
+ super && @fields == other.fields
332
+ end
333
+
334
+ def hash
335
+ [self.class, @fields].hash
336
+ end
337
+
338
+ def to_s
339
+ return "{ }" if @fields.empty?
340
+
341
+ fields_str = @fields.map { |k, v| "#{k}: #{v}" }.join(", ")
342
+ "{ #{fields_str} }"
343
+ end
344
+
345
+ def merge_field(key, value_type, max_fields: RubyLsp::TypeGuessr::Config.hash_shape_max_fields)
346
+ new_fields = @fields.merge(key => value_type)
347
+ HashShape.new(new_fields, max_fields: max_fields)
348
+ end
349
+
350
+ def substitute(substitutions)
351
+ new_fields = @fields.transform_values { |v| v.substitute(substitutions) }
352
+ return self if new_fields.all? { |k, v| v.equal?(@fields[k]) }
353
+
354
+ HashShape.new(new_fields)
355
+ end
356
+
357
+ def rbs_class_name
358
+ "Hash"
359
+ end
360
+
361
+ def type_variable_substitutions
362
+ key_type = ClassInstance.new("Symbol")
363
+ value_types = @fields.values.uniq
364
+ value_type = if value_types.empty?
365
+ Unknown.instance
366
+ elsif value_types.size == 1
367
+ value_types.first
368
+ else
369
+ Union.new(value_types)
370
+ end
371
+ { K: key_type, V: value_type }
372
+ end
373
+ end
374
+
375
+ # TypeVariable - represents a type variable from RBS (e.g., Elem, K, V, U)
376
+ class TypeVariable < Type
377
+ attr_reader :name
378
+
379
+ def initialize(name)
380
+ super()
381
+ @name = name
382
+ end
383
+
384
+ def eql?(other)
385
+ super && @name == other.name
386
+ end
387
+
388
+ def hash
389
+ [self.class, @name].hash
390
+ end
391
+
392
+ def to_s
393
+ @name.to_s
394
+ end
395
+
396
+ def substitute(substitutions)
397
+ substitutions[@name] || self
398
+ end
399
+ end
400
+
401
+ # SelfType - represents the 'self' type from RBS
402
+ # Gets substituted with the receiver type at resolution time
403
+ class SelfType < Type
404
+ include Singleton
405
+
406
+ def to_s
407
+ "self"
408
+ end
409
+
410
+ def substitute(substitutions)
411
+ substitutions[:self] || self
412
+ end
413
+ end
414
+
415
+ # ForwardingArgs - represents the forwarding parameter (...)
416
+ class ForwardingArgs < Type
417
+ include Singleton
418
+
419
+ def to_s
420
+ "..."
421
+ end
422
+ end
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeGuessr
4
+ VERSION = "0.0.1"
5
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: type-guessr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - riseshia
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ruby-lsp
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.22'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.22'
26
+ description: TypeGuessr provides heuristic type inference
27
+ email:
28
+ - ''
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - lib/ruby_lsp/type_guessr/addon.rb
36
+ - lib/ruby_lsp/type_guessr/config.rb
37
+ - lib/ruby_lsp/type_guessr/debug_server.rb
38
+ - lib/ruby_lsp/type_guessr/graph_builder.rb
39
+ - lib/ruby_lsp/type_guessr/hover.rb
40
+ - lib/ruby_lsp/type_guessr/runtime_adapter.rb
41
+ - lib/ruby_lsp/type_guessr/type_inferrer.rb
42
+ - lib/type-guessr.rb
43
+ - lib/type_guessr/core/converter/prism_converter.rb
44
+ - lib/type_guessr/core/converter/rbs_converter.rb
45
+ - lib/type_guessr/core/index/location_index.rb
46
+ - lib/type_guessr/core/inference/resolver.rb
47
+ - lib/type_guessr/core/inference/result.rb
48
+ - lib/type_guessr/core/ir/nodes.rb
49
+ - lib/type_guessr/core/logger.rb
50
+ - lib/type_guessr/core/rbs_provider.rb
51
+ - lib/type_guessr/core/registry/method_registry.rb
52
+ - lib/type_guessr/core/registry/variable_registry.rb
53
+ - lib/type_guessr/core/signature_provider.rb
54
+ - lib/type_guessr/core/type_simplifier.rb
55
+ - lib/type_guessr/core/types.rb
56
+ - lib/type_guessr/version.rb
57
+ homepage: https://github.com/riseshia/type-guessr
58
+ licenses: []
59
+ metadata:
60
+ homepage_uri: https://github.com/riseshia/type-guessr
61
+ source_code_uri: https://github.com/riseshia/type-guessr
62
+ changelog_uri: https://github.com/riseshia/type-guessr/blob/main/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.3.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.9
79
+ specification_version: 4
80
+ summary: A heuristic type inference tool for Ruby
81
+ test_files: []