steep 0.24.0 → 0.30.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.
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