steep 0.24.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/bin/smoke_runner.rb +3 -4
  4. data/lib/steep.rb +6 -4
  5. data/lib/steep/annotation_parser.rb +2 -4
  6. data/lib/steep/ast/builtin.rb +11 -21
  7. data/lib/steep/ast/types.rb +5 -3
  8. data/lib/steep/ast/types/any.rb +1 -3
  9. data/lib/steep/ast/types/boolean.rb +1 -3
  10. data/lib/steep/ast/types/bot.rb +1 -3
  11. data/lib/steep/ast/types/class.rb +2 -2
  12. data/lib/steep/ast/types/factory.rb +251 -89
  13. data/lib/steep/ast/types/helper.rb +6 -0
  14. data/lib/steep/ast/types/instance.rb +2 -2
  15. data/lib/steep/ast/types/intersection.rb +20 -13
  16. data/lib/steep/ast/types/literal.rb +1 -3
  17. data/lib/steep/ast/types/logic.rb +63 -0
  18. data/lib/steep/ast/types/name.rb +15 -67
  19. data/lib/steep/ast/types/nil.rb +1 -3
  20. data/lib/steep/ast/types/proc.rb +5 -2
  21. data/lib/steep/ast/types/record.rb +9 -4
  22. data/lib/steep/ast/types/self.rb +1 -1
  23. data/lib/steep/ast/types/top.rb +1 -3
  24. data/lib/steep/ast/types/tuple.rb +5 -3
  25. data/lib/steep/ast/types/union.rb +16 -9
  26. data/lib/steep/ast/types/var.rb +2 -2
  27. data/lib/steep/ast/types/void.rb +1 -3
  28. data/lib/steep/drivers/check.rb +4 -0
  29. data/lib/steep/errors.rb +14 -0
  30. data/lib/steep/interface/interface.rb +5 -62
  31. data/lib/steep/interface/method_type.rb +394 -93
  32. data/lib/steep/interface/substitution.rb +48 -6
  33. data/lib/steep/module_helper.rb +25 -0
  34. data/lib/steep/project.rb +25 -0
  35. data/lib/steep/project/completion_provider.rb +48 -51
  36. data/lib/steep/project/file_loader.rb +7 -2
  37. data/lib/steep/project/hover_content.rb +4 -6
  38. data/lib/steep/project/signature_file.rb +33 -0
  39. data/lib/steep/project/{file.rb → source_file.rb} +24 -54
  40. data/lib/steep/project/target.rb +36 -14
  41. data/lib/steep/server/base_worker.rb +5 -3
  42. data/lib/steep/server/code_worker.rb +31 -45
  43. data/lib/steep/server/master.rb +23 -31
  44. data/lib/steep/server/utils.rb +46 -13
  45. data/lib/steep/server/worker_process.rb +4 -2
  46. data/lib/steep/signature/validator.rb +3 -3
  47. data/lib/steep/source.rb +4 -3
  48. data/lib/steep/subtyping/check.rb +46 -59
  49. data/lib/steep/subtyping/constraints.rb +8 -0
  50. data/lib/steep/type_construction.rb +771 -513
  51. data/lib/steep/type_inference/block_params.rb +5 -0
  52. data/lib/steep/type_inference/constant_env.rb +3 -6
  53. data/lib/steep/type_inference/context.rb +8 -0
  54. data/lib/steep/type_inference/context_array.rb +4 -3
  55. data/lib/steep/type_inference/logic.rb +31 -0
  56. data/lib/steep/type_inference/logic_type_interpreter.rb +219 -0
  57. data/lib/steep/type_inference/type_env.rb +2 -2
  58. data/lib/steep/typing.rb +7 -0
  59. data/lib/steep/version.rb +1 -1
  60. data/smoke/alias/a.rb +1 -1
  61. data/smoke/case/a.rb +1 -1
  62. data/smoke/hash/d.rb +1 -1
  63. data/smoke/if/a.rb +1 -1
  64. data/smoke/module/a.rb +1 -1
  65. data/smoke/rescue/a.rb +4 -13
  66. data/smoke/toplevel/Steepfile +5 -0
  67. data/smoke/toplevel/a.rb +4 -0
  68. data/smoke/toplevel/a.rbs +3 -0
  69. data/smoke/type_case/a.rb +0 -7
  70. data/steep.gemspec +2 -2
  71. metadata +15 -11
  72. data/lib/steep/ast/method_type.rb +0 -126
  73. data/lib/steep/ast/namespace.rb +0 -80
  74. data/lib/steep/names.rb +0 -86
@@ -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,10 +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
- @status = SignatureOtherErrorStatus.new(error: exn, timestamp: Time.now)
194
+ Steep.log_error exn
195
+ @status = SignatureOtherErrorStatus.new(error: exn, timestamp: now)
176
196
  end
