steep 0.13.0 → 0.16.2

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