steep 0.42.0 → 0.43.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/steep.rb +4 -3
  4. data/lib/steep/annotation_parser.rb +10 -2
  5. data/lib/steep/cli.rb +1 -0
  6. data/lib/steep/diagnostic/ruby.rb +15 -6
  7. data/lib/steep/diagnostic/signature.rb +28 -11
  8. data/lib/steep/drivers/annotations.rb +1 -3
  9. data/lib/steep/drivers/check.rb +17 -7
  10. data/lib/steep/drivers/diagnostic_printer.rb +4 -0
  11. data/lib/steep/drivers/langserver.rb +1 -0
  12. data/lib/steep/drivers/print_project.rb +1 -1
  13. data/lib/steep/drivers/stats.rb +125 -105
  14. data/lib/steep/drivers/utils/driver_helper.rb +35 -0
  15. data/lib/steep/drivers/validate.rb +1 -1
  16. data/lib/steep/drivers/watch.rb +12 -10
  17. data/lib/steep/index/signature_symbol_provider.rb +20 -6
  18. data/lib/steep/project/target.rb +4 -4
  19. data/lib/steep/server/interaction_worker.rb +2 -3
  20. data/lib/steep/server/master.rb +621 -170
  21. data/lib/steep/server/type_check_worker.rb +127 -13
  22. data/lib/steep/server/worker_process.rb +7 -4
  23. data/lib/steep/services/completion_provider.rb +2 -2
  24. data/lib/steep/services/hover_content.rb +5 -4
  25. data/lib/steep/services/path_assignment.rb +6 -8
  26. data/lib/steep/services/signature_service.rb +43 -9
  27. data/lib/steep/services/type_check_service.rb +184 -138
  28. data/lib/steep/signature/validator.rb +17 -9
  29. data/lib/steep/source.rb +21 -18
  30. data/lib/steep/subtyping/constraints.rb +2 -2
  31. data/lib/steep/type_construction.rb +223 -125
  32. data/lib/steep/type_inference/block_params.rb +1 -1
  33. data/lib/steep/type_inference/context.rb +22 -0
  34. data/lib/steep/type_inference/logic.rb +1 -1
  35. data/lib/steep/type_inference/logic_type_interpreter.rb +3 -3
  36. data/lib/steep/version.rb +1 -1
  37. data/smoke/implements/b.rb +13 -0
  38. data/smoke/implements/b.rbs +12 -0
  39. data/smoke/regression/issue_328.rb +1 -0
  40. data/smoke/regression/issue_328.rbs +0 -0
  41. data/smoke/regression/issue_332.rb +11 -0
  42. data/smoke/regression/issue_332.rbs +19 -0
  43. data/smoke/regression/masgn.rb +4 -0
  44. data/smoke/regression/test_expectations.yml +29 -0
  45. data/smoke/regression/thread.rb +7 -0
  46. data/smoke/super/test_expectations.yml +2 -12
  47. data/steep.gemspec +2 -2
  48. metadata +40 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3bed885aa8ba3f60076879eb1ca1572c56ae59aae2a9ee3745affbbd1f3f93d2
4
- data.tar.gz: 344c77c6676f8c6772d28bab9598c8fa45835100f24603985750de076ebe57f3
3
+ metadata.gz: '0568c96d6a39d5c6db14e2259b5cdf3cd39bfaf5f2a4368ce060de4c803c0c96'
4
+ data.tar.gz: d820c5f0c0d6394893a907999d4fdbd0f3a90b4821380c20598e9251d29852e6
5
5
  SHA512:
6
- metadata.gz: dce04d256960ee738d029a9c66187a1ef5f11beb3fd0613cbb02dde6494eeafde8aa0bf12aadc02d07bb6a64a785444f18636df1eaa3f1be02bce1d24ad63385
7
- data.tar.gz: df9772b46abe7c2acd10cf497ded086b3614016d74b26578d2da6720f26afa01a73a7b43c6a0170b67157e7615e2ec989597ed1081a3f6e4ac69553adc338589
6
+ metadata.gz: 8f4f17d938d9f7f462f3db8aff1e48f8bf4118ab66bf40b35abd9b38ea3585014a0d8d372277e5589e9697043946b0e646193e4d7413c55fcf490d6065494203
7
+ data.tar.gz: d7ece354f2e50faa86b65ed4a62c14f97dc74550fb81326142b8932f8c58deb2da40679a34e1869183e15ab4a90e939fb0041c44c63a5149b8234c82058481b0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.43.0 (2021-03-30)
6
+
7
+ * LSP responsiveness improvements ([\#352](https://github.com/soutaro/steep/issues/352))
8
+ * `@implements` annotation in blocks ([#338](https://github.com/soutaro/steep/issues/338))
9
+ * Better `steep stats` table formatting ([\#300](https://github.com/soutaro/steep/issues/300))
10
+ * Fix retry type checking ([\#293](https://github.com/soutaro/steep/issues/293))
11
+ * Better tuple type checking ([\#328](https://github.com/soutaro/steep/issues/328))
12
+ * Fix unexpected `add_call` error ([\#358](https://github.com/soutaro/steep/pull/358))
13
+ * Ignore passing nil as a block `&nil` ([\#356](https://github.com/soutaro/steep/pull/356))
14
+ * Better type checking for non-trivial block parameters ([\#354](https://github.com/soutaro/steep/pull/354))
15
+ * Avoid unexpected error on splat assignments ([\#330](https://github.com/soutaro/steep/pull/330))
16
+ * Fix constraint solver ([\#343](https://github.com/soutaro/steep/pull/343))
17
+ * Ruby 2.7 compatible private method call typing ([\#344](https://github.com/soutaro/steep/pull/344))
18
+
5
19
  ## 0.42.0 (2021-03-08)
6
20
 
7
21
  * Type checking performance improvement ([\#309](https://github.com/soutaro/steep/pull/309), [\#311](https://github.com/soutaro/steep/pull/311), [\#312](https://github.com/soutaro/steep/pull/312), [\#313](https://github.com/soutaro/steep/pull/313), [\#314](https://github.com/soutaro/steep/pull/314), [\#315](https://github.com/soutaro/steep/pull/315), [\#316](https://github.com/soutaro/steep/pull/316), [\#320](https://github.com/soutaro/steep/pull/320), [\#322](https://github.com/soutaro/steep/pull/322))
data/lib/steep.rb CHANGED
@@ -2,7 +2,6 @@ require "steep/version"
2
2
 
3
3
  require "pathname"
4
4
  require "parser/ruby27"
5
- require "ast_utils"
6
5
  require "active_support/core_ext/object/try"
7
6
  require "active_support/core_ext/string/inflections"
8
7
  require "logger"
@@ -15,8 +14,10 @@ require "open3"
15
14
  require "stringio"
16
15
  require 'uri'
17
16
  require "yaml"
17
+ require "securerandom"
18
18
 
19
19
  require "parallel/processor_count"
20
+ require "terminal-table"
20
21
 
21
22
  require "rbs"
22
23
 
@@ -169,9 +170,9 @@ module Steep
169
170
  end
170
171
 
171
172
  def self.log_error(exn, message: "Unexpected error: #{exn.inspect}")
172
- Steep.logger.error message
173
+ Steep.logger.fatal message
173
174
  exn.backtrace.each do |loc|
174
- Steep.logger.warn " #{loc}"
175
+ Steep.logger.error " #{loc}"
175
176
  end
176
177
  end
177
178
 
@@ -23,7 +23,15 @@ module Steep
23
23
  def initialize(source:, location:, exn: nil)
24
24
  @source = source
25
25
  @location = location
26
- super "Syntax error on annotation: `#{source}`, cause=#{exn.inspect}"
26
+
27
+ message = case exn
28
+ when RBS::Parser::SyntaxError
29
+ Diagnostic::Signature::SyntaxError.parser_syntax_error_message(exn)
30
+ else
31
+ exn.message
32
+ end
33
+
34
+ super message
27
35
  end
28
36
  end
29
37
 
@@ -158,7 +166,7 @@ module Steep
158
166
  end
159
167
  end
160
168
 
161
- rescue RBS::Parser::SyntaxError, RBS::Parser::SemanticsError => exn
169
+ rescue RBS::ParsingError => exn
162
170
  raise SyntaxError.new(source: src, location: location, exn: exn)
163
171
  end
164
172
  end
data/lib/steep/cli.rb CHANGED
@@ -113,6 +113,7 @@ module Steep
113
113
  opts.banner = "Usage: steep stats [options] [sources]"
114
114
 
115
115
  opts.on("--steepfile=PATH") {|path| check.steepfile = Pathname(path) }
116
+ opts.on("--format=FORMAT", "Specify output format: csv, table") {|format| check.format = format }
116
117
  handle_jobs_option check, opts
117
118
  handle_logging_options opts
118
119
  end.parse!(argv)
@@ -220,10 +220,6 @@ module Steep
220
220
  def header_line
221
221
  "The method cannot be called with a block"
222
222
  end
223
-
224
- def to_s
225
- format_message "method_type=#{method_type}"
226
- end
227
223
  end
228
224
 
229
225
  class RequiredBlockMissing < Base
@@ -395,8 +391,8 @@ module Steep
395
391
  @method = method
396
392
  end
397
393
 
398
- def to_s
399
- format_message "method=#{method}"
394
+ def header_line
395
+ "No superclass method `#{method}` defined"
400
396
  end
401
397
  end
402
398
 
@@ -614,6 +610,19 @@ module Steep
614
610
  "UnexpectedError: #{error.message}"
615
611
  end
616
612
  end
613
+
614
+ class SyntaxError < Base
615
+ attr_reader :message
616
+
617
+ def initialize(message: ,location:)
618
+ super(node: nil, location: location)
619
+ @message = message
620
+ end
621
+
622
+ def header_line
623
+ "SyntaxError: #{message}"
624
+ end
625
+ end
617
626
  end
618
627
  end
619
628
  end
@@ -37,19 +37,23 @@ module Steep
37
37
  @exception = exception
38
38
  end
39
39
 
40
+ def self.parser_syntax_error_message(exception)
41
+ value = if exception.error_value.is_a?(RBS::Parser::LocatedValue)
42
+ exception.error_value.value
43
+ else
44
+ exception.error_value
45
+ end
46
+ string = value.to_s
47
+ unless string.empty?
48
+ string = " (#{string})"
49
+ end
50
+
51
+ "Syntax error caused by token `#{exception.token_str}`#{string}"
52
+ end
53
+
40
54
  def header_line
41
55
  if exception.is_a?(RBS::Parser::SyntaxError)
42
- value = if exception.error_value.is_a?(RBS::Parser::LocatedValue)
43
- exception.error_value.value
44
- else
45
- exception.error_value
46
- end
47
- string = value.to_s
48
- unless string.empty?
49
- string = " (#{string})"
50
- end
51
-
52
- "Syntax error caused by token `#{exception.token_str}`#{string}"
56
+ SyntaxError.parser_syntax_error_message(exception)
53
57
  else
54
58
  exception.message
55
59
  end
@@ -271,6 +275,19 @@ module Steep
271
275
  end
272
276
  end
273
277
 
278
+ class UnexpectedError < Base
279
+ attr_reader :message
280
+
281
+ def initialize(message:, location:)
282
+ @message = message
283
+ @location = location
284
+ end
285
+
286
+ def header_line
287
+ "Unexpected error: #{message}"
288
+ end
289
+ end
290
+
274
291
  def self.from_rbs_error(error, factory:)
275
292
  case error
276
293
  when RBS::Parser::SemanticsError, RBS::Parser::LexerError
@@ -4,7 +4,6 @@ module Steep
4
4
  attr_reader :command_line_patterns
5
5
  attr_reader :stdout
6
6
  attr_reader :stderr
7
- attr_reader :labeling
8
7
 
9
8
  include Utils::DriverHelper
10
9
 
@@ -13,7 +12,6 @@ module Steep
13
12
  @stderr = stderr
14
13
 
15
14
  @command_line_patterns = []
16
- @labeling = ASTUtils::Labeling.new
17
15
  end
18
16
 
19
17
  def run
@@ -23,7 +21,7 @@ module Steep
23
21
 
24
22
  project.targets.each do |target|
25
23
  Steep.logger.tagged "target=#{target.name}" do
26
- service = Services::SignatureService.load_from(target.new_env_loader)
24
+ service = Services::SignatureService.load_from(target.new_env_loader(project: project))
27
25
 
28
26
  sigs = loader.load_changes(target.signature_pattern, changes: {})
29
27
  service.update(sigs)
@@ -47,16 +47,22 @@ module Steep
47
47
  interaction_worker: nil,
48
48
  typecheck_workers: typecheck_workers
49
49
  )
50
+ master.typecheck_automatically = false
51
+ master.commandline_args.push(*command_line_patterns)
50
52
 
51
53
  main_thread = Thread.start do
52
54
  master.start()
53
55
  end
54
56
  main_thread.abort_on_exception = true
55
57
 
56
- client_writer.write({ method: :initialize, id: 0 })
58
+ Steep.logger.info { "Initializing server" }
59
+ initialize_id = request_id()
60
+ client_writer.write({ method: :initialize, id: initialize_id, params: {} })
61
+ wait_for_response_id(reader: client_reader, id: initialize_id)
57
62
 
58
- shutdown_id = -1
59
- client_writer.write({ method: :shutdown, id: shutdown_id })
63
+ request_guid = SecureRandom.uuid
64
+ Steep.logger.info { "Starting type checking: #{request_guid}" }
65
+ client_writer.write({ method: "$/typecheck", params: { guid: request_guid } })
60
66
 
61
67
  diagnostic_notifications = []
62
68
  error_messages = []
@@ -77,14 +83,18 @@ module Steep
77
83
  if message[:type] == LSP::Constant::MessageType::ERROR
78
84
  error_messages << message[:message]
79
85
  end
80
- when response[:id] == shutdown_id
81
- break
86
+ when response[:method] == "$/progress"
87
+ if response[:params][:token] == request_guid
88
+ if response[:params][:value][:kind] == "end"
89
+ break
90
+ end
91
+ end
82
92
  end
83
93
  end
84
94
 
85
- client_writer.write({ method: :exit })
86
- client_writer.io.close()
95
+ Steep.logger.info { "Shutting down..." }
87
96
 
97
+ shutdown_exit(reader: client_reader, writer: client_writer)
88
98
  main_thread.join()
89
99
 
90
100
  stdout.puts
@@ -91,6 +91,10 @@ module Steep
91
91
  trailing = ""
92
92
  end
93
93
 
94
+ unless subject.valid_encoding?
95
+ subject.scrub!
96
+ end
97
+
94
98
  stdout.puts "#{prefix}└ #{leading}#{color_severity(subject, severity: diagnostic[:severity])}#{trailing}"
95
99
  stdout.puts "#{prefix} #{" " * leading.size}#{"~" * subject.size}"
96
100
  end
@@ -47,6 +47,7 @@ module Steep
47
47
  interaction_worker: interaction_worker,
48
48
  typecheck_workers: typecheck_workers
49
49
  )
50
+ master.typecheck_automatically = true
50
51
 
51
52
  master.start()
52
53
 
@@ -49,7 +49,7 @@ module Steep
49
49
  stdout.puts " - #{lib}"
50
50
  end
51
51
  stdout.puts " library dirs:"
52
- target.new_env_loader.tap do |loader|
52
+ target.new_env_loader(project: project).tap do |loader|
53
53
  loader.each_dir do |lib, path|
54
54
  case lib
55
55
  when :core
@@ -3,9 +3,103 @@ require "csv"
3
3
  module Steep
4
4
  module Drivers
5
5
  class Stats
6
+ class CSVPrinter
7
+ attr_reader :io
8
+
9
+ def initialize(io:)
10
+ @io = io
11
+ end
12
+
13
+ def print(stats_result)
14
+ io.puts(
15
+ CSV.generate do |csv|
16
+ csv << ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"]
17
+ stats_result.each do |row|
18
+ if row[:type] == "success"
19
+ csv << [
20
+ row[:target],
21
+ row[:path],
22
+ row[:type],
23
+ row[:typed_calls],
24
+ row[:untyped_calls],
25
+ row[:total_calls],
26
+ if row[:total_calls].nonzero?
27
+ (row[:typed_calls].to_f / row[:total_calls] * 100).to_i
28
+ else
29
+ 100
30
+ end
31
+ ]
32
+ else
33
+ csv << [
34
+ row[:target],
35
+ row[:path],
36
+ row[:type],
37
+ 0,
38
+ 0,
39
+ 0,
40
+ 0
41
+ ]
42
+ end
43
+ end
44
+ end
45
+ )
46
+ end
47
+ end
48
+
49
+ class TablePrinter
50
+ attr_reader :io
51
+
52
+ def initialize(io:)
53
+ @io = io
54
+ end
55
+
56
+ def print(stats_result)
57
+ rows = []
58
+ stats_result.sort_by {|row| row[:path] }.each do |row|
59
+ if row[:type] == "success"
60
+ rows << [
61
+ row[:target],
62
+ row[:path] + " ",
63
+ row[:type],
64
+ row[:typed_calls],
65
+ row[:untyped_calls],
66
+ row[:total_calls],
67
+ if row[:total_calls].nonzero?
68
+ "#{(row[:typed_calls].to_f / row[:total_calls] * 100).to_i}%"
69
+ else
70
+ "100%"
71
+ end
72
+ ]
73
+ else
74
+ rows << [
75
+ row[:target],
76
+ row[:path],
77
+ row[:type],
78
+ 0,
79
+ 0,
80
+ 0,
81
+ "N/A"
82
+ ]
83
+ end
84
+ end
85
+
86
+ table = Terminal::Table.new(
87
+ headings: ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"],
88
+ rows: rows
89
+ )
90
+ table.align_column(3, :right)
91
+ table.align_column(4, :right)
92
+ table.align_column(5, :right)
93
+ table.align_column(6, :right)
94
+ table.style = { border_top: false, border_bottom: false, border_y: "", border_i: "" }
95
+ io.puts(table)
96
+ end
97
+ end
98
+
6
99
  attr_reader :stdout
7
100
  attr_reader :stderr
8
101
  attr_reader :command_line_patterns
102
+ attr_accessor :format
9
103
 
10
104
  include Utils::DriverHelper
11
105
  include Utils::JobsCount
@@ -45,15 +139,27 @@ module Steep
45
139
  interaction_worker: nil,
46
140
  typecheck_workers: typecheck_workers
47
141
  )
142
+ master.typecheck_automatically = false
143
+ master.commandline_args.push(*command_line_patterns)
48
144
 
49
145
  main_thread = Thread.start do
50
146
  master.start()
51
147
  end
52
148
  main_thread.abort_on_exception = true
53
149
 
54
- client_writer.write({ method: :initialize, id: 0 })
150
+ initialize_id = request_id()
151
+ client_writer.write({ method: :initialize, id: initialize_id })
152
+ wait_for_response_id(reader: client_reader, id: initialize_id)
55
153
 
56
- stats_id = -1
154
+ typecheck_guid = SecureRandom.uuid
155
+ client_writer.write({ method: "$/typecheck", params: { guid: typecheck_guid }})
156
+ wait_for_message(reader: client_reader) do |message|
157
+ message[:method] == "$/progress" &&
158
+ message[:params][:token] == typecheck_guid &&
159
+ message[:params][:value][:kind] == "end"
160
+ end
161
+
162
+ stats_id = request_id()
57
163
  client_writer.write(
58
164
  {
59
165
  id: stats_id,
@@ -61,116 +167,30 @@ module Steep
61
167
  params: { command: "steep/stats", arguments: [] }
62
168
  })
63
169
 
64
- stats_result = []
65
- client_reader.read do |response|
66
- if response[:id] == stats_id
67
- stats_result.push(*response[:result])
68
- break
69
- end
70
- end
71
-
72
- shutdown_id = -2
73
- client_writer.write({ method: :shutdown, id: shutdown_id })
74
-
75
- client_reader.read do |response|
76
- if response[:id] == shutdown_id
77
- break
78
- end
79
- end
170
+ stats_response = wait_for_response_id(reader: client_reader, id: stats_id)
171
+ stats_result = stats_response[:result]
80
172
 
81
- client_writer.write({ method: "exit" })
173
+ shutdown_exit(reader: client_reader, writer: client_writer)
82
174
  main_thread.join()
83
175
 
84
- stdout.puts(
85
- CSV.generate do |csv|
86
- csv << ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"]
87
- stats_result.each do |row|
88
- if row[:type] == "success"
89
- csv << [
90
- row[:target],
91
- row[:path],
92
- row[:type],
93
- row[:typed_calls],
94
- row[:untyped_calls],
95
- row[:total_calls],
96
- if row[:total_calls].nonzero?
97
- (row[:typed_calls].to_f / row[:total_calls] * 100).to_i
176
+ printer = case format
177
+ when "csv"
178
+ CSVPrinter.new(io: stdout)
179
+ when "table"
180
+ TablePrinter.new(io: stdout)
181
+ when nil
182
+ if stdout.tty?
183
+ TablePrinter.new(io: stdout)
184
+ else
185
+ CSVPrinter.new(io: stdout)
186
+ end
98
187
  else
99
- 100
188
+ raise ArgumentError.new("Invalid format: #{format}")
100
189
  end
101
- ]
102
- else
103
- csv << [
104
- row[:target],
105
- row[:path],
106
- row[:type],
107
- 0,
108
- 0,
109
- 0,
110
- 0
111
- ]
112
- end
113
- end
114
- end
115
- )
116
- #
117
- # type_check(project)
118
- #
119
- # stdout.puts(
120
- # CSV.generate do |csv|
121
- # csv << ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"]
122
- #
123
- # project.targets.each do |target|
124
- # case (status = target.status)
125
- # when Project::Target::TypeCheckStatus
126
- # status.type_check_sources.each do |source_file|
127
- # case source_file.status
128
- # when Project::SourceFile::TypeCheckStatus
129
- # typing = source_file.status.typing
130
- #
131
- # typed = 0
132
- # untyped = 0
133
- # total = 0
134
- # typing.method_calls.each_value do |call|
135
- # case call
136
- # when TypeInference::MethodCall::Typed
137
- # typed += 1
138
- # when TypeInference::MethodCall::Untyped
139
- # untyped += 1
140
- # end
141
- #
142
- # total += 1
143
- # end
144
- #
145
- # csv << format_stats(target, source_file.path, "success", typed, untyped, total)
146
- # when Project::SourceFile::TypeCheckErrorStatus
147
- # csv << format_stats(target, source_file.path, "error", 0, 0, 0)
148
- # else
149
- # csv << format_stats(target, source_file.path, "unknown (#{source_file.status.class.to_s.split(/::/).last})", 0, 0, 0)
150
- # end
151
- # end
152
- # end
153
- # end
154
- # end
155
- # )
156
190
 
157
- 0
158
- end
191
+ printer.print(stats_result)
159
192
 
160
- def format_stats(target, path, status, typed, untyped, total)
161
- [
162
- target.name,
163
- path.to_s,
164
- status,
165
- typed,
166
- untyped,
167
- total,
168
- if total.nonzero?
169
- format("%.2f", (typed.to_f/total)*100)
170
- else
171
- 0
172
- end
173
- ]
193
+ 0
174
194
  end
175
195
  end
176
196
  end