steep 0.13.0 → 0.16.2

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 (214) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/CHANGELOG.md +28 -0
  4. data/Rakefile +0 -13
  5. data/bin/setup +0 -2
  6. data/bin/smoke_runner.rb +0 -1
  7. data/exe/steep +0 -1
  8. data/lib/steep.rb +33 -1
  9. data/lib/steep/annotation_parser.rb +4 -4
  10. data/lib/steep/ast/buffer.rb +11 -7
  11. data/lib/steep/ast/builtin.rb +8 -0
  12. data/lib/steep/ast/types/factory.rb +124 -89
  13. data/lib/steep/cli.rb +16 -1
  14. data/lib/steep/drivers/annotations.rb +1 -1
  15. data/lib/steep/drivers/check.rb +20 -4
  16. data/lib/steep/drivers/init.rb +5 -5
  17. data/lib/steep/drivers/langserver.rb +13 -287
  18. data/lib/steep/drivers/utils/driver_helper.rb +1 -1
  19. data/lib/steep/drivers/vendor.rb +2 -2
  20. data/lib/steep/drivers/watch.rb +97 -85
  21. data/lib/steep/drivers/worker.rb +51 -0
  22. data/lib/steep/project.rb +9 -5
  23. data/lib/steep/project/completion_provider.rb +298 -0
  24. data/lib/steep/project/dsl.rb +14 -0
  25. data/lib/steep/project/file.rb +54 -47
  26. data/lib/steep/project/hover_content.rb +17 -8
  27. data/lib/steep/project/options.rb +25 -3
  28. data/lib/steep/project/target.rb +40 -24
  29. data/lib/steep/server/base_worker.rb +56 -0
  30. data/lib/steep/server/code_worker.rb +151 -0
  31. data/lib/steep/server/interaction_worker.rb +281 -0
  32. data/lib/steep/server/master.rb +196 -0
  33. data/lib/steep/server/signature_worker.rb +148 -0
  34. data/lib/steep/server/utils.rb +36 -0
  35. data/lib/steep/server/worker_process.rb +62 -0
  36. data/lib/steep/signature/errors.rb +1 -1
  37. data/lib/steep/signature/validator.rb +13 -13
  38. data/lib/steep/source.rb +1 -1
  39. data/lib/steep/type_construction.rb +1004 -727
  40. data/lib/steep/type_inference/constant_env.rb +3 -11
  41. data/lib/steep/type_inference/context.rb +8 -3
  42. data/lib/steep/type_inference/context_array.rb +111 -0
  43. data/lib/steep/type_inference/local_variable_type_env.rb +226 -0
  44. data/lib/steep/type_inference/logic.rb +130 -0
  45. data/lib/steep/type_inference/type_env.rb +5 -69
  46. data/lib/steep/typing.rb +91 -23
  47. data/lib/steep/version.rb +1 -1
  48. data/smoke/alias/Steepfile +1 -0
  49. data/smoke/alias/a.rb +1 -1
  50. data/smoke/and/Steepfile +1 -0
  51. data/smoke/array/Steepfile +1 -0
  52. data/smoke/array/b.rb +0 -2
  53. data/smoke/block/Steepfile +1 -0
  54. data/smoke/case/Steepfile +1 -0
  55. data/smoke/class/Steepfile +1 -0
  56. data/smoke/const/Steepfile +1 -0
  57. data/smoke/dstr/Steepfile +1 -0
  58. data/smoke/ensure/Steepfile +1 -0
  59. data/smoke/enumerator/Steepfile +1 -0
  60. data/smoke/extension/Steepfile +1 -0
  61. data/smoke/extension/c.rb +1 -0
  62. data/smoke/hash/Steepfile +1 -0
  63. data/smoke/hello/Steepfile +1 -0
  64. data/smoke/if/Steepfile +1 -0
  65. data/smoke/if/a.rb +1 -1
  66. data/smoke/implements/Steepfile +1 -0
  67. data/smoke/initialize/Steepfile +1 -0
  68. data/smoke/integer/Steepfile +1 -0
  69. data/smoke/interface/Steepfile +1 -0
  70. data/smoke/kwbegin/Steepfile +1 -0
  71. data/smoke/lambda/Steepfile +1 -0
  72. data/smoke/literal/Steepfile +1 -0
  73. data/smoke/map/Steepfile +1 -0
  74. data/smoke/method/Steepfile +1 -0
  75. data/smoke/module/Steepfile +1 -0
  76. data/smoke/regexp/Steepfile +1 -0
  77. data/smoke/regexp/b.rb +4 -4
  78. data/smoke/regression/Steepfile +1 -0
  79. data/smoke/rescue/Steepfile +1 -0
  80. data/smoke/rescue/a.rb +1 -1
  81. data/smoke/self/Steepfile +1 -0
  82. data/smoke/skip/Steepfile +1 -0
  83. data/smoke/stdout/Steepfile +1 -0
  84. data/smoke/super/Steepfile +1 -0
  85. data/smoke/type_case/Steepfile +1 -0
  86. data/smoke/yield/Steepfile +1 -0
  87. data/steep.gemspec +8 -8
  88. metadata +38 -138
  89. data/exe/rbs +0 -3
  90. data/exe/ruby-signature +0 -3
  91. data/vendor/ruby-signature/.github/workflows/ruby.yml +0 -27
  92. data/vendor/ruby-signature/.gitignore +0 -12
  93. data/vendor/ruby-signature/.rubocop.yml +0 -15
  94. data/vendor/ruby-signature/BSDL +0 -22
  95. data/vendor/ruby-signature/COPYING +0 -56
  96. data/vendor/ruby-signature/Gemfile +0 -6
  97. data/vendor/ruby-signature/README.md +0 -93
  98. data/vendor/ruby-signature/Rakefile +0 -66
  99. data/vendor/ruby-signature/bin/annotate-with-rdoc +0 -156
  100. data/vendor/ruby-signature/bin/console +0 -14
  101. data/vendor/ruby-signature/bin/query-rdoc +0 -103
  102. data/vendor/ruby-signature/bin/setup +0 -10
  103. data/vendor/ruby-signature/bin/sort +0 -88
  104. data/vendor/ruby-signature/bin/test_runner.rb +0 -17
  105. data/vendor/ruby-signature/docs/CONTRIBUTING.md +0 -97
  106. data/vendor/ruby-signature/docs/sigs.md +0 -148
  107. data/vendor/ruby-signature/docs/stdlib.md +0 -152
  108. data/vendor/ruby-signature/docs/syntax.md +0 -528
  109. data/vendor/ruby-signature/exe/rbs +0 -3
  110. data/vendor/ruby-signature/exe/ruby-signature +0 -7
  111. data/vendor/ruby-signature/lib/ruby/signature.rb +0 -64
  112. data/vendor/ruby-signature/lib/ruby/signature/ast/annotation.rb +0 -29
  113. data/vendor/ruby-signature/lib/ruby/signature/ast/comment.rb +0 -29
  114. data/vendor/ruby-signature/lib/ruby/signature/ast/declarations.rb +0 -391
  115. data/vendor/ruby-signature/lib/ruby/signature/ast/members.rb +0 -364
  116. data/vendor/ruby-signature/lib/ruby/signature/buffer.rb +0 -52
  117. data/vendor/ruby-signature/lib/ruby/signature/builtin_names.rb +0 -54
  118. data/vendor/ruby-signature/lib/ruby/signature/cli.rb +0 -534
  119. data/vendor/ruby-signature/lib/ruby/signature/constant.rb +0 -28
  120. data/vendor/ruby-signature/lib/ruby/signature/constant_table.rb +0 -152
  121. data/vendor/ruby-signature/lib/ruby/signature/definition.rb +0 -172
  122. data/vendor/ruby-signature/lib/ruby/signature/definition_builder.rb +0 -921
  123. data/vendor/ruby-signature/lib/ruby/signature/environment.rb +0 -283
  124. data/vendor/ruby-signature/lib/ruby/signature/environment_loader.rb +0 -138
  125. data/vendor/ruby-signature/lib/ruby/signature/environment_walker.rb +0 -126
  126. data/vendor/ruby-signature/lib/ruby/signature/errors.rb +0 -189
  127. data/vendor/ruby-signature/lib/ruby/signature/location.rb +0 -104
  128. data/vendor/ruby-signature/lib/ruby/signature/method_type.rb +0 -125
  129. data/vendor/ruby-signature/lib/ruby/signature/namespace.rb +0 -93
  130. data/vendor/ruby-signature/lib/ruby/signature/parser.y +0 -1343
  131. data/vendor/ruby-signature/lib/ruby/signature/prototype/rb.rb +0 -441
  132. data/vendor/ruby-signature/lib/ruby/signature/prototype/rbi.rb +0 -579
  133. data/vendor/ruby-signature/lib/ruby/signature/prototype/runtime.rb +0 -383
  134. data/vendor/ruby-signature/lib/ruby/signature/substitution.rb +0 -48
  135. data/vendor/ruby-signature/lib/ruby/signature/test.rb +0 -28
  136. data/vendor/ruby-signature/lib/ruby/signature/test/errors.rb +0 -63
  137. data/vendor/ruby-signature/lib/ruby/signature/test/hook.rb +0 -290
  138. data/vendor/ruby-signature/lib/ruby/signature/test/setup.rb +0 -58
  139. data/vendor/ruby-signature/lib/ruby/signature/test/spy.rb +0 -324
  140. data/vendor/ruby-signature/lib/ruby/signature/test/test_helper.rb +0 -185
  141. data/vendor/ruby-signature/lib/ruby/signature/test/type_check.rb +0 -256
  142. data/vendor/ruby-signature/lib/ruby/signature/type_name.rb +0 -72
  143. data/vendor/ruby-signature/lib/ruby/signature/types.rb +0 -932
  144. data/vendor/ruby-signature/lib/ruby/signature/variance_calculator.rb +0 -140
  145. data/vendor/ruby-signature/lib/ruby/signature/vendorer.rb +0 -49
  146. data/vendor/ruby-signature/lib/ruby/signature/version.rb +0 -5
  147. data/vendor/ruby-signature/lib/ruby/signature/writer.rb +0 -271
  148. data/vendor/ruby-signature/ruby-signature.gemspec +0 -45
  149. data/vendor/ruby-signature/stdlib/abbrev/abbrev.rbs +0 -3
  150. data/vendor/ruby-signature/stdlib/base64/base64.rbs +0 -15
  151. data/vendor/ruby-signature/stdlib/builtin/array.rbs +0 -1997
  152. data/vendor/ruby-signature/stdlib/builtin/basic_object.rbs +0 -280
  153. data/vendor/ruby-signature/stdlib/builtin/binding.rbs +0 -177
  154. data/vendor/ruby-signature/stdlib/builtin/builtin.rbs +0 -35
  155. data/vendor/ruby-signature/stdlib/builtin/class.rbs +0 -145
  156. data/vendor/ruby-signature/stdlib/builtin/comparable.rbs +0 -116
  157. data/vendor/ruby-signature/stdlib/builtin/complex.rbs +0 -400
  158. data/vendor/ruby-signature/stdlib/builtin/constants.rbs +0 -37
  159. data/vendor/ruby-signature/stdlib/builtin/data.rbs +0 -5
  160. data/vendor/ruby-signature/stdlib/builtin/deprecated.rbs +0 -2
  161. data/vendor/ruby-signature/stdlib/builtin/dir.rbs +0 -419
  162. data/vendor/ruby-signature/stdlib/builtin/encoding.rbs +0 -606
  163. data/vendor/ruby-signature/stdlib/builtin/enumerable.rbs +0 -404
  164. data/vendor/ruby-signature/stdlib/builtin/enumerator.rbs +0 -260
  165. data/vendor/ruby-signature/stdlib/builtin/errno.rbs +0 -781
  166. data/vendor/ruby-signature/stdlib/builtin/errors.rbs +0 -582
  167. data/vendor/ruby-signature/stdlib/builtin/exception.rbs +0 -193
  168. data/vendor/ruby-signature/stdlib/builtin/false_class.rbs +0 -40
  169. data/vendor/ruby-signature/stdlib/builtin/fiber.rbs +0 -68
  170. data/vendor/ruby-signature/stdlib/builtin/fiber_error.rbs +0 -12
  171. data/vendor/ruby-signature/stdlib/builtin/file.rbs +0 -476
  172. data/vendor/ruby-signature/stdlib/builtin/file_test.rbs +0 -59
  173. data/vendor/ruby-signature/stdlib/builtin/float.rbs +0 -696
  174. data/vendor/ruby-signature/stdlib/builtin/gc.rbs +0 -121
  175. data/vendor/ruby-signature/stdlib/builtin/hash.rbs +0 -1029
  176. data/vendor/ruby-signature/stdlib/builtin/integer.rbs +0 -710
  177. data/vendor/ruby-signature/stdlib/builtin/io.rbs +0 -683
  178. data/vendor/ruby-signature/stdlib/builtin/kernel.rbs +0 -574
  179. data/vendor/ruby-signature/stdlib/builtin/marshal.rbs +0 -135
  180. data/vendor/ruby-signature/stdlib/builtin/match_data.rbs +0 -141
  181. data/vendor/ruby-signature/stdlib/builtin/math.rbs +0 -66
  182. data/vendor/ruby-signature/stdlib/builtin/method.rbs +0 -182
  183. data/vendor/ruby-signature/stdlib/builtin/module.rbs +0 -248
  184. data/vendor/ruby-signature/stdlib/builtin/nil_class.rbs +0 -82
  185. data/vendor/ruby-signature/stdlib/builtin/numeric.rbs +0 -409
  186. data/vendor/ruby-signature/stdlib/builtin/object.rbs +0 -824
  187. data/vendor/ruby-signature/stdlib/builtin/proc.rbs +0 -426
  188. data/vendor/ruby-signature/stdlib/builtin/process.rbs +0 -354
  189. data/vendor/ruby-signature/stdlib/builtin/random.rbs +0 -93
  190. data/vendor/ruby-signature/stdlib/builtin/range.rbs +0 -226
  191. data/vendor/ruby-signature/stdlib/builtin/rational.rbs +0 -424
  192. data/vendor/ruby-signature/stdlib/builtin/rb_config.rbs +0 -10
  193. data/vendor/ruby-signature/stdlib/builtin/regexp.rbs +0 -131
  194. data/vendor/ruby-signature/stdlib/builtin/ruby_vm.rbs +0 -14
  195. data/vendor/ruby-signature/stdlib/builtin/signal.rbs +0 -55
  196. data/vendor/ruby-signature/stdlib/builtin/string.rbs +0 -770
  197. data/vendor/ruby-signature/stdlib/builtin/string_io.rbs +0 -13
  198. data/vendor/ruby-signature/stdlib/builtin/struct.rbs +0 -40
  199. data/vendor/ruby-signature/stdlib/builtin/symbol.rbs +0 -230
  200. data/vendor/ruby-signature/stdlib/builtin/thread.rbs +0 -1112
  201. data/vendor/ruby-signature/stdlib/builtin/thread_group.rbs +0 -23
  202. data/vendor/ruby-signature/stdlib/builtin/time.rbs +0 -739
  203. data/vendor/ruby-signature/stdlib/builtin/trace_point.rbs +0 -91
  204. data/vendor/ruby-signature/stdlib/builtin/true_class.rbs +0 -46
  205. data/vendor/ruby-signature/stdlib/builtin/unbound_method.rbs +0 -159
  206. data/vendor/ruby-signature/stdlib/builtin/warning.rbs +0 -17
  207. data/vendor/ruby-signature/stdlib/erb/erb.rbs +0 -18
  208. data/vendor/ruby-signature/stdlib/find/find.rbs +0 -44
  209. data/vendor/ruby-signature/stdlib/pathname/pathname.rbs +0 -21
  210. data/vendor/ruby-signature/stdlib/prime/integer-extension.rbs +0 -23
  211. data/vendor/ruby-signature/stdlib/prime/prime.rbs +0 -188
  212. data/vendor/ruby-signature/stdlib/securerandom/securerandom.rbs +0 -9
  213. data/vendor/ruby-signature/stdlib/set/set.rbs +0 -77
  214. data/vendor/ruby-signature/stdlib/tmpdir/tmpdir.rbs +0 -53
