steep 0.27.0 → 0.31.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/bin/smoke_runner.rb +3 -4
  4. data/bin/steep-prof +1 -2
  5. data/lib/steep.rb +6 -4
  6. data/lib/steep/annotation_parser.rb +2 -4
  7. data/lib/steep/ast/builtin.rb +11 -21
  8. data/lib/steep/ast/types/factory.rb +234 -101
  9. data/lib/steep/ast/types/intersection.rb +12 -9
  10. data/lib/steep/ast/types/logic.rb +63 -0
  11. data/lib/steep/ast/types/name.rb +2 -58
  12. data/lib/steep/ast/types/union.rb +5 -6
  13. data/lib/steep/errors.rb +14 -0
  14. data/lib/steep/interface/interface.rb +5 -62
  15. data/lib/steep/interface/method_type.rb +346 -75
  16. data/lib/steep/interface/substitution.rb +16 -4
  17. data/lib/steep/module_helper.rb +25 -0
  18. data/lib/steep/project.rb +25 -0
  19. data/lib/steep/project/completion_provider.rb +57 -58
  20. data/lib/steep/project/file_loader.rb +7 -2
  21. data/lib/steep/project/hover_content.rb +92 -83
  22. data/lib/steep/project/signature_file.rb +33 -0
  23. data/lib/steep/project/{file.rb → source_file.rb} +24 -54
  24. data/lib/steep/project/target.rb +31 -12
  25. data/lib/steep/server/base_worker.rb +1 -0
  26. data/lib/steep/server/code_worker.rb +31 -45
  27. data/lib/steep/server/interaction_worker.rb +42 -38
  28. data/lib/steep/server/master.rb +23 -33
  29. data/lib/steep/server/utils.rb +46 -13
  30. data/lib/steep/server/worker_process.rb +4 -2
  31. data/lib/steep/signature/validator.rb +3 -3
  32. data/lib/steep/source.rb +60 -3
  33. data/lib/steep/subtyping/check.rb +34 -47
  34. data/lib/steep/subtyping/constraints.rb +8 -0
  35. data/lib/steep/type_construction.rb +366 -365
  36. data/lib/steep/type_inference/block_params.rb +5 -0
  37. data/lib/steep/type_inference/constant_env.rb +2 -5
  38. data/lib/steep/type_inference/logic_type_interpreter.rb +219 -0
  39. data/lib/steep/type_inference/type_env.rb +2 -2
  40. data/lib/steep/version.rb +1 -1
  41. data/smoke/alias/a.rb +1 -1
  42. data/smoke/case/a.rb +1 -1
  43. data/smoke/if/a.rb +1 -1
  44. data/smoke/module/a.rb +1 -1
  45. data/smoke/rescue/a.rb +4 -13
  46. data/smoke/toplevel/Steepfile +5 -0
  47. data/smoke/toplevel/a.rb +4 -0
  48. data/smoke/toplevel/a.rbs +3 -0
  49. data/smoke/type_case/a.rb +0 -7
  50. data/steep.gemspec +3 -3
  51. metadata +20 -16
  52. data/lib/steep/ast/method_type.rb +0 -126
  53. data/lib/steep/ast/namespace.rb +0 -80
  54. data/lib/steep/names.rb +0 -86
@@ -90,20 +90,32 @@ module Steep
90
90
 
91
91
  def except(vars)
92
92
  self.class.new(
93
- dictionary: dictionary.reject {|k, _| vars.include?(k) },
93
+ dictionary: dictionary,
94
94
  instance_type: instance_type,
95
95
  module_type: module_type,
96
96
  self_type: self_type
97
- )
97
+ ).except!(vars)
98
98
  end
99
99
 
100
- def merge!(s)
100
+ def except!(vars)
101
+ vars.each do |var|
102
+ dictionary.delete(var)
103
+ end
104
+
105
+ self
106
+ end
107
+
108
+ def merge!(s, overwrite: false)
101
109
  dictionary.transform_values! {|ty| ty.subst(s) }
102
110
  dictionary.merge!(s.dictionary) do |key, a, b|
103
111
  if a == b
104
112
  a
105
113
  else
