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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/bin/steep-prof +1 -2
- data/lib/steep.rb +2 -3
- data/lib/steep/ast/types/factory.rb +43 -28
- data/lib/steep/project.rb +25 -0
- data/lib/steep/project/completion_provider.rb +9 -7
- data/lib/steep/project/file_loader.rb +7 -2
- data/lib/steep/project/hover_content.rb +91 -80
- data/lib/steep/project/signature_file.rb +33 -0
- data/lib/steep/project/{file.rb → source_file.rb} +21 -51
- data/lib/steep/project/target.rb +31 -12
- data/lib/steep/server/code_worker.rb +30 -46
- data/lib/steep/server/interaction_worker.rb +42 -38
- data/lib/steep/server/master.rb +13 -30
- data/lib/steep/server/utils.rb +46 -13
- data/lib/steep/server/worker_process.rb +4 -2
- data/lib/steep/source.rb +60 -3
- data/lib/steep/type_inference/context_array.rb +1 -1
- data/lib/steep/type_inference/logic_type_interpreter.rb +6 -0
- data/lib/steep/version.rb +1 -1
- data/smoke/regression/fun.rb +8 -0
- data/smoke/regression/fun.rbs +4 -0
- data/smoke/toplevel/Steepfile +5 -0
- data/smoke/toplevel/a.rb +4 -0
- data/smoke/toplevel/a.rbs +3 -0
- data/steep.gemspec +1 -1
- metadata +11 -7
- data/lib/steep/ast/buffer.rb +0 -51
- data/lib/steep/ast/location.rb +0 -86
@@ -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
|
-
|
24
|
-
|
25
|
-
|
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:
|
59
|
-
module_type:
|
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:
|
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
|
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
|
data/lib/steep/project/target.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
173
|
+
yield env, check, now
|
155
174
|
else
|
156
175
|
@status = SignatureValidationErrorStatus.new(
|
157
176
|
errors: validator.each_error.to_a,
|
158
|
-
timestamp:
|
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:
|
191
|
+
timestamp: now
|
173
192
|
)
|
174
193
|
rescue => exn
|
175
194
|
Steep.log_error exn
|
176
|
-
@status = SignatureOtherErrorStatus.new(error: exn, timestamp:
|
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 :
|
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
|
-
@
|
14
|
+
@typecheck_paths = Set[]
|
15
15
|
@queue = queue
|
16
16
|
end
|
17
17
|
|
18
|
-
def enqueue_type_check(target:, path
|
19
|
-
Steep.logger.info "Enqueueing type check: #{
|
20
|
-
|
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: #{
|
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
|
-
|
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
|
113
|
-
enqueue_type_check(target: target, path: 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
|
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,
|
128
|
-
|
129
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
131
|
+
target, path = job
|
143
132
|
|
144
|
-
path,
|
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.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
-
|
147
|
-
|
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
|