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,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_lsp/type_inferrer"
|
|
4
|
+
|
|
5
|
+
module RubyLsp
|
|
6
|
+
module TypeGuessr
|
|
7
|
+
# Custom TypeInferrer that enhances ruby-lsp's type inference with TypeGuessr's
|
|
8
|
+
# IR-based heuristic inference. Used by Go to Definition and other features.
|
|
9
|
+
class TypeInferrer < ::RubyLsp::TypeInferrer
|
|
10
|
+
# Core layer shortcuts
|
|
11
|
+
Types = ::TypeGuessr::Core::Types
|
|
12
|
+
IR = ::TypeGuessr::Core::IR
|
|
13
|
+
private_constant :Types, :IR
|
|
14
|
+
|
|
15
|
+
def initialize(index, runtime_adapter)
|
|
16
|
+
super(index)
|
|
17
|
+
@runtime_adapter = runtime_adapter
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Override to add TypeGuessr's heuristic type inference
|
|
21
|
+
# Returns nil when type cannot be determined (no fallback to ruby-lsp's default)
|
|
22
|
+
#
|
|
23
|
+
# @param node_context [RubyLsp::NodeContext] The context of the node
|
|
24
|
+
# @return [GuessedType, nil] The inferred type or nil if unknown
|
|
25
|
+
def infer_receiver_type(node_context)
|
|
26
|
+
guess_type_from_ir(node_context)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Attempt to guess type using IR-based inference
|
|
32
|
+
# Returns GuessedType only when type can be resolved
|
|
33
|
+
#
|
|
34
|
+
# @param node_context [RubyLsp::NodeContext] The context of the node
|
|
35
|
+
# @return [GuessedType, nil] The guessed type or nil if unknown
|
|
36
|
+
def guess_type_from_ir(node_context)
|
|
37
|
+
node = node_context.node
|
|
38
|
+
|
|
39
|
+
# For CallNode, we need to infer the receiver's type
|
|
40
|
+
target_node = if node.is_a?(Prism::CallNode)
|
|
41
|
+
extract_receiver_variable(node)
|
|
42
|
+
else
|
|
43
|
+
node
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return nil unless target_node && variable_node?(target_node)
|
|
47
|
+
|
|
48
|
+
# Find the IR node
|
|
49
|
+
ir_node = find_ir_node(target_node, node_context)
|
|
50
|
+
return nil unless ir_node
|
|
51
|
+
|
|
52
|
+
# Infer type from IR node
|
|
53
|
+
result = @runtime_adapter.infer_type(ir_node)
|
|
54
|
+
return nil if result.type.is_a?(Types::Unknown)
|
|
55
|
+
|
|
56
|
+
# Convert to ruby-lsp's Type format
|
|
57
|
+
type_string = result.type.to_s
|
|
58
|
+
return nil if type_string == "untyped" || type_string.empty?
|
|
59
|
+
|
|
60
|
+
GuessedType.new(type_string)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Find the IR node corresponding to a Prism node
|
|
64
|
+
# @param node [Prism::Node] The Prism node
|
|
65
|
+
# @param node_context [RubyLsp::NodeContext] The context of the node
|
|
66
|
+
# @return [TypeGuessr::Core::IR::Node, nil] The IR node or nil
|
|
67
|
+
def find_ir_node(node, node_context)
|
|
68
|
+
scope_id = generate_scope_id(node_context)
|
|
69
|
+
node_hash = generate_node_hash(node, node_context)
|
|
70
|
+
return nil unless node_hash
|
|
71
|
+
|
|
72
|
+
node_key = "#{scope_id}:#{node_hash}"
|
|
73
|
+
@runtime_adapter.find_node_by_key(node_key)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Extract the variable node from a CallNode's receiver
|
|
77
|
+
# @param call_node [Prism::CallNode] The call node
|
|
78
|
+
# @return [Prism::Node, nil] The receiver variable node or nil
|
|
79
|
+
def extract_receiver_variable(call_node)
|
|
80
|
+
receiver = call_node.receiver
|
|
81
|
+
return nil unless receiver
|
|
82
|
+
|
|
83
|
+
# Unwrap parentheses if present
|
|
84
|
+
if receiver.is_a?(Prism::ParenthesesNode)
|
|
85
|
+
statements = receiver.body
|
|
86
|
+
receiver = statements.body.first if statements.is_a?(Prism::StatementsNode) && statements.body.length == 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
receiver
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Check if the node is a variable node that we can analyze
|
|
93
|
+
# @param node [Prism::Node] The node to check
|
|
94
|
+
# @return [Boolean] true if the node is a variable node
|
|
95
|
+
def variable_node?(node)
|
|
96
|
+
case node
|
|
97
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode, Prism::LocalVariableTargetNode,
|
|
98
|
+
Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableTargetNode,
|
|
99
|
+
Prism::RequiredParameterNode, Prism::OptionalParameterNode, Prism::RestParameterNode,
|
|
100
|
+
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
|
|
101
|
+
Prism::KeywordRestParameterNode, Prism::BlockParameterNode
|
|
102
|
+
true
|
|
103
|
+
else
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Generate scope_id from node_context
|
|
109
|
+
# Format: "ClassName#method_name" or "ClassName" or "#method_name" or ""
|
|
110
|
+
# @param node_context [RubyLsp::NodeContext] The context of the node
|
|
111
|
+
# @return [String] The scope identifier
|
|
112
|
+
def generate_scope_id(node_context)
|
|
113
|
+
class_path = node_context.nesting.map do |n|
|
|
114
|
+
n.is_a?(String) ? n : n.name.to_s
|
|
115
|
+
end.join("::")
|
|
116
|
+
|
|
117
|
+
method_name = node_context.surrounding_method
|
|
118
|
+
|
|
119
|
+
if method_name
|
|
120
|
+
"#{class_path}##{method_name}"
|
|
121
|
+
else
|
|
122
|
+
class_path
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Generate node_hash from Prism node to match IR node_hash format
|
|
127
|
+
# @param node [Prism::Node] The Prism node
|
|
128
|
+
# @param node_context [RubyLsp::NodeContext] The context (for block param detection)
|
|
129
|
+
# @return [String, nil] The node hash or nil
|
|
130
|
+
def generate_node_hash(node, node_context)
|
|
131
|
+
line = node.location.start_line
|
|
132
|
+
case node
|
|
133
|
+
when Prism::LocalVariableWriteNode, Prism::LocalVariableTargetNode
|
|
134
|
+
"local_write:#{node.name}:#{line}"
|
|
135
|
+
when Prism::LocalVariableReadNode
|
|
136
|
+
"local_read:#{node.name}:#{line}"
|
|
137
|
+
when Prism::InstanceVariableWriteNode, Prism::InstanceVariableTargetNode
|
|
138
|
+
"ivar_write:#{node.name}:#{line}"
|
|
139
|
+
when Prism::InstanceVariableReadNode
|
|
140
|
+
"ivar_read:#{node.name}:#{line}"
|
|
141
|
+
when Prism::RequiredParameterNode, Prism::OptionalParameterNode, Prism::RestParameterNode,
|
|
142
|
+
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
|
|
143
|
+
Prism::KeywordRestParameterNode, Prism::BlockParameterNode
|
|
144
|
+
# Check if this is a block parameter
|
|
145
|
+
if block_parameter?(node, node_context)
|
|
146
|
+
index = block_parameter_index(node, node_context)
|
|
147
|
+
"bparam:#{index}:#{line}"
|
|
148
|
+
else
|
|
149
|
+
"param:#{node.name}:#{line}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Check if a parameter node is inside a block (not a method definition)
|
|
155
|
+
# @param node [Prism::Node] The parameter node
|
|
156
|
+
# @param node_context [RubyLsp::NodeContext] The context
|
|
157
|
+
# @return [Boolean] true if inside a block
|
|
158
|
+
def block_parameter?(node, node_context)
|
|
159
|
+
call_node = node_context.call_node
|
|
160
|
+
return false unless call_node&.block
|
|
161
|
+
|
|
162
|
+
block_params = call_node.block.parameters&.parameters
|
|
163
|
+
return false unless block_params
|
|
164
|
+
|
|
165
|
+
all_params = collect_block_params(block_params)
|
|
166
|
+
all_params.include?(node)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Get the index of a block parameter
|
|
170
|
+
# @param node [Prism::Node] The parameter node
|
|
171
|
+
# @param node_context [RubyLsp::NodeContext] The context
|
|
172
|
+
# @return [Integer] The parameter index
|
|
173
|
+
def block_parameter_index(node, node_context)
|
|
174
|
+
call_node = node_context.call_node
|
|
175
|
+
return 0 unless call_node&.block
|
|
176
|
+
|
|
177
|
+
block_params = call_node.block.parameters&.parameters
|
|
178
|
+
return 0 unless block_params
|
|
179
|
+
|
|
180
|
+
all_params = collect_block_params(block_params)
|
|
181
|
+
all_params.index(node) || 0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Collect all parameters from block parameters node
|
|
185
|
+
# @param block_params [Prism::ParametersNode] The block parameters
|
|
186
|
+
# @return [Array<Prism::Node>] All parameter nodes
|
|
187
|
+
def collect_block_params(block_params)
|
|
188
|
+
params = []
|
|
189
|
+
params.concat(block_params.requireds) if block_params.respond_to?(:requireds)
|
|
190
|
+
params.concat(block_params.optionals) if block_params.respond_to?(:optionals)
|
|
191
|
+
params << block_params.rest if block_params.respond_to?(:rest) && block_params.rest
|
|
192
|
+
params.concat(block_params.posts) if block_params.respond_to?(:posts)
|
|
193
|
+
params.concat(block_params.keywords) if block_params.respond_to?(:keywords)
|
|
194
|
+
params << block_params.keyword_rest if block_params.respond_to?(:keyword_rest) && block_params.keyword_rest
|
|
195
|
+
params << block_params.block if block_params.respond_to?(:block) && block_params.block
|
|
196
|
+
params.compact
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
data/lib/type-guessr.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Main module for TypeGuessr
|
|
4
|
+
module TypeGuessr
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Load version
|
|
9
|
+
require_relative "type_guessr/version"
|
|
10
|
+
|
|
11
|
+
# Load core components (IR-based architecture)
|
|
12
|
+
require_relative "type_guessr/core/types"
|
|
13
|
+
require_relative "type_guessr/core/ir/nodes"
|
|
14
|
+
require_relative "type_guessr/core/index/location_index"
|
|
15
|
+
require_relative "type_guessr/core/converter/prism_converter"
|
|
16
|
+
require_relative "type_guessr/core/converter/rbs_converter"
|
|
17
|
+
require_relative "type_guessr/core/inference/result"
|
|
18
|
+
require_relative "type_guessr/core/inference/resolver"
|
|
19
|
+
require_relative "type_guessr/core/rbs_provider"
|
|
20
|
+
require_relative "type_guessr/core/logger"
|
|
21
|
+
|
|
22
|
+
# Load Ruby LSP integration
|
|
23
|
+
# NOTE: addon.rb is NOT required here - it's auto-discovered by Ruby LSP
|
|
24
|
+
# Requiring it here would cause double activation
|
|
25
|
+
require_relative "ruby_lsp/type_guessr/config"
|
|
26
|
+
require_relative "ruby_lsp/type_guessr/runtime_adapter"
|
|
27
|
+
require_relative "ruby_lsp/type_guessr/hover"
|
|
28
|
+
require_relative "ruby_lsp/type_guessr/debug_server"
|