177
197
  end
178
198
 
@@ -194,8 +214,10 @@ module Steep
194
214
  type_check_sources = []
195
215
 
196
216
  target_sources.each do |file|
197
- if file.type_check(check, timestamp)
198
- type_check_sources << file
217
+ Steep.logger.tagged("path=#{file.path}") do
218
+ if file.type_check(check, timestamp)
219
+ type_check_sources << file
220
+ end
199
221
  end
200
222
  end
201
223
 
@@ -12,6 +12,7 @@ module Steep
12
12
  @project = project
13
13
  @reader = reader
14
14
  @writer = writer
15
+ @shutdown = false
15
16
  end
16
17
 
17
18
  def handle_request(request)
@@ -28,7 +29,7 @@ module Steep
28
29
  Steep.logger.formatter.push_tags(*tags)
29
30
  Steep.logger.tagged "background" do
30
31
  while job = queue.pop
31
- handle_job(job)
32
+ handle_job(job) unless @shutdown
32
33
  end
33
34
  end
34
35
  end
@@ -38,11 +39,12 @@ module Steep
38
39
  reader.read do |request|
39
40
  case request[:method]
40
41
  when "shutdown"
41
- # nop
42
+ @shutdown = true
43
+ writer.write(id: request[:id], result: nil)
42
44
  when "exit"
43
45
  break
44
46
  else
45
- handle_request(request)
47
+ handle_request(request) unless @shutdown
46
48
  end
47
49
  end
48
50
  ensure
@@ -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,33 +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
- path, version, target = job
143
- if !version || target_files[path] == version
144
- typecheck_file(path, target)
145
- else
146
- Steep.logger.info "Skipping type check: #{path}@#{target.name}, queued version=#{version}, latest version=#{target_files[path]}"
147
- end
131
+ target, path = job
132
+
133
+ typecheck_file(path, target)
148
134
  end
149
135
  end
150
136
  end
@@ -14,6 +14,8 @@ module Steep
14
14
  attr_reader :signature_worker
15
15
  attr_reader :code_workers
16
16
 
17
+ include Utils
18
+
17
19
  def initialize(project:, reader:, writer:, interaction_worker:, signature_worker:, code_workers:, queue: Queue.new)
18
20
  @project = project
19
21
  @reader = reader
@@ -23,22 +25,28 @@ module Steep
23
25
  @signature_worker = signature_worker
24
26
  @code_workers = code_workers
25
27
  @worker_to_paths = {}
28
+ @shutdown_request_id = nil
26
29
  end
27
30
 
28
31
  def start
29
- source_paths = project.targets.flat_map {|target| target.source_files.keys }
32
+ source_paths = project.all_source_files
30
33
  bin_size = (source_paths.size / code_workers.size) + 1
31
34
  source_paths.each_slice(bin_size).with_index do |paths, index|
32
35
  register_code_to_worker(paths, worker: code_workers[index])
33
36
  end
34
37
 
38
+ tags = Steep.logger.formatter.current_tags.dup
39
+ tags << "master"
40
+
35
41
  Thread.new do
42
+ Steep.logger.formatter.push_tags(*tags, "from-worker@interaction")
36
43
  interaction_worker.reader.read do |message|
37
44
  process_message_from_worker(message)
38
45
  end
39
46
  end
40
47
 
41
48
  Thread.new do
49
+ Steep.logger.formatter.push_tags(*tags, "from-worker@signature")
42
50
  signature_worker.reader.read do |message|
43
51
  process_message_from_worker(message)
44
52
  end
@@ -46,6 +54,7 @@ module Steep
46
54
 
47
55
  code_workers.each do |worker|
48
56
  Thread.new do
57
+ Steep.logger.formatter.push_tags(*tags, "from-worker@#{worker.name}")
49
58
  worker.reader.read do |message|
50
59
  process_message_from_worker(message)
51
60
  end
@@ -53,13 +62,21 @@ module Steep
53
62
  end
54
63
 
55
64
  Thread.new do
65
+ Steep.logger.formatter.push_tags(*tags, "from-client")
56
66
  reader.read do |request|
57
67
  process_message_from_client(request)
58
68
  end
59
69
  end
60
70
 
61
71
  while job = queue.pop
62
- writer.write(job)
72
+ if @shutdown_request_id
73
+ if job[:id] == @shutdown_request_id
74
+ writer.write(job)
75
+ break
76
+ end
77
+ else
78
+ writer.write(job)
79
+ end
63
80
  end
64
81
 
65
82
  writer.io.close
