steep 0.44.0 → 0.47.0

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/ruby.yml +3 -2
  4. data/.gitignore +0 -1
  5. data/CHANGELOG.md +42 -0
  6. data/Gemfile +0 -3
  7. data/Gemfile.lock +75 -0
  8. data/README.md +2 -1
  9. data/lib/steep/annotation_parser.rb +1 -1
  10. data/lib/steep/ast/builtin.rb +7 -1
  11. data/lib/steep/ast/types/factory.rb +19 -25
  12. data/lib/steep/cli.rb +7 -1
  13. data/lib/steep/diagnostic/lsp_formatter.rb +59 -6
  14. data/lib/steep/diagnostic/ruby.rb +188 -60
  15. data/lib/steep/diagnostic/signature.rb +38 -15
  16. data/lib/steep/drivers/check.rb +3 -0
  17. data/lib/steep/drivers/init.rb +10 -3
  18. data/lib/steep/drivers/utils/driver_helper.rb +15 -0
  19. data/lib/steep/drivers/validate.rb +1 -1
  20. data/lib/steep/drivers/watch.rb +3 -0
  21. data/lib/steep/equatable.rb +21 -0
  22. data/lib/steep/interface/function.rb +798 -579
  23. data/lib/steep/project/dsl.rb +135 -36
  24. data/lib/steep/project/options.rb +13 -53
  25. data/lib/steep/project/target.rb +22 -8
  26. data/lib/steep/server/interaction_worker.rb +245 -26
  27. data/lib/steep/server/master.rb +2 -2
  28. data/lib/steep/server/type_check_worker.rb +6 -9
  29. data/lib/steep/services/file_loader.rb +26 -19
  30. data/lib/steep/services/goto_service.rb +1 -0
  31. data/lib/steep/services/hover_content.rb +135 -80
  32. data/lib/steep/source.rb +12 -11
  33. data/lib/steep/type_construction.rb +435 -502
  34. data/lib/steep/type_inference/block_params.rb +3 -6
  35. data/lib/steep/type_inference/method_params.rb +483 -0
  36. data/lib/steep/type_inference/send_args.rb +599 -128
  37. data/lib/steep/typing.rb +46 -21
  38. data/lib/steep/version.rb +1 -1
  39. data/lib/steep.rb +4 -2
  40. data/sample/Steepfile +10 -3
  41. data/smoke/alias/Steepfile +2 -1
  42. data/smoke/and/Steepfile +2 -1
  43. data/smoke/array/Steepfile +2 -1
  44. data/smoke/array/test_expectations.yml +3 -3
  45. data/smoke/block/Steepfile +2 -2
  46. data/smoke/block/c.rb +0 -1
  47. data/smoke/case/Steepfile +2 -1
  48. data/smoke/class/Steepfile +2 -1
  49. data/smoke/class/test_expectations.yml +12 -15
  50. data/smoke/const/Steepfile +2 -1
  51. data/smoke/diagnostics/Steepfile +2 -1
  52. data/smoke/diagnostics/different_method_parameter_kind.rb +9 -0
  53. data/smoke/diagnostics/method_arity_mismatch.rb +2 -2
  54. data/smoke/diagnostics/method_parameter_mismatch.rb +10 -0
  55. data/smoke/diagnostics/test_expectations.yml +108 -31
  56. data/smoke/diagnostics-rbs/Steepfile +1 -1
  57. data/smoke/diagnostics-rbs/mixin-class-error.rbs +6 -0
  58. data/smoke/diagnostics-rbs/test_expectations.yml +12 -0
  59. data/smoke/diagnostics-rbs-duplicated/Steepfile +2 -1
  60. data/smoke/diagnostics-ruby-unsat/Steepfile +2 -1
  61. data/smoke/dstr/Steepfile +2 -1
  62. data/smoke/ensure/Steepfile +2 -1
  63. data/smoke/ensure/test_expectations.yml +3 -3
  64. data/smoke/enumerator/Steepfile +2 -1
  65. data/smoke/enumerator/test_expectations.yml +1 -1
  66. data/smoke/extension/Steepfile +2 -1
  67. data/smoke/extension/e.rbs +1 -1
  68. data/smoke/hash/Steepfile +2 -1
  69. data/smoke/hello/Steepfile +2 -1
  70. data/smoke/if/Steepfile +2 -1
  71. data/smoke/implements/Steepfile +2 -1
  72. data/smoke/initialize/Steepfile +2 -1
  73. data/smoke/integer/Steepfile +2 -1
  74. data/smoke/interface/Steepfile +2 -1
  75. data/smoke/kwbegin/Steepfile +2 -1
  76. data/smoke/lambda/Steepfile +2 -1
  77. data/smoke/literal/Steepfile +2 -1
  78. data/smoke/literal/test_expectations.yml +2 -2
  79. data/smoke/map/Steepfile +2 -1
  80. data/smoke/method/Steepfile +2 -1
  81. data/smoke/method/test_expectations.yml +11 -10
  82. data/smoke/module/Steepfile +2 -1
  83. data/smoke/regexp/Steepfile +2 -1
  84. data/smoke/regression/Steepfile +2 -1
  85. data/smoke/rescue/Steepfile +2 -1
  86. data/smoke/rescue/test_expectations.yml +3 -3
  87. data/smoke/self/Steepfile +2 -1
  88. data/smoke/skip/Steepfile +2 -1
  89. data/smoke/stdout/Steepfile +2 -1
  90. data/smoke/super/Steepfile +2 -1
  91. data/smoke/toplevel/Steepfile +2 -1
  92. data/smoke/toplevel/test_expectations.yml +3 -3
  93. data/smoke/tsort/Steepfile +4 -5
  94. data/smoke/tsort/test_expectations.yml +2 -2
  95. data/smoke/type_case/Steepfile +2 -1
  96. data/smoke/unexpected/Steepfile +2 -1
  97. data/smoke/yield/Steepfile +2 -1
  98. data/steep.gemspec +2 -2
  99. metadata +16 -10
  100. data/sig/project.rbi +0 -109
