steep 0.30.0 → 0.31.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: b5293cbc87834cee36e50cb66bab84e466a5182c1e868732b7bd3e685b2bd954
4
- data.tar.gz: 7794e42573b5ac04c74780824213cfbe1ca255f1feaa4465a1066377d2647e0b
3
+ metadata.gz: e315b0a9541b0289ae3c9dd3acb19c2112e990a321176f94ed44f25d4190acb4
4
+ data.tar.gz: 9ce6e2024e81e5a2452fc5d7ca381decd1d91ed917d385dc280133652af51e06
5
5
  SHA512:
6
- metadata.gz: e723bf77a7452e0696c38d6d1797e71f246fb97d1b1f18082ba21b8eadb8174d6bcc4deda872898b5f39d162b91173c413cacfa28175018517170f8f2568bd70
7
- data.tar.gz: 38dbe78df43706fe33d5350ad04524c25606955bcba5397ad18690717a60c59d361485ad2d5d3077787336d23902bd91ba9fa5b31e1933a93d1fe32747837688
6
+ metadata.gz: bd8db01573d1a2e31b362fb893bfad1325969b8343415d1bfd82428a76f0a6c8f8fe2875f7e474ab94244476e9901b1dda186f57cb90a49602638453826534a8
7
+ data.tar.gz: f1f30e2afc48265917468a4c5dd040fff3e226f92cb17441c3f3fcbba7f95aaa1002172ece399410d567358d9caf7bd80c1f1c27b9083c81a39e1abe61b74a68
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.31.0 (2020-10-04)
6
+
7
+ * Fix type checking performance ([#230](https://github.com/soutaro/steep/pull/230))
8
+ * Improve LSP completion/hover performance ([#232](https://github.com/soutaro/steep/pull/232))
9
+ * Fix instance variable completion ([#234](https://github.com/soutaro/steep/pull/234))
10
+ * Relax version requirements on Listen to allow installing on Ruby 3 ([#235](https://github.com/soutaro/steep/pull/235))
11
+
5
12
  ## 0.30.0 (2020-10-03)
6
13
 
7
14
  * Let top-level defs be methods of Object ([#227](https://github.com/soutaro/steep/pull/227))
@@ -9,8 +9,7 @@ def exit(*)
9
9
 
10
10
  end
11
11
 
12
-
13
12
  STDERR.puts "Running profiler: mode=#{mode}, out=#{out}"
14
- StackProf.run(mode: mode, out: out) do
13
+ StackProf.run(mode: mode, out: out, raw: true) do
15
14
  load File.join(__dir__, "../exe/steep")
16
15
  end
@@ -7,11 +7,14 @@ module Steep
7
7
  attr_reader :type_name_cache
8
8
  attr_reader :type_cache
9
9
 
10
+ attr_reader :type_interface_cache
11
+
10
12
  def initialize(builder:)
11
13
  @definition_builder = builder
12
14
 
13
15
  @type_name_cache = {}
14
16
  @type_cache = {}
17
+ @type_interface_cache = {}
15
18
  end
16
19
 
17
20
  def type_name_resolver
@@ -390,15 +393,23 @@ module Steep
390
393
 
391
394
  def interface(type, private:, self_type: type)
392
395
  Steep.logger.debug { "Factory#interface: #{type}, private=#{private}, self_type=#{self_type}" }
393
- type = expand_alias(type)
396
+
397
+ cache_key = [type, self_type, private]
398
+ if type_interface_cache.key?(cache_key)
399
+ return type_interface_cache[cache_key]
400
+ end
394
401
 
395
402
  case type
403
+ when Name::Alias
404
+ interface(expand_alias(type), private: private, self_type: self_type)
405
+
396
406
  when Self
397
407
  if self_type != type
398
408
  interface self_type, private: private, self_type: Self.new
399
409
  else
400
410
  raise "Unexpected `self` type interface"
401
411
  end
412
+
402
413
  when Name::Instance
403
414
  Interface::Interface.new(type: self_type, private: private).tap do |interface|
404
415
  definition = definition_builder.build_instance(type.name)
@@ -689,6 +700,8 @@ module Steep
689
700
 
690
701
  else
691
702
  raise "Unexpected type for interface: #{type}"
703
+ end.tap do |interface|
704
+ type_interface_cache[cache_key] = interface
692
705
  end
693
706
  end
694
707
 
@@ -29,11 +29,13 @@ module Steep
29
29
  @subtyping = subtyping
30
30
  end
31
31
 
32
- def type_check!(text)
32
+ def type_check!(text, line:, column:)
33
33
  @modified_text = text
34
34
 
35
35
  Steep.measure "parsing" do
36
- @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)
37
39
  end
38
40
 
39
41
  Steep.measure "typechecking" do
@@ -53,7 +55,7 @@ module Steep
53
55
  begin
54
56
  Steep.logger.tagged "completion_provider#run(line: #{line}, column: #{column})" do
55
57
  Steep.measure "type_check!" do
56
- type_check!(source_text)
58
+ type_check!(source_text, line: line, column: column)
57
59
  end
58
60
  end
59
61
 
@@ -66,11 +68,11 @@ module Steep
66
68
  case possible_trigger
67
69
  when "."
68
70
  source_text[index-1] = " "
69
- type_check!(source_text)
71
+ type_check!(source_text, line: line, column: column)
70
72
  items_for_dot(position: position)
71
73
  when "@"
72
74
  source_text[index-1] = " "
73
- type_check!(source_text)
75
+ type_check!(source_text, line: line, column: column)
74
76
  items_for_atmark(position: position)
75
77
  else
76
78
  []
@@ -219,14 +221,14 @@ module Steep
219
221
  def items_for_atmark(position:)
220
222
  # @ ←
221
223
  shift_pos = position-1
222
- 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)
223
225
  node ||= source.node
224
226
 
225
227
  return [] unless node
226
228
 
227
229
  context = typing.context_at(line: position.line, column: position.column)
228
230
  items = []
229
- instance_variable_items_for_context(context, prefix: "", position: position, items: items)
231
+ instance_variable_items_for_context(context, prefix: "@", position: position, items: items)
230
232
  items
231
233
  end
232
234
 
@@ -36,102 +36,113 @@ module Steep
36
36
  end
37
37
  end
38
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
+
39
54
  def content_for(path:, line:, column:)
40
- target = project.targets.find {|target| target.source_file?(path) }
55
+ target = project.target_for_source_path(path)
41
56
 
42
57
  if target
43
- source_file = target.source_files[path]
44
- 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)
61
+
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)
45
68
 
46
- case (status = source_file.status)
47
- when SourceFile::TypeCheckStatus
48
- node, *parents = status.source.find_nodes(line: line, column: column)
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)
49
74
 
50
- if node
51
- case node.type
52
- when :lvar
53
- var_name = node.children[0]
54
- context = status.typing.context_at(line: line, column: column)
55
- var_type = context.lvar_env[var_name.name] || AST::Types::Any.new(location: nil)
75
+ VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
76
+ when :send
77
+ receiver, method_name, *_ = node.children
56
78
 
57
- VariableContent.new(node: node, name: var_name.name, type: var_type, location: node.location.name)
58
- when :lvasgn
59
- var_name, rhs = node.children
60
- context = status.typing.context_at(line: line, column: column)
61
- type = context.lvar_env[var_name.name] || status.typing.type_of(node: rhs)
62
79
 
63
- VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
64
- when :send
65
- receiver, method_name, *_ = node.children
80
+ result_node = if parents[0]&.type == :block
81
+ parents[0]
82
+ else
83
+ node
84
+ end
66
85
 
86
+ context = typing.context_at(line: line, column: column)
67
87
 
68
- result_node = if parents[0]&.type == :block
69
- parents[0]
88
+ receiver_type = if receiver
89
+ typing.type_of(node: receiver)
70
90
  else
71
- node
91
+ context.self_type
72
92
  end
73
93
 
74
- context = status.typing.context_at(line: line, column: column)
75
-
76
- receiver_type = if receiver
77
- status.typing.type_of(node: receiver)
78
- else
79
- context.self_type
80
- end
81
-
82
- factory = context.type_env.subtyping.factory
83
- method_name, definition = case receiver_type
84
- when AST::Types::Name::Instance
85
- method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
86
- if method_definition&.defined_in
87
- owner_name = method_definition.defined_in
88
- [
89
- InstanceMethodName.new(owner_name, method_name),
90
- method_definition
91
- ]
92
- end
93
- when AST::Types::Name::Singleton
94
- method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
95
- if method_definition&.defined_in
96
- owner_name = method_definition.defined_in
97
- [
98
- SingletonMethodName.new(owner_name, method_name),
99
- method_definition
100
- ]
101
- end
102
- else
103
- 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
+ ]
104
104
  end
105
-
106
- MethodCallContent.new(
107
- node: node,
108
- method_name: method_name,
109
- type: status.typing.type_of(node: result_node),
110
- definition: definition,
111
- location: result_node.location.expression
112
- )
113
- when :def, :defs
114
- context = status.typing.context_at(line: line, column: column)
115
- method_context = context.method_context
116
-
117
- if method_context && method_context.method
118
- DefinitionContent.new(
119
- node: node,
120
- method_name: method_context.name,
121
- method_type: method_context.method_type,
122
- definition: method_context.method,
123
- location: node.loc.expression
124
- )
125
- end
126
- else
127
- type = status.typing.type_of(node: node)
128
-
129
- 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(
130
131
  node: node,
131
- type: type,
132
- 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
133
136
  )
134
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
+ )
135
146
  end
136
147
  end
137
148
  end
@@ -52,21 +52,23 @@ module Steep
52
52
 
53
53
  def response_to_hover(path:, line:, column:)
54
54
  Steep.logger.tagged "#response_to_hover" do
55
- Steep.logger.debug { "path=#{path}, line=#{line}, column=#{column}" }
56
-
57
- hover = Project::HoverContent.new(project: project)
58
- content = hover.content_for(path: path, line: line+1, column: column+1)
59
- if content
60
- range = content.location.yield_self do |location|
61
- start_position = { line: location.line - 1, character: location.column }
62
- end_position = { line: location.last_line - 1, character: location.last_column }
63
- { start: start_position, end: end_position }
64
- end
55
+ Steep.measure "Generating response" do
56
+ Steep.logger.info { "path=#{path}, line=#{line}, column=#{column}" }
57
+
58
+ hover = Project::HoverContent.new(project: project)
59
+ content = hover.content_for(path: path, line: line+1, column: column+1)
60
+ if content
61
+ range = content.location.yield_self do |location|
62
+ start_position = { line: location.line - 1, character: location.column }
63
+ end_position = { line: location.last_line - 1, character: location.last_column }
64
+ { start: start_position, end: end_position }
65
+ end
65
66
 
66
- LSP::Interface::Hover.new(
67
- contents: { kind: "markdown", value: format_hover(content) },
68
- range: range
69
- )
67
+ LSP::Interface::Hover.new(
68
+ contents: { kind: "markdown", value: format_hover(content) },
69
+ range: range
70
+ )
71
+ end
70
72
  end
71
73
  rescue Typing::UnknownNodeError => exn
72
74
  Steep.log_error exn, message: "Failed to compute hover: #{exn.inspect}"
@@ -126,33 +128,35 @@ HOVER
126
128
 
127
129
  def response_to_completion(path:, line:, column:, trigger:)
128
130
  Steep.logger.tagged("#response_to_completion") do
129
- Steep.logger.info "path: #{path}, line: #{line}, column: #{column}, trigger: #{trigger}"
130
-
131
- target = project.targets.find {|target| target.source_file?(path) } or return
132
- target.type_check(target_sources: [], validate_signatures: false)
133
-
134
- case (status = target&.status)
135
- when Project::Target::TypeCheckStatus
136
- subtyping = status.subtyping
137
- source = target.source_files[path]
131
+ Steep.measure "Generating response" do
132
+ Steep.logger.info "path: #{path}, line: #{line}, column: #{column}, trigger: #{trigger}"
133
+
134
+ target = project.target_for_source_path(path) or return
135
+ target.type_check(target_sources: [], validate_signatures: false)
136
+
137
+ case (status = target&.status)
138
+ when Project::Target::TypeCheckStatus
139
+ subtyping = status.subtyping
140
+ source = target.source_files[path]
141
+
142
+ provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
143
+ items = begin
144
+ provider.run(line: line, column: column)
145
+ rescue Parser::SyntaxError
146
+ []
147
+ end
148
+
149
+ completion_items = items.map do |item|
150
+ format_completion_item(item)
151
+ end
138
152
 
139
- provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
140
- items = begin
141
- provider.run(line: line, column: column)
142
- rescue Parser::SyntaxError
143
- []
144
- end
153
+ Steep.logger.debug "items = #{completion_items.inspect}"
145
154
 
146
- completion_items = items.map do |item|
147
- format_completion_item(item)
155
+ LSP::Interface::CompletionList.new(
156
+ is_incomplete: false,
157
+ items: completion_items
158
+ )
148
159
  end
149
-
150
- Steep.logger.debug "items = #{completion_items.inspect}"
151
-
152
- LSP::Interface::CompletionList.new(
153
- is_incomplete: false,
154
- items: completion_items
155
- )
156
160
  end
157
161
  end
158
162
  end
@@ -276,6 +276,18 @@ module Steep
276
276
  end
277
277
  end
278
278
 
279
+ def self.map_child_nodes(node)
280
+ children = node.children.map do |child|
281
+ if child.is_a?(::AST::Node)
282
+ yield child
283
+ else
284
+ child
285
+ end
286
+ end
287
+
288
+ node.updated(nil, children)
289
+ end
290
+
279
291
  def annotations(block:, factory:, current_module:)
280
292
  AST::Annotation::Collection.new(
281
293
  annotations: mapping[block.__id__] || [],
@@ -316,5 +328,50 @@ module Steep
316
328
  end
317
329
  end
318
330
  end
331
+
332
+ def self.delete_defs(node, allow_list)
333
+ case node.type
334
+ when :def
335
+ if allow_list.include?(node)
336
+ node
337
+ else
338
+ node.updated(:nil, [])
339
+ end
340
+ when :defs
341
+ if allow_list.include?(node)
342
+ node
343
+ else
344
+ delete_defs(node.children[0], allow_list)
345
+ end
346
+ else
347
+ map_child_nodes(node) do |child|
348
+ delete_defs(child, allow_list)
349
+ end
350
+ end
351
+ end
352
+
353
+ def without_unrelated_defs(line:, column:)
354
+ nodes = find_nodes(line: line, column: column) || []
355
+ defs = Set[].compare_by_identity.merge(nodes.select {|node| node.type == :def || node.type == :defs })
356
+
357
+ node_ = Source.delete_defs(node, defs)
358
+
359
+ Source.new(path: path, node: node_, mapping: mapping)
360
+ end
361
+
362
+ def compact_siblings(node)
363
+ case node
364
+ when :def
365
+ node.updated(:nil, [])
366
+ when :defs
367
+ node.children[0]
368
+ when :class
369
+ node.updated(:class, [node.children[0], node.children[1], nil])
370
+ when :module
371
+ node.updated(:module, [node.children[0], nil])
372
+ else
373
+ node
374
+ end
375
+ end
319
376
  end
320
377
  end
@@ -1,3 +1,3 @@
1
1
  module Steep
2
- VERSION = "0.30.0"
2
+ VERSION = "0.31.0"
3
3
  end
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_runtime_dependency "ast_utils", "~> 0.3.0"
33
33
  spec.add_runtime_dependency "activesupport", ">= 5.1"
34
34
  spec.add_runtime_dependency "rainbow", ">= 2.2.2", "< 4.0"
35
- spec.add_runtime_dependency "listen", "~> 3.1"
35
+ spec.add_runtime_dependency "listen", "~> 3.0"
36
36
  spec.add_runtime_dependency "language_server-protocol", "~> 3.15.0.1"
37
37
  spec.add_runtime_dependency "rbs", "~> 0.12.0"
38
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.0
4
+ version: 0.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-02 00:00:00.000000000 Z
11
+ date: 2020-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '3.1'
81
+ version: '3.0'
82
82
  type: :runtime
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '3.1'
88
+ version: '3.0'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: language_server-protocol
91
91
  requirement: !ruby/object:Gem::Requirement