@@ -15,110 +15,122 @@ module Steep
15
15
  @queue = Thread::Queue.new
16
16
  end
17
17
 
18
- def listener
19
- @listener ||= begin
20
- Steep.logger.info "Watching #{dirs.join(", ")}..."
21
- Listen.to(*dirs.map(&:to_s)) do |modified, added, removed|
22
- Steep.logger.tagged "watch" do
23
- Steep.logger.info "Received file system updates: modified=[#{modified.join(",")}], added=[#{added.join(",")}], removed=[#{removed.join(",")}]"
24
- end
25
- queue << [modified, added, removed]
26
- end
18
+ def run()
19
+ if dirs.empty?
20
+ stdout.puts "Specify directories to watch"
21
+ return 1
27
22
  end
28
- end
29
23
 
30
- def type_check_loop(project)
31
- until queue.closed?
32
- stdout.puts "🚥 Waiting for updates..."
24
+ project = load_config()
33
25
 
34
- events = []
35
- events << queue.deq
36
- until queue.empty?
37
- events << queue.deq(nonblock: true)
38
- end
26
+ loader = Project::FileLoader.new(project: project)
27
+ loader.load_sources([])
28
+ loader.load_signatures()
39
29
 
40
- events.compact.each do |modified, added, removed|
41
- modified.each do |name|
42
- path = Pathname(name).relative_path_from(Pathname.pwd)
30
+ client_read, server_write = IO.pipe
31
+ server_read, client_write = IO.pipe
43
32
 
