steep 0.29.0 → 0.33.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.
@@ -0,0 +1,33 @@
1
+ module Steep
2
+ class Project
3
+ class SignatureFile
4
+ attr_reader :path
5
+ attr_reader :content
6
+ attr_reader :content_updated_at
7
+
8
+ attr_reader :status
9
+
10
+ ParseErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
11
+ DeclarationsStatus = Struct.new(:declarations, :timestamp, keyword_init: true)
12
+
13
+ def initialize(path:)
14
+ @path = path
15
+ self.content = ""
16
+ end
17
+
18
+ def content=(content)
19
+ @content_updated_at = Time.now
20
+ @content = content
21
+ @status = nil
22
+ end
23
+
24
+ def load!
25
+ buffer = RBS::Buffer.new(name: path, content: content)
26
+ decls = RBS::Parser.parse_signature(buffer)
27
+ @status = DeclarationsStatus.new(declarations: decls, timestamp: Time.now)
28
+ rescue RBS::Parser::SyntaxError, RBS::Parser::SemanticsError => exn
29
+ @status = ParseErrorStatus.new(error: exn, timestamp: Time.now)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -8,10 +8,10 @@ module Steep
8
8
 
9
9
  attr_accessor :status
10
10
 
11
- ParseErrorStatus = Struct.new(:error, keyword_init: true)
12
- AnnotationSyntaxErrorStatus = Struct.new(:error, :location, keyword_init: true)
11
+ ParseErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
12
+ AnnotationSyntaxErrorStatus = Struct.new(:error, :location, :timestamp, keyword_init: true)
13
13
  TypeCheckStatus = Struct.new(:typing, :source, :timestamp, keyword_init: true)
14
- TypeCheckErrorStatus = Struct.new(:error, keyword_init: true)
14
+ TypeCheckErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
15
15
 
16
16
  def initialize(path:)
17
17
  @path = path
@@ -20,11 +20,9 @@ module Steep
20
20
  end
21
21
 
22
22
  def content=(content)
23
- if @content != content
24
- @content_updated_at = Time.now
25
- @content = content
26
- @status = nil
27
- end
23
+ @content_updated_at = Time.now
24
+ @content = content
25
+ @status = nil
28
26
  end
29
27
 
30
28
  def errors
