steep 0.37.0 → 0.42.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +34 -0
  4. data/Rakefile +5 -2
  5. data/bin/output_rebaseline.rb +34 -0
  6. data/bin/output_test.rb +53 -0
  7. data/lib/steep.rb +95 -14
  8. data/lib/steep/ast/types/bot.rb +1 -1
  9. data/lib/steep/ast/types/class.rb +4 -0
  10. data/lib/steep/ast/types/factory.rb +10 -0
  11. data/lib/steep/ast/types/logic.rb +16 -3
  12. data/lib/steep/ast/types/top.rb +1 -1
  13. data/lib/steep/cli.rb +31 -7
  14. data/lib/steep/diagnostic/helper.rb +17 -0
  15. data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
  16. data/lib/steep/diagnostic/ruby.rb +619 -0
  17. data/lib/steep/diagnostic/signature.rb +357 -0
  18. data/lib/steep/drivers/annotations.rb +19 -28
  19. data/lib/steep/drivers/check.rb +182 -60
  20. data/lib/steep/drivers/diagnostic_printer.rb +99 -0
  21. data/lib/steep/drivers/langserver.rb +3 -8
  22. data/lib/steep/drivers/print_project.rb +10 -9
  23. data/lib/steep/drivers/stats.rb +124 -32
  24. data/lib/steep/drivers/trace_printer.rb +5 -1
  25. data/lib/steep/drivers/utils/jobs_count.rb +9 -0
  26. data/lib/steep/drivers/validate.rb +31 -13
  27. data/lib/steep/drivers/watch.rb +69 -48
  28. data/lib/steep/drivers/worker.rb +16 -8
  29. data/lib/steep/expectations.rb +159 -0
  30. data/lib/steep/index/rbs_index.rb +334 -0
  31. data/lib/steep/index/signature_symbol_provider.rb +162 -0
  32. data/lib/steep/index/source_index.rb +100 -0
  33. data/lib/steep/project.rb +0 -30
  34. data/lib/steep/project/dsl.rb +5 -3
  35. data/lib/steep/project/options.rb +4 -4
  36. data/lib/steep/project/pattern.rb +56 -0
  37. data/lib/steep/project/target.rb +9 -214
  38. data/lib/steep/range_extension.rb +29 -0
  39. data/lib/steep/server/base_worker.rb +43 -7
  40. data/lib/steep/server/change_buffer.rb +63 -0
  41. data/lib/steep/server/interaction_worker.rb +73 -56
  42. data/lib/steep/server/master.rb +245 -109
  43. data/lib/steep/server/type_check_worker.rb +122 -0
  44. data/lib/steep/server/worker_process.rb +17 -15
  45. data/lib/steep/{project → services}/completion_provider.rb +3 -3
  46. data/lib/steep/services/content_change.rb +61 -0
  47. data/lib/steep/services/file_loader.rb +48 -0
  48. data/lib/steep/{project → services}/hover_content.rb +14 -16
  49. data/lib/steep/services/path_assignment.rb +29 -0
  50. data/lib/steep/services/signature_service.rb +369 -0
  51. data/lib/steep/services/stats_calculator.rb +69 -0
  52. data/lib/steep/services/type_check_service.rb +342 -0
  53. data/lib/steep/signature/validator.rb +174 -32
  54. data/lib/steep/subtyping/check.rb +248 -47
  55. data/lib/steep/subtyping/constraints.rb +2 -2
  56. data/lib/steep/type_construction.rb +565 -295
  57. data/lib/steep/type_inference/constant_env.rb +5 -1
  58. data/lib/steep/type_inference/local_variable_type_env.rb +26 -12
  59. data/lib/steep/type_inference/logic_type_interpreter.rb +99 -26
  60. data/lib/steep/type_inference/type_env.rb +43 -17
  61. data/lib/steep/typing.rb +8 -2
  62. data/lib/steep/version.rb +1 -1
  63. data/smoke/alias/a.rb +0 -3
  64. data/smoke/alias/b.rb +0 -1
  65. data/smoke/alias/c.rb +0 -2
  66. data/smoke/alias/test_expectations.yml +96 -0
  67. data/smoke/and/a.rb +0 -3
  68. data/smoke/and/test_expectations.yml +31 -0
  69. data/smoke/array/a.rb +0 -3
  70. data/smoke/array/b.rb +0 -2
  71. data/smoke/array/c.rb +0 -1
  72. data/smoke/array/test_expectations.yml +103 -0
  73. data/smoke/block/a.rb +0 -2
  74. data/smoke/block/b.rb +0 -2
  75. data/smoke/block/d.rb +0 -4
  76. data/smoke/block/test_expectations.yml +125 -0
  77. data/smoke/case/a.rb +0 -3
  78. data/smoke/case/test_expectations.yml +47 -0
  79. data/smoke/class/a.rb +0 -3
  80. data/smoke/class/c.rb +0 -1
  81. data/smoke/class/f.rb +0 -1
  82. data/smoke/class/g.rb +0 -2
  83. data/smoke/class/i.rb +0 -2
  84. data/smoke/class/test_expectations.yml +120 -0
  85. data/smoke/const/a.rb +0 -3
  86. data/smoke/const/b.rb +7 -0
  87. data/smoke/const/b.rbs +5 -0
  88. data/smoke/const/test_expectations.yml +139 -0
  89. data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
  90. data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
  91. data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +13 -0
  92. data/smoke/diagnostics-rbs/Steepfile +8 -0
  93. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
  94. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
  95. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
  96. data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
  97. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
  98. data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
  99. data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
  100. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
  101. data/smoke/diagnostics-rbs/test_expectations.yml +231 -0
  102. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
  103. data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +5 -0
  104. data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
  105. data/smoke/diagnostics/Steepfile +5 -0
  106. data/smoke/diagnostics/a.rbs +26 -0
  107. data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
  108. data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
  109. data/smoke/diagnostics/block_type_mismatch.rb +3 -0
  110. data/smoke/diagnostics/break_type_mismatch.rb +1 -0
  111. data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
  112. data/smoke/diagnostics/incompatible_annotation.rb +6 -0
  113. data/smoke/diagnostics/incompatible_argument.rb +1 -0
  114. data/smoke/diagnostics/incompatible_assignment.rb +8 -0
  115. data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
  116. data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
  117. data/smoke/diagnostics/method_definition_missing.rb +2 -0
  118. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
  119. data/smoke/diagnostics/missing_keyword.rb +1 -0
  120. data/smoke/diagnostics/no_method.rb +1 -0
  121. data/smoke/diagnostics/required_block_missing.rb +1 -0
  122. data/smoke/diagnostics/return_type_mismatch.rb +6 -0
  123. data/smoke/diagnostics/test_expectations.yml +477 -0
  124. data/smoke/diagnostics/unexpected_block_given.rb +1 -0
  125. data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
  126. data/smoke/diagnostics/unexpected_jump.rb +4 -0
  127. data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
  128. data/smoke/diagnostics/unexpected_keyword.rb +1 -0
  129. data/smoke/diagnostics/unexpected_splat.rb +1 -0
  130. data/smoke/diagnostics/unexpected_yield.rb +6 -0
  131. data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
  132. data/smoke/diagnostics/unresolved_overloading.rb +1 -0
  133. data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
  134. data/smoke/diagnostics/unsupported_syntax.rb +2 -0
  135. data/smoke/dstr/a.rb +0 -1
  136. data/smoke/dstr/test_expectations.yml +13 -0
  137. data/smoke/ensure/a.rb +0 -4
  138. data/smoke/ensure/test_expectations.yml +62 -0
  139. data/smoke/enumerator/a.rb +0 -6
  140. data/smoke/enumerator/b.rb +0 -3
  141. data/smoke/enumerator/test_expectations.yml +135 -0
  142. data/smoke/extension/a.rb +0 -1
  143. data/smoke/extension/b.rb +0 -2
  144. data/smoke/extension/c.rb +0 -1
  145. data/smoke/extension/f.rb +2 -0
  146. data/smoke/extension/f.rbs +3 -0
  147. data/smoke/extension/test_expectations.yml +73 -0
  148. data/smoke/hash/b.rb +0 -1
  149. data/smoke/hash/c.rb +0 -3
  150. data/smoke/hash/d.rb +0 -1
  151. data/smoke/hash/e.rb +0 -1
  152. data/smoke/hash/test_expectations.yml +81 -0
  153. data/smoke/hello/hello.rb +0 -2
  154. data/smoke/hello/test_expectations.yml +25 -0
  155. data/smoke/if/a.rb +0 -2
  156. data/smoke/if/test_expectations.yml +34 -0
  157. data/smoke/implements/a.rb +0 -2
  158. data/smoke/implements/test_expectations.yml +23 -0
  159. data/smoke/initialize/test_expectations.yml +1 -0
  160. data/smoke/integer/a.rb +0 -7
  161. data/smoke/integer/test_expectations.yml +101 -0
  162. data/smoke/interface/a.rb +0 -2
  163. data/smoke/interface/test_expectations.yml +23 -0
  164. data/smoke/kwbegin/a.rb +0 -1
  165. data/smoke/kwbegin/test_expectations.yml +17 -0
  166. data/smoke/lambda/a.rb +1 -4
  167. data/smoke/lambda/test_expectations.yml +39 -0
  168. data/smoke/literal/a.rb +0 -5
  169. data/smoke/literal/b.rb +0 -2
  170. data/smoke/literal/test_expectations.yml +106 -0
  171. data/smoke/map/test_expectations.yml +1 -0
  172. data/smoke/method/a.rb +0 -5
  173. data/smoke/method/b.rb +0 -1
  174. data/smoke/method/test_expectations.yml +90 -0
  175. data/smoke/module/a.rb +0 -2
  176. data/smoke/module/b.rb +0 -2
  177. data/smoke/module/c.rb +0 -1
  178. data/smoke/module/d.rb +0 -1
  179. data/smoke/module/f.rb +0 -2
  180. data/smoke/module/test_expectations.yml +75 -0
  181. data/smoke/regexp/a.rb +0 -38
  182. data/smoke/regexp/b.rb +0 -26
  183. data/smoke/regexp/test_expectations.yml +615 -0
  184. data/smoke/regression/set_divide.rb +0 -4
  185. data/smoke/regression/test_expectations.yml +43 -0
  186. data/smoke/rescue/a.rb +0 -5
  187. data/smoke/rescue/test_expectations.yml +79 -0
  188. data/smoke/self/a.rb +0 -2
  189. data/smoke/self/test_expectations.yml +23 -0
  190. data/smoke/skip/skip.rb +0 -2
  191. data/smoke/skip/test_expectations.yml +23 -0
  192. data/smoke/stdout/test_expectations.yml +1 -0
  193. data/smoke/super/a.rb +0 -4
  194. data/smoke/super/test_expectations.yml +79 -0
  195. data/smoke/toplevel/a.rb +0 -1
  196. data/smoke/toplevel/test_expectations.yml +15 -0
  197. data/smoke/tsort/Steepfile +2 -0
  198. data/smoke/tsort/a.rb +0 -3
  199. data/smoke/tsort/test_expectations.yml +63 -0
  200. data/smoke/type_case/a.rb +0 -4
  201. data/smoke/type_case/test_expectations.yml +48 -0
  202. data/smoke/unexpected/Steepfile +5 -0
  203. data/smoke/unexpected/test_expectations.yml +25 -0
  204. data/smoke/unexpected/unexpected.rb +1 -0
  205. data/smoke/unexpected/unexpected.rbs +3 -0
  206. data/smoke/yield/a.rb +0 -3
  207. data/smoke/yield/b.rb +6 -0
  208. data/smoke/yield/test_expectations.yml +68 -0
  209. data/steep.gemspec +4 -3
  210. metadata +144 -29
  211. data/bin/smoke_runner.rb +0 -139
  212. data/lib/steep/drivers/signature_error_printer.rb +0 -25
  213. data/lib/steep/errors.rb +0 -565
  214. data/lib/steep/project/file_loader.rb +0 -68
  215. data/lib/steep/project/signature_file.rb +0 -33
  216. data/lib/steep/project/source_file.rb +0 -129
  217. data/lib/steep/server/code_worker.rb +0 -137
  218. data/lib/steep/server/signature_worker.rb +0 -152
  219. data/lib/steep/server/utils.rb +0 -69
  220. data/lib/steep/signature/errors.rb +0 -82
  221. data/lib/steep/type_assignability.rb +0 -367
