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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/lib/ruby_lsp/type_guessr/addon.rb +138 -0
- data/lib/ruby_lsp/type_guessr/config.rb +90 -0
- data/lib/ruby_lsp/type_guessr/debug_server.rb +861 -0
- data/lib/ruby_lsp/type_guessr/graph_builder.rb +349 -0
- data/lib/ruby_lsp/type_guessr/hover.rb +565 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +506 -0
- data/lib/ruby_lsp/type_guessr/type_inferrer.rb +200 -0
- data/lib/type-guessr.rb +28 -0
- data/lib/type_guessr/core/converter/prism_converter.rb +1649 -0
- data/lib/type_guessr/core/converter/rbs_converter.rb +88 -0
- data/lib/type_guessr/core/index/location_index.rb +72 -0
- data/lib/type_guessr/core/inference/resolver.rb +664 -0
- data/lib/type_guessr/core/inference/result.rb +41 -0
- data/lib/type_guessr/core/ir/nodes.rb +599 -0
- data/lib/type_guessr/core/logger.rb +43 -0
- data/lib/type_guessr/core/rbs_provider.rb +304 -0
- data/lib/type_guessr/core/registry/method_registry.rb +106 -0
- data/lib/type_guessr/core/registry/variable_registry.rb +87 -0
- data/lib/type_guessr/core/signature_provider.rb +101 -0
- data/lib/type_guessr/core/type_simplifier.rb +64 -0
- data/lib/type_guessr/core/types.rb +425 -0
- data/lib/type_guessr/version.rb +5 -0
- metadata +81 -0
|
@@ -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
|
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: []
|