@@ -5,6 +5,10 @@ module Steep
5
5
  VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
6
6
  MethodCallContent = Struct.new(:node, :method_name, :type, :definition, :location, keyword_init: true)
7
7
  DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true) do
8
+ TypeAliasContent = Struct.new(:location, :decl, keyword_init: true)
9
+ ClassContent = Struct.new(:location, :decl, keyword_init: true)
10
+ InterfaceContent = Struct.new(:location, :decl, keyword_init: true)
11
+
8
12
  def comment_string
9
13
  if comments = definition&.comments
10
14
  comments.map {|c| c.string.chomp }.uniq.join("\n----\n")
@@ -50,99 +54,150 @@ module Steep
50
54
  end
51
55
 
52
56
  def content_for(path:, line:, column:)
53
- target = project.target_for_source_path(path)
54
-
55
- if target
56
- file = service.source_files[path]
57
- typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
57
+ target_for_code, targets_for_sigs = project.targets_for_path(path)
58
58
 
59
- node, *parents = typing.source.find_nodes(line: line, column: column)
59
+ case
60
+ when target = target_for_code
61
+ Steep.logger.info "target #{target}"
60
62
 
61
- if node
62
- case node.type
63
- when :lvar
64
- var_name = node.children[0]
65
- context = typing.context_at(line: line, column: column)
66
- var_type = context.lvar_env[var_name] || AST::Types::Any.new(location: nil)
63
+ hover_for_source(column, line, path, target)
67
64
 
68
- VariableContent.new(node: node, name: var_name, type: var_type, location: node.location.name)
69
- when :lvasgn
70
- var_name, rhs = node.children
71
- context = typing.context_at(line: line, column: column)
72
- type = context.lvar_env[var_name] || typing.type_of(node: rhs)
65
+ when target = targets_for_sigs[0]
66
+ service = self.service.signature_services[target.name]
73
67
 
74
- VariableContent.new(node: node, name: var_name, type: type, location: node.location.name)
75
- when :send
76
- receiver, method_name, *_ = node.children
68
+ _buffer, decls = service.latest_env.buffers_decls.find do |buffer, _|
69
+ Pathname(buffer.name) == path
70
+ end
77
71
 
