steep 0.42.0 → 0.43.0

Sign up to get free protection for your applications and to get access to all the features.
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