steep 0.44.1 → 0.47.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) 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 +43 -0
  6. data/Gemfile +1 -4
  7. data/Gemfile.lock +73 -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/type_check_worker.rb +6 -9
  28. data/lib/steep/services/file_loader.rb +26 -19
  29. data/lib/steep/services/hover_content.rb +135 -80
  30. data/lib/steep/source.rb +12 -11
  31. data/lib/steep/type_construction.rb +435 -502
  32. data/lib/steep/type_inference/block_params.rb +3 -6
  33. data/lib/steep/type_inference/method_params.rb +483 -0
  34. data/lib/steep/type_inference/send_args.rb +599 -128
  35. data/lib/steep/typing.rb +46 -21
  36. data/lib/steep/version.rb +1 -1
  37. data/lib/steep.rb +5 -3
  38. data/sample/Steepfile +10 -3
  39. data/smoke/alias/Steepfile +2 -1
  40. data/smoke/and/Steepfile +2 -1
  41. data/smoke/array/Steepfile +2 -1
  42. data/smoke/array/test_expectations.yml +3 -3
  43. data/smoke/block/Steepfile +2 -2
  44. data/smoke/block/c.rb +0 -1
  45. data/smoke/case/Steepfile +2 -1
  46. data/smoke/class/Steepfile +2 -1
  47. data/smoke/class/test_expectations.yml +12 -15
  48. data/smoke/const/Steepfile +2 -1
  49. data/smoke/diagnostics/Steepfile +2 -1
  50. data/smoke/diagnostics/different_method_parameter_kind.rb +9 -0
  51. data/smoke/diagnostics/method_arity_mismatch.rb +2 -2
  52. data/smoke/diagnostics/method_parameter_mismatch.rb +10 -0
  53. data/smoke/diagnostics/test_expectations.yml +108 -31
  54. data/smoke/diagnostics-rbs/Steepfile +1 -1
  55. data/smoke/diagnostics-rbs/mixin-class-error.rbs +6 -0
  56. data/smoke/diagnostics-rbs/test_expectations.yml +12 -0
  57. data/smoke/diagnostics-rbs-duplicated/Steepfile +2 -1
  58. data/smoke/diagnostics-ruby-unsat/Steepfile +2 -1
  59. data/smoke/dstr/Steepfile +2 -1
  60. data/smoke/ensure/Steepfile +2 -1
  61. data/smoke/ensure/test_expectations.yml +3 -3
  62. data/smoke/enumerator/Steepfile +2 -1
  63. data/smoke/enumerator/test_expectations.yml +1 -1
  64. data/smoke/extension/Steepfile +2 -1
  65. data/smoke/extension/e.rbs +1 -1
  66. data/smoke/hash/Steepfile +2 -1
  67. data/smoke/hello/Steepfile +2 -1
  68. data/smoke/if/Steepfile +2 -1
  69. data/smoke/implements/Steepfile +2 -1
  70. data/smoke/initialize/Steepfile +2 -1
  71. data/smoke/integer/Steepfile +2 -1
  72. data/smoke/interface/Steepfile +2 -1
  73. data/smoke/kwbegin/Steepfile +2 -1
  74. data/smoke/lambda/Steepfile +2 -1
  75. data/smoke/literal/Steepfile +2 -1
  76. data/smoke/literal/test_expectations.yml +2 -2
  77. data/smoke/map/Steepfile +2 -1
  78. data/smoke/method/Steepfile +2 -1
  79. data/smoke/method/test_expectations.yml +11 -10
  80. data/smoke/module/Steepfile +2 -1
  81. data/smoke/regexp/Steepfile +2 -1
  82. data/smoke/regression/Steepfile +2 -1
  83. data/smoke/rescue/Steepfile +2 -1
  84. data/smoke/rescue/test_expectations.yml +3 -3
  85. data/smoke/self/Steepfile +2 -1
  86. data/smoke/skip/Steepfile +2 -1
  87. data/smoke/stdout/Steepfile +2 -1
  88. data/smoke/super/Steepfile +2 -1
  89. data/smoke/toplevel/Steepfile +2 -1
  90. data/smoke/toplevel/test_expectations.yml +3 -3
  91. data/smoke/tsort/Steepfile +4 -5
  92. data/smoke/tsort/test_expectations.yml +2 -2
  93. data/smoke/type_case/Steepfile +2 -1
  94. data/smoke/unexpected/Steepfile +2 -1
  95. data/smoke/yield/Steepfile +2 -1
  96. data/steep.gemspec +2 -2
  97. metadata +16 -10
  98. data/sig/project.rbi +0 -109