72
+ return if decls.nil?
73
+
74
+ locator = RBS::Locator.new(decls: decls)
75
+ hd, tail = locator.find2(line: line, column: column)
76
+
77
+ # Maybe hover on comment
78
+ return if tail.nil?
79
+
80
+ case type = tail[0]
81
+ when RBS::Types::Alias
82
+ alias_decl = service.latest_env.alias_decls[type.name]&.decl or raise
83
+
84
+ location = tail[0].location
85
+ TypeAliasContent.new(
86
+ location: location,
87
+ decl: alias_decl
88
+ )
89
+ when RBS::Types::ClassInstance, RBS::Types::ClassSingleton
90
+ if hd == :name
91
+ env = service.latest_env
92
+ class_decl = env.class_decls[type.name]&.decls[0]&.decl or raise
93
+ location = tail[0].location[:name]
94
+ ClassContent.new(
95
+ location: location,
96
+ decl: class_decl
97
+ )
98
+ end
99
+ when RBS::Types::Interface
100
+ env = service.latest_env
101
+ interface_decl = env.interface_decls[type.name]&.decl or raise
102
+ location = type.location[:name]
103
+
104
+ InterfaceContent.new(
105
+ location: location,
106
+ decl: interface_decl
107
+ )
108
+ end
109
+ end
110
+ end
78
111
 
79
- result_node = if parents[0]&.type == :block
80
- parents[0]
112
+ def hover_for_source(column, line, path, target)
113
+ file = service.source_files[path]
114
+ typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
115
+ node, *parents = typing.source.find_nodes(line: line, column: column)
116
+
117
+ if node
118
+ case node.type
119
+ when :lvar
120
+ var_name = node.children[0]
121
+ context = typing.context_at(line: line, column: column)
122
+ var_type = context.lvar_env[var_name] || AST::Types::Any.new(location: nil)
123
+
124
+ VariableContent.new(node: node, name: var_name, type: var_type, location: node.location.name)
125
+ when :lvasgn
126
+ var_name, rhs = node.children
127
+ context = typing.context_at(line: line, column: column)
128
+ type = context.lvar_env[var_name] || typing.type_of(node: rhs)
129
+
130
+ VariableContent.new(node: node, name: var_name, type: type, location: node.location.name)
131
+ when :send
132
+ receiver, method_name, *_ = node.children
133
+
134
+ result_node = case parents[0]&.type
135
+ when :block, :numblock
136
+ parents[0]
137
+ else
138
+ node
139
+ end
140
+
141
+ context = typing.context_at(line: line, column: column)
142
+
143
+ receiver_type = if receiver
144
+ typing.type_of(node: receiver)
81
145
  else
82
- node
146
+ context.self_type
83
147
  end
84
148
 
85
- context = typing.context_at(line: line, column: column)
86
-
87
- receiver_type = if receiver
88
- typing.type_of(node: receiver)
89
- else
90
- context.self_type
91
- end
92
-
93
- factory = context.type_env.subtyping.factory
94
- method_name, definition = case receiver_type
95
- when AST::Types::Name::Instance
96
- method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
97
- if method_definition&.defined_in
98
- owner_name = method_definition.defined_in
99
- [
100
- InstanceMethodName.new(owner_name, method_name),
101
- method_definition
102
- ]
103
- end
104
- when AST::Types::Name::Singleton
105
- method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
106
- if method_definition&.defined_in
107
- owner_name = method_definition.defined_in
108
- [
109
- SingletonMethodName.new(owner_name, method_name),
110
- method_definition
111
- ]
112
- end
113
- else
114
- nil
149
+ factory = context.type_env.subtyping.factory
150
+ method_name, definition = case receiver_type
151
+ when AST::Types::Name::Instance
152
+ method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
153
+ if method_definition&.defined_in
154
+ owner_name = method_definition.defined_in
155
+ [
156
+ InstanceMethodName.new(owner_name, method_name),
157
+ method_definition
158
+ ]
115
159
  end
