steep 0.52.0 → 0.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6846cc048d7515ef655b81cf7f399e5852a96c390d90c7f859c07cc6a5c0f94
4
- data.tar.gz: fecb8b0b0be4e338f3ccf0375064f39167693c0b25b68e3b4ea2f103f117260f
3
+ metadata.gz: c373c6a4366397b2b014850a8993ce6d8a0c27bcdd8bc8c5135bb64b4132b162
4
+ data.tar.gz: '097245d5e3745ceb634a2f2bcd007cca0c549449c835b644feb62c23c3c6e3ed'
5
5
  SHA512:
6
- metadata.gz: 9c08fa692eda8915ef4ce4fe725aecb2a9823c2da188900a3811fdf1d02c6b0369dae8755aed83fe9cc80a376c3309df29efcf849681cd2a9a249559ff6c3ae2
7
- data.tar.gz: 77953f34eb6ab001dfee4f03e0ec1250f1128fedae9e5b427e0037b97b6b809c5bb8ae4c94d9d93f3af3a702de7dd33a182399fe2b7a81ba8ac0d2708c422579
6
+ metadata.gz: eadc49c7c81bce14e4e4c5f9adbcb0411669cd1f78eb290029953e26ea3997d15a65db53329124812e841f3fbe486d30f25bbf89d7bce74d528692176250c795
7
+ data.tar.gz: 3257fa14e2888e68afd6266e1c1d28dc95b8ef7e1ec499393994f35cb715abfc133f005d0ce79962fc6642f7fdeb0b28c35fa0d3bb5687972ea1b31d0a9d6c38
@@ -1,8 +1,15 @@
1
1
  version: 2
2
+
2
3
  updates:
3
- - package-ecosystem: bundler
4
- directory: "/"
5
- schedule:
6
- interval: daily
7
- time: "20:00"
8
- open-pull-requests-limit: 10
4
+ - package-ecosystem: bundler
5
+ directory: "/"
6
+ schedule:
7
+ interval: daily
8
+ time: "20:00"
9
+ open-pull-requests-limit: 10
10
+
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ # Check for updates to GitHub Actions every weekday
15
+ interval: "daily"
@@ -14,6 +14,7 @@ jobs:
14
14
  container_tag:
15
15
  - "2.7"
16
16
  - "3.0"
17
+ - "3.1"
17
18
  - "master-nightly-focal"
18
19
  task:
19
20
  - test
@@ -22,9 +23,10 @@ jobs:
22
23
  container:
23
24
  image: rubylang/ruby:${{ matrix.container_tag }}
24
25
  steps:
25
- - uses: actions/checkout@v1
26
+ - uses: actions/checkout@v3
26
27
  - name: Run test
27
28
  run: |
29
+ git config --global --add safe.directory /__w/steep/steep
28
30
  ruby -v
29
31
  gem install bundler