@@ -71,46 +71,104 @@ module Steep
71
71
  end
72
72
  end
73
73
 
74
- class IncompatibleArguments < Base
74
+ class UnexpectedPositionalArgument < Base
75
75
  attr_reader :node
76
+ attr_reader :method_type
76
77
  attr_reader :method_name
77
- attr_reader :receiver_type
78
- attr_reader :method_types
79
78
 
80
- def initialize(node:, method_name:, receiver_type:, method_types:)
81
- location = case node.type
82
- when :send
83
- node.loc.selector
84
- when :block
85
- node.children[0].yield_self do |node|
86
- node.loc.selector
87
- end
88
- when :super
89
- node.loc.expression
90
- else
91
- Steep.logger.error { "Unexpected node given: #{node.type} (IncompatibleArguments#initialize)"}
92
- node.loc.expression
93
- end
94
- super(node: node, location: location)
95
- @receiver_type = receiver_type
96
- @method_types = method_types
79
+ def initialize(node:, method_name:, method_type:)
80
+ super(node: node)
97
81
  @method_name = method_name
82
+ @method_type = method_type
98
83
  end
99
84
 
100
85
  def header_line
101
- "Cannot find method `#{method_name}` of type `#{receiver_type}` with compatible arity"
86
+ "Unexpected positional argument"
102
87
  end
88
+ end
103
89
 
104
- def detail_lines
105
- StringIO.new.tap do |io|
106
- io.puts "Method types:"
107
- first_type, *rest_types = method_types
108
- defn = " def #{method_name}"
109
- io.puts "#{defn}: #{first_type}"
110
- rest_types.each do |method_type|
111
- io.puts "#{" " * defn.size}| #{method_type}"
112
- end
113
- end.string.chomp
90
+ class InsufficientPositionalArguments < Base
91
+ attr_reader :node
92
+ attr_reader :method_name
93
+ attr_reader :method_type
94
+
95
+ def initialize(node:, method_name:, method_type:)
96
+ send = case node.type
97
+ when :send, :csend
98
+ node
99
+ when :block, :numblock
100
+ node.children[0]
101
+ end
102
+
103
+ loc = if send
104
+ send.loc.selector.with(end_pos: send.loc.expression.end_pos)
105
+ else
106
+ node.loc.expression
107
+ end
108
+
109
+ super(node: node, location: loc)
110
+ @method_name = method_name
111
+ @method_type = method_type
112
+ end
113
+
114
+ def header_line
115
+ "More positional arguments are required"
116
+ end
117
+ end
118
+
119
+ class UnexpectedKeywordArgument < Base
120
+ attr_reader :node
121
+ attr_reader :method_name
122
+ attr_reader :method_type
123
+
124
+ def initialize(node:, method_name:, method_type:)
125
+ loc = case node.type
126
+ when :pair
127
+ node.children[0].location.expression
128
+ when :kwsplat
129
+ node.location.expression
130
+ else
131
+ raise
132
+ end
133
+ super(node: node, location: loc)
134
+ @method_name = method_name
135
+ @method_type = method_type
136
+ end
137
+
138
+ def header_line
139
+ "Unexpected keyword argument"
140
+ end
141
+ end
142
+
143
+ class InsufficientKeywordArguments < Base
144
+ attr_reader :node
145
+ attr_reader :method_name
146
+ attr_reader :method_type
147
+ attr_reader :missing_keywords
148
+
149
+ def initialize(node:, method_name:, method_type:, missing_keywords:)
150
+ send = case node.type
151
+ when :send, :csend
152
+ node
153
+ when :block, :numblock
154
+ node.children[0]
155
+ end
156
+
157
+ loc = if send
158
+ send.loc.selector.with(end_pos: send.loc.expression.end_pos)
159
+ else
160
+ node.loc.expression
161
+ end
162
+
163
+ super(node: node, location: loc)
164
+
165
+ @method_name = method_name
166
+ @method_type = method_type
167
+ @missing_keywords = missing_keywords
168
+ end
169
+
170
+ def header_line
171
+ "More keyword arguments are required: #{missing_keywords.join(", ")}"
114
172
  end