@@ -0,0 +1,122 @@
1
+ module Steep
2
+ module Server
3
+ class TypeCheckWorker < BaseWorker
4
+ attr_reader :project, :assignment, :service
5
+ attr_reader :commandline_args
6
+
7
+ TypeCheckJob = Class.new
8
+ WorkspaceSymbolJob = Struct.new(:query, :id, keyword_init: true)
9
+ StatsJob = Struct.new(:id, keyword_init: true)
10
+
11
+ include ChangeBuffer
12
+
13
+ def initialize(project:, reader:, writer:, assignment:, commandline_args:)
14
+ super(project: project, reader: reader, writer: writer)
15
+
16
+ @assignment = assignment
17
+ @service = Services::TypeCheckService.new(project: project, assignment: assignment)
18
+ @buffered_changes = {}
19
+ @mutex = Mutex.new()
20
+ @queue = Queue.new
21
+ @commandline_args = commandline_args
22
+ end
23
+
24
+ def handle_request(request)
25
+ case request[:method]
26
+ when "initialize"
27
+ load_files(project: project, commandline_args: commandline_args)
28
+ queue << TypeCheckJob.new()
29
+ writer.write({ id: request[:id], result: nil})
30
+ when "textDocument/didChange"
31
+ collect_changes(request)
32
+ queue << TypeCheckJob.new()
33
+ when "workspace/symbol"
34
+ query = request[:params][:query]
35
+ queue << WorkspaceSymbolJob.new(id: request[:id], query: query)
36
+ when "workspace/executeCommand"
37
+ case request[:params][:command]
38
+ when "steep/stats"
39
+ queue << StatsJob.new(id: request[:id])
40
+ end
41
+ end
42
+ end
43
+
44
+ def handle_job(job)
45
+ case job
46
+ when TypeCheckJob
47
+ pop_buffer() do |changes|
48
+ break if changes.empty?
49
+
50
+ formatter = Diagnostic::LSPFormatter.new()
51
+
52
+ service.update(changes: changes) do |path, diagnostics|
53
+ if target = project.target_for_source_path(path)
54
+ diagnostics = diagnostics.select {|diagnostic| target.options.error_to_report?(diagnostic) }
55
+ end
56
+
57
+ writer.write(
58
+ method: :"textDocument/publishDiagnostics",
59
+ params: LSP::Interface::PublishDiagnosticsParams.new(
60
+ uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file"},
61
+ diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
62
+ )
63
+ )
64
+ end
65
+ end
66
+ when WorkspaceSymbolJob
67
+ writer.write(
68
+ id: job.id,
69
+ result: workspace_symbol_result(job.query)
70
+ )
71
+ when StatsJob
72
+ writer.write(
73
+ id: job.id,
74
+ result: stats_result().map(&:as_json)
75
+ )
76
+ end
77
+ end
78
+
79
+ def workspace_symbol_result(query)
80
+ Steep.measure "Generating workspace symbol list for query=`#{query}`" do
81
+ indexes = project.targets.map {|target| service.signature_services[target.name].latest_rbs_index }
82
+
83
+ provider = Index::SignatureSymbolProvider.new()
84
+ provider.indexes.push(*indexes)
85
+
86
+ symbols = provider.query_symbol(query, assignment: assignment)
87
+
88
+ symbols.map do |symbol|
89
+ LSP::Interface::SymbolInformation.new(
90
+ name: symbol.name,
91
+ kind: symbol.kind,
92
+ location: symbol.location.yield_self do |location|
93
+ path = Pathname(location.buffer.name)
94
+ {
95
+ uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file" },
96
+ range: {
97
+ start: { line: location.start_line - 1, character: location.start_column },
98
+ end: { line: location.end_line - 1, character: location.end_column }
99
+ }
100
+ }
101
+ end,
102
+ container_name: symbol.container_name
103
+ )
104
+ end
105
+ end
106
+ end
107
+
108
+ def stats_result
109
+ calculator = Services::StatsCalculator.new(service: service)
110
+
111
+ project.targets.each.with_object([]) do |target, stats|
112
+ service.source_files.each_value do |file|
113
+ next unless assignment =~ file.path
114
+ next unless target.possible_source_file?(file.path)
115
+
116
+ stats << calculator.calc_stats(target, file: file)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -16,19 +16,21 @@ module Steep
16
16
  @name = name