@@ -89,7 +106,7 @@ module Steep
89
106
  result: LSP::Interface::InitializeResult.new(
90
107
  capabilities: LSP::Interface::ServerCapabilities.new(
91
108
  text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
92
- change: LSP::Constant::TextDocumentSyncKind::FULL
109
+ change: LSP::Constant::TextDocumentSyncKind::INCREMENTAL
93
110
  ),
94
111
  hover_provider: true,
95
112
  completion_provider: LSP::Interface::CompletionOptions.new(
@@ -104,36 +121,10 @@ module Steep
104
121
  end
105
122
 
106
123
  when "textDocument/didChange"
124
+ update_source(message)
125
+
107
126
  uri = URI.parse(message[:params][:textDocument][:uri])
108
127
  path = project.relative_path(Pathname(uri.path))
109
- text = message[:params][:contentChanges][0][:text]
110
-
111
- project.targets.each do |target|
112
- case
113
- when target.source_file?(path)
114
- if text.empty? && !path.file?
115
- Steep.logger.info { "Deleting source file: #{path}..." }
116
- target.remove_source(path)
117
- else
118
- Steep.logger.info { "Updating source file: #{path}..." }
119
- target.update_source(path, text)
120
- end
121
- when target.possible_source_file?(path)
122
- Steep.logger.info { "Adding source file: #{path}..." }
123
- target.add_source(path, text)
124
- when target.signature_file?(path)
125
- if text.empty? && !path.file?
126
- Steep.logger.info { "Deleting signature file: #{path}..." }
127
- target.remove_signature(path)
128
- else
129
- Steep.logger.info { "Updating signature file: #{path}..." }
130
- target.update_signature(path, text)
131
- end
132
- when target.possible_signature_file?(path)
133
- Steep.logger.info { "Adding signature file: #{path}..." }
134
- target.add_signature(path, text)
135
- end
136
- end
137
128
 
138
129
  unless registered_path?(path)
139
130
  register_code_to_worker [path], worker: least_busy_worker()
@@ -154,6 +145,7 @@ module Steep
154
145
 
155
146
  when "shutdown"
156
147
  queue << { id: id, result: nil }
148
+ @shutdown_request_id = id
157
149
 
158
150
  when "exit"
159
151
  queue << nil
@@ -7,23 +7,56 @@ module Steep
7
7
  project.relative_path(Pathname(uri.path))
8
8
  end
9
9
 
10
+ def apply_change(change, text)
11
+ range = change[:range]
12
+
13
+ if range
14
+ text = text.dup
15
+
16
+ buf = AST::Buffer.new(name: :_, content: text)
17
+
18
+ start_pos = buf.loc_to_pos(range[:start].yield_self {|pos| [pos[:line]+1, pos[:character]] })
19
+ end_pos = buf.loc_to_pos(range[:end].yield_self {|pos| [pos[:line]+1, pos[:character]] })
20
+
21
+ text[start_pos...end_pos] = change[:text]
22
+ text
23
+ else
24
+ change[:text]
25
+ end
26
+ end
27
+
10
28
  def update_source(request)
11
29
  path = source_path(URI.parse(request[:params][:textDocument][:uri]))
12
- text = request[:params][:contentChanges][0][:text]
13
30
  version = request[:params][:textDocument][:version]
31
+ Steep.logger.info { "Updating source: path=#{path}, version=#{version}..." }
32
+
33
+ changes = request[:params][:contentChanges]
34
+
35
+ source_target, signature_targets = project.targets_for_path(path)
36
+
37
+ if source_target
38
+ changes.each do |change|
39
+ case
40
+ when source_target.source_file?(path)
41
+ Steep.logger.debug { "Updating source in #{source_target.name}: path=#{path}" }
42
+ source_target.update_source(path) {|text| apply_change(change, text) }
43
+ when source_target.possible_source_file?(path)
44
+ Steep.logger.debug { "Adding source to #{source_target.name}: path=#{path}" }
45
+ source_target.add_source(path, change[:text])
46
+ end
47
+ end
48
+ end
14
49
 
15
- Steep.logger.debug "Updateing source: path=#{path}, version=#{version}, size=#{text.bytesize}"
16
-
17
- project.targets.each do |target|
18
- case
19
- when target.source_file?(path)
20
- target.update_source path, text
21
- when target.possible_source_file?(path)
22
- target.add_source path, text
23
- when target.signature_file?(path)
24
- target.update_signature path, text
25
- when target.possible_signature_file?(path)
26
- target.add_signature path, text
50
+ signature_targets.each do |target|
51
+ changes.each do |change|
52
+ case
53
+ when target.signature_file?(path)
54
+ Steep.logger.debug { "Updating signature in #{target.name}: path=#{path}" }
55
+ target.update_signature(path) {|text| apply_change(change, text) }
56
+ when target.possible_signature_file?(path)
57
+ Steep.logger.debug { "Adding signature to #{target.name}: path=#{path}" }
58
+ target.add_signature(path, change[:text])
59
+ end
27
60
  end
28
61
  end
29
62