@@ -55,12 +53,14 @@ module Steep
55
53
  context = TypeInference::Context.new(
56
54
  block_context: nil,
57
55
  module_context: TypeInference::Context::ModuleContext.new(
58
- instance_type: nil,
59
- module_type: nil,
56
+ instance_type: AST::Builtin::Object.instance_type,
57
+ module_type: AST::Builtin::Object.module_type,
60
58
  implement_name: nil,
61
59
  current_namespace: RBS::Namespace.root,
62
60
  const_env: const_env,
63
- class_name: nil
61
+ class_name: AST::Builtin::Object.module_name,
62
+ instance_definition: subtyping.factory.definition_builder.build_instance(AST::Builtin::Object.module_name),
63
+ module_definition: subtyping.factory.definition_builder.build_singleton(AST::Builtin::Object.module_name)
64
64
  ),
65
65
  method_context: nil,
66
66
  break_context: nil,
@@ -86,31 +86,31 @@ module Steep
86
86
 
87
87
  def type_check(subtyping, env_updated_at)
88
88
  # skip type check
89
- return false if status.is_a?(TypeCheckStatus) && env_updated_at <= status.timestamp
89
+ return false if status && env_updated_at <= status.timestamp
90
+
91
+ now = Time.now
90
92
 
91
93
  parse(subtyping.factory) do |source|
92
94
  typing = self.class.type_check(source, subtyping: subtyping)
93
- @status = TypeCheckStatus.new(
94
- typing: typing,
95
- source: source,
96
- timestamp: Time.now
97
- )
95
+ @status = TypeCheckStatus.new(typing: typing, source: source, timestamp: now)
98
96
  rescue RBS::NoTypeFoundError,
99
97
  RBS::NoMixinFoundError,
100
98
  RBS::NoSuperclassFoundError,
101
99
  RBS::DuplicatedMethodDefinitionError,
102
100
  RBS::InvalidTypeApplicationError => exn
103
101
  # Skip logging known signature errors (they are handled with load_signatures(validate: true))
104
- @status = TypeCheckErrorStatus.new(error: exn)
102
+ @status = TypeCheckErrorStatus.new(error: exn, timestamp: now)
105
103
  rescue => exn
106
104
  Steep.log_error(exn)
107
- @status = TypeCheckErrorStatus.new(error: exn)
105
+ @status = TypeCheckErrorStatus.new(error: exn, timestamp: now)
108
106
  end
109
107
 
110
108
  true
111
109
  end
112
110
 
113
111
  def parse(factory)
112
+ now = Time.now
113
+
114
114
  if status.is_a?(TypeCheckStatus)
115
115
  yield status.source
116
116
  else
@@ -118,40 +118,10 @@ module Steep
118
118
  end
119
119
  rescue AnnotationParser::SyntaxError => exn
120
120
  Steep.logger.warn { "Annotation syntax error on #{path}: #{exn.inspect}" }
121
- @status = AnnotationSyntaxErrorStatus.new(error: exn, location: exn.location)
121
+ @status = AnnotationSyntaxErrorStatus.new(error: exn, location: exn.location, timestamp: now)
122
122
  rescue ::Parser::SyntaxError, EncodingError => exn
123
123
  Steep.logger.warn { "Source parsing error on #{path}: #{exn.inspect}" }
124
- @status = ParseErrorStatus.new(error: exn)
125
- end
126
- end
127
-
128
- class SignatureFile
129
- attr_reader :path
130
- attr_reader :content
131
- attr_reader :content_updated_at
132
-
133
- attr_reader :status
134
-
135
- ParseErrorStatus = Struct.new(:error, keyword_init: true)
136
- DeclarationsStatus = Struct.new(:declarations, keyword_init: true)
137
-
138
- def initialize(path:)
139
- @path = path
140
- self.content = ""
141
- end
142
-
143
- def content=(content)
144
- @content_updated_at = Time.now
145
- @content = content
146
- @status = nil
147
- end
148
-
149
- def load!
150
- buffer = RBS::Buffer.new(name: path, content: content)
151
- decls = RBS::Parser.parse_signature(buffer)
152
- @status = DeclarationsStatus.new(declarations: decls)
153
- rescue RBS::Parser::SyntaxError, RBS::Parser::SemanticsError => exn
154
- @status = ParseErrorStatus.new(error: exn)
124
+ @status = ParseErrorStatus.new(error: exn, timestamp: now)
155
125
  end
156
126
  end
157
127
  end
@@ -29,9 +29,15 @@ module Steep
29
29
  @signature_files = {}
30
30
  end
31
31
 
32
- def add_source(path, content)
32
+ def add_source(path, content = "")
33
33
  file = SourceFile.new(path: path)
34
- file.content = content
34
+
35
+ if block_given?
36
+ file.content = yield
37
+ else
38
+ file.content = content
39
+ end
40
+
35
41
  source_files[path] = file
36
42
  end
37
43
 
@@ -39,14 +45,22 @@ module Steep
39
45
  source_files.delete(path)
40
46
  end
41
47
 
42
- def update_source(path, content)
48
+ def update_source(path, content = nil)
43
49
  file = source_files[path]
44
- file.content = content
50
+ if block_given?
51
+ file.content = yield(file.content)
52
+ else
53
+ file.content = content || file.content
54
+ end
45
55
  end
46
56
 
47
- def add_signature(path, content)
57
+ def add_signature(path, content = "")
48
58
  file = SignatureFile.new(path: path)
49
- file.content = content
59
+ if block_given?
60
+ file.content = yield
61
+ else
62
+ file.content = content
63
+ end
50
64
  signature_files[path] = file
51
65
  end
52
66
 
@@ -54,9 +68,13 @@ module Steep
54
68
  signature_files.delete(path)
55
69
  end
56
70
 
57
- def update_signature(path, content)
71
+ def update_signature(path, content = nil)
58
72
  file = signature_files[path]
59
- file.content = content
73
+ if block_given?
74
+ file.content = yield(file.content)
75
+ else
76
+ file.content = content || file.content
77
+ end
60
78
  end
61
79
 
62
80
  def source_file?(path)
@@ -115,6 +133,7 @@ module Steep
115
133
  when TypeCheckStatus
116
134
  status.timestamp
117
135
  end
136
+ now = Time.now
118
137
 
119
138
  updated_files = []
120
139
 
@@ -151,11 +170,11 @@ module Steep
151
170
  validator.validate()
152
171
 
153
172
  if validator.no_error?
154
- yield env, check, Time.now
173
+ yield env, check, now
155
174
  else
156
175
  @status = SignatureValidationErrorStatus.new(
157
176
  errors: validator.each_error.to_a,
158
- timestamp: Time.now
177
+ timestamp: now
159
178
  )
160
179
  end
161
180
  else
@@ -169,11 +188,11 @@ module Steep
169
188
  location: exn.decls[0].location
170
189
  )
171
190
  ],
172
- timestamp: Time.now
191
+ timestamp: now
173
192
  )
174
193
  rescue => exn
175
194
  Steep.log_error exn
176
- @status = SignatureOtherErrorStatus.new(error: exn, timestamp: Time.now)
195
+ @status = SignatureOtherErrorStatus.new(error: exn, timestamp: now)
177
196
  end
