steep 0.30.0 → 0.31.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: 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