115
173
  end
116
174
 
@@ -292,6 +350,23 @@ module Steep
292
350
  end
293
351
  end
294
352
 
353
+ class ImplicitBreakValueMismatch < Base
354
+ attr_reader :jump_type
355
+ attr_reader :result
356
+
357
+ include ResultPrinter
358
+
359
+ def initialize(node:, jump_type:, result:)
360
+ super(node: node)
361
+ @jump_type = jump_type
362
+ @result = result
363
+ end
364
+
365
+ def header_line
366
+ "Breaking without a value may result an error because a value of type `#{jump_type}` is expected"
367
+ end
368
+ end
369
+
295
370
  class UnexpectedJump < Base
296
371
  def header_line
297
372
  "Cannot jump from here"
@@ -323,6 +398,36 @@ module Steep
323
398
  end
324
399
  end
325
400
 
401
+ class MethodParameterMismatch < Base
402
+ attr_reader :method_param
403
+ attr_reader :method_type
404
+
405
+ def initialize(method_param:, method_type:)
406
+ super(node: method_param.node)
407
+ @method_param = method_param
408
+ @method_type = method_type
409
+ end
410
+
411
+ def header_line
412
+ "The method parameter is incompatible with the declaration `#{method_type}`"
413
+ end
414
+ end
415
+
416
+ class DifferentMethodParameterKind < Base
417
+ attr_reader :method_param
418
+ attr_reader :method_type
419
+
420
+ def initialize(method_param:, method_type:)
421
+ super(node: method_param.node)
422
+ @method_param = method_param
423
+ @method_type = method_type
424
+ end
425
+
426
+ def header_line
427
+ "The method parameter has different kind from the declaration `#{method_type}`"
428
+ end
429
+ end
430
+
326
431
  class IncompatibleMethodTypeAnnotation < Base
327
432
  attr_reader :interface_method
328
433
  attr_reader :annotation_method
@@ -366,7 +471,7 @@ module Steep
366
471
  include ResultPrinter
367
472
 
368
473
  def initialize(node:, expected:, actual:, result:)
369
- super(node: node)
474
+ super(node: node, location: node.loc.name)
370
475
  @expected = expected
371
476
  @actual = actual
372
477
  @result = result
@@ -552,34 +657,6 @@ module Steep
552
657
  end
553
658
  end
554
659
 
555
- class UnexpectedKeyword < Base
556
- attr_reader :unexpected_keywords
557
-
558
- def initialize(node:, unexpected_keywords:)
559
- super(node: node)
560
- @unexpected_keywords = unexpected_keywords
561
- end
562
-
563
- def header_line
564
- keywords = unexpected_keywords.sort.map {|x| "`#{x}`" }
565
- "Cannot specify unexpected keyword arguments: #{keywords.join(", ")}"
566
- end
567
- end
568
-
569
- class MissingKeyword < Base
570
- attr_reader :missing_keywords
571
-
572
- def initialize(node:, missing_keywords:)
573
- super(node: node)
574
- @missing_keywords = missing_keywords
575
- end
576
-
577
- def header_line
578
- keywords = missing_keywords.sort.map {|x| "`#{x}`" }
579
- "Cannot omit required keywords: #{keywords.join(", ")}"
580
- end
581
- end
582
-
583
660
  class UnsupportedSyntax < Base
584
661
  attr_reader :message
585
662
 
@@ -623,6 +700,57 @@ module Steep
623
700
  "SyntaxError: #{message}"
624
701
  end
625
702
  end