106
- raise "Duplicated key on merge!: #{key}, #{a}, #{b}"
114
+ if overwrite
115
+ b
116
+ else
117
+ raise "Duplicated key on merge!: #{key}, #{a}, #{b} (#{self})"
118
+ end
107
119
  end
108
120
  end
109
121
 
@@ -0,0 +1,25 @@
1
+ module Steep
2
+ module ModuleHelper
3
+ def module_name_from_node(node)
4
+ case node.type
5
+ when :const, :casgn
6
+ namespace = namespace_from_node(node.children[0]) or return
7
+ name = node.children[1]
8
+ RBS::TypeName.new(name: name, namespace: namespace)
9
+ end
10
+ end
11
+
12
+ def namespace_from_node(node)
13
+ case node&.type
14
+ when nil
15
+ RBS::Namespace.empty
16
+ when :cbase
17
+ RBS::Namespace.root
18
+ when :const
19
+ namespace_from_node(node.children[0])&.yield_self do |parent|
20
+ parent.append(node.children[1])
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -24,6 +24,31 @@ module Steep
24
24
  (base_dir + path).cleanpath
25
25
  end
26
26
 
27
+ def target_for_source_path(path)
28
+ targets.find do |target|
29
+ target.possible_source_file?(path)
30
+ end
31
+ end
32
+
33
+ def targets_for_path(path)
34
+ if target = target_for_source_path(path)
35
+ [target, []]
36
+ else
37
+ [
38
+ nil,
39
+ targets.select do |target|
40
+ target.possible_signature_file?(path)
41
+ end
42
+ ]
43
+ end
44
+ end
45
+
46
+ def all_source_files
47
+ targets.each.with_object(Set[]) do |target, paths|
48
+ paths.merge(target.source_files.keys)
49
+ end
50
+ end
51
+
27
52
  def type_of_node(path:, line:, column:)
28
53
  source_file = targets.map {|target| target.source_files[path] }.compact[0]
29
54
 
@@ -10,13 +10,9 @@ module Steep
10
10
 
11
11
  InstanceVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
12
12
  LocalVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
13
- MethodNameItem = Struct.new(:identifier, :range, :definition, :def_type, :inherited_method, keyword_init: true) do
14
- def method_type
15
- def_type.type
16
- end
17
-
13
+ MethodNameItem = Struct.new(:identifier, :range, :method_def, :method_type, :inherited_method, keyword_init: true) do
18
14
  def comment
19
- def_type.comment
15
+ method_def&.comment
20
16
  end
21
17
  end
22
18
 
@@ -33,11 +29,13 @@ module Steep
33
29
  @subtyping = subtyping
34
30
  end
35
31
 
36
- def type_check!(text)
32
+ def type_check!(text, line:, column:)
37
33
  @modified_text = text
38
34
 
39
35
  Steep.measure "parsing" do
40
- @source = SourceFile.parse(text, path: path, factory: subtyping.factory)
36
+ @source = SourceFile
37
+ .parse(text, path: path, factory: subtyping.factory)
38
+ .without_unrelated_defs(line: line, column: column)
41
39
  end
42
40
 
43
41
  Steep.measure "typechecking" do
@@ -57,7 +55,7 @@ module Steep
57
55
  begin
58
56
  Steep.logger.tagged "completion_provider#run(line: #{line}, column: #{column})" do
59
57
  Steep.measure "type_check!" do
60
- type_check!(source_text)
58
+ type_check!(source_text, line: line, column: column)
61
59
  end
62
60
  end
63
61
 
@@ -70,11 +68,11 @@ module Steep
70
68
  case possible_trigger
71
69
  when "."
72
70
  source_text[index-1] = " "
73
- type_check!(source_text)
71
+ type_check!(source_text, line: line, column: column)
74
72
  items_for_dot(position: position)
75
73
  when "@"
76
74
  source_text[index-1] = " "
77
- type_check!(source_text)
75
+ type_check!(source_text, line: line, column: column)
78
76
  items_for_atmark(position: position)
79
77
  else
80
78
  []
@@ -90,7 +88,9 @@ module Steep
90
88
  end
91
89
 
92
90
  def at_end?(pos, of:)
