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,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
@@ -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"