44
- project.targets.each do |target|
45
- target.update_source path, path.read if target.source_file?(path)
46
- target.update_signature path, path.read if target.signature_file?(path)
47
- end
48
- end
33
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
34
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
49
35
 
50
- added.each do |name|
51
- path = Pathname(name).relative_path_from(Pathname.pwd)
36
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
37
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
52
38
 
53
- project.targets.each do |target|
54
- target.add_source path, path.read if target.possible_source_file?(path)
55
- target.add_signature path, path.read if target.possible_signature_file?(path)
56
- end
57
- end
39
+ interaction_worker = Server::InteractionWorker.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path)
40
+ signature_worker = Server::WorkerProcess.spawn_worker(:signature, name: "signature", steepfile: project.steepfile_path)
41
+ code_workers = Server::WorkerProcess.spawn_code_workers(steepfile: project.steepfile_path)
58
42
 
59
- removed.each do |name|
60
- path = Pathname(name).relative_path_from(Pathname.pwd)
43
+ master = Server::Master.new(
44
+ project: project,
45
+ reader: server_reader,
46
+ writer: server_writer,
47
+ interaction_worker: interaction_worker,
48
+ signature_worker: signature_worker,
49
+ code_workers: code_workers
50
+ )
61
51
 
62
- project.targets.each do |target|
63
- target.remove_source path if target.source_file?(path)
64
- target.remove_signature path if target.signature_file?(path)
65
- end
52
+ main_thread = Thread.start do
53
+ master.start()
54
+ end
55
+ main_thread.abort_on_exception = true
56
+
57
+ client_writer.write(method: "initialize", id: 0)
58
+
59
+ Steep.logger.info "Watching #{dirs.join(", ")}..."
60
+ listener = Listen.to(*dirs.map(&:to_s)) do |modified, added, removed|
61
+ stdout.puts "🔬 Type checking updated files..."
62
+
63
+ version = Time.now.to_i
64
+ Steep.logger.tagged "watch" do
65
+ Steep.logger.info "Received file system updates: modified=[#{modified.join(",")}], added=[#{added.join(",")}], removed=[#{removed.join(",")}]"
66
+
67
+ (modified + added).each do |path|
68
+ client_writer.write(
69
+ method: "textDocument/didChange",
70
+ params: {
71
+ textDocument: {
72
+ uri: "file://#{path}",
73
+ version: version
74
+ },
75
+ contentChanges: [
76
+ {
77
+ text: Pathname(path).read
78
+ }
79
+ ]
80
+ }
81
+ )
66
82
  end
