steep 1.5.0.pre.5 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed79428764aa83fbd2e7f3c5a8e52ff863b4e65bef05aae43ba9ca4b7c9d2f4e
4
- data.tar.gz: be79772ce19f152e3048ae565802640cae96f1bfca98adf60eab47a28ab3ccad
3
+ metadata.gz: 77aea7baf36285cf4a5b0941e63cc8f5f95d52132d3a5d4933a396a79b949721
4
+ data.tar.gz: 2506370595dde6633abbab74b1e370d435579415a154b4a82b4da3bb1b065627
5
5
  SHA512:
6
- metadata.gz: aa071be8eaf425a2a36ffed4c8d0a97446997f9cbf85c9dc4190b5bf26c9bf5f66c7106ab27b96378d05e246a6327689ef491dedbd16ad7cb9eb8d8079b3818d
7
- data.tar.gz: d7f842d51c84a2f48bdfd79a07de3e6265dd746497aa9661871f12b062832c2a24f46b37ce1ddb614ac5ba1c65f4c408e9e1eafec27fd69d7158514fe77e0fec
6
+ metadata.gz: 6f3190f0ca65a931f9c88d99cbd67836478d754439b5630b1b68f89e9fa6d84a84cc8bde32335d4bdb38b3f13aefaeaf93a75b53722a0b6841eab27e9b689ed8
7
+ data.tar.gz: 94576f1eedcb8c46ca8228e84e4100e860c0e3ed2a28dc209afa80abdeeed188118f1589dcb15d0a8edb0cd8c751f611035a15b613d284113c8cbebcce42dc6e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.5.0 (2023-07-13)
6
+
7
+ ### Type checker core
8
+
9
+ * Fix for the case `untyped` is the proc type hint ([#868](https://github.com/soutaro/steep/pull/868))
10
+ * Type case with type variable ([#869](https://github.com/soutaro/steep/pull/869))
11
+ * Filx `nil?` unreachability detection ([#867](https://github.com/soutaro/steep/pull/867))
12
+
13
+ ### Commandline tool
14
+
15
+ * Update `#configure_code_diagnostics` type ([#873](https://github.com/soutaro/steep/pull/873))
16
+ * Update diagnostics templates ([#871](https://github.com/soutaro/steep/pull/871))
17
+ * Removed "set" from "libray" in init.rb and README ([#870](https://github.com/soutaro/steep/pull/870))
18
+
19
+ ### Language server
20
+
21
+ * Use RBS::Buffer method to calculate position ([#872](https://github.com/soutaro/steep/pull/872))
22
+
23
+ ## 1.5.0.pre.6 (2023-07-11)
24
+
25
+ ### Type checker core
26
+
27
+ * Report RBS validation errors in Ruby code ([#859](https://github.com/soutaro/steep/pull/859))
28
+ * Fix proc type assignment ([#858](https://github.com/soutaro/steep/pull/858))
29
+ * Report `UnexpectedKeywordArgument` even if no keyword param is accepted ([#856](https://github.com/soutaro/steep/pull/856))
30
+ * Unfold type alias on unwrap optional ([#855](https://github.com/soutaro/steep/pull/855))
31
+
32
+ ### Language server
33
+
34
+ * Keyword completion in block call ([#865](https://github.com/soutaro/steep/pull/865))
35
+ * Indicate the current or next argument on signature help ([#850](https://github.com/soutaro/steep/pull/850))
36
+ * Support completion for keyword arguments ([#851](https://github.com/soutaro/steep/pull/851))
37
+ * Let hover show the type of method call node ([#864](https://github.com/soutaro/steep/pull/864))
38
+ * Fix UnknownNodeError in SignatureHelp ([#863](https://github.com/soutaro/steep/pull/863))
39
+ * hover: Fix NoMethodError on generating hover for not supported files ([#853](https://github.com/soutaro/steep/pull/853))
40
+
5
41
  ## 1.5.0.pre.5 (2023-07-07)
6
42
 
7
43
  ### Type checker core
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- steep (1.5.0.pre.5)
4
+ steep (1.5.0)
5
5
  activesupport (>= 5.1)
6
6
  concurrent-ruby (>= 1.1.10)
7
7
  csv (>= 3.0.9)
data/README.md CHANGED
@@ -30,7 +30,7 @@ target :app do
30
30
  check "lib"
31
31
  signature "sig"
32
32
 
33
- library "set", "pathname"
33
+ library "pathname"
34
34
  end
35
35
  ```
36
36
 
@@ -37,10 +37,12 @@ module Steep
37
37
  ty = ty.map_type_name {|name| resolver.resolve(name, context: context) || name.absolute! }
38
38
 
39
39
  validator = Signature::Validator.new(checker: subtyping)
40
- validator.validate_type(ty)
40
+ validator.rescue_validation_errors do
41
+ validator.validate_type(ty)
42
+ end
41
43
 
42
44
  if validator.has_error?
43
- return
45
+ return validator.each_error
44
46
  end
45
47
 
46
48
  ty = subtyping.factory.type(ty)
@@ -58,7 +60,7 @@ module Steep
58
60
 
59
61
  def types?(context, subtyping, type_vars)
60
62
  case types = types(context, subtyping, type_vars)
61
- when RBS::ParsingError
63
+ when RBS::ParsingError, Enumerator
62
64
  nil
63
65
  else
64
66
  types
@@ -22,10 +22,14 @@ module Steep
22
22
  ty = ty.map_type_name {|name| resolver.resolve(name, context: context) || name.absolute! }
23
23
 
24
24
  validator = Signature::Validator.new(checker: subtyping)
25
- validator.validate_type(ty)
25
+ validator.rescue_validation_errors do
26
+ validator.validate_type(ty)
27
+ end
26
28
 
27
29
  unless validator.has_error?
28
30
  subtyping.factory.type(ty)
31
+ else
32
+ validator.each_error.to_a
29
33
  end
30
34
  else
31
35
  nil
@@ -37,7 +41,7 @@ module Steep
37
41
  def type_syntax?
38
42
  RBS::Parser.parse_type(type_location.buffer, range: type_location.range, variables: [], require_eof: true)
39
43
  true
40
- rescue::RBS::ParsingError
44
+ rescue ::RBS::ParsingError
41
45
  false
42
46
  end
43
47
 
@@ -45,7 +49,7 @@ module Steep
45
49
  type = type(context, subtyping, type_vars)
46
50
 
47
51
  case type
48
- when RBS::ParsingError, nil
52
+ when RBS::ParsingError, nil, Array
49
53
  nil
50
54
  else
51
55
  type
@@ -401,6 +401,13 @@ module Steep
401
401
  end
402
402
  when AST::Types::Nil
403
403
  nil
404
+ when AST::Types::Name::Alias
405
+ type_ = expand_alias(type)
406
+ if type_ == type
407
+ type_
408
+ else
409
+ unwrap_optional(type_)
410
+ end
404
411
  else
405
412
  type
406
413
  end
@@ -964,6 +964,19 @@ module Steep
964
964
  end
965
965
  end
966
966
 
967
+ class RBSError < Base
968
+ attr_reader :error
969
+
970
+ def initialize(error:, node:, location:)
971
+ @error = error
972
+ super(node: node, location: location)
973
+ end
974
+
975
+ def header_line
976
+ error.header_line
977
+ end
978
+ end
979
+
967
980
  ALL = ObjectSpace.each_object(Class).with_object([]) do |klass, array|
968
981
  if klass < Base
969
982
  array << klass
@@ -979,20 +992,57 @@ module Steep
979
992
  def self.default
980
993
  @default ||= _ = all_error.merge(
981
994
  {
982
- ImplicitBreakValueMismatch => :warning,
983
- FallbackAny => :information,
984
- UnreachableValueBranch => :warning,
985
- UnreachableBranch => :information,
995
+ ArgumentTypeMismatch => :error,
996
+ BlockBodyTypeMismatch => :hint,
997
+ BlockTypeMismatch => :hint,
998
+ BreakTypeMismatch => :hint,
999
+ DifferentMethodParameterKind => :hint,
1000
+ FallbackAny => :hint,
1001
+ FalseAssertion => :hint,
1002
+ ImplicitBreakValueMismatch => :hint,
1003
+ IncompatibleAnnotation => :hint,
1004
+ IncompatibleArgumentForwarding => :warning,
1005
+ IncompatibleAssignment => :hint,
1006
+ IncompatibleMethodTypeAnnotation => :hint,
1007
+ IncompatibleTypeCase => :hint,
1008
+ InsufficientKeywordArguments => :error,
1009
+ InsufficientPositionalArguments => :error,
1010
+ InsufficientTypeArgument => :hint,
1011
+ MethodArityMismatch => :error,
1012
+ MethodBodyTypeMismatch => :error,
1013
+ MethodDefinitionMissing => nil,
1014
+ MethodParameterMismatch => :error,
1015
+ MethodReturnTypeAnnotationMismatch => :hint,
1016
+ MultipleAssignmentConversionError => :hint,
1017
+ NoMethod => :error,
1018
+ ProcHintIgnored => :hint,
1019
+ ProcTypeExpected => :hint,
1020
+ RBSError => :information,
1021
+ RequiredBlockMissing => :error,
1022
+ ReturnTypeMismatch => :error,
1023
+ SetterBodyTypeMismatch => :information,
1024
+ SetterReturnTypeMismatch => :information,
1025
+ SyntaxError => :hint,
1026
+ TypeArgumentMismatchError => :hint,
1027
+ UnexpectedBlockGiven => :warning,
1028
+ UnexpectedDynamicMethod => :hint,
1029
+ UnexpectedError => :hint,
1030
+ UnexpectedJump => :hint,
1031
+ UnexpectedJumpValue => :hint,
1032
+ UnexpectedKeywordArgument => :error,
1033
+ UnexpectedPositionalArgument => :error,
1034
+ UnexpectedSplat => :hint,
1035
+ UnexpectedSuper => :information,
1036
+ UnexpectedTypeArgument => :hint,
1037
+ UnexpectedYield => :warning,
986
1038
  UnknownConstant => :warning,
987
- MethodDefinitionMissing => :information,
988
- FalseAssertion => :information,
989
- UnexpectedTypeArgument => :information,
990
- InsufficientTypeArgument => :information,
991
- UnexpectedTypeArgument => :information,
992
- UnsupportedSyntax => nil,
993
- ProcHintIgnored => :information,
994
- SetterBodyTypeMismatch => nil,
995
- SetterReturnTypeMismatch => nil
1039
+ UnknownGlobalVariable => :warning,
1040
+ UnknownInstanceVariable => :information,
1041
+ UnreachableBranch => :hint,
1042
+ UnreachableValueBranch => :hint,
1043
+ UnresolvedOverloading => :error,
1044
+ UnsatisfiableConstraint => :hint,
1045
+ UnsupportedSyntax => :hint,
996
1046
  }
997
1047
  ).freeze
998
1048
  end
@@ -1000,17 +1050,57 @@ module Steep
1000
1050
  def self.strict
1001
1051
  @strict ||= _ = all_error.merge(
1002
1052
  {
1003
- NoMethod => nil,
1004
- ImplicitBreakValueMismatch => nil,
1005
- FallbackAny => nil,
1006
- UnreachableValueBranch => nil,
1007
- UnreachableBranch => nil,
1008
- UnknownConstant => nil,
1009
- MethodDefinitionMissing => nil,
1010
- UnsupportedSyntax => nil,
1011
- ProcHintIgnored => :warning,
1053
+ ArgumentTypeMismatch => :error,
1054
+ BlockBodyTypeMismatch => :error,
1055
+ BlockTypeMismatch => :error,
1056
+ BreakTypeMismatch => :error,
1057
+ DifferentMethodParameterKind => :error,
1058
+ FallbackAny => :warning,
1059
+ FalseAssertion => :error,
1060
+ ImplicitBreakValueMismatch => :information,
1061
+ IncompatibleAnnotation => :error,
1062
+ IncompatibleArgumentForwarding => :error,
1063
+ IncompatibleAssignment => :error,
1064
+ IncompatibleMethodTypeAnnotation => :error,
1065
+ IncompatibleTypeCase => :error,
1066
+ InsufficientKeywordArguments => :error,
1067
+ InsufficientPositionalArguments => :error,
1068
+ InsufficientTypeArgument => :error,
1069
+ MethodArityMismatch => :error,
1070
+ MethodBodyTypeMismatch => :error,
1071
+ MethodDefinitionMissing => :hint,
1072
+ MethodParameterMismatch => :error,
1073
+ MethodReturnTypeAnnotationMismatch => :error,
1074
+ MultipleAssignmentConversionError => :error,
1075
+ NoMethod => :error,
1076
+ ProcHintIgnored => :information,
1077
+ ProcTypeExpected => :error,
1078
+ RBSError => :error,
1079
+ RequiredBlockMissing => :error,
1080
+ ReturnTypeMismatch => :error,
1012
1081
  SetterBodyTypeMismatch => :error,
1013
- SetterReturnTypeMismatch => :error
1082
+ SetterReturnTypeMismatch => :error,
1083
+ SyntaxError => :hint,
1084
+ TypeArgumentMismatchError => :error,
1085
+ UnexpectedBlockGiven => :error,
1086
+ UnexpectedDynamicMethod => :information,
1087
+ UnexpectedError => :information,
1088
+ UnexpectedJump => :error,
1089
+ UnexpectedJumpValue => :error,
1090
+ UnexpectedKeywordArgument => :error,
1091
+ UnexpectedPositionalArgument => :error,
1092
+ UnexpectedSplat => :warning,
1093
+ UnexpectedSuper => :error,
1094
+ UnexpectedTypeArgument => :error,
1095
+ UnexpectedYield => :error,
1096
+ UnknownConstant => :error,
1097
+ UnknownGlobalVariable => :error,
1098
+ UnknownInstanceVariable => :error,
1099
+ UnreachableBranch => :information,
1100
+ UnreachableValueBranch => :warning,
1101
+ UnresolvedOverloading => :error,
1102
+ UnsatisfiableConstraint => :error,
1103
+ UnsupportedSyntax => :information,
1014
1104
  }
1015
1105
  ).freeze
1016
1106
  end
@@ -1018,19 +1108,57 @@ module Steep
1018
1108
  def self.lenient
1019
1109
  @lenient ||= _ = all_error.merge(
1020
1110
  {
1021
- NoMethod => nil,
1022
- ImplicitBreakValueMismatch => nil,
1111
+ ArgumentTypeMismatch => :information,
1112
+ BlockBodyTypeMismatch => :hint,
1113
+ BlockTypeMismatch => :hint,
1114
+ BreakTypeMismatch => :hint,
1115
+ DifferentMethodParameterKind => nil,
1023
1116
  FallbackAny => nil,
1024
- UnreachableValueBranch => nil,
1025
- UnreachableBranch => nil,
1026
- UnknownConstant => nil,
1117
+ FalseAssertion => nil,
1118
+ ImplicitBreakValueMismatch => nil,
1119
+ IncompatibleAnnotation => nil,
1120
+ IncompatibleArgumentForwarding => :information,
1121
+ IncompatibleAssignment => :hint,
1122
+ IncompatibleMethodTypeAnnotation => nil,
1123
+ IncompatibleTypeCase => nil,
1124
+ InsufficientKeywordArguments => :information,
1125
+ InsufficientPositionalArguments => :information,
1126
+ InsufficientTypeArgument => nil,
1127
+ MethodArityMismatch => :information,
1128
+ MethodBodyTypeMismatch => :warning,
1027
1129
  MethodDefinitionMissing => nil,
1028
- UnexpectedJump => nil,
1029
- FalseAssertion => :hint,
1030
- UnsupportedSyntax => nil,
1031
- ProcHintIgnored => :hint,
1130
+ MethodParameterMismatch => :warning,
1131
+ MethodReturnTypeAnnotationMismatch => nil,
1132
+ MultipleAssignmentConversionError => nil,
1133
+ NoMethod => :information,
1134
+ ProcHintIgnored => nil,
1135
+ ProcTypeExpected => nil,
1136
+ RBSError => :information,
1137
+ RequiredBlockMissing => :hint,
1138
+ ReturnTypeMismatch => :warning,
1032
1139
  SetterBodyTypeMismatch => nil,
1033
- SetterReturnTypeMismatch => nil
1140
+ SetterReturnTypeMismatch => nil,
1141
+ SyntaxError => :hint,
1142
+ TypeArgumentMismatchError => nil,
1143
+ UnexpectedBlockGiven => :hint,
1144
+ UnexpectedDynamicMethod => nil,
1145
+ UnexpectedError => :hint,
1146
+ UnexpectedJump => nil,
1147
+ UnexpectedJumpValue => nil,
1148
+ UnexpectedKeywordArgument => :information,
1149
+ UnexpectedPositionalArgument => :information,
1150
+ UnexpectedSplat => nil,
1151
+ UnexpectedSuper => nil,
1152
+ UnexpectedTypeArgument => nil,
1153
+ UnexpectedYield => :information,
1154
+ UnknownConstant => :hint,
1155
+ UnknownGlobalVariable => :hint,
1156
+ UnknownInstanceVariable => :hint,
1157
+ UnreachableBranch => :hint,
1158
+ UnreachableValueBranch => :hint,
1159
+ UnresolvedOverloading => :information,
1160
+ UnsatisfiableConstraint => :hint,
1161
+ UnsupportedSyntax => :hint,
1034
1162
  }
1035
1163
  ).freeze
1036
1164
  end
@@ -18,9 +18,10 @@ module Steep
18
18
  # check "app/models/**/*.rb" # Glob
19
19
  # # ignore "lib/templates/*.rb"
20
20
  #
21
- # # library "pathname", "set" # Standard libraries
21
+ # # library "pathname" # Standard libraries
22
22
  # # library "strong_json" # Gems
23
23
  #
24
+ # # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
24
25
  # # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
25
26
  # # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
26
27
  # # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
@@ -34,7 +35,7 @@ module Steep
34
35
  #
35
36
  # check "test"
36
37
  #
37
- # # library "pathname", "set" # Standard libraries
38
+ # # library "pathname" # Standard libraries
38
39
  # end
39
40
  EOF
40
41
 
@@ -217,5 +217,28 @@ module Steep
217
217
  false
218
218
  end
219
219
  end
220
+
221
+ def deconstruct_sendish_and_block_nodes(*nodes)
222
+ send_node, block_node = nodes.take(2)
223
+
224
+ if send_node
225
+ case send_node.type
226
+ when :send, :csend, :super
227
+ if block_node
228
+ case block_node.type
229
+ when :block, :numblock
230
+ if send_node.equal?(block_node.children[0])
231
+ return [send_node, block_node]
232
+ end
233
+ end
234
+ end
235
+
236
+ [send_node, nil]
237
+ when :zsuper
238
+ # zsuper doesn't receive block
239
+ [send_node, nil]
240
+ end
241
+ end
242
+ end
220
243
  end
221
244
  end
@@ -143,27 +143,6 @@ module Steep
143
143
  @repo_paths.push(*paths.map {|s| Pathname(s) })
144
144
  end
145
145
 
146
- # Configure the code diagnostics printing setup.
147
- #
148
- # Yields a hash, and the update the hash in the block.
149
- #
150
- # ```rb
151
- # D = Steep::Diagnostic
152
- #
153
- # configure_code_diagnostics do |hash|
154
- # # Assign one of :error, :warning, :information, :hint or :nil to error classes.
155
- # hash[D::Ruby::UnexpectedPositionalArgument] = :error
156
- # end
157
- # ```
158
- #
159
- # Passing a hash is also allowed.
160
- #
161
- # ```rb
162
- # D = Steep::Diagnostic
163
- #
164
- # configure_code_diagnostics(D::Ruby.lenient)
165
- # ```
166
- #
167
146
  def configure_code_diagnostics(hash = nil)
168
147
  if hash
169
148
  code_diagnostics_config.merge!(hash)
@@ -361,6 +361,17 @@ module Steep
361
361
  new_text: item.identifier.to_s
362
362
  )
363
363
  )
364
+ when Services::CompletionProvider::KeywordArgumentItem
365
+ LSP::Interface::CompletionItem.new(
366
+ label: item.identifier.to_s,
367
+ kind: LSP::Constant::CompletionItemKind::FIELD,
368
+ label_details: LSP::Interface::CompletionItemLabelDetails.new(description: 'Keyword argument'),
369
+ documentation: LSPFormatter.markup_content { LSPFormatter.format_completion_docs(item) },
370
+ text_edit: LSP::Interface::TextEdit.new(
371
+ range: range,
372
+ new_text: item.identifier.to_s
373
+ )
374
+ )
364
375
  when Services::CompletionProvider::TypeNameItem
365
376
  kind =
366
377
  case
@@ -397,6 +408,8 @@ module Steep
397
408
  signatures = items.map do |item|
398
409
  LSP::Interface::SignatureInformation.new(
399
410
  label: "(#{item.method_type.type.param_to_s})",
411
+ parameters: item.parameters.map { |param| LSP::Interface::ParameterInformation.new(label: param)},
412
+ active_parameter: item.active_parameter,
400
413
  documentation: item.comment&.yield_self do |comment|
401
414
  LSP::Interface::MarkupContent.new(
402
415
  kind: LSP::Constant::MarkupKind::MARKDOWN,
@@ -28,7 +28,16 @@ module Steep
28
28
 
29
29
  case call
30
30
  when TypeInference::MethodCall::Typed
31
+ io.puts <<~MD
32
+ ```rbs
33
+ #{call.actual_method_type.type.return_type}
34
+ ```
35
+
36
+ ----
37
+ MD
38
+
31
39
  method_types = call.method_decls.map(&:method_type)
40
+
32
41
  if call.is_a?(TypeInference::MethodCall::Special)
33
42
  method_types = [
34
43
  call.actual_method_type.with(
@@ -250,6 +259,10 @@ module Steep
250
259
  end
251
260
 
252
261
  io.string
262
+ when Services::CompletionProvider::KeywordArgumentItem
263
+ <<~MD
264
+ **Keyword argument**: `#{item.identifier}`
265
+ MD
253
266
  end
254
267
  end
255
268
 
@@ -1,6 +1,8 @@
1
1
  module Steep
2
2
  module Services
3
3
  class CompletionProvider
4
+ include NodeHelper
5
+
4
6
  Position = _ = Struct.new(:line, :column, keyword_init: true) do
5
7
  # @implements Position
6
8
  def -(size)
@@ -11,6 +13,7 @@ module Steep
11
13
  Range = _ = Struct.new(:start, :end, keyword_init: true)
12
14
 
13
15
  InstanceVariableItem = _ = Struct.new(:identifier, :range, :type, keyword_init: true)
16
+ KeywordArgumentItem = _ = Struct.new(:identifier, :range, keyword_init: true)
14
17
  LocalVariableItem = _ = Struct.new(:identifier, :range, :type, keyword_init: true)
15
18
  ConstantItem = _ = Struct.new(:env, :identifier, :range, :type, :full_name, keyword_init: true) do
16
19
  # @implements ConstantItem
@@ -267,7 +270,9 @@ module Steep
267
270
  []
268
271
  end
269
272
  else
270
- []
273
+ items = [] #: Array[item]
274
+ items_for_following_keyword_arguments(source_text, index: index, line: line, column: column, items: items)
275
+ items
271
276
  end
272
277
  end
273
278
  end
@@ -302,10 +307,10 @@ module Steep
302
307
  end
303
308
 
304
309
  def items_for_trigger(position:)
305
- node, *_parents = source.find_nodes(line: position.line, column: position.column)
310
+ node, *parents = source.find_nodes(line: position.line, column: position.column)
306
311
  node ||= source.node
307
312
 
308
- return [] unless node
313
+ return [] unless node && parents
309
314
 
310
315
  items = [] #: Array[item]
311
316
 
@@ -319,6 +324,16 @@ module Steep
319
324
  method_items_for_receiver_type(context.self_type, include_private: true, prefix: prefix, position: position, items: items)
320
325
  local_variable_items_for_context(context, position: position, prefix: prefix, items: items)
321
326
 
327
+ if (send_node, block_node = deconstruct_sendish_and_block_nodes(*parents))
328
+ keyword_argument_items_for_method(
329
+ call_node: block_node || send_node,
330
+ send_node: send_node,
331
+ position: position,
332
+ prefix: prefix,
333
+ items: items
334
+ )
335
+ end
336
+
322
337
  when node.type == :lvar && at_end?(position, of: node.loc)
323
338
  # foo ← (lvar)
324
339
  local_variable_items_for_context(context, position: position, prefix: node.children[0].to_s, items: items)
@@ -527,6 +542,37 @@ module Steep
527
542
  items
528
543
  end
529
544
 
545
+ def items_for_following_keyword_arguments(text, index:, line:, column:, items:)
546
+ return if text[index - 1] !~ /[a-zA-Z0-9]/
547
+
548
+ text = text.dup
549
+ argname = [] #: Array[String]
550
+ while text[index - 1] =~ /[a-zA-Z0-9]/
551
+ argname.unshift(text[index - 1] || '')
552
+ source_text[index - 1] = " "
553
+ index -= 1
554
+ end
555
+
556
+ begin
557
+ type_check!(source_text, line: line, column: column)
558
+ rescue Parser::SyntaxError
559
+ return
560
+ end
561
+
562
+ if nodes = source.find_nodes(line: line, column: column)
563
+ if (send_node, block_node = deconstruct_sendish_and_block_nodes(*nodes))
564
+ position = Position.new(line: line, column: column)
565
+ keyword_argument_items_for_method(
566
+ call_node: block_node || send_node,
567
+ send_node: send_node,
568
+ position: position,
569
+ prefix: argname.join,
570
+ items: items
571
+ )
572
+ end
573
+ end
574
+ end
575
+
530
576
  def method_items_for_receiver_type(type, include_private:, prefix:, position:, items:)
531
577
  range = range_for(position, prefix: prefix)
532
578
  context = typing.context_at(line: position.line, column: position.column)
@@ -641,6 +687,42 @@ module Steep
641
687
  end
642
688
  end
643
689
 
690
+ def keyword_argument_items_for_method(call_node:, send_node:, position:, prefix:, items:)
691
+ receiver_node, method_name, argument_nodes = deconstruct_send_node!(send_node)
692
+
693
+ call = typing.call_of(node: call_node)
694
+
695
+ case call
696
+ when TypeInference::MethodCall::Typed, TypeInference::MethodCall::Error
697
+ type = call.receiver_type
698
+ shape = subtyping.builder.shape(
699
+ type,
700
+ public_only: !!receiver_node,
701
+ config: Interface::Builder::Config.new(self_type: type, class_type: nil, instance_type: nil, variable_bounds: {})
702
+ )
703
+ if shape
704
+ if method = shape.methods[call.method_name]
705
+ method.method_types.each.with_index do |method_type, i|
706
+ defn = method_type.method_decls.to_a[0]&.method_def
707
+ if defn
708
+ range = range_for(position, prefix: prefix)
709
+ kwargs = argument_nodes.find { |arg| arg.type == :kwargs }&.children || []
710
+ used_kwargs = kwargs.filter_map { |arg| arg.type == :pair && arg.children.first.children.first }
711
+
712
+ kwargs = defn.type.type.required_keywords.keys + defn.type.type.optional_keywords.keys
713
+ kwargs.each do |name|
714
+ if name.to_s.start_with?(prefix) && !used_kwargs.include?(name)
715
+ items << KeywordArgumentItem.new(identifier: "#{name}:", range: range)
716
+ end
717
+ end
718
+ end
719
+ end
720
+ end
721
+ end
722
+ end
723
+ end
724
+
725
+
644
726
  def index_for(string, line:, column:)
645
727
  index = 0
646
728
 
@@ -110,7 +110,7 @@ module Steep
110
110
  end
111
111
 
112
112
  def content_for(target:, path:, line:, column:)
113
- file = service.source_files[path]
113
+ file = service.source_files[path] or return
114
114
  typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
115
115
  node, *parents = typing.source.find_nodes(line: line, column: column)
116
116