703
+
704
+ ALL = ObjectSpace.each_object(Class).with_object([]) do |klass, array|
705
+ if klass < Base
706
+ array << klass
707
+ end
708
+ end
709
+
710
+ def self.all_error
711
+ @all_error ||= ALL.each.with_object({}) do |klass, hash|
712
+ hash[klass] = LSPFormatter::ERROR
713
+ end.freeze
714
+ end
715
+
716
+ def self.default
717
+ @default ||= all_error.merge(
718
+ {
719
+ ImplicitBreakValueMismatch => :warning,
720
+ FallbackAny => :information,
721
+ ElseOnExhaustiveCase => :warning,
722
+ UnknownConstantAssigned => :warning,
723
+ MethodDefinitionMissing => :information
724
+ }
725
+ ).freeze
726
+ end
727
+
728
+ def self.strict
729
+ @strict ||= all_error.merge(
730
+ {
731
+ NoMethod => nil,
732
+ ImplicitBreakValueMismatch => nil,
733
+ FallbackAny => nil,
734
+ ElseOnExhaustiveCase => nil,
735
+ UnknownConstantAssigned => nil,
736
+ MethodDefinitionMissing => nil
737
+ }
738
+ ).freeze
739
+ end
740
+
741
+ def self.lenient
742
+ @lenient ||= all_error.merge(
743
+ {
744
+ NoMethod => nil,
745
+ ImplicitBreakValueMismatch => nil,
746
+ FallbackAny => nil,
747
+ ElseOnExhaustiveCase => nil,
748
+ UnknownConstantAssigned => nil,
749
+ MethodDefinitionMissing => nil,
750
+ UnexpectedJump => nil
751
+ }
752
+ ).freeze
753
+ end
626
754
  end
627
755
  end
628
756
  end
@@ -38,25 +38,16 @@ module Steep
38
38
  end
39
39
 
40
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
41
+ string = exception.location.source.to_s
47
42
  unless string.empty?
48
43
  string = " (#{string})"
49
44
  end
50
45
 
51
- "Syntax error caused by token `#{exception.token_str}`#{string}"
46
+ "Syntax error caused by token `#{exception.token_type}`#{string}"
52
47
  end
53
48
 
54
49
  def header_line
55
- if exception.is_a?(RBS::Parser::SyntaxError)
56
- SyntaxError.parser_syntax_error_message(exception)
57
- else
58
- exception.message
59
- end
50
+ exception.message
60
51
  end
61
52
  end
62
53
 
@@ -275,6 +266,34 @@ module Steep
275
266
  end
276
267
  end
277
268
 
269
+ class MixinClassError < Base
270
+ attr_reader :member
271
+ attr_reader :type_name
272
+
273
+ def initialize(location:, member:, type_name:)
274
+ super(location: location)
275
+ @member = member
276
+ @type_name = type_name
277
+ end
278
+
279
+ def header_line
280
+ "Cannot #{mixin_name} a class `#{member.name}` in the definition of `#{type_name}`"
281
+ end
282
+
283
+ private
284
+
285
+ def mixin_name
286
+ case member
287
+ when RBS::AST::Members::Prepend
288
+ "prepend"
289
+ when RBS::AST::Members::Include
290
+ "include"
291
+ when RBS::AST::Members::Extend
292
+ "extend"
293
+ end
294
+ end
295
+ end
296
+
278
297
  class UnexpectedError < Base
279
298
  attr_reader :message
280
299
 
@@ -290,10 +309,8 @@ module Steep
290
309
 
291
310
  def self.from_rbs_error(error, factory:)
292
311
  case error
293
- when RBS::Parser::SemanticsError, RBS::Parser::LexerError
312
+ when RBS::ParsingError
294
313
  Diagnostic::Signature::SyntaxError.new(error, location: error.location)
295
- when RBS::Parser::SyntaxError
296
- Diagnostic::Signature::SyntaxError.new(error, location: error.error_value.location)
297
314
  when RBS::DuplicatedDeclarationError
298
315
  Diagnostic::Signature::DuplicatedDeclaration.new(
299
316
  type_name: error.name,
@@ -365,6 +382,12 @@ module Steep
365
382
  param: error.param,
366
383
  location: error.location
367
384
  )
385
+ when RBS::MixinClassError
386
+ Diagnostic::Signature::MixinClassError.new(
387
+ location: error.location,
388
+ type_name: error.type_name,
389
+ member: error.member,
390
+ )
368
391
  else
369
392
  raise error
370
393
  end
@@ -8,6 +8,7 @@ module Steep
8
8
  attr_reader :command_line_patterns
9
9
  attr_accessor :with_expectations_path
10
10
  attr_accessor :save_expectations_path