67
- end
68
83
 
69
- stdout.puts "🔬 Type checking..."
70
- type_check project
71
- print_project_result project
72
- end
73
- rescue ClosedQueueError
74
- # nop
75
- end
84
+ removed.each do |path|
85
+ client_writer.write(
86
+ method: "textDocument/didChange",
87
+ params: {
88
+ textDocument: {
89
+ uri: "file://#{path}",
90
+ version: version
91
+ },
92
+ contentChanges: [
93
+ {
94
+ text: ""
95
+ }
96
+ ]
97
+ }
98
+ )
99
+ end
100
+ end
101
+ end.tap(&:start)
76
102
 
77
- def print_project_result(project)
78
- project.targets.each do |target|
79
- Steep.logger.tagged "target=#{target.name}" do
80
- case (status = target.status)
81
- when Project::Target::SignatureSyntaxErrorStatus
82
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
83
- printer.print_syntax_errors(status.errors)
84
- when Project::Target::SignatureValidationErrorStatus
85
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
86
- printer.print_semantic_errors(status.errors)
87
- when Project::Target::TypeCheckStatus
88
- status.type_check_sources.each do |source_file|
89
- source_file.errors.each do |error|
90
- error.print_to stdout
103
+ begin
104
+ stdout.puts "👀 Watching directories, Ctrl-C to stop."
105
+ client_reader.read do |response|
106
+ case response[:method]
107
+ when "textDocument/publishDiagnostics"
108
+ uri = URI.parse(response[:params][:uri])
109
+ path = project.relative_path(Pathname(uri.path))
110
+
111
+ diagnostics = response[:params][:diagnostics]
112
+
113
+ unless diagnostics.empty?
114
+ diagnostics.each do |diagnostic|
115
+ start = diagnostic[:range][:start]
116
+ loc = "#{start[:line]+1}:#{start[:character]}"
117
+ message = diagnostic[:message].chomp.lines.join(" ")
118
+
119
+ stdout.puts "#{path}:#{loc}: #{message}"
91
120
  end