30
32
  bundle install --jobs 4 --retry 3
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.52.1 (2022-04-25)
6
+
7
+ * Better union type inference (it type checks `Array#filter_map` now!) ([\#531](https://github.com/soutaro/steep/pull/531))
8
+ * Improve method call hover message ([\#537](https://github.com/soutaro/steep/pull/537), [\#538](https://github.com/soutaro/steep/pull/538))
9
+ * Make `NilClass#!` a special method to improve flow-sensitive typing ([\#539](https://github.com/soutaro/steep/pull/539))
10
+ * Fix `steep binstub` ([\#540](https://github.com/soutaro/steep/pull/540), [\#541](https://github.com/soutaro/steep/pull/541))
11
+
5
12
  ## 0.52.0 (2022-04-05)
6
13
 
7
14
  * Add `steep binstub` command ([\#527](https://github.com/soutaro/steep/pull/527))
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- steep (0.52.0)
4
+ steep (0.52.1)
5
5
  activesupport (>= 5.1)
6
6
  language_server-protocol (>= 3.15, < 4.0)
7
7
  listen (~> 3.0)
8
8
  parallel (>= 1.0.0)
9
9
  parser (>= 3.0)
10
10
  rainbow (>= 2.2.2, < 4.0)
11
- rbs (>= 2.3.0)
11
+ rbs (>= 2.3.2)
12
12
  terminal-table (>= 2, < 4)
13
13
 
14
14
  PATH
@@ -44,14 +44,14 @@ GEM
44
44
  minitest-slow_test (0.2.0)
45
45
  minitest (>= 5.0)
46
46
  parallel (1.22.1)
47
- parser (3.1.1.0)
47
+ parser (3.1.2.0)
48
48
  ast (~> 2.4.1)
49
49
  rainbow (3.1.1)
50
50
  rake (13.0.6)
51
51
  rb-fsevent (0.11.1)
52
52
  rb-inotify (0.10.1)
53
53
  ffi (~> 1.0)
54
- rbs (2.3.1)
54
+ rbs (2.3.2)
55
55
  stackprof (0.2.19)
56
56
  terminal-table (3.0.2)
57
57
  unicode-display_width (>= 1.1.1, < 3)
@@ -431,7 +431,8 @@ module Steep
431
431
  case defined_in
432
432
  when RBS::BuiltinNames::BasicObject.name,
433
433
  RBS::BuiltinNames::TrueClass.name,
434
- RBS::BuiltinNames::FalseClass.name
434
+ RBS::BuiltinNames::FalseClass.name,
435
+ AST::Builtin::NilClass.module_name
435
436
  return method_type.with(
436
437
  type: method_type.type.with(
437
438
  return_type: AST::Types::Logic::Not.new(location: method_type.type.return_type.location)
data/lib/steep/cli.rb CHANGED
@@ -210,6 +210,7 @@ module Steep
210
210
 
211
211
  def process_binstub
212
212
  path = Pathname("bin/steep")
213
+ root_path = Pathname.pwd
213
214
  force = false
214
215
 
215
216
  OptionParser.new do |opts|
@@ -227,21 +228,28 @@ BANNER
227
228
  path = Pathname(v)
228
229
  end
229
230
 
231
+ opts.on("--root=PATH", "The repository root path (defaults to `.`)") do |v|
232
+ root_path = (Pathname.pwd + v).cleanpath
233
+ end
234
+
230
235
  opts.on("--[no-]force", "Overwrite file (defaults to false)") do
231
236
  force = true
232
237
  end
233
238
  end.parse!(argv)
234
239
 
235
- path.parent.mkpath
240
+ binstub_path = (Pathname.pwd + path).cleanpath
241
+ bindir_path = binstub_path.parent
242
+
243
+ bindir_path.mkpath
236
244
 
237
245
  gemfile_path =
238
246
  if defined?(Bundler)
239
- Bundler.default_gemfile.relative_path_from(Pathname.pwd + path.parent)
247
+ Bundler.default_gemfile.relative_path_from(bindir_path)
240
248
  else
241
249
  Pathname("../Gemfile")
242
250
  end
243
251
 
244
- if path.file?
252
+ if binstub_path.file?
245
253
  if force
246
254
  stdout.puts Rainbow("#{path} already exists. Overwriting...").yellow
247
255
  else
@@ -254,7 +262,8 @@ BANNER
254
262
  #!/usr/bin/env bash
255
263
 
256
264
  BINSTUB_DIR=$(cd $(dirname $0); pwd)
257
- GEMFILE=${BINSTUB_DIR}/#{gemfile_path}
265
+ GEMFILE=$(readlink -f ${BINSTUB_DIR}/#{gemfile_path})
266
+ ROOT_DIR=$(readlink -f ${BINSTUB_DIR}/#{root_path.relative_path_from(bindir_path)})
258
267
 
259
268
  STEEP="bundle exec --gemfile=${GEMFILE} steep"
260
269
 
@@ -262,15 +271,15 @@ if type "rbenv" > /dev/null 2>&1; then
262
271
  STEEP="rbenv exec ${STEEP}"
263
272
  else
264
273
  if type "rvm" > /dev/null 2>&1; then
265
- STEEP="rvm ${REPO_ROOT} do ${STEEP}"
274
+ STEEP="rvm ${ROOT_DIR} do ${STEEP}"
266
275
  fi
267
276
  fi
268
277
 
269
278
  exec $STEEP $@
270
279
  TEMPLATE
271
280
 
272
- path.write(template)
273
- path.chmod(0755)
281
+ binstub_path.write(template)
282
+ binstub_path.chmod(0755)
274
283
 
275
284
  stdout.puts Rainbow("Successfully generated executable #{path} 🎉").blue
276
285
 
@@ -78,8 +78,7 @@ module Steep
78
78
  Steep.measure "Generating hover response" do
79
79
  Steep.logger.info { "path=#{job.path}, line=#{job.line}, column=#{job.column}" }
80
80
 
81
- hover = Services::HoverContent.new(service: service)
82
- content = hover.content_for(path: job.path, line: job.line, column: job.column)
81
+ content = Services::HoverProvider.content_for(service: service, path: job.path, line: job.line, column: job.column)
83
82
  if content
84
83
  range = content.location.yield_self do |location|
85
84
  lsp_range = location.as_lsp_range
@@ -89,7 +88,10 @@ module Steep
89
88
  end
90
89
 
91
90
  LSP::Interface::Hover.new(
92
- contents: { kind: "markdown", value: format_hover(content)&.gsub(/<!--(?~-->)-->/, "") },
91
+ contents: {
92
+ kind: "markdown",
93
+ value: LSPFormatter.format_hover_content(content).to_s
94
+ },
93
95
  range: range
94
96
  )
95
97
  end
@@ -100,101 +102,6 @@ module Steep
100
102
  end
101
103
  end
102
104
 
103
- def format_hover(content)
104
- case content
105
- when Services::HoverContent::TypeAliasContent
106
- comment = content.decl.comment&.string || ''
107
-
108
- <<-MD
109
- #{comment}
110
-
111
- ```rbs
112
- #{retrieve_decl_information(content.decl)}
113
- ```
114
- MD
115
- when Services::HoverContent::InterfaceContent
116
- comment = content.decl.comment&.string || ''
117
-
118
- <<-MD
119
- #{comment}
120
-
121
- ```rbs
122
- #{retrieve_decl_information(content.decl)}
123
- ```
124
- MD
125
- when Services::HoverContent::ClassContent
126
- comment = content.decl.comment&.string || ''
127
-
128
- <<-MD
129
- #{comment}
130
-
131
- ```rbs
132
- #{retrieve_decl_information(content.decl)}
133
- ```
134
- MD
135
- when Services::HoverContent::VariableContent
136
- "`#{content.name}`: `#{content.type.to_s}`"
137
- when Services::HoverContent::MethodCallContent
138
- method_name = case content.method_name
139
- when Services::HoverContent::InstanceMethodName
140
- "#{content.method_name.class_name}##{content.method_name.method_name}"
141
- when Services::HoverContent::SingletonMethodName
142
- "#{content.method_name.class_name}.#{content.method_name.method_name}"
143
- else
144
- nil
145
- end
146
-
147
- if method_name
148
- string = <<HOVER
149
- ```
150
- #{method_name} ~> #{content.type}
151
- ```
152
- HOVER
153
- if content.definition
154
- if content.definition.comments
155
- string << "\n----\n\n#{content.definition.comments.map(&:string).join("\n\n")}"
156
- end
157
-
158
- string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
159
- end
160
- else
161
- "`#{content.type}`"
162
- end
163
- when Services::HoverContent::DefinitionContent
164
- string = <<HOVER
165
- ```
166
- def #{content.method_name}: #{content.method_type}
167
- ```
168
- HOVER
169
- if (comment = content.comment_string)
170
- string << "\n----\n\n#{comment}\n"
171
- end
172
-
173
- if content.definition.method_types.size > 1
174
- string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
175
- end
176
-
177
- string
178
- when Services::HoverContent::ConstantContent
179
- ss = []
180
- if content.class_or_module?
181
- ss << ["```rbs", retrieve_decl_information(content.decl.primary.decl), "```"].join("\n")
182
- end
183
-
184
- if content.constant?
185
- ss << ["```rbs", "#{content.full_name}: #{content.type}", "```"].join("\n")
186
- end
187
-
188
- if s = content.comment_string
189
- ss << s
190
- end
191
-
192
- ss.join("\n\n----\n\n")
193
- when Services::HoverContent::TypeContent
194
- "`#{content.type}`"
195
- end
196
- end
197
-
198
105
  def process_completion(job)
199
106
  Steep.logger.tagged("#response_to_completion") do
200
107
  Steep.measure "Generating response" do
@@ -356,59 +263,6 @@ HOVER
356
263
  end
357
264
  end
358
265
 
359
- def name_and_params(name, params)
360
- if params.empty?
361
- "#{name}"
362
- else
363
- ps = params.each.map do |param|
364
- s = ""
365
- if param.unchecked?
366
- s << "unchecked "
367
- end
368
- case param.variance
369
- when :invariant
370
- # nop
371
- when :covariant
372
- s << "out "
373
- when :contravariant
374
- s << "in "
375
- end
376
- s + param.name.to_s
377
- end
378
-
379
- "#{name}[#{ps.join(", ")}]"
380
- end
381
- end
382
-
383
- def name_and_args(name, args)
384
- if name && args
385
- if args.empty?
386
- "#{name}"
387
- else
388
- "#{name}[#{args.join(", ")}]"
389
- end
390
- end
391
- end
392
-
393
- def retrieve_decl_information(decl)
394
- case decl
395
- when RBS::AST::Declarations::Class
396
- super_class = if super_class = decl.super_class
397
- " < #{name_and_args(super_class.name, super_class.args)}"
398
- end
399
- "class #{name_and_params(decl.name, decl.type_params)}#{super_class}"
400
- when RBS::AST::Declarations::Module
401
- self_type = unless decl.self_types.empty?
402
- " : #{decl.self_types.join(", ")}"
403
- end
404
- "module #{name_and_params(decl.name, decl.type_params)}#{self_type}"
405
- when RBS::AST::Declarations::Alias
406
- "type #{decl.name} = #{decl.type}"
407
- when RBS::AST::Declarations::Interface
408
- "interface #{name_and_params(decl.name, decl.type_params)}"
409
- end
410
- end
411
-
412
266
  def format_completion_item(item)
413
267
  range = LanguageServer::Protocol::Interface::Range.new(
414
268
  start: LanguageServer::Protocol::Interface::Position.new(
@@ -0,0 +1,234 @@
1
+ module Steep
2
+ module Server
3
+ module LSPFormatter
4
+ include Services
5
+
6
+ class CommentBuilder
7
+ def initialize
8
+ @array = []
9
+ end
10
+
11
+ def self.build
12
+ builder = CommentBuilder.new
13
+ yield builder
14
+ builder.to_s
15
+ end
16
+
17
+ def to_s
18
+ unless @array.empty?
19
+ @array.join("\n\n----\n\n")
20
+ end
21
+ end
22
+
23
+ def <<(string)
24
+ if string
25
+ s = string.rstrip.gsub(/^[ \t]*<!--(?~-->)-->\n/, "").gsub(/\A([ \t]*\n)+/, "")
26
+ unless @array.include?(s)
27
+ @array << s
28
+ end
29
+ end
30
+ end
31
+
32
+ def push
33
+ s = ""
34
+ yield s
35
+ self << s
36
+ end
37
+ end
38
+
39
+ module_function
40
+
41
+ def format_hover_content(content)
42
+ case content
43
+ when HoverProvider::Ruby::VariableContent
44
+ "`#{content.name}`: `#{content.type.to_s}`"
45
+
46
+ when HoverProvider::Ruby::MethodCallContent
47
+ CommentBuilder.build do |builder|
48
+ call = content.method_call
49
+ builder.push do |s|
50
+ case call
51
+ when TypeInference::MethodCall::Typed
52
+ mt = call.actual_method_type.with(
53
+ type: call.actual_method_type.type.with(return_type: call.return_type)
54
+ )
55
+ s << "```rbs\n#{mt.to_s}\n```\n\n"
56
+ when TypeInference::MethodCall::Error
57
+ s << "```rbs\n( ??? ) -> #{call.return_type.to_s}\n```\n\n"
58
+ end
59
+
60
+ s << to_list(call.method_decls) do |decl|
61
+ "`#{decl.method_name}`"
62
+ end
63
+ end
64
+
65
+ call.method_decls.each do |decl|
66
+ if comment = decl.method_def.comment
67
+ builder << <<EOM
68
+ **#{decl.method_name.to_s}**
69
+
70
+ ```rbs
71
+ #{decl.method_type}
72
+ ```
73
+
74
+ #{decl.method_def.comment.string.gsub(/\A([ \t]*\n)+/, "")}
75
+ EOM
76
+ end
77
+ end
78
+ end
79
+
80
+ when HoverProvider::Ruby::DefinitionContent
81
+ CommentBuilder.build do |builder|
82
+ builder << <<EOM
83
+ ```
84
+ #{content.method_name}: #{content.method_type}
85
+ ```
86
+ EOM
87
+ if comments = content.definition&.comments
88
+ comments.each do |comment|
89
+ builder << comment.string
90
+ end
91
+ end
92
+
93
+ if content.definition.method_types.size > 1
94
+ builder << to_list(content.definition.method_types) {|type| "`#{type.to_s}`" }
95
+ end
96
+ end
97
+ when HoverProvider::Ruby::ConstantContent
98
+ CommentBuilder.build do |builder|
99
+ if content.class_or_module?
100
+ builder << <<EOM
101
+ ```rbs
102
+ #{declaration_summary(content.decl.primary.decl)}
103
+ ```
104
+ EOM
105
+ end
106
+
107
+ if content.constant?
108
+ builder << <<EOM
109
+ ```rbs
110
+ #{content.full_name}: #{content.type}
111
+ ```
112
+ EOM
113
+ end
114
+
115
+ content.comments.each do |comment|
116
+ builder << comment.string
117
+ end
118
+ end
119
+ when HoverProvider::Ruby::TypeContent
120
+ "`#{content.type}`"
121
+ when HoverProvider::RBS::TypeAliasContent
122
+ CommentBuilder.build do |builder|
123
+ builder << <<EOM
124
+ ```rbs
125
+ #{declaration_summary(content.decl)}
126
+ ```
127
+ EOM
128
+ if comment = content.decl.comment
129
+ builder << comment.string
130
+ end
131
+ end
132
+ when HoverProvider::RBS::ClassContent
133
+ CommentBuilder.build do |builder|
134
+ builder << <<EOM
135
+ ```rbs
136
+ #{declaration_summary(content.decl)}
137
+ ```
138
+ EOM
139
+ if comment = content.decl.comment
140
+ builder << comment.string
141
+ end
142
+ end
143
+ when HoverProvider::RBS::InterfaceContent
144
+ CommentBuilder.build do |builder|
145
+ builder << <<EOM
146
+ ```rbs
147
+ #{declaration_summary(content.decl)}
148
+ ```
149
+ EOM
150
+ if comment = content.decl.comment
151
+ builder << comment.string
152
+ end
153
+ end
154
+ else
155
+ raise content.class.to_s
156
+ end
157
+ end
158
+
159
+ def to_list(collection, &block)
160
+ buffer = ""
161
+
162
+ strings =
163
+ if block
164
+ collection.map(&block)
165
+ else
166
+ collection.map(&:to_s)
167
+ end
168
+
169
+ strings.each do |s|
170
+ buffer << "- #{s}\n"
171
+ end
172
+
173
+ buffer
174
+ end
175
+
176
+ def name_and_args(name, args)
177
+ if args.empty?
178
+ "#{name}"
179
+ else
180
+ "#{name}[#{args.map(&:to_s).join(", ")}]"
181
+ end
182
+ end
183
+
184
+ def name_and_params(name, params)
185
+ if params.empty?
186
+ "#{name}"
187
+ else
188
+ ps = params.each.map do |param|
189
+ s = ""
190
+ if param.unchecked?
191
+ s << "unchecked "
192
+ end
193
+ case param.variance
194
+ when :invariant
195
+ # nop
196
+ when :covariant
197
+ s << "out "
198
+ when :contravariant
199
+ s << "in "
200
+ end
201
+ s << param.name.to_s
202
+
203
+ if param.upper_bound
204
+ s << " < #{param.upper_bound.to_s}"
205
+ end
206
+
207
+ s
208
+ end
209
+
210
+ "#{name}[#{ps.join(", ")}]"
211
+ end
212
+ end
213
+
214
+ def declaration_summary(decl)
215
+ case decl
216
+ when RBS::AST::Declarations::Class
217
+ super_class = if super_class = decl.super_class
218
+ " < #{name_and_args(super_class.name, super_class.args)}"
219
+ end
220
+ "class #{name_and_params(decl.name, decl.type_params)}#{super_class}"
221
+ when RBS::AST::Declarations::Module
222
+ self_type = unless decl.self_types.empty?
223
+ " : #{decl.self_types.map {|s| name_and_args(s.name, s.args) }.join(", ")}"
224
+ end
225
+ "module #{name_and_params(decl.name, decl.type_params)}#{self_type}"
226
+ when RBS::AST::Declarations::Alias
227
+ "type #{decl.name} = #{decl.type}"
228
+ when RBS::AST::Declarations::Interface
229
+ "interface #{name_and_params(decl.name, decl.type_params)}"
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,63 @@
1
+ module Steep
2
+ module Services
3
+ module HoverProvider
4
+ class RBS
5
+ TypeAliasContent = Struct.new(:location, :decl, keyword_init: true)
6
+ ClassContent = Struct.new(:location, :decl, keyword_init: true)
7
+ InterfaceContent = Struct.new(:location, :decl, keyword_init: true)
8
+
9
+ attr_reader :service
10
+
11
+ def initialize(service:)
12
+ @service = service
13
+ end
14
+
15
+ def project
16
+ service.project
17
+ end
18
+
19
+ def content_for(target:, path:, line:, column:)
20
+ service = self.service.signature_services[target.name]
21
+
22
+ _, decls = service.latest_env.buffers_decls.find do |buffer, _|
23
+ Pathname(buffer.name) == path
24
+ end
25
+
26
+ return if decls.nil?
27
+
28
+ loc_key, path = ::RBS::Locator.new(decls: decls).find2(line: line, column: column) || return
29
+ head, *tail = path
30
+
31
+ case head
32
+ when ::RBS::Types::Alias
33
+ alias_decl = service.latest_env.alias_decls[head.name]&.decl or raise
34
+
35
+ TypeAliasContent.new(
36
+ location: head.location,
37
+ decl: alias_decl
38
+ )
39
+ when ::RBS::Types::ClassInstance, ::RBS::Types::ClassSingleton
40
+ if loc_key == :name
41
+ env = service.latest_env
42
+ class_decl = env.class_decls[head.name]&.decls[0]&.decl or raise
43
+ location = head.location[:name]
44
+ ClassContent.new(
45
+ location: location,
46
+ decl: class_decl
47
+ )
48
+ end
49
+ when ::RBS::Types::Interface
50
+ env = service.latest_env
51
+ interface_decl = env.interface_decls[head.name]&.decl or raise
52
+ location = head.location[:name]
53
+
54
+ InterfaceContent.new(
55
+ location: location,
56
+ decl: interface_decl
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,168 @@
1
+ module Steep
2
+ module Services
3
+ module HoverProvider
4
+ class Ruby
5
+ TypeContent = Struct.new(:node, :type, :location, keyword_init: true)
6
+ VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
7
+ MethodCallContent = Struct.new(:node, :method_call, :location, keyword_init: true)
8
+ DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true)
9
+ ConstantContent = Struct.new(:location, :full_name, :type, :decl, keyword_init: true) do
10
+ def comments
11
+ case
12
+ when class_or_module?
13
+ decl.decls.filter_map {|d| d.decl.comment }
14
+ when constant?
15
+ [decl.decl.comment]
16
+ else
17
+ []
18
+ end.compact
19
+ end
20
+
21
+ def constant?
22
+ decl.is_a?(::RBS::Environment::SingleEntry)
23
+ end
24
+
25
+ def class_or_module?
26
+ decl.is_a?(::RBS::Environment::MultiEntry)
27
+ end
28
+ end
29
+
30
+ attr_reader :service
31
+
32
+ def initialize(service:)
33
+ @service = service
34
+ end
35
+
36
+ def project
37
+ service.project
38
+ end
39
+
40
+ def method_definition_for(factory, type_name, singleton_method: nil, instance_method: nil)
41
+ case
42
+ when instance_method
43
+ factory.definition_builder.build_instance(type_name).methods[instance_method]
44
+ when singleton_method
45
+ methods = factory.definition_builder.build_singleton(type_name).methods
46
+
47
+ if singleton_method == :new
48
+ methods[:new] || methods[:initialize]
49
+ else
50
+ methods[singleton_method]
51
+ end
52
+ end
53
+ end
54
+
55
+ def typecheck(target, path:, content:, line:, column:)
56
+ subtyping = service.signature_services[target.name].current_subtyping or return
57
+ source = Source.parse(content, path: path, factory: subtyping.factory)
58
+ source = source.without_unrelated_defs(line: line, column: column)
59
+ Services::TypeCheckService.type_check(source: source, subtyping: subtyping)
60
+ rescue
61
+ nil
62
+ end
63
+
64
+ def method_name_from_method(context, builder:)
65
+ defined_in = context.method.defined_in
66
+ method_name = context.name
67
+
68
+ case
69
+ when defined_in.class?
70
+ case
71
+ when builder.build_instance(defined_in).methods.key?(method_name)
72
+ InstanceMethodName.new(type_name: defined_in, method_name: method_name)
73
+ when builder.build_singleton(defined_in).methods.key?(method_name)
74
+ SingletonMethodName.new(type_name: defined_in, method_name: method_name)
75
+ end
76
+ else
77
+ InstanceMethodName.new(type_name: defined_in, method_name: method_name)
78
+ end
79
+ end
80
+
81
+ def content_for(target:, path:, line:, column:)
82
+ file = service.source_files[path]
83
+ typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
84
+ node, *parents = typing.source.find_nodes(line: line, column: column)
85
+
86
+ if node
87
+ case node.type
88
+ when :lvar
89
+ var_name = node.children[0]
90
+ context = typing.context_at(line: line, column: column)
91
+ var_type = context.lvar_env[var_name] || AST::Types::Any.new(location: nil)
92
+
93
+ return VariableContent.new(node: node, name: var_name, type: var_type, location: node.location.name)
94
+
95
+ when :lvasgn
96
+ var_name, rhs = node.children
97
+ context = typing.context_at(line: line, column: column)
98
+ type = context.lvar_env[var_name] || typing.type_of(node: rhs)
99
+
100
+ return VariableContent.new(node: node, name: var_name, type: type, location: node.location.name)
101
+
102
+ when :send, :csend
103
+ result_node =
104
+ case parents[0]&.type
105
+ when :block, :numblock
106
+ if node == parents[0].children[0]
107
+ parents[0]
108
+ else
109
+ node
110
+ end
111
+ else
112
+ node
113
+ end
114
+
115
+ case call = typing.call_of(node: result_node)
116
+ when TypeInference::MethodCall::Typed, TypeInference::MethodCall::Error
117
+ unless call.method_decls.empty?
118
+ return MethodCallContent.new(
119
+ node: result_node,
120
+ method_call: call,
121
+ location: node.location.selector
122
+ )
123
+ end
124
+ end
125
+
126
+ when :def, :defs
127
+ context = typing.context_at(line: line, column: column)
128
+ method_context = context.method_context
129
+
130
+ if method_context && method_context.method
131
+ return DefinitionContent.new(
132
+ node: node,
133
+ method_name: method_name_from_method(method_context, builder: context.factory.definition_builder),
134
+ method_type: method_context.method_type,
135
+ definition: method_context.method,
136
+ location: node.loc.name
137
+ )
138
+ end
139
+
140
+ when :const, :casgn
141
+ context = typing.context_at(line: line, column: column)
142
+
143
+ type = typing.type_of(node: node)
144
+ const_name = typing.source_index.reference(constant_node: node)
145
+
146
+ if const_name
147
+ decl = context.env.class_decls[const_name] || context.env.constant_decls[const_name]
148
+
149
+ return ConstantContent.new(
150
+ location: node.location.name,
151
+ full_name: const_name,
152
+ type: type,
153
+ decl: decl
154
+ )
155
+ end
156
+ end
157
+
158
+ TypeContent.new(
159
+ node: node,
160
+ type: typing.type_of(node: node),
161
+ location: node.location.expression
162
+ )
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,21 @@
1
+ module Steep
2
+ module Services
3
+ module HoverProvider
4
+ module SingletonMethods
5
+ def content_for(service:, path:, line:, column:)
6
+ project = service.project
7
+ target_for_code, targets_for_sigs = project.targets_for_path(path)
8
+
9
+ case
10
+ when target_for_code
11
+ Ruby.new(service: service).content_for(target: target_for_code, path: path, line: line, column: column)
12
+ when target = targets_for_sigs.first
13
+ RBS.new(service: service).content_for(target: target, path: path, line: line, column: column)
14
+ end
15
+ end
16
+ end
17
+
18
+ extend SingletonMethods
19
+ end
20
+ end
21
+ end
@@ -347,7 +347,7 @@ module Steep
347
347
 
348
348
  when relation.super_type.is_a?(AST::Types::Union)
349
349
  Any(relation) do |result|
350
- relation.super_type.types.sort_by {|ty| (path = hole_path(ty)) ? -path.size : 1 }.each do |super_type|
350
+ relation.super_type.types.sort_by {|ty| (path = hole_path(ty)) ? -path.size : -Float::INFINITY }.each do |super_type|
351
351
  rel = Relation.new(sub_type: relation.sub_type, super_type: super_type)
352
352
  result.add(rel) do
353
353
  check_type(rel)
@@ -357,7 +357,7 @@ module Steep
357
357
 
358
358
  when relation.sub_type.is_a?(AST::Types::Intersection)
359
359
  Any(relation) do |result|
360
- relation.sub_type.types.sort_by {|ty| (path = hole_path(ty)) ? -path.size : 1 }.each do |sub_type|
360
+ relation.sub_type.types.sort_by {|ty| (path = hole_path(ty)) ? -path.size : -Float::INFINITY }.each do |sub_type|
361
361
  rel = Relation.new(sub_type: sub_type, super_type: relation.super_type)
362
362
  result.add(rel) do
363
363
  check_type(rel)
@@ -2910,6 +2910,11 @@ module Steep
2910
2910
  call = call.with_return_type(typing.type_of(node: arguments.last))
2911
2911
  end
2912
2912
  end
2913
+
2914
+ if node.type == :csend || ((node.type == :block || node.type == :numblock) && node.children[0].type == :csend)
2915
+ optional_type = AST::Types::Union.build(types: [call.return_type, AST::Builtin.nil_type])
2916
+ call = call.with_return_type(optional_type)
2917
+ end
2913
2918
  else
2914
2919
  error = Diagnostic::Ruby::UnresolvedOverloading.new(
2915
2920
  node: node,
@@ -171,6 +171,10 @@ module Steep
171
171
  )
172
172
  end
173
173
 
174
+ def factory
175
+ type_env.subtyping.factory
176
+ end
177
+
174
178
  def env
175
179
  type_env.subtyping.factory.env
176
180
  end
data/lib/steep/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Steep
2
- VERSION = "0.52.0"
2
+ VERSION = "0.52.1"
3
3
  end
data/lib/steep.rb CHANGED
@@ -92,23 +92,26 @@ require "steep/index/rbs_index"
92
92
  require "steep/index/signature_symbol_provider"
93
93
  require "steep/index/source_index"
94
94
 
95
- require "steep/server/change_buffer"
96
- require "steep/server/base_worker"
97
- require "steep/server/worker_process"
98
- require "steep/server/interaction_worker"
99
- require "steep/server/type_check_worker"
100
- require "steep/server/master"
101
-
102
95
  require "steep/services/content_change"
103
96
  require "steep/services/path_assignment"
104
97
  require "steep/services/signature_service"
105
98
  require "steep/services/type_check_service"
106
- require "steep/services/hover_content"
99
+ require "steep/services/hover_provider/singleton_methods"
100
+ require "steep/services/hover_provider/ruby"
101
+ require "steep/services/hover_provider/rbs"
107
102
  require "steep/services/completion_provider"
108
103
  require "steep/services/stats_calculator"
109
104
  require "steep/services/file_loader"
110
105
  require "steep/services/goto_service"
111
106
 
107
+ require "steep/server/lsp_formatter"
108
+ require "steep/server/change_buffer"
109
+ require "steep/server/base_worker"
110
+ require "steep/server/worker_process"
111
+ require "steep/server/interaction_worker"
112
+ require "steep/server/type_check_worker"
113
+ require "steep/server/master"
114
+
112
115
  require "steep/project"
113
116
  require "steep/project/pattern"
114
117
  require "steep/project/options"
data/steep.gemspec CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency "rainbow", ">= 2.2.2", "< 4.0"
34
34
  spec.add_runtime_dependency "listen", "~> 3.0"
35
35
  spec.add_runtime_dependency "language_server-protocol", ">= 3.15", "< 4.0"
36
- spec.add_runtime_dependency "rbs", ">= 2.3.0"
36
+ spec.add_runtime_dependency "rbs", ">= 2.3.2"
37
37
  spec.add_runtime_dependency "parallel", ">= 1.0.0"
38
38
  spec.add_runtime_dependency "terminal-table", ">= 2", "< 4"
39
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.52.0
4
+ version: 0.52.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-05 00:00:00.000000000 Z
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -98,14 +98,14 @@ dependencies:
98
98
  requirements:
99
99
  - - ">="
100
100
  - !ruby/object:Gem::Version
101
- version: 2.3.0
101
+ version: 2.3.2
102
102
  type: :runtime
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - ">="
107
107
  - !ruby/object:Gem::Version
108
- version: 2.3.0
108
+ version: 2.3.2
109
109
  - !ruby/object:Gem::Dependency
110
110
  name: parallel
111
111
  requirement: !ruby/object:Gem::Requirement
@@ -233,6 +233,7 @@ files:
233
233
  - lib/steep/server/base_worker.rb
234
234
  - lib/steep/server/change_buffer.rb
235
235
  - lib/steep/server/interaction_worker.rb
236
+ - lib/steep/server/lsp_formatter.rb
236
237
  - lib/steep/server/master.rb
237
238
  - lib/steep/server/type_check_worker.rb
238
239
  - lib/steep/server/worker_process.rb
@@ -240,7 +241,9 @@ files:
240
241
  - lib/steep/services/content_change.rb
241
242
  - lib/steep/services/file_loader.rb
242
243
  - lib/steep/services/goto_service.rb
243
- - lib/steep/services/hover_content.rb
244
+ - lib/steep/services/hover_provider/rbs.rb
245
+ - lib/steep/services/hover_provider/ruby.rb
246
+ - lib/steep/services/hover_provider/singleton_methods.rb
244
247
  - lib/steep/services/path_assignment.rb
245
248
  - lib/steep/services/signature_service.rb
246
249
  - lib/steep/services/stats_calculator.rb
@@ -1,254 +0,0 @@
1
- module Steep
2
- module Services
3
- class HoverContent
4
- TypeContent = Struct.new(:node, :type, :location, keyword_init: true)
5
- VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
6
- MethodCallContent = Struct.new(:node, :method_name, :type, :definition, :location, keyword_init: true)
7
- DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true) do
8
- def comment_string
9
- if comments = definition&.comments
10
- comments.map {|c| c.string.chomp }.uniq.join("\n----\n")
11
- end
12
- end
13
- end
14
- ConstantContent = Struct.new(:location, :full_name, :type, :decl, keyword_init: true) do
15
- def comment_string
16
- if class_or_module?
17
- comments = decl.decls.filter_map {|d| d.decl.comment&.string }
18
- unless comments.empty?
19
- return comments.join("\n----\n")
20
- end
21
- end
22
-
23
- if constant?
24
- if comment = decl.decl.comment
25
- return comment.string
26
- end
27
- end
28
-
29
- nil
30
- end
31
-
32
- def constant?
33
- decl.is_a?(RBS::Environment::SingleEntry)
34
- end
35
-
36
- def class_or_module?
37
- decl.is_a?(RBS::Environment::MultiEntry)
38
- end
39
- end
40
-
41
- TypeAliasContent = Struct.new(:location, :decl, keyword_init: true)
42
- ClassContent = Struct.new(:location, :decl, keyword_init: true)
43
- InterfaceContent = Struct.new(:location, :decl, keyword_init: true)
44
-
45
- InstanceMethodName = Struct.new(:class_name, :method_name)
46
- SingletonMethodName = Struct.new(:class_name, :method_name)
47
-
48
- attr_reader :service
49
-
50
- def initialize(service:)
51
- @service = service
52
- end
53
-
54
- def project
55
- service.project
56
- end
57
-
58
- def method_definition_for(factory, type_name, singleton_method: nil, instance_method: nil)
59
- case
60
- when instance_method
61
- factory.definition_builder.build_instance(type_name).methods[instance_method]
62
- when singleton_method
63
- methods = factory.definition_builder.build_singleton(type_name).methods
64
-
65
- if singleton_method == :new
66
- methods[:new] || methods[:initialize]
67
- else
68
- methods[singleton_method]
69
- end
70
- end
71
- end
72
-
73
- def typecheck(target, path:, content:, line:, column:)
74
- subtyping = service.signature_services[target.name].current_subtyping or return
75
- source = Source.parse(content, path: path, factory: subtyping.factory)
76
- source = source.without_unrelated_defs(line: line, column: column)
77
- Services::TypeCheckService.type_check(source: source, subtyping: subtyping)
78
- rescue
79
- nil
80
- end
81
-
82
- def content_for(path:, line:, column:)
83
- target_for_code, targets_for_sigs = project.targets_for_path(path)
84
-
85
- case
86
- when target = target_for_code
87
- Steep.logger.info "target #{target}"
88
-
89
- hover_for_source(column, line, path, target)
90
-
91
- when target = targets_for_sigs[0]
92
- service = self.service.signature_services[target.name]
93
-
94
- _buffer, decls = service.latest_env.buffers_decls.find do |buffer, _|
95
- Pathname(buffer.name) == path
96
- end
97
-
98
- return if decls.nil?
99
-
100
- locator = RBS::Locator.new(decls: decls)
101
- hd, tail = locator.find2(line: line, column: column)
102
-
103
- # Maybe hover on comment
104
- return if tail.nil?
105
-
106
- case type = tail[0]
107
- when RBS::Types::Alias
108
- alias_decl = service.latest_env.alias_decls[type.name]&.decl or raise
109
-
110
- location = tail[0].location
111
- TypeAliasContent.new(
112
- location: location,
113
- decl: alias_decl
114
- )
115
- when RBS::Types::ClassInstance, RBS::Types::ClassSingleton
116
- if hd == :name
117
- env = service.latest_env
118
- class_decl = env.class_decls[type.name]&.decls[0]&.decl or raise
119
- location = tail[0].location[:name]
120
- ClassContent.new(
121
- location: location,
122
- decl: class_decl
123
- )
124
- end
125
- when RBS::Types::Interface
126
- env = service.latest_env
127
- interface_decl = env.interface_decls[type.name]&.decl or raise
128
- location = type.location[:name]
129
-
130
- InterfaceContent.new(
131
- location: location,
132
- decl: interface_decl
133
- )
134
- end
135
- end
136
- end
137
-
138
- def hover_for_source(column, line, path, target)
139
- file = service.source_files[path]
140
- typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
141
- node, *parents = typing.source.find_nodes(line: line, column: column)
142
-
143
- if node
144
- case node.type
145
- when :lvar
146
- var_name = node.children[0]
147
- context = typing.context_at(line: line, column: column)
148
- var_type = context.lvar_env[var_name] || AST::Types::Any.new(location: nil)
149
-
150
- VariableContent.new(node: node, name: var_name, type: var_type, location: node.location.name)
151
- when :lvasgn
152
- var_name, rhs = node.children
153
- context = typing.context_at(line: line, column: column)
154
- type = context.lvar_env[var_name] || typing.type_of(node: rhs)
155
-
156
- VariableContent.new(node: node, name: var_name, type: type, location: node.location.name)
157
- when :send
158
- receiver, method_name, *_ = node.children
159
-
160
- result_node = case parents[0]&.type
161
- when :block, :numblock
162
- parents[0]
163
- else
164
- node
165
- end
166
-
167
- context = typing.context_at(line: line, column: column)
168
-
169
- receiver_type = if receiver
170
- typing.type_of(node: receiver)
171
- else
172
- context.self_type
173
- end
174
-
175
- factory = context.type_env.subtyping.factory
176
- method_name, definition = case receiver_type
177
- when AST::Types::Name::Instance
178
- method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
179
- if method_definition&.defined_in
180
- owner_name = method_definition.defined_in
181
- [
182
- InstanceMethodName.new(owner_name, method_name),
183
- method_definition
184
- ]
185
- end
186
- when AST::Types::Name::Singleton
187
- method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
188
- if method_definition&.defined_in
189
- owner_name = method_definition.defined_in
190
- [
191
- SingletonMethodName.new(owner_name, method_name),
192
- method_definition
193
- ]
194
- end
195
- else
196
- nil
197
- end
198
-
199
- MethodCallContent.new(
200
- node: node,
201
- method_name: method_name,
202
- type: typing.type_of(node: result_node),
203
- definition: definition,
204
- location: result_node.location.expression
205
- )
206
- when :def, :defs
207
- context = typing.context_at(line: line, column: column)
208
- method_context = context.method_context
209
-
210
- if method_context && method_context.method
211
- DefinitionContent.new(
212
- node: node,
213
- method_name: method_context.name,
214
- method_type: method_context.method_type,
215
- definition: method_context.method,
216
- location: node.loc.expression
217
- )
218
- end
219
- when :const, :casgn
220
- context = typing.context_at(line: line, column: column)
221
-
222
- type = typing.type_of(node: node)
223
- const_name = typing.source_index.reference(constant_node: node)
224
-
225
- if const_name
226
- decl = context.env.class_decls[const_name] || context.env.constant_decls[const_name]
227
-
228
- ConstantContent.new(
229
- location: node.location.name,
230
- full_name: const_name,
231
- type: type,
232
- decl: decl
233
- )
234
- else
235
- TypeContent.new(
236
- node: node,
237
- type: type,
238
- location: node.location.expression
239
- )
240
- end
241
- else
242
- type = typing.type_of(node: node)
243
-
244
- TypeContent.new(
245
- node: node,
246
- type: type,
247
- location: node.location.expression
248
- )
249
- end
250
- end
251
- end
252
- end
253
- end
254
- end