steep 0.29.0 → 0.33.0

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