116
-
117
- MethodCallContent.new(
118
- node: node,
119
- method_name: method_name,
120
- type: typing.type_of(node: result_node),
121
- definition: definition,
122
- location: result_node.location.expression
123
- )
124
- when :def, :defs
125
- context = typing.context_at(line: line, column: column)
126
- method_context = context.method_context
127
-
128
- if method_context && method_context.method
129
- DefinitionContent.new(
130
- node: node,
131
- method_name: method_context.name,
132
- method_type: method_context.method_type,
133
- definition: method_context.method,
134
- location: node.loc.expression
135
- )
136
- end
137
- else
138
- type = typing.type_of(node: node)
139
-
140
- TypeContent.new(
160
+ when AST::Types::Name::Singleton
161
+ method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
162
+ if method_definition&.defined_in
163
+ owner_name = method_definition.defined_in
164
+ [
165
+ SingletonMethodName.new(owner_name, method_name),
166
+ method_definition
167
+ ]
168
+ end
169
+ else
170
+ nil
171
+ end
172
+
173
+ MethodCallContent.new(
174
+ node: node,
175
+ method_name: method_name,
176
+ type: typing.type_of(node: result_node),
177
+ definition: definition,
178
+ location: result_node.location.expression
179
+ )
180
+ when :def, :defs
181
+ context = typing.context_at(line: line, column: column)
182
+ method_context = context.method_context
183
+
184
+ if method_context && method_context.method
185
+ DefinitionContent.new(
141
186
  node: node,
142
- type: type,
143
- location: node.location.expression
187
+ method_name: method_context.name,
188
+ method_type: method_context.method_type,
189
+ definition: method_context.method,
190
+ location: node.loc.expression
144
191
  )
145
192
  end
193
+ else
194
+ type = typing.type_of(node: node)
195
+
196
+ TypeContent.new(
197
+ node: node,
198
+ type: type,
199
+ location: node.location.expression
200
+ )
146
201
  end
147
202
  end
148
203
  end
data/lib/steep/source.rb CHANGED
@@ -35,28 +35,25 @@ module Steep
35
35
 
36
36
  self.emit_lambda = true
37
37
  self.emit_procarg0 = true
38
+ self.emit_kwargs = true
38
39
  end
39
40
 
40
- def self.parser
41
- ::Parser::Ruby27.new(Builder.new).tap do |parser|
41
+ def self.new_parser
42
+ ::Parser::Ruby30.new(Builder.new).tap do |parser|
42
43
  parser.diagnostics.all_errors_are_fatal = true
43
44
  parser.diagnostics.ignore_warnings = true
44
45
  end
45
46
  end
46
47
 
47
48
  def self.parse(source_code, path:, factory:)
48
- buffer = ::Parser::Source::Buffer.new(path.to_s, 1)
49
- buffer.source = source_code
50
- node = parser.parse(buffer)
49
+ buffer = ::Parser::Source::Buffer.new(path.to_s, 1, source: source_code)
50
+ node = new_parser().parse(buffer)
51
51
 
52
52
  annotations = []
53
53
 
54
54
  _, comments, _ = yield_self do
55
- buffer = ::Parser::Source::Buffer.new(path.to_s)
56
- buffer.source = source_code
57
- parser = ::Parser::Ruby27.new
58
-
59
- parser.tokenize(buffer)
55
+ buffer = ::Parser::Source::Buffer.new(path.to_s, 1, source: source_code)
56
+ new_parser().tokenize(buffer)
60
57
  end
61
58
 
62
59
  buffer = RBS::Buffer.new(name: path, content: source_code)
@@ -74,7 +71,11 @@ module Steep
74
71
 
75
72
  mapping = {}.compare_by_identity
76
73
 
77
- construct_mapping(node: node, annotations: annotations, mapping: mapping)
74
+ if node
75
+ construct_mapping(node: node, annotations: annotations, mapping: mapping)
76
+ else
77
+ Steep.logger.fatal { "#{path} is empty source code" }
78
+ end
78
79
 
79
80
  annotations.each do |annot|
80
81
  mapping[node] ||= []