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
@@ -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