17
17
  end
18
18
 
19
- def self.spawn_worker(type, name:, steepfile:)
19
+ def self.spawn_worker(type, name:, steepfile:, options: [], delay_shutdown: false)
20
20
  log_level = %w(debug info warn error fatal unknown)[Steep.logger.level]
21
21
  command = case type
22
- when :code
23
- ["steep", "worker", "--code", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
24
- when :signature
25
- ["steep", "worker", "--signature", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
26
22
  when :interaction
27
- ["steep", "worker", "--interaction", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
23
+ ["steep", "worker", "--interaction", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
24
+ when :typecheck
25
+ ["steep", "worker", "--typecheck", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
28
26
  else
29
- raise
27
+ raise "Unknown type: #{type}"
30
28
  end
31
29
 
30
+ if delay_shutdown
31
+ command << "--delay-shutdown"
32
+ end
33
+
32
34
  stdin, stdout, thread = Open3.popen2(*command, pgroup: true)
33
35
  stderr = nil
34
36
 
@@ -38,9 +40,13 @@ module Steep
38
40
  new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name)
39
41
  end
40
42
 
41
- def self.spawn_code_workers(steepfile:, count: [Etc.nprocessors-3, 1].max)
43
+ def self.spawn_typecheck_workers(steepfile:, args:, count: [Etc.nprocessors - 1, 1].max, delay_shutdown: false)
42
44
  count.times.map do |i|
43
- spawn_worker(:code, name: "code@#{i}", steepfile: steepfile)
45
+ spawn_worker(:typecheck,
46
+ name: "typecheck@#{i}",
47
+ steepfile: steepfile,
48
+ options: ["--max-index=#{count}", "--index=#{i}", *args],
49
+ delay_shutdown: delay_shutdown)
44
50
  end
45
51
  end
46
52
 
@@ -52,12 +58,8 @@ module Steep
52
58
  reader.read(&block)
53
59
  end
54
60
 
55
- def shutdown
56
- self << { method: :shutdown, params: nil }
57
- self << { method: :exit, params: nil }
58
-
59
- writer.io.close()
60
- @wait_thread.join()
61
+ def kill
62
+ Process.kill(:TERM, @wait_thread.pid)
61
63
  end
62
64
  end
63
65
  end
@@ -1,5 +1,5 @@
1
1
  module Steep
2
- class Project
2
+ module Services
3
3
  class CompletionProvider
4
4
  Position = Struct.new(:line, :column, keyword_init: true) do
5
5
  def -(size)
@@ -51,13 +51,13 @@ module Steep
51
51
  @modified_text = text
52
52
 
53
53
  Steep.measure "parsing" do
54
- @source = SourceFile
54
+ @source = Source
55
55
  .parse(text, path: path, factory: subtyping.factory)
56
56
  .without_unrelated_defs(line: line, column: column)
57
57
  end
58
58
 
59
59
  Steep.measure "typechecking" do
60
- @typing = SourceFile.type_check(source, subtyping: subtyping)
60
+ @typing = TypeCheckService.type_check(source: source, subtyping: subtyping)
61
61
  end
62
62
  end
63
63
 
@@ -0,0 +1,61 @@
1
+ module Steep
2
+ module Services
3
+ class ContentChange
4
+ class Position
5
+ attr_reader :line, :column
6
+
7
+ def initialize(line:, column:)
8
+ @line = line
9
+ @column = column
10
+ end
11
+
12
+ def ==(other)
13
+ other.is_a?(Position) && other.line == line && other.column == column
14
+ end
15
+
16
+ alias eql? ==
17
+
18
+ def hash
19
+ self.class.hash ^ line ^ column
20
+ end
21
+ end
22
+
23
+ attr_reader :range, :text
24
+
25
+ def initialize(range: nil, text:)
26
+ @range = range
27
+ @text = text
28
+ end
29
+
30
+ def ==(other)
31
+ other.is_a?(ContentChange) && other.range == range && other.text == text
32
+ end
33
+
34
+ alias eql? ==
35
+
36
+ def hash
37
+ self.class.hash ^ range.hash ^ text.hash
38
+ end
39
+
40
+ def self.string(string)
41
+ new(text: string)
42
+ end
43
+
44
+ def apply_to(text)
45
+ if range
46
+ text = text.dup
47
+ start_pos, end_pos = range
48
+
49
+ buf = RBS::Buffer.new(name: :_, content: text)
50
+ start_pos = buf.loc_to_pos([start_pos.line, start_pos.column])
51
+ end_pos = buf.loc_to_pos([end_pos.line, end_pos.column])
52
+
53
+ text[start_pos...end_pos] = self.text
54
+ text
55
+ else
56
+ self.text
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,48 @@
1
+ module Steep
2
+ module Services
3
+ class FileLoader
4
+ attr_reader :base_dir
5
+
6
+ def initialize(base_dir:)
7
+ @base_dir = base_dir
8
+ end
9
+
10
+ def each_path_in_patterns(pattern, commandline_patterns = [])
11
+ pats = commandline_patterns.empty? ? pattern.patterns : commandline_patterns
12
+
13
+ pats.each do |path|
14
+ absolute_path = base_dir + path
15
+
16
+ if absolute_path.file?
17
+ yield absolute_path.relative_path_from(base_dir)
18
+ else
19
+ files = if absolute_path.directory?
20
+ Pathname.glob("#{absolute_path}/**/*#{pattern.ext}")
21
+ else
22
+ Pathname.glob(absolute_path)
23
+ end
24
+
25
+ files.sort.each do |source_path|
26
+ if source_path.file?
27
+ relative_path = source_path.relative_path_from(base_dir)
28
+ unless pattern.ignore?(relative_path)
29
+ yield relative_path
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def load_changes(pattern, command_line_patterns = [], changes:)
38
+ each_path_in_patterns(pattern, command_line_patterns) do |path|
39
+ unless changes.key?(path)
40
+ changes[path] = [ContentChange.string((base_dir + path).read)]
41
+ end
42
+ end
43
+
44
+ changes
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,5 @@
1
1
  module Steep
2
- class Project
2
+ module Services
3
3
  class HoverContent
4
4
  TypeContent = Struct.new(:node, :type, :location, keyword_init: true)
5
5
  VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
@@ -15,10 +15,14 @@ module Steep
15
15
  InstanceMethodName = Struct.new(:class_name, :method_name)
16
16
  SingletonMethodName = Struct.new(:class_name, :method_name)
17
17
 
18
- attr_reader :project
18
+ attr_reader :service
19
19
 
20
- def initialize(project:)
21
- @project = project
20
+ def initialize(service:)
21
+ @service = service
22
+ end
23
+
24
+ def project
25
+ service.project
22
26
  end
23
27
 
24
28
  def method_definition_for(factory, type_name, singleton_method: nil, instance_method: nil)
@@ -36,17 +40,10 @@ module Steep
36
40
  end
37
41
  end
38
42
 
39
- def typecheck(target, path:, line:, column:)
40
- target.type_check(target_sources: [], validate_signatures: false)
41
-
42
- case (status = target.status)
43
- when Project::Target::TypeCheckStatus
44
- subtyping = status.subtyping
45
- source = SourceFile
46
- .parse(target.source_files[path].content, path: path, factory: subtyping.factory)
47
- .without_unrelated_defs(line: line, column: column)
48
- SourceFile.type_check(source, subtyping: subtyping)
49
- end
43
+ def typecheck(target, path:, content:, line:, column:)
44
+ subtyping = service.signature_services[target.name].current_subtyping or return
45
+ source = Source.parse(content, path: path, factory: subtyping.factory)
46
+ Services::TypeCheckService.type_check(source: source, subtyping: subtyping)
50
47
  rescue
51
48
  nil
52
49
  end
@@ -55,7 +52,8 @@ module Steep
55
52
  target = project.target_for_source_path(path)
56
53
 
57
54
  if target
58
- typing = typecheck(target, path: path, line: line, column: column) or return
55
+ file = service.source_files[path]
56
+ typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
59
57
 
60
58
  node, *parents = typing.source.find_nodes(line: line, column: column)
61
59
 
@@ -0,0 +1,29 @@
1
+ module Steep
2
+ module Services
3
+ class PathAssignment
4
+ attr_reader :index, :max_index, :cache
5
+
6
+ def initialize(index:, max_index:)
7
+ @index = index
8
+ @max_index = max_index
9
+ @cache = {}
10
+ end
11
+
12
+ def self.all
13
+ new(index: 0, max_index: 1)
14
+ end
15
+
16
+ def =~(path)
17
+ path = path.to_s
18
+
19
+ if cache.key?(path)
20
+ cache[path]
21
+ else
22
+ value = Digest::MD5.hexdigest(path).hex % max_index == index
23
+ cache[path] = value
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,369 @@
1
+ module Steep
2
+ module Services
3
+ class SignatureService
4
+ attr_reader :status
5
+
6
+ class SyntaxErrorStatus
7
+ attr_reader :files, :changed_paths, :diagnostics, :last_builder
8
+
9
+ def initialize(files:, changed_paths:, diagnostics:, last_builder:)
10
+ @files = files
11
+ @changed_paths = changed_paths
12
+ @diagnostics = diagnostics
13
+ @last_builder = last_builder
14
+ end
15
+
16
+ def rbs_index
17
+ @rbs_index ||= Index::RBSIndex.new().tap do |index|
18
+ builder = Index::RBSIndex::Builder.new(index: index)
19
+ builder.env(last_builder.env)
20
+ end
21
+ end
22
+ end
23
+
24
+ class AncestorErrorStatus
25
+ attr_reader :files, :changed_paths, :diagnostics, :last_builder
26
+
27
+ def initialize(files:, changed_paths:, diagnostics:, last_builder:)
28
+ @files = files
29
+ @changed_paths = changed_paths
30
+ @diagnostics = diagnostics
31
+ @last_builder = last_builder
32
+ end
33
+
34
+ def rbs_index
35
+ @rbs_index ||= Index::RBSIndex.new().tap do |index|
36
+ builder = Index::RBSIndex::Builder.new(index: index)
37
+ builder.env(last_builder.env)
38
+ end
39
+ end
40
+ end
41
+
42
+ class LoadedStatus
43
+ attr_reader :files, :builder
44
+
45
+ def initialize(files:, builder:)
46
+ @files = files
47
+ @builder = builder
48
+ end
49
+
50
+ def subtyping
51
+ @subtyping ||= Subtyping::Check.new(factory: AST::Types::Factory.new(builder: builder))
52
+ end
53
+
54
+ def rbs_index
55
+ @rbs_index ||= Index::RBSIndex.new().tap do |index|
56
+ builder = Index::RBSIndex::Builder.new(index: index)
57
+ builder.env(self.builder.env)
58
+ end
59
+ end
60
+ end
61
+
62
+ FileStatus = Struct.new(:path, :content, :decls, keyword_init: true)
63
+
64
+ def initialize(env:)
65
+ builder = RBS::DefinitionBuilder.new(env: env)
66
+ @status = LoadedStatus.new(builder: builder, files: {})
67
+ end
68
+
69
+ def self.load_from(loader)
70
+ env = RBS::Environment.from_loader(loader).resolve_type_names
71
+ new(env: env)
72
+ end
73
+
74
+ def each_rbs_path(&block)
75
+ if block
76
+ latest_env.buffers.each do |buffer|
77
+ unless files.key?(buffer.name)
78
+ yield Pathname(buffer.name)
79
+ end
80
+ end
81
+
82
+ files.each_key(&block)
83
+ else
84
+ enum_for :each_rbs_path
85
+ end
86
+ end
87
+
88
+ def files
89
+ status.files
90
+ end
91
+
92
+ def pending_changed_paths
93
+ case status
94
+ when LoadedStatus
95
+ Set[]
96
+ when SyntaxErrorStatus, AncestorErrorStatus
97
+ Set.new(status.changed_paths)
98
+ end
99
+ end
100
+
101
+ def latest_env
102
+ latest_builder.env
103
+ end
104
+
105
+ def latest_builder
106
+ case status
107
+ when LoadedStatus
108
+ status.builder
109
+ when SyntaxErrorStatus, AncestorErrorStatus
110
+ status.last_builder
111
+ end
112
+ end
113
+
114
+ def latest_rbs_index
115
+ status.rbs_index
116
+ end
117
+
118
+ def current_subtyping
119
+ if status.is_a?(LoadedStatus)
120
+ status.subtyping
121
+ end
122
+ end
123
+
124
+ def apply_changes(files, changes)
125
+ Steep.logger.tagged "#apply_changes" do
126
+ Steep.measure2 "Applying change" do |sampler|
127
+ changes.each.with_object({}) do |(path, cs), update|
128
+ sampler.sample "#{path}" do
129
+ old_text = files[path]&.content
130
+ content = cs.inject(old_text || "") {|text, change| change.apply_to(text) }
131
+
132
+ buffer = RBS::Buffer.new(name: path, content: content)
133
+
134
+ update[path] = begin
135
+ FileStatus.new(path: path, content: content, decls: RBS::Parser.parse_signature(buffer))
136
+ rescue RBS::ParsingError => exn
137
+ FileStatus.new(path: path, content: content, decls: exn)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def update(changes)
146
+ Steep.logger.tagged "#update" do
147
+ updates = apply_changes(files, changes)
148
+ paths = Set.new(updates.each_key)
149
+ paths.merge(pending_changed_paths)
150
+
151
+ if updates.each_value.any? {|file| file.decls.is_a?(RBS::ParsingError) }
152
+ diagnostics = []
153
+
154
+ updates.each_value do |file|
155
+ if file.decls.is_a?(RBS::ParsingError)
156
+ # factory is not used here because the error is a syntax error.
157
+ diagnostics << Diagnostic::Signature.from_rbs_error(file.decls, factory: nil)
158
+ end
159
+ end
160
+
161
+ @status = SyntaxErrorStatus.new(
162
+ files: self.files.merge(updates),
163
+ diagnostics: diagnostics,
164
+ last_builder: latest_builder,
165
+ changed_paths: paths
166
+ )
167
+ else
168
+ files = self.files.merge(updates)
169
+ updated_files = paths.each.with_object({}) do |path, hash|
170
+ hash[path] = files[path]
171
+ end
172
+ result =
173
+ Steep.measure "#update_env with updated #{paths.size} files" do
174
+ update_env(updated_files, paths: paths)
175
+ end
176
+
177
+ @status = case result
178
+ when Array
179
+ AncestorErrorStatus.new(
180
+ changed_paths: paths,
181
+ last_builder: latest_builder,
182
+ diagnostics: result,
183
+ files: files
184
+ )
185
+ when RBS::DefinitionBuilder::AncestorBuilder
186
+ builder2 = update_builder(ancestor_builder: result, paths: paths)
187
+ LoadedStatus.new(builder: builder2, files: files)
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ def update_env(updated_files, paths:)
194
+ Steep.logger.tagged "#update_env" do
195
+ errors = []
196
+ new_decls = Set[].compare_by_identity
197
+
198
+ env =
199
+ Steep.measure "Deleting out of date decls" do
200
+ latest_env.reject do |decl|
201
+ if decl.location
202
+ paths.include?(decl.location.buffer.name)
203
+ end
204
+ end
205
+ end
206
+
207
+ Steep.measure "Loading new decls" do
208
+ updated_files.each_value do |content|
209
+ if content.decls.is_a?(RBS::ErrorBase)
210
+ errors << content.decls
211
+ else
212
+ begin
213
+ content.decls.each do |decl|
214
+ env << decl
215
+ new_decls << decl
216
+ end
217
+ rescue RBS::LoadingError => exn
218
+ errors << exn
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ Steep.measure "validate type params" do
225
+ begin
226
+ env.validate_type_params
227
+ rescue RBS::LoadingError => exn
228
+ errors << exn
229
+ end
230
+ end
231
+
232
+ unless errors.empty?
233
+ return errors.map {|error|
234
+ # Factory will not be used because of the possible error types.
235
+ Diagnostic::Signature.from_rbs_error(error, factory: nil)
236
+ }
237
+ end
238
+
239
+ Steep.measure "resolve type names with #{new_decls.size} top-level decls" do
240
+ env = env.resolve_type_names(only: new_decls)
241
+ end
242
+
243
+ builder = RBS::DefinitionBuilder::AncestorBuilder.new(env: env)
244
+
245
+ Steep.measure("Pre-loading one ancestors") do
246
+ builder.env.class_decls.each_key do |type_name|
247
+ rescue_rbs_error(errors) { builder.one_instance_ancestors(type_name) }
248
+ rescue_rbs_error(errors) { builder.one_singleton_ancestors(type_name) }
249
+ end
250
+ builder.env.interface_decls.each_key do |type_name|
251
+ rescue_rbs_error(errors) { builder.one_interface_ancestors(type_name) }
252
+ end
253
+ end
254
+
255
+ unless errors.empty?
256
+ # Builder won't be used.
257
+ factory = AST::Types::Factory.new(builder: nil)
258
+ return errors.map {|error| Diagnostic::Signature.from_rbs_error(error, factory: factory) }
259
+ end
260
+
261
+ builder
262
+ end
263
+ end
264
+
265
+ def rescue_rbs_error(errors)
266
+ begin
267
+ yield
268
+ rescue RBS::ErrorBase => exn
269
+ errors << exn
270
+ end
271
+ end
272
+
273
+ def update_builder(ancestor_builder:, paths:)
274
+ Steep.measure "#update_builder with #{paths.size} files" do
275
+ changed_names = Set[]
276
+
277
+ old_definition_builder = latest_builder
278
+ old_env = old_definition_builder.env
279
+ old_names = type_names(paths: paths, env: old_env)
280
+ old_ancestor_builder = old_definition_builder.ancestor_builder
281
+ old_graph = RBS::AncestorGraph.new(env: old_env, ancestor_builder: old_ancestor_builder)
282
+ add_descendants(graph: old_graph, names: old_names, set: changed_names)
283
+ add_nested_decls(env: old_env, names: old_names, set: changed_names)
284
+
285
+ new_env = ancestor_builder.env
286
+ new_ancestor_builder = ancestor_builder
287
+ new_names = type_names(paths: paths, env: new_env)
288
+ new_graph = RBS::AncestorGraph.new(env: new_env, ancestor_builder: new_ancestor_builder)
289
+ add_descendants(graph: new_graph, names: new_names, set: changed_names)
290
+ add_nested_decls(env: new_env, names: new_names, set: changed_names)
291
+
292
+ old_definition_builder.update(
293
+ env: new_env,
294
+ ancestor_builder: new_ancestor_builder,
295
+ except: changed_names
296
+ )
297
+ end
298
+ end
299
+
300
+ def type_names(paths:, env:)
301
+ env.declarations.each.with_object(Set[]) do |decl, set|
302
+ if decl.location
303
+ if paths.include?(decl.location.buffer.name)
304
+ type_name_from_decl(decl, set: set)
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ def type_name_from_decl(decl, set:)
311
+ case decl
312
+ when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
313
+ set << decl.name
314
+
315
+ decl.members.each do |member|
316
+ if member.is_a?(RBS::AST::Declarations::Base)
317
+ type_name_from_decl(member, set: set)
318
+ end
319
+ end
320
+ when RBS::AST::Declarations::Alias
321
+ set << decl.name
322
+ end
323
+ end
324
+
325
+ def add_descendants(graph:, names:, set:)
326
+ set.merge(names)
327
+ names.each do |name|
328
+ case
329
+ when name.interface?
330
+ graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
331
+ set << node.type_name
332
+ end
333
+ when name.class?
334
+ graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
335
+ set << node.type_name
336
+ end
337
+ graph.each_descendant(RBS::AncestorGraph::SingletonNode.new(type_name: name)) do |node|
338
+ set << node.type_name
339
+ end
340
+ end
341
+ end
342
+ end
343
+
344
+ def add_nested_decls(env:, names:, set:)
345
+ tops = names.each.with_object(Set[]) do |name, tops|
346
+ unless name.namespace.empty?
347
+ tops << name.namespace.path[0]
348
+ end
349
+ end
350
+
351
+ env.class_decls.each_key do |name|
352
+ unless name.namespace.empty?
353
+ if tops.include?(name.namespace.path[0])
354
+ set << name
355
+ end
356
+ end
357
+ end
358
+
359
+ env.interface_decls.each_key do |name|
360
+ unless name.namespace.empty?
361
+ if tops.include?(name.namespace.path[0])
362
+ set << name
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end