steep 0.27.0 → 0.31.1

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