93
- of.last_line == pos.line && of.last_column == pos.column
91
+ if of
92
+ of.last_line == pos.line && of.last_column == pos.column
93
+ end
94
94
  end
95
95
 
96
96
  def range_for(position, prefix: "")
@@ -194,21 +194,25 @@ module Steep
194
194
  return [] unless node
195
195
 
196
196
  if at_end?(shift_pos, of: node.loc)
197
- context = typing.context_at(line: position.line, column: position.column)
198
- receiver_type = case (type = typing.type_of(node: node))
199
- when AST::Types::Self
200
- context.self_type
201
- else
202
- type
203
- end
204
-
205
- items = []
206
- method_items_for_receiver_type(receiver_type,
207
- include_private: false,
208
- prefix: "",
209
- position: position,
210
- items: items)
211
- items
197
+ begin
198
+ context = typing.context_at(line: position.line, column: position.column)
199
+ receiver_type = case (type = typing.type_of(node: node))
200
+ when AST::Types::Self
201
+ context.self_type
202
+ else
203
+ type
204
+ end
205
+
206
+ items = []
207
+ method_items_for_receiver_type(receiver_type,
208
+ include_private: false,
209
+ prefix: "",
210
+ position: position,
211
+ items: items)
212
+ items
213
+ rescue Typing::UnknownNodeError
214
+ []
215
+ end
212
216
  else
213
217
  []
214
218
  end
@@ -217,50 +221,40 @@ module Steep
217
221
  def items_for_atmark(position:)
218
222
  # @ ←
219
223
  shift_pos = position-1
220
- node, *parents = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
224
+ node, *_ = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
221
225
  node ||= source.node
222
226
 
223
227
  return [] unless node
224
228
 
225
229
  context = typing.context_at(line: position.line, column: position.column)
226
230
  items = []
227
- instance_variable_items_for_context(context, prefix: "", position: position, items: items)
231
+ instance_variable_items_for_context(context, prefix: "@", position: position, items: items)
228
232
  items
229
233
  end
230
234
 
231
235
  def method_items_for_receiver_type(type, include_private:, prefix:, position:, items:)
232
236
  range = range_for(position, prefix: prefix)
233
- definition = case type
234
- when AST::Types::Name::Instance
235
- type_name = subtyping.factory.type_name_1(type.name)
236
- subtyping.factory.definition_builder.build_instance(type_name)
237
- when AST::Types::Name::Class, AST::Types::Name::Module
238
- type_name = subtyping.factory.type_name_1(type.name)
239
- subtyping.factory.definition_builder.build_singleton(type_name)
240
- when AST::Types::Name::Interface
241
- type_name = subtyping.factory.type_name_1(type.name)
242
- subtyping.factory.definition_builder.build_interface(type_name)
243
- end
244
-
245
- if definition
246
- definition.methods.each do |name, method|
247
- next if disallowed_method?(name)
248
-
249
- if include_private || method.public?
250
- if name.to_s.start_with?(prefix)
251
- if word_name?(name.to_s)
252
- method.defs.each do |def_type|
253
- items << MethodNameItem.new(identifier: name,
254
- range: range,
255
- definition: method,
256
- def_type: def_type,
257
- inherited_method: inherited_method?(method, definition))
258
- end
259
- end
237
+ interface = subtyping.factory.interface(type, self_type: type, private: include_private)
238
+
239
+ interface.methods.each do |name, method_entry|
240
+ next if disallowed_method?(name)
241
+
242
+ if name.to_s.start_with?(prefix)
243
+ if word_name?(name.to_s)
244
+ method_entry.method_types.each do |method_type|
245
+ items << MethodNameItem.new(
246
+ identifier: name,
247
+ range: range,
248
+ method_def: method_type.method_def,
249
+ method_type: method_type.method_def&.type || subtyping.factory.method_type_1(method_type, self_type: type),
250
+ inherited_method: inherited_method?(method_type.method_def, type)
251
+ )
260
252
  end
261
253
  end
262
254
  end
263
255
  end
256
+ rescue
257
+ # nop
264
258
  end
265
259
 
266
260
  def word_name?(name)
@@ -304,8 +298,13 @@ module Steep
304
298
  index
305
299
  end
306
300
 