11
+ attr_accessor :severity_level
11
12
 
12
13
  include Utils::DriverHelper
13
14
  include Utils::JobsCount
@@ -16,6 +17,7 @@ module Steep
16
17
  @stdout = stdout
17
18
  @stderr = stderr
18
19
  @command_line_patterns = []
20
+ @severity_level = :warning
19
21
  end
20
22
 
21
23
  def run
@@ -70,6 +72,7 @@ module Steep
70
72
  case
71
73
  when response[:method] == "textDocument/publishDiagnostics"
72
74
  ds = response[:params][:diagnostics]
75
+ ds.select! {|d| keep_diagnostic?(d) }
73
76
  if ds.empty?
74
77
  stdout.print "."
75
78
  else
@@ -8,6 +8,8 @@ module Steep
8
8
  include Utils::DriverHelper
9
9
 
10
10
  TEMPLATE = <<~EOF
11
+ # D = Steep::Diagnostic
12
+ #
11
13
  # target :lib do
12
14
  # signature "sig"
13
15
  #
@@ -18,15 +20,20 @@ module Steep
18
20
  #
19
21
  # # library "pathname", "set" # Standard libraries
20
22
  # # library "strong_json" # Gems
23
+ #
24
+ # # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
25
+ # # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
26
+ # # configure_code_diagnostics do |hash| # You can setup everything yourself
27
+ # # hash[D::Ruby::NoMethod] = :information
28
+ # # end
21
29
  # end
22
30
 
23
- # target :spec do
31
+ # target :test do
24
32
  # signature "sig", "sig-private"
25
33
  #
26
- # check "spec"
34
+ # check "test"
27
35
  #
28
36
  # # library "pathname", "set" # Standard libraries
29
- # # library "rspec"
30
37
  # end
31
38
  EOF
32
39
 
@@ -55,6 +55,21 @@ module Steep
55
55
  end
56
56
  end
57
57
  end
58
+
59
+ def keep_diagnostic?(diagnostic)
60
+ severity = diagnostic[:severity]
61
+
62
+ case self.severity_level
63
+ when nil, :hint
64
+ true
65
+ when :error
66
+ severity <= LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR
67
+ when :warning
68
+ severity <= LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING
69
+ when :information
70
+ severity <= LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION
71
+ end
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -36,7 +36,7 @@ module Steep
36
36
 
37
37
  any_error ||= !errors.empty?
38
38
 
39
- formatter = Diagnostic::LSPFormatter.new
39
+ formatter = Diagnostic::LSPFormatter.new({})
40
40
  diagnostics = errors.group_by {|e| e.location.buffer }.transform_values do |errors|
41
41
  errors.map {|error| formatter.format(error) }
42
42
  end
@@ -5,6 +5,7 @@ module Steep
5
5
  attr_reader :stdout
6
6
  attr_reader :stderr
7
7
  attr_reader :queue
8
+ attr_accessor :severity_level
8
9
 
9
10
  include Utils::DriverHelper
10
11
  include Utils::JobsCount
@@ -16,6 +17,7 @@ module Steep
16
17
  @stdout = stdout
17
18
  @stderr = stderr
18
19
  @queue = Thread::Queue.new
20
+ @severity_level = :warning
19
21
  end
20
22
 
21
23
  def watching?(changed_path, files:, dirs:)
@@ -126,6 +128,7 @@ module Steep
126
128
  printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
127
129
 
128
130
  diagnostics = response[:params][:diagnostics]
131
+ diagnostics.filter! {|d| keep_diagnostic?(d) }
129
132
 
130
133
  unless diagnostics.empty?
131
134
  diagnostics.each do |diagnostic|
@@ -0,0 +1,21 @@
1
+ module Steep
2
+ module Equatable
3
+ def ==(other)
4
+ if other.class == self.class
5
+ instance_variables.all? do |name|
6
+ other.instance_variable_get(name) == instance_variable_get(name)
7
+ end
8
+ end
9
+ end
10
+
11
+ def eql?(other)
12
+ self == other
13
+ end
14
+
15
+ def hash
16
+ instance_variables.inject(self.class.hash) do |hash, name|
17
+ hash ^ instance_variable_get(name).hash
18
+ end
19
+ end
20
+ end
21
+ end