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
@@ -0,0 +1,151 @@
1
+ module Steep
2
+ module Server
3
+ class CodeWorker < BaseWorker
4
+ LSP = LanguageServer::Protocol
5
+
6
+ include Utils
7
+
8
+ attr_reader :target_files
9
+ attr_reader :queue
10
+
11
+ def initialize(project:, reader:, writer:, queue: Queue.new)
12
+ super(project: project, reader: reader, writer: writer)
13
+
14
+ @target_files = {}
15
+ @queue = queue
16
+ end
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
40
+ end
41
+
42
+ def typecheck_file(path, target)
43
+ Steep.logger.info "Starting type checking: #{path}@#{target.name}..."
44
+
45
+ source = target.source_files[path]
46
+ target.type_check(target_sources: [source], validate_signatures: false)
47
+
48
+ Steep.logger.info "Finished type checking: #{path}@#{target.name}"
49
+
50
+ diagnostics = source_diagnostics(source)
51
+
52
+ writer.write(
53
+ method: :"textDocument/publishDiagnostics",
54
+ params: LSP::Interface::PublishDiagnosticsParams.new(
55
+ uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file"},
56
+ diagnostics: diagnostics
57
+ )
58
+ )
59
+ end
60
+
61
+ def source_diagnostics(source)
62
+ case status = source.status
63
+ when Project::SourceFile::ParseErrorStatus
64
+ []
65
+ when Project::SourceFile::AnnotationSyntaxErrorStatus
66
+ [
67
+ LSP::Interface::Diagnostic.new(
68
+ message: "Annotation syntax error: #{status.error.cause.message}",
69
+ severity: LSP::Constant::DiagnosticSeverity::ERROR,
70
+ range: LSP::Interface::Range.new(
71
+ start: LSP::Interface::Position.new(
72
+ line: status.location.start_line - 1,
73
+ character: status.location.start_column
74
+ ),
75
+ end: LSP::Interface::Position.new(
76
+ line: status.location.end_line - 1,
77
+ character: status.location.end_column
78
+ )
79
+ )
80
+ )
81
+ ]
82
+ when Project::SourceFile::TypeCheckStatus
83
+ status.typing.errors.map do |error|
84
+ loc = error.location_to_str
85
+
86
+ LSP::Interface::Diagnostic.new(
87
+ message: StringIO.new.tap {|io| error.print_to(io) }.string.gsub(/\A#{Regexp.escape(loc)}: /, "").chomp,
88
+ severity: LSP::Constant::DiagnosticSeverity::ERROR,
89
+ range: LSP::Interface::Range.new(
90
+ start: LSP::Interface::Position.new(
91
+ line: error.node.loc.line - 1,
92
+ character: error.node.loc.column
93
+ ),
94
+ end: LSP::Interface::Position.new(
95
+ line: error.node.loc.last_line - 1,
96
+ character: error.node.loc.last_column
97
+ )
98
+ )
99
+ )
100
+ end
101
+ when Project::SourceFile::TypeCheckErrorStatus
102
+ []
103
+ end
104
+ end
105
+
106
+ def handle_request(request)
107
+ case request[:method]
108
+ when "initialize"
109
+ # Don't respond to initialize request, but start type checking.
110
+ project.targets.each do |target|
111
+ 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])
114
+ end
115
+ end
116
+ end
117
+
118
+ when "workspace/executeCommand"
119
+ if request[:params][:command] == "steep/registerSourceToWorker"
120
+ paths = request[:params][:arguments].map {|arg| source_path(URI.parse(arg)) }
121
+ paths.each do |path|
122
+ target_files[path] = 0
123
+ end
124
+ end
125
+
126
+ when "textDocument/didChange"
127
+ update_source(request) do |path, version|
128
+ if target_files.key?(path)
129
+ target_files[path] = version
130
+ end
131
+ end
132
+
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)
137
+ end
138
+ end
139
+ end
140
+
141
+ 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
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,281 @@
1
+ module Steep
2
+ module Server
3
+ class InteractionWorker < BaseWorker
4
+ attr_reader :queue
5
+
6
+ def initialize(project:, reader:, writer:, queue: Queue.new)
7
+ super(project: project, reader: reader, writer: writer)
8
+ @queue = queue
9
+ end
10
+
11
+ def handle_job(job)
12
+ Steep.logger.debug "Handling job: id=#{job[:id]}, result=#{job[:result]&.to_hash}"
13
+ writer.write(job)
14
+ end
15
+
16
+ def handle_request(request)
17
+ case request[:method]
18
+ when "initialize"
19
+ # nop
20
+
21
+ when "textDocument/didChange"
22
+ update_source(request)
23
+
24
+ when "textDocument/hover"
25
+ id = request[:id]
26
+
27
+ uri = URI.parse(request[:params][:textDocument][:uri])
28
+ path = project.relative_path(Pathname(uri.path))
29
+ line = request[:params][:position][:line]
30
+ column = request[:params][:position][:character]
31
+
32
+ queue << {
33
+ id: id,
34
+ result: response_to_hover(path: path, line: line, column: column)
35
+ }
36
+
37
+ when "textDocument/completion"
38
+ id = request[:id]
39
+
40
+ params = request[:params]
41
+ uri = URI.parse(params[:textDocument][:uri])
42
+ path = project.relative_path(Pathname(uri.path))
43
+ line, column = params[:position].yield_self {|hash| [hash[:line]+1, hash[:character]] }
44
+ trigger = params[:context][:triggerCharacter]
45
+
46
+ queue << {
47
+ id: id,
48
+ result: response_to_completion(path: path, line: line, column: column, trigger: trigger)
49
+ }
50
+ end
51
+ end
52
+
53
+ def response_to_hover(path:, line:, column:)
54
+ Steep.logger.tagged "#response_to_hover" do
55
+ Steep.logger.debug { "path=#{path}, line=#{line}, column=#{column}" }
56
+
57
+ hover = Project::HoverContent.new(project: project)
58
+ content = hover.content_for(path: path, line: line+1, column: column+1)
59
+ if content
60
+ range = content.location.yield_self do |location|
61
+ start_position = { line: location.line - 1, character: location.column }
62
+ end_position = { line: location.last_line - 1, character: location.last_column }
63
+ { start: start_position, end: end_position }
64
+ end
65
+
66
+ LSP::Interface::Hover.new(
67
+ contents: { kind: "markdown", value: format_hover(content) },
68
+ range: range
69
+ )
70
+ end
71
+ rescue Typing::UnknownNodeError => exn
72
+ Steep.log_error exn, message: "Failed to compute hover: #{exn.inspect}"
73
+ nil
74
+ end
75
+ end
76
+
77
+ def format_hover(content)
78
+ case content
79
+ when Project::HoverContent::VariableContent
80
+ "`#{content.name}`: `#{content.type.to_s}`"
81
+ when Project::HoverContent::MethodCallContent
82
+ method_name = case content.method_name
83
+ when Project::HoverContent::InstanceMethodName
84
+ "#{content.method_name.class_name}##{content.method_name.method_name}"
85
+ when Project::HoverContent::SingletonMethodName
86
+ "#{content.method_name.class_name}.#{content.method_name.method_name}"
87
+ else
88
+ nil
89
+ end
90
+
91
+ if method_name
92
+ string = <<HOVER
93
+ ```
94
+ #{method_name} ~> #{content.type}
95
+ ```
96
+ HOVER
97
+ if content.definition
98
+ if content.definition.comment
99
+ string << "\n----\n\n#{content.definition.comment.string}"
100
+ end
101
+
102
+ string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
103
+ end
104
+ else
105
+ "`#{content.type}`"
106
+ end
107
+ when Project::HoverContent::DefinitionContent
108
+ string = <<HOVER
109
+ ```
110
+ def #{content.method_name}: #{content.method_type}
111
+ ```
112
+ HOVER
113
+ if (comment = content.definition.comment)
114
+ string << "\n----\n\n#{comment.string}\n"
115
+ end
116
+
117
+ if content.definition.method_types.size > 1
118
+ string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
119
+ end
120
+
121
+ string
122
+ when Project::HoverContent::TypeContent
123
+ "`#{content.type}`"
124
+ end
125
+ end
126
+
127
+ def response_to_completion(path:, line:, column:, trigger:)
128
+ Steep.logger.tagged("#response_to_completion") do
129
+ Steep.logger.info "path: #{path}, line: #{line}, column: #{column}, trigger: #{trigger}"
130
+
131
+ target = project.targets.find {|target| target.source_file?(path) } or return
132
+ target.type_check(target_sources: [], validate_signatures: false)
133
+
134
+ case (status = target&.status)
135
+ when Project::Target::TypeCheckStatus
136
+ subtyping = status.subtyping
137
+ source = target.source_files[path]
138
+
139
+ provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
140
+ items = begin
141
+ provider.run(line: line, column: column)
142
+ rescue Parser::SyntaxError
143
+ []
144
+ end
145
+
146
+ completion_items = items.map do |item|
147
+ format_completion_item(item)
148
+ end
149
+
150
+ Steep.logger.debug "items = #{completion_items.inspect}"
151
+
152
+ LSP::Interface::CompletionList.new(
153
+ is_incomplete: false,
154
+ items: completion_items
155
+ )
156
+ end
157
+ end
158
+ end
159
+
160
+ def format_completion_item(item)
161
+ range = LanguageServer::Protocol::Interface::Range.new(
162
+ start: LanguageServer::Protocol::Interface::Position.new(
163
+ line: item.range.start.line-1,
164
+ character: item.range.start.column
165
+ ),
166
+ end: LanguageServer::Protocol::Interface::Position.new(
167
+ line: item.range.end.line-1,
168
+ character: item.range.end.column
169
+ )
170
+ )
171
+
172
+ case item
173
+ when Project::CompletionProvider::LocalVariableItem
174
+ LanguageServer::Protocol::Interface::CompletionItem.new(
175
+ label: item.identifier,
176
+ kind: LanguageServer::Protocol::Constant::CompletionItemKind::VARIABLE,
177
+ detail: "#{item.identifier}: #{item.type}",
178
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
179
+ range: range,
180
+ new_text: "#{item.identifier}"
181
+ )
182
+ )
183
+ when Project::CompletionProvider::MethodNameItem
184
+ label = "def #{item.identifier}: #{item.method_type}"
185
+ method_type_snippet = method_type_to_snippet(item.method_type)
186
+ LanguageServer::Protocol::Interface::CompletionItem.new(
187
+ label: label,
188
+ kind: LanguageServer::Protocol::Constant::CompletionItemKind::METHOD,
189
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
190
+ new_text: "#{item.identifier}#{method_type_snippet}",
191
+ range: range
192
+ ),
193
+ documentation: item.definition.comment&.string,
194
+ insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
195
+ )
196
+ when Project::CompletionProvider::InstanceVariableItem
197
+ label = "#{item.identifier}: #{item.type}"
198
+ LanguageServer::Protocol::Interface::CompletionItem.new(
199
+ label: label,
200
+ kind: LanguageServer::Protocol::Constant::CompletionItemKind::FIELD,
201
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
202
+ range: range,
203
+ new_text: item.identifier,
204
+ ),
205
+ insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
206
+ )
207
+ end
208
+ end
209
+
210
+ def method_type_to_snippet(method_type)
211
+ params = if method_type.type.each_param.count == 0
212
+ ""
213
+ else
214
+ "(#{params_to_snippet(method_type.type)})"
215
+ end
216
+
217
+
218
+ block = if method_type.block
219
+ open, space, close = if method_type.block.type.return_type.is_a?(RBS::Types::Bases::Void)
220
+ ["do", " ", "end"]
221
+ else
222
+ ["{", "", "}"]
223
+ end
224
+
225
+ if method_type.block.type.each_param.count == 0
226
+ " #{open} $0 #{close}"
227
+ else
228
+ " #{open}#{space}|#{params_to_snippet(method_type.block.type)}| $0 #{close}"
229
+ end
230
+ else
231
+ ""
232
+ end
233
+
234
+ "#{params}#{block}"
235
+ end
236
+
237
+ def params_to_snippet(fun)
238
+ params = []
239
+
240
+ index = 1
241
+
242
+ fun.required_positionals.each do |param|
243
+ if name = param.name
244
+ params << "${#{index}:#{param.type}}"
245
+ else
246
+ params << "${#{index}:#{param.type}}"
247
+ end
248
+
249
+ index += 1
250
+ end
251
+
252
+ if fun.rest_positionals
253
+ params << "${#{index}:*#{fun.rest_positionals.type}}"
254
+ index += 1
255
+ end
256
+
257
+ fun.trailing_positionals.each do |param|
258
+ if name = param.name
259
+ params << "${#{index}:#{param.type}}"
260
+ else
261
+ params << "${#{index}:#{param.type}}"
262
+ end
263
+
264
+ index += 1
265
+ end
266
+
267
+ fun.required_keywords.each do |keyword, param|
268
+ if name = param.name
269
+ params << "#{keyword}: ${#{index}:#{name}_}"
270
+ else
271
+ params << "#{keyword}: ${#{index}:#{param.type}_}"
272
+ end
273
+
274
+ index += 1
275
+ end
276
+
277
+ params.join(", ")
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,196 @@
1
+ module Steep
2
+ module Server
3
+ class Master
4
+ LSP = LanguageServer::Protocol
5
+
6
+ attr_reader :steepfile
7
+ attr_reader :project
8
+ attr_reader :reader, :writer
9
+ attr_reader :queue
10
+ attr_reader :worker_count
11
+ attr_reader :worker_to_paths
12
+
13
+ attr_reader :interaction_worker
14
+ attr_reader :signature_worker
15
+ attr_reader :code_workers
16
+
17
+ def initialize(project:, reader:, writer:, interaction_worker:, signature_worker:, code_workers:, queue: Queue.new)
18
+ @project = project
19
+ @reader = reader
20
+ @writer = writer
21
+ @queue = queue
22
+ @interaction_worker = interaction_worker
23
+ @signature_worker = signature_worker
24
+ @code_workers = code_workers
25
+ @worker_to_paths = {}
26
+ end
27
+
28
+ def start
29
+ source_paths = project.targets.flat_map {|target| target.source_files.keys }
30
+ bin_size = (source_paths.size / code_workers.size) + 1
31
+ source_paths.each_slice(bin_size).with_index do |paths, index|
32
+ register_code_to_worker(paths, worker: code_workers[index])
33
+ end
34
+
35
+ Thread.new do
36
+ interaction_worker.reader.read do |message|
37
+ process_message_from_worker(message)
38
+ end
39
+ end
40
+
41
+ Thread.new do
42
+ signature_worker.reader.read do |message|
43
+ process_message_from_worker(message)
44
+ end
45
+ end
46
+
47
+ code_workers.each do |worker|
48
+ Thread.new do
49
+ worker.reader.read do |message|
50
+ process_message_from_worker(message)
51
+ end
52
+ end
53
+ end
54
+
55
+ Thread.new do
56
+ reader.read do |request|
57
+ process_message_from_client(request)
58
+ end
59
+ end
60
+
61
+ while job = queue.pop
62
+ writer.write(job)
63
+ end
64
+
65
+ writer.io.close
66
+
67
+ each_worker do |w|
68
+ w.shutdown()
69
+ end
70
+ end
71
+
72
+ def each_worker(&block)
73
+ if block_given?
74
+ yield interaction_worker
75
+ yield signature_worker
76
+ code_workers.each &block
77
+ else
78
+ enum_for :each_worker
79
+ end
80
+ end
81
+
82
+ def process_message_from_client(message)
83
+ id = message[:id]
84
+
85
+ case message[:method]
86
+ when "initialize"
87
+ queue << {
88
+ id: id,
89
+ result: LSP::Interface::InitializeResult.new(
90
+ capabilities: LSP::Interface::ServerCapabilities.new(
91
+ text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
92
+ change: LSP::Constant::TextDocumentSyncKind::FULL
93
+ ),
94
+ hover_provider: true,
95
+ completion_provider: LSP::Interface::CompletionOptions.new(
96
+ trigger_characters: [".", "@"]
97
+ )
98
+ )
99
+ )
100
+ }
101
+
102
+ each_worker do |worker|
103
+ worker << message
104
+ end
105
+
106
+ when "textDocument/didChange"
107
+ uri = URI.parse(message[:params][:textDocument][:uri])
108
+ 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
+
138
+ unless registered_path?(path)
139
+ register_code_to_worker [path], worker: least_busy_worker()
140
+ end
141
+
142
+ each_worker do |worker|
143
+ worker << message
144
+ end
145
+
146
+ when "textDocument/hover"
147
+ interaction_worker << message
148
+
149
+ when "textDocument/completion"
150
+ interaction_worker << message
151
+
152
+ when "textDocument/open"
153
+ # Ignores open notification
154
+
155
+ when "shutdown"
156
+ queue << { id: id, result: nil }
157
+
158
+ when "exit"
159
+ queue << nil
160
+ end
161
+ end
162
+
163
+ def process_message_from_worker(message)
164
+ queue << message
165
+ end
166
+
167
+ def paths_for(worker)
168
+ worker_to_paths[worker] ||= Set[]
169
+ end
170
+
171
+ def least_busy_worker
172
+ code_workers.min_by do |w|
173
+ paths_for(w).size
174
+ end
175
+ end
176
+
177
+ def registered_path?(path)
178
+ worker_to_paths.each_value.any? {|set| set.include?(path) }
179
+ end
180
+
181
+ def register_code_to_worker(paths, worker:)
182
+ paths_for(worker).merge(paths)
183
+
184
+ worker << {
185
+ method: "workspace/executeCommand",
186
+ params: LSP::Interface::ExecuteCommandParams.new(
187
+ command: "steep/registerSourceToWorker",
188
+ arguments: paths.map do |path|
189
+ "file://#{project.absolute_path(path)}"
190
+ end
191
+ )
192
+ }
193
+ end
194
+ end
195
+ end
196
+ end