steep 1.5.0.pre.5 → 1.5.0

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