178
197
  end
179
198
 
@@ -5,47 +5,32 @@ module Steep
5
5
 
6
6
  include Utils
7
7
 
8
- attr_reader :target_files
8
+ attr_reader :typecheck_paths
9
9
  attr_reader :queue
10
10
 
11
11
  def initialize(project:, reader:, writer:, queue: Queue.new)
12
12
  super(project: project, reader: reader, writer: writer)
13
13
 
14
- @target_files = {}
14
+ @typecheck_paths = Set[]
15
15
  @queue = queue
16
16
  end
17
17
 
18
- def enqueue_type_check(target:, path:, version: target_files[path])
19
- Steep.logger.info "Enqueueing type check: #{path}(#{version})@#{target.name}..."
20
- target_files[path] = version
21
- queue << [path, version, target]
22
- end
23
-
24
- def each_type_check_subject(path:, version:)
25
- case
26
- when !(updated_targets = project.targets.select {|target| target.signature_file?(path) }).empty?
27
- updated_targets.each do |target|
28
- target_files.each_key do |path|
29
- if target.source_file?(path)
30
- yield target, path, target_files[path]
31
- end
32
- end
33
- end
34
-
35
- when target = project.targets.find {|target| target.source_file?(path) }
36
- if target_files.key?(path)
37
- yield target, path, version
38
- end
39
- end
18
+ def enqueue_type_check(target:, path:)
19
+ Steep.logger.info "Enqueueing type check: #{target.name}::#{path}..."
20
+ queue << [target, path]
40
21
  end
41
22
 
42
23
  def typecheck_file(path, target)
43
- Steep.logger.info "Starting type checking: #{path}@#{target.name}..."
24
+ Steep.logger.info "Starting type checking: #{target.name}::#{path}..."
44
25
 
45
26
  source = target.source_files[path]
46
27
  target.type_check(target_sources: [source], validate_signatures: false)
47
28
 
48
- Steep.logger.info "Finished type checking: #{path}@#{target.name}"
29
+ if target.status.is_a?(Project::Target::TypeCheckStatus) && target.status.type_check_sources.empty?
30
+ Steep.logger.debug "Skipped type checking: #{target.name}::#{path}"
31
+ else
32
+ Steep.logger.info "Finished type checking: #{target.name}::#{path}"
33
+ end
49
34
 
50
35
  diagnostics = source_diagnostics(source, target.options)
51
36
 
@@ -109,8 +94,8 @@ module Steep
109
94
  # Don't respond to initialize request, but start type checking.
110
95
  project.targets.each do |target|
111
96
  target.source_files.each_key do |path|
112
- if target_files.key?(path)
113
- enqueue_type_check(target: target, path: path, version: target_files[path])
97
+ if typecheck_paths.include?(path)
98
+ enqueue_type_check(target: target, path: path)
114
99
  end
115
100
  end
116
101
  end
@@ -118,35 +103,34 @@ module Steep
118
103
  when "workspace/executeCommand"
119
104
  if request[:params][:command] == "steep/registerSourceToWorker"
120
105
  paths = request[:params][:arguments].map {|arg| source_path(URI.parse(arg)) }
121
- paths.each do |path|
122
- target_files[path] = 0
123
- end
106
+ typecheck_paths.merge(paths)
124
107
  end
125
108
 
126
109
  when "textDocument/didChange"
127
- update_source(request) do |path, version|
128
- if target_files.key?(path)
129
- target_files[path] = version
110
+ update_source(request) do |path, _|
111
+ source_target, signature_targets = project.targets_for_path(path)
112
+
113
+ if source_target
114
+ if typecheck_paths.include?(path)
115
+ enqueue_type_check(target: source_target, path: path)
116
+ end
130
117
  end
131
- end
132
118
 
133
- path = source_path(URI.parse(request[:params][:textDocument][:uri]))
134
- version = request[:params][:textDocument][:version]
135
- each_type_check_subject(path: path, version: version) do |target, path, version|
136
- enqueue_type_check(target: target, path: path, version: version)
119
+ signature_targets.each do |target|
120
+ target.source_files.each_key do |source_path|
121
+ if typecheck_paths.include?(source_path)
122
+ enqueue_type_check(target: target, path: source_path)
123
+ end
124
+ end
125
+ end
137
126
  end
138
127
  end
139
128
  end
140
129
 
141
130
  def handle_job(job)
142
- sleep 0.1
131
+ target, path = job
143
132
 
144
- path, version, target = job
145
- if !version || target_files[path] == version
146
- typecheck_file(path, target)
147
- else
148
- Steep.logger.info "Skipping type check: #{path}@#{target.name}, queued version=#{version}, latest version=#{target_files[path]}"
149
- end
133
+ typecheck_file(path, target)
150
134
  end
151
135
  end
152
136
  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