307
- def inherited_method?(method, definition)
308
- method.implemented_in != definition.type_name
301
+ def inherited_method?(method_def, type)
302
+ case type
303
+ when AST::Types::Name::Instance, AST::Types::Name::Singleton, AST::Types::Name::Interface
304
+ method_def.implemented_in != type.name
305
+ else
306
+ false
307
+ end
309
308
  end
310
309
 
311
310
  def disallowed_method?(name)
@@ -28,15 +28,20 @@ module Steep
28
28
  end
29
29
 
30
30
  def load_sources(command_line_patterns)
31
+ loaded_paths = Set[]
32
+
31
33
  project.targets.each do |target|
32
34
  Steep.logger.tagged "target=#{target.name}" do
33
35
  target_patterns = command_line_patterns.empty? ? target.source_patterns : command_line_patterns
34
36
 
35
- each_path_in_patterns target_patterns, ".rb" do |path|
37
+ each_path_in_patterns(target_patterns, ".rb") do |path|
36
38
  if target.possible_source_file?(path)
37
- unless target.source_file?(path)
39
+ if loaded_paths.include?(path)
40
+ Steep.logger.warn { "Skipping #{target} while loading #{path}... (Already loaded to another target.)" }
41
+ else
38
42
  Steep.logger.info { "Adding source file: #{path}" }
39
43
  target.add_source path, project.absolute_path(path).read
44
+ loaded_paths << path
40
45
  end
41
46
  end
42
47
  end
@@ -21,9 +21,7 @@ module Steep
21
21
  @project = project
22
22
  end
23
23
 
24
- def method_definition_for(factory, module_name, singleton_method: nil, instance_method: nil)
25
- type_name = factory.type_name_1(module_name)
26
-
24
+ def method_definition_for(factory, type_name, singleton_method: nil, instance_method: nil)
27
25
  case
28
26
  when instance_method
29
27
  factory.definition_builder.build_instance(type_name).methods[instance_method]
@@ -38,102 +36,113 @@ module Steep
38
36
  end
39
37
  end
40
38
 
39
+ def typecheck(target, path:, line:, column:)
40
+ target.type_check(target_sources: [], validate_signatures: false)
41
+
42
+ case (status = target.status)
43
+ when Project::Target::TypeCheckStatus
44
+ subtyping = status.subtyping
45
+ source = SourceFile
46
+ .parse(target.source_files[path].content, path: path, factory: subtyping.factory)
47
+ .without_unrelated_defs(line: line, column: column)
48
+ SourceFile.type_check(source, subtyping: subtyping)
49
+ end
50
+ rescue
51
+ nil
52
+ end
53
+
41
54
  def content_for(path:, line:, column:)
42
- target = project.targets.find {|target| target.source_file?(path) }
55
+ target = project.target_for_source_path(path)
43
56
 
44
57
  if target
45
- source_file = target.source_files[path]
46
- target.type_check(target_sources: [source_file], validate_signatures: false)
58
+ typing = typecheck(target, path: path, line: line, column: column) or return
59
+
60
+ node, *parents = typing.source.find_nodes(line: line, column: column)
47
61
 
48
- case (status = source_file.status)
49
- when SourceFile::TypeCheckStatus
50
- node, *parents = status.source.find_nodes(line: line, column: column)
62
+ if node
63
+ case node.type
64
+ when :lvar
65
+ var_name = node.children[0]
66
+ context = typing.context_at(line: line, column: column)
67
+ var_type = context.lvar_env[var_name.name] || AST::Types::Any.new(location: nil)
51
68
 
52
- if node
53
- case node.type
54
- when :lvar
55
- var_name = node.children[0]
56
- context = status.typing.context_at(line: line, column: column)
57
- var_type = context.lvar_env[var_name.name] || AST::Types::Any.new(location: nil)
69
+ VariableContent.new(node: node, name: var_name.name, type: var_type, location: node.location.name)
70
+ when :lvasgn
71
+ var_name, rhs = node.children
72
+ context = typing.context_at(line: line, column: column)
73
+ type = context.lvar_env[var_name.name] || typing.type_of(node: rhs)
58
74
 