92
121
  end
93
122
  end
94
123
  end
95
- end
96
- end
97
-
98
- def run()
99
- if dirs.empty?
100
- stdout.puts "Specify directories to watch"
101
- return 1
102
- end
103
-
104
- project = load_config()
105
-
106
- loader = Project::FileLoader.new(project: project)
107
- loader.load_sources([])
108
- loader.load_signatures()
109
-
110
- type_check project
111
- print_project_result project
112
-
113
- listener.start
114
-
115
- stdout.puts "👀 Watching directories, Ctrl-C to stop."
116
- begin
117
- type_check_loop project
118
124
  rescue Interrupt
119
- # bye
125
+ stdout.puts "Shutting down workers..."
126
+ client_writer.write({ method: :shutdown, id: 10000 })
127
+ client_writer.write({ method: :exit })
128
+ client_writer.io.close()
120
129
  end
121
130
 
131
+ listener.stop
132
+ main_thread.join
133
+
122
134
  0
123
135
  end
124
136
  end
@@ -0,0 +1,51 @@
1
+ module Steep
2
+ module Drivers
3
+ class Worker
4
+ attr_reader :stdout, :stderr, :stdin
5
+
6
+ attr_accessor :steepfile_path
7
+ attr_accessor :worker_type
8
+ attr_accessor :worker_name
9
+
10
+ include Utils::DriverHelper
11
+
12
+ def initialize(stdout:, stderr:, stdin:)
13
+ @stdout = stdout
14
+ @stderr = stderr
15
+ @stdin = stdin
16
+ end
17
+
18
+ def run()
19
+ Steep.logger.tagged("#{worker_type}:#{worker_name}") do
20
+ project = load_config()
21
+
22
+ loader = Project::FileLoader.new(project: project)
23
+ loader.load_sources([])
24
+ loader.load_signatures()
25
+
26
+ reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdin)
27
+ writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdout)
28
+
29
+ worker = case worker_type
30
+ when :code
31
+ Server::CodeWorker.new(project: project, reader: reader, writer: writer)
32
+ when :signature
33
+ Server::SignatureWorker.new(project: project, reader: reader, writer: writer)
34
+ when :interaction
35
+ Server::InteractionWorker.new(project: project, reader: reader, writer: writer)
36
+ else
37
+ raise "Unknown worker type: #{worker_type}"
38
+ end
39
+
40
+ Steep.logger.info "Starting #{worker_type} worker..."
41
+
42
+ worker.run()
43
+ rescue Interrupt
44
+ Steep.logger.info "Shutting down by interrupt..."
45
+ end
46
+
47
+ 0
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,17 +1,21 @@
1
1
  module Steep
2
2
  class Project
3
3
  attr_reader :targets
4
- attr_reader :base_dir
4
+ attr_reader :steepfile_path
5
5
 
6
- def initialize(base_dir:)
6
+ def initialize(steepfile_path:)
7
7
  @targets = []
8
- @base_dir = base_dir
8
+ @steepfile_path = steepfile_path
9
9
 
10
- unless base_dir.absolute?
11
- raise "Project#initialize(base_dir:): base_dir should be absolute path"
10
+ unless steepfile_path.absolute?
11
+ raise "Project#initialize(steepfile_path:): steepfile_path should be absolute path"
12
12
  end
13
13
  end
14
14
 
15
+ def base_dir
16
+ steepfile_path.parent
17
+ end
18
+
15
19
  def relative_path(path)
16
20
  path.relative_path_from(base_dir)
17
21
  end
@@ -0,0 +1,298 @@
1
+ module Steep
2
+ class Project
3
+ class CompletionProvider
4
+ Position = Struct.new(:line, :column, keyword_init: true) do
5
+ def -(size)
6
+ Position.new(line: line, column: column - size)
7
+ end
8
+ end
9
+ Range = Struct.new(:start, :end, keyword_init: true)
10
+
11
+ InstanceVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
12
+ LocalVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
13
+ MethodNameItem = Struct.new(:identifier, :range, :definition, :method_type, keyword_init: true)
14
+
15
+ attr_reader :source_text
16
+ attr_reader :path
17
+ attr_reader :subtyping
18
+ attr_reader :modified_text
19
+ attr_reader :source
20
+ attr_reader :typing
21
+
22
+ def initialize(source_text:, path:, subtyping:)
23
+ @source_text = source_text
24
+ @path = path
25
+ @subtyping = subtyping
26
+ end
27
+
28
+ def type_check!(text)
29
+ @modified_text = text
30
+
31
+ Steep.measure "parsing" do
32
+ @source = SourceFile.parse(text, path: path, factory: subtyping.factory)
33
+ end
34
+
35
+ Steep.measure "typechecking" do
36
+ @typing = SourceFile.type_check(source, subtyping: subtyping)
37
+ end
38
+ end
39
+
40
+ def run(line:, column:)
41
+ source_text = self.source_text.dup
42
+ index = index_for(source_text, line:line, column: column)
43
+ possible_trigger = source_text[index-1]
44
+
45
+ Steep.logger.debug "possible_trigger: #{possible_trigger.inspect}"
46
+
47
+ position = Position.new(line: line, column: column)
48
+
49
+ begin
50
+ Steep.logger.tagged "completion_provider#run(line: #{line}, column: #{column})" do
51
+ Steep.measure "type_check!" do
52
+ type_check!(source_text)
53
+ end
54
+ end
55
+
56
+ Steep.measure "completion item collection" do
57
+ items_for_trigger(position: position)
58
+ end
59
+
60
+ rescue Parser::SyntaxError => exn
61
+ Steep.logger.error "recovering syntax error: #{exn.inspect}"
62
+ case possible_trigger
63
+ when "."
64
+ source_text[index-1] = " "
65
+ type_check!(source_text)
66
+ items_for_dot(position: position)
67
+ when "@"
68
+ source_text[index-1] = " "
69
+ type_check!(source_text)
70
+ items_for_atmark(position: position)
71
+ else
72
+ []
73
+ end
74
+ end
75
+ end
76
+
77
+ def range_from_loc(loc)
78
+ Range.new(
79
+ start: Position.new(line: loc.line, column: loc.column),
80
+ end: Position.new(line: loc.last_line, column: loc.last_line)
81
+ )
82
+ end
83
+
84
+ def at_end?(pos, of:)
85
+ of.last_line == pos.line && of.last_column == pos.column
86
+ end
87
+
88
+ def range_for(position, prefix: "")
89
+ if prefix.empty?
90
+ Range.new(start: position, end: position)
91
+ else
92
+ Range.new(start: position - prefix.size, end: position)
93
+ end
94
+ end
95
+
96
+ def items_for_trigger(position:)
97
+ node, *parents = source.find_nodes(line: position.line, column: position.column)
98
+ node ||= source.node
99
+
100
+ return [] unless node
101
+
102
+ items = []
103
+
104
+ context = typing.context_at(line: position.line, column: position.column)
105
+
106
+ case
107
+ when node.type == :send && node.children[0] == nil && at_end?(position, of: node.loc.selector)
108
+ # foo ←
109
+ prefix = node.children[1].to_s
110
+
111
+ method_items_for_receiver_type(context.self_type,
112
+ include_private: true,
113
+ prefix: prefix,
114
+ position: position,
115
+ items: items)
116
+ local_variable_items_for_context(context, position: position, prefix: prefix, items: items)
117
+
118
+ when node.type == :lvar && at_end?(position, of: node.loc)
119
+ # foo ← (lvar)
120
+ local_variable_items_for_context(context, position: position, prefix: node.children[0].name.to_s, items: items)
121
+
122
+ when node.type == :send && node.children[0] && at_end?(position, of: node.loc.selector)
123
+ # foo.ba ←
124
+ receiver_type = case (type = typing.type_of(node: node.children[0]))
125
+ when AST::Types::Self
126
+ context.self_type
127
+ else
128
+ type
129
+ end
130
+ prefix = node.children[1].to_s
131
+
132
+ method_items_for_receiver_type(receiver_type,
133
+ include_private: false,
134
+ prefix: prefix,
135
+ position: position,
136
+ items: items)
137
+
138
+ when node.type == :const && node.children[0] == nil && at_end?(position, of: node.loc)
139
+ # Foo ← (const)
140
+ prefix = node.children[1].to_s
141
+
142
+ method_items_for_receiver_type(context.self_type,
143
+ include_private: false,
144
+ prefix: prefix,
145
+ position: position,
146
+ items: items)
147
+
148
+ when node.type == :send && at_end?(position, of: node.loc.dot)
149
+ # foo.← ba
150
+ receiver_type = case (type = typing.type_of(node: node.children[0]))
151
+ when AST::Types::Self
152
+ context.self_type
153
+ else
154
+ type
155
+ end
156
+
157
+ method_items_for_receiver_type(receiver_type,
158
+ include_private: false,
159
+ prefix: "",
160
+ position: position,
161
+ items: items)
162
+
163
+ when node.type == :ivar && at_end?(position, of: node.loc)
164
+ # @fo ←
165
+ instance_variable_items_for_context(context, position: position, prefix: node.children[0].to_s, items: items)
166
+
167
+ else
168
+ method_items_for_receiver_type(context.self_type,
169
+ include_private: true,
170
+ prefix: "",
171
+ position: position,
172
+ items: items)
173
+ local_variable_items_for_context(context, position: position, prefix: "", items: items)
174
+ instance_variable_items_for_context(context, position: position, prefix: "", items: items)
175
+ end
176
+
177
+ items
178
+ end
179
+
180
+ def items_for_dot(position:)
181
+ # foo. ←
182
+ shift_pos = position-1
183
+ node, *parents = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
184
+ node ||= source.node
185
+
186
+ return [] unless node
187
+
188
+ if at_end?(shift_pos, of: node.loc)
189
+ context = typing.context_at(line: position.line, column: position.column)
190
+ receiver_type = case (type = typing.type_of(node: node))
191
+ when AST::Types::Self
192
+ context.self_type
193
+ else
194
+ type
195
+ end
196
+
197
+ items = []
198
+ method_items_for_receiver_type(receiver_type,
199
+ include_private: false,
200
+ prefix: "",
201
+ position: position,
202
+ items: items)
203
+ items
204
+ else
205
+ []
206
+ end
207
+ end
208
+
209
+ def items_for_atmark(position:)
210
+ # @ ←
211
+ shift_pos = position-1
212
+ node, *parents = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
213
+ node ||= source.node
214
+
215
+ return [] unless node
216
+
217
+ context = typing.context_at(line: position.line, column: position.column)
218
+ items = []
219
+ instance_variable_items_for_context(context, prefix: "", position: position, items: items)
220
+ items
221
+ end
222
+
223
+ def method_items_for_receiver_type(type, include_private:, prefix:, position:, items:)
224
+ range = range_for(position, prefix: prefix)
225
+ definition = case type
226
+ when AST::Types::Name::Instance
227
+ type_name = subtyping.factory.type_name_1(type.name)
228
+ subtyping.factory.definition_builder.build_instance(type_name)
229
+ when AST::Types::Name::Class, AST::Types::Name::Module
230
+ type_name = subtyping.factory.type_name_1(type.name)
231
+ subtyping.factory.definition_builder.build_singleton(type_name)
232
+ when AST::Types::Name::Interface
233
+ type_name = subtyping.factory.type_name_1(type.name)
234
+ interface = subtyping.factory.env.find_class(type_name)
235
+ subtyping.factory.definition_builder.build_interface(type_name, interface)
236
+ end
237
+
238
+ if definition
239
+ definition.methods.each do |name, method|
240
+ if include_private || method.public?
241
+ if name.to_s.start_with?(prefix)
242
+ if word_name?(name.to_s)
243
+ method.method_types.each do |method_type|
244
+ items << MethodNameItem.new(identifier: name,
245
+ range: range,
246
+ definition: method,
247
+ method_type: method_type)
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ def word_name?(name)
257
+ name =~ /\w/
258
+ end
259
+
260
+ def local_variable_items_for_context(context, position:, prefix:, items:)
261
+ range = range_for(position, prefix: prefix)
262
+ context.lvar_env.each do |name, type|
263
+ if name.to_s.start_with?(prefix)
264
+ items << LocalVariableItem.new(identifier: name,
265
+ range: range,
266
+ type: type)
267
+ end
268
+ end
269
+ end
270
+
271
+ def instance_variable_items_for_context(context, position:, prefix:, items:)
272
+ range = range_for(position, prefix: prefix)
273
+ context.type_env.ivar_types.map do |name, type|
274
+ if name.to_s.start_with?(prefix)
275
+ items << InstanceVariableItem.new(identifier: name,
276
+ range: range,
277
+ type: type)
278
+ end
279
+ end
280
+ end
281
+
282
+ def index_for(string, line:, column:)
283
+ index = 0
284
+
285
+ string.each_line.with_index do |s, i|
286
+ if i+1 == line
287
+ index += column
288
+ break
289
+ else
290
+ index += s.size
291
+ end
292
+ end
293
+
294
+ index
295
+ end
296
+ end
297
+ end
298
+ end