59
- VariableContent.new(node: node, name: var_name.name, type: var_type, location: node.location.name)
60
- when :lvasgn
61
- var_name, rhs = node.children
62
- context = status.typing.context_at(line: line, column: column)
63
- type = context.lvar_env[var_name.name] || status.typing.type_of(node: rhs)
75
+ VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
76
+ when :send
77
+ receiver, method_name, *_ = node.children
64
78
 
65
- VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
66
- when :send
67
- receiver, method_name, *_ = node.children
68
79
 
80
+ result_node = if parents[0]&.type == :block
81
+ parents[0]
82
+ else
83
+ node
84
+ end
69
85
 
70
- result_node = if parents[0]&.type == :block
71
- parents[0]
86
+ context = typing.context_at(line: line, column: column)
87
+
88
+ receiver_type = if receiver
89
+ typing.type_of(node: receiver)
72
90
  else
73
- node
91
+ context.self_type
74
92
  end
75
93
 
76
- context = status.typing.context_at(line: line, column: column)
77
-
78
- receiver_type = if receiver
79
- status.typing.type_of(node: receiver)
80
- else
81
- context.self_type
82
- end
83
-
84
- factory = context.type_env.subtyping.factory
85
- method_name, definition = case receiver_type
86
- when AST::Types::Name::Instance
87
- method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
88
- if method_definition&.defined_in
89
- owner_name = factory.type_name(method_definition.defined_in)
90
- [
91
- InstanceMethodName.new(owner_name, method_name),
92
- method_definition
93
- ]
94
- end
95
- when AST::Types::Name::Class
96
- method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
97
- if method_definition&.defined_in
98
- owner_name = factory.type_name(method_definition.defined_in)
99
- [
100
- SingletonMethodName.new(owner_name, method_name),
101
- method_definition
102
- ]
103
- end
104
- else
105
- nil
94
+ factory = context.type_env.subtyping.factory
95
+ method_name, definition = case receiver_type
96
+ when AST::Types::Name::Instance
97
+ method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
98
+ if method_definition&.defined_in
99
+ owner_name = method_definition.defined_in
100
+ [
101
+ InstanceMethodName.new(owner_name, method_name),
102
+ method_definition
103
+ ]
106
104
  end
107
-
108
- MethodCallContent.new(
109
- node: node,
110
- method_name: method_name,
111
- type: status.typing.type_of(node: result_node),
112
- definition: definition,
113
- location: result_node.location.expression
114
- )
115
- when :def, :defs
116
- context = status.typing.context_at(line: line, column: column)
117
- method_context = context.method_context
118
-
119
- if method_context && method_context.method
120
- DefinitionContent.new(
121
- node: node,
122
- method_name: method_context.name,
123
- method_type: method_context.method_type,
124
- definition: method_context.method,
125
- location: node.loc.expression
126
- )
127
- end
128
- else
129
- type = status.typing.type_of(node: node)
130
-
131
- TypeContent.new(
105
+ when AST::Types::Name::Singleton
106
+ method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
107
+ if method_definition&.defined_in
108
+ owner_name = method_definition.defined_in
109
+ [
110
+ SingletonMethodName.new(owner_name, method_name),
111
+ method_definition
112
+ ]
113
+ end
114
+ else
115
+ nil
116
+ end
117
+
118
+ MethodCallContent.new(
119
+ node: node,
120
+ method_name: method_name,
121
+ type: typing.type_of(node: result_node),
122
+ definition: definition,
123
+ location: result_node.location.expression
124
+ )
125
+ when :def, :defs
126
+ context = typing.context_at(line: line, column: column)
127
+ method_context = context.method_context
128
+
129
+ if method_context && method_context.method
130
+ DefinitionContent.new(
132
131
  node: node,
133
- type: type,
134
- location: node.location.expression
132
+ method_name: method_context.name,
133
+ method_type: method_context.method_type,
134
+ definition: method_context.method,
135
+ location: node.loc.expression
135
136
  )
136
137
  end
138
+ else
139
+ type = typing.type_of(node: node)
140
+
141
+ TypeContent.new(
142
+ node: node,
143
+ type: type,
144
+ location: node.location.expression
145
+ )
137
146
  end
138
147
  end
139
148
  end