steep 1.8.3 → 1.9.0.dev.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -22
  3. data/Steepfile +35 -26
  4. data/bin/rbs-inline +19 -0
  5. data/lib/steep/cli.rb +38 -5
  6. data/lib/steep/diagnostic/ruby.rb +11 -58
  7. data/lib/steep/drivers/annotations.rb +1 -1
  8. data/lib/steep/drivers/check.rb +103 -1
  9. data/lib/steep/drivers/checkfile.rb +10 -8
  10. data/lib/steep/drivers/print_project.rb +83 -40
  11. data/lib/steep/drivers/utils/driver_helper.rb +39 -6
  12. data/lib/steep/drivers/watch.rb +24 -2
  13. data/lib/steep/index/signature_symbol_provider.rb +8 -8
  14. data/lib/steep/interface/builder.rb +14 -1
  15. data/lib/steep/interface/function.rb +2 -2
  16. data/lib/steep/path_helper.rb +4 -2
  17. data/lib/steep/project/dsl.rb +176 -151
  18. data/lib/steep/project/group.rb +31 -0
  19. data/lib/steep/project/pattern.rb +4 -0
  20. data/lib/steep/project/target.rb +32 -6
  21. data/lib/steep/project.rb +38 -10
  22. data/lib/steep/server/custom_methods.rb +16 -0
  23. data/lib/steep/server/delay_queue.rb +0 -3
  24. data/lib/steep/server/interaction_worker.rb +2 -11
  25. data/lib/steep/server/master.rb +129 -279
  26. data/lib/steep/server/target_group_files.rb +205 -0
  27. data/lib/steep/server/type_check_controller.rb +366 -0
  28. data/lib/steep/server/type_check_worker.rb +60 -86
  29. data/lib/steep/services/file_loader.rb +23 -0
  30. data/lib/steep/services/goto_service.rb +40 -31
  31. data/lib/steep/services/hover_provider/singleton_methods.rb +4 -4
  32. data/lib/steep/services/path_assignment.rb +23 -4
  33. data/lib/steep/services/type_check_service.rb +76 -159
  34. data/lib/steep/signature/validator.rb +4 -4
  35. data/lib/steep/subtyping/check.rb +2 -2
  36. data/lib/steep/thread_waiter.rb +24 -16
  37. data/lib/steep/type_construction.rb +12 -3
  38. data/lib/steep/type_inference/block_params.rb +1 -2
  39. data/lib/steep/type_inference/context.rb +1 -1
  40. data/lib/steep/type_inference/type_env.rb +4 -4
  41. data/lib/steep/type_inference/type_env_builder.rb +1 -1
  42. data/lib/steep/version.rb +1 -1
  43. data/lib/steep.rb +6 -4
  44. data/sample/Steepfile +6 -0
  45. data/sample/lib/conference.rb +1 -5
  46. data/steep.gemspec +7 -1
  47. metadata +9 -6
  48. data/lib/steep/drivers/validate.rb +0 -65
@@ -1,68 +1,111 @@
1
+ require "active_support/core_ext/hash/keys"
2
+
1
3
  module Steep
2
4
  module Drivers
3
5
  class PrintProject
4
6
  attr_reader :stdout
5
7
  attr_reader :stderr
6
8
 
9
+ attr_accessor :print_files
10
+ attr_reader :files
11
+
7
12
  include Utils::DriverHelper
8
13
 
9
14
  def initialize(stdout:, stderr:)
10
15
  @stdout = stdout
11
16
  @stderr = stderr
17
+ @print_files = false
12
18
  end
13
19
 
14
- def run
15
- project = load_config()
16
-
17
- loader = Services::FileLoader.new(base_dir: project.base_dir)
18
-
19
- project.targets.each do |target|
20
- source_changes = loader.load_changes(target.source_pattern, changes: {})
21
- signature_changes = loader.load_changes(target.signature_pattern, changes: {})
22
-
23
- stdout.puts "Target:"
24
- stdout.puts " #{target.name}:"
25
- stdout.puts " sources:"
26
- stdout.puts " patterns:"
27
- target.source_pattern.patterns.each do |pattern|
28
- stdout.puts " - #{pattern}"
29
- end
30
- stdout.puts " ignores:"
31
- target.source_pattern.ignores.each do |pattern|
32
- stdout.puts " - #{pattern}"
33
- end
34
- stdout.puts " files:"
35
- source_changes.each_key do |path|
36
- stdout.puts " - #{path}"
20
+ def as_json(project)
21
+ {
22
+ steepfile: project.steepfile_path.to_s,
23
+ targets: project.targets.map do |target|
24
+ target_as_json(target)
37
25
  end
38
- stdout.puts " signatures:"
39
- stdout.puts " patterns:"
40
- target.signature_pattern.patterns.each do |pattern|
41
- stdout.puts " - #{pattern}"
42
- end
43
- stdout.puts " files:"
44
- signature_changes.each_key do |path|
45
- stdout.puts " - #{path}"
46
- end
47
- stdout.puts " libraries:"
48
- target.options.libraries.each do |lib|
49
- stdout.puts " - #{lib}"
50
- end
51
- stdout.puts " library dirs:"
52
- target.new_env_loader(project: project).tap do |loader|
26
+ }.stringify_keys
27
+ end
28
+
29
+ def target_as_json(target)
30
+ json = {
31
+ "name" => target.name.to_s,
32
+ "source_pattern" => pattern_as_json(target.source_pattern),
33
+ "signature_pattern" => pattern_as_json(target.signature_pattern),
34
+ "groups" => target.groups.map do |group|
35
+ group_as_json(group)
36
+ end,
37
+ "libraries" => target.new_env_loader().yield_self do |loader|
38
+ libs = [] #: Array[library_json]
53
39
  loader.each_dir do |lib, path|
54
40
  case lib
55
41
  when :core
56
- stdout.puts " - core: #{path}"
42
+ libs << { "name" => "__core__", "path" => path.to_s }
57
43
  when Pathname
58
44
  raise "Unexpected pathname from loader: path=#{path}"
59
45
  else
60
- stdout.puts " - #{lib.name}(#{lib.version}): #{path}"
46
+ libs << { "name" => lib.name, "version" => lib.version, "path" => path.to_s }
61
47
  end
62
48
  end
49
+ libs
50
+ end,
51
+ "unreferenced" => target.unreferenced
52
+ } #: target_json
53
+
54
+ if files
55
+ files.each_group_signature_path(target, true) do |path|
56
+ (json["signature_paths"] ||= []) << path.to_s
57
+ end
58
+ files.each_group_source_path(target, true) do |path|
59
+ (json["source_paths"] ||= []) << path.to_s
63
60
  end
64
61
  end
65
62
 
63
+ json
64
+ end
65
+
66
+ def group_as_json(group)
67
+ json = {
68
+ "name" => group.name.to_s,
69
+ "source_pattern" => pattern_as_json(group.source_pattern),
70
+ "signature_pattern" => pattern_as_json(group.signature_pattern)
71
+ } #: group_json
72
+
73
+ if files
74
+ files.each_group_signature_path(group, true) do |path|
75
+ (json["signature_paths"] ||= []) << path.to_s
76
+
77
+ end
78
+ files.each_group_source_path(group, true) do |path|
79
+ (json["source_paths"] ||= []) << path.to_s
80
+ end
81
+ end
82
+
83
+ json
84
+ end
85
+
86
+ def pattern_as_json(pattern)
87
+ {
88
+ "pattern" => pattern.patterns,
89
+ "ignore" => pattern.ignores
90
+ }
91
+ end
92
+
93
+ def run
94
+ project = load_config()
95
+ if print_files
96
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
97
+ @files = files = Server::TargetGroupFiles.new(project)
98
+ project.targets.each do |target|
99
+ loader.each_path_in_target(target) do |path|
100
+ files.add_path(path)
101
+ end
102
+ end
103
+ else
104
+ @files = nil
105
+ end
106
+
107
+ stdout.puts YAML.dump(as_json(project))
108
+
66
109
  0
67
110
  end
68
111
  end
@@ -3,6 +3,7 @@ module Steep
3
3
  module Utils
4
4
  module DriverHelper
5
5
  attr_accessor :steepfile
6
+ attr_accessor :disable_install_collection
6
7
 
7
8
  def load_config(path: steepfile || Pathname("Steepfile"))
8
9
  if path.file?
@@ -13,9 +14,11 @@ module Steep
13
14
  else
14
15
  Steep.ui_logger.error { "Cannot find a configuration at #{path}: `steep init` to scaffold. Using current directory..." }
15
16
  Project.new(steepfile_path: nil, base_dir: Pathname.pwd).tap do |project|
16
- Project::DSL.new(project: project).target :'.' do
17
- check '.'
18
- signature '.'
17
+ Project::DSL.eval(project) do
18
+ target :'.' do
19
+ check '.'
20
+ signature '.'
21
+ end
19
22
  end
20
23
  end
21
24
  end.tap do |project|
@@ -23,17 +26,47 @@ module Steep
23
26
  case result = target.options.load_collection_lock
24
27
  when nil, RBS::Collection::Config::Lockfile
25
28
  # ok
26
- else
29
+ when Pathname
30
+ # File is missing
27
31
  if result == target.options.collection_config_path
28
- Steep.ui_logger.error { "rbs-collection setup is broken: `#{result}` is missing" }
32
+ # Config file is missing
33
+ Steep.ui_logger.error { "rbs-collection configuration is missing: `#{result}`" }
34
+ else
35
+ # Lockfile is missing
36
+ Steep.ui_logger.error { "Run `rbs collection install` to generate missing lockfile: `#{result}`" }
37
+ end
38
+ when YAML::SyntaxError
39
+ # File is broken
40
+ Steep.ui_logger.error { "rbs-collection setup is broken:\nsyntax error #{result.inspect}" }
41
+ when RBS::Collection::Config::CollectionNotAvailable
42
+ unless disable_install_collection
43
+ install_collection(target, target.options.collection_config_path || raise)
29
44
  else
30
- Steep.ui_logger.error { "Run `rbs collection install` to install type definitions" }
45
+ Steep.ui_logger.error { "Run `rbs collection install` to set up RBS files for gems" }
31
46
  end
32
47
  end
33
48
  end
34
49
  end
35
50
  end
36
51
 
52
+ def install_collection(target, config_path)
53
+ Steep.ui_logger.info { "Installing RBS files for collection: #{config_path}" }
54
+ lockfile_path = RBS::Collection::Config.to_lockfile_path(config_path)
55
+ io = StringIO.new
56
+ begin
57
+ RBS::Collection::Installer.new(lockfile_path: lockfile_path, stdout: io).install_from_lockfile()
58
+ target.options.load_collection_lock(force: true)
59
+ Steep.ui_logger.debug { "Finished setting up RBS collection: " + io.string }
60
+
61
+ result = target.options.load_collection_lock(force: true)
62
+ unless result.is_a?(RBS::Collection::Config::Lockfile)
63
+ raise "Failed to set up RBS collection: #{result.inspect}"
64
+ end
65
+ rescue => exn
66
+ Steep.ui_logger.error { "Failed to set up RBS collection: #{exn.inspect}" }
67
+ end
68
+ end
69
+
37
70
  def request_id
38
71
  SecureRandom.alphanumeric(10)
39
72
  end
@@ -112,7 +112,19 @@ module Steep
112
112
  end
113
113
  end
114
114
 
115
- client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, { guid: nil }))
115
+ params = { library_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
116
+
117
+ (modified + added).each do |path|
118
+ path = Pathname(path)
119
+ if target = project.target_for_source_path(path)
120
+ params[:code_paths] << [target.name.to_s, path.to_s]
121
+ end
122
+ if target = project.target_for_signature_path(path)
123
+ params[:signature_paths] << [target.name.to_s, path.to_s]
124
+ end
125
+ end
126
+
127
+ client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, params))
116
128
 
117
129
  stdout.puts Rainbow("done!").bold
118
130
  end.tap(&:start)
@@ -120,7 +132,17 @@ module Steep
120
132
  begin
121
133
  stdout.puts Rainbow("👀 Watching directories, Ctrl-C to stop.").bold
122
134
 
123
- client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, { guid: nil }))
135
+ params = { library_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
136
+ file_loader = Services::FileLoader.new(base_dir: project.base_dir)
137
+ project.targets.each do |target|
138
+ file_loader.each_path_in_patterns(target.source_pattern, dirs.map(&:to_s)) do |path|
139
+ params[:code_paths] << [target.name.to_s, project.absolute_path(path).to_s]
140
+ end
141
+ file_loader.each_path_in_patterns(target.signature_pattern, dirs.map(&:to_s)) do |path|
142
+ params[:signature_paths] << [target.name.to_s, project.absolute_path(path).to_s]
143
+ end
144
+ end
145
+ client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, params))
124
146
 
125
147
  client_reader.read do |response|
126
148
  case response[:method]
@@ -11,7 +11,7 @@ module Steep
11
11
  attr_reader :assignment
12
12
 
13
13
  def initialize(project:, assignment:)
14
- @indexes = []
14
+ @indexes = {}
15
15
  @project = project
16
16
  @assignment = assignment
17
17
  end
@@ -39,20 +39,20 @@ module Steep
39
39
  end
40
40
  end
41
41
 
42
- def assigned?(path)
42
+ def assigned?(target, path)
43
43
  if path.relative?
44
44
  if project.targets.any? {|target| target.possible_signature_file?(path) }
45
45
  path = project.absolute_path(path)
46
46
  end
47
47
  end
48
48
 
49
- assignment =~ path
49
+ assignment =~ [target, path]
50
50
  end
51
51
 
52
52
  def query_symbol(query)
53
53
  symbols = [] #: Array[SymbolInformation]
54
54
 
55
- indexes.each do |index|
55
+ indexes.each do |target, index|
56
56
  index.each_entry do |entry|
57
57
  case entry
58
58
  when RBSIndex::TypeEntry
@@ -63,7 +63,7 @@ module Steep
63
63
 
64
64
  entry.declarations.each do |decl|
65
65
  location = decl.location or next
66
- next unless assigned?(Pathname(location.buffer.name))
66
+ next unless assigned?(target, Pathname(location.buffer.name))
67
67
 
68
68
  case decl
69
69
  when RBS::AST::Declarations::Class
@@ -111,7 +111,7 @@ module Steep
111
111
 
112
112
  entry.declarations.each do |decl|
113
113
  location = decl.location or next
114
- next unless assigned?(Pathname(location.buffer.name))
114
+ next unless assigned?(target, Pathname(location.buffer.name))
115
115
 
116
116
  case decl
117
117
  when RBS::AST::Members::MethodDefinition
@@ -151,7 +151,7 @@ module Steep
151
151
 
152
152
  entry.declarations.each do |decl|
153
153
  next unless decl.location
154
- next unless assigned?(Pathname(decl.location.buffer.name))
154
+ next unless assigned?(target, Pathname(decl.location.buffer.name))
155
155
 
156
156
  symbols << SymbolInformation.new(
157
157
  name: entry.const_name.name.to_s,
@@ -165,7 +165,7 @@ module Steep
165
165
 
166
166
  entry.declarations.each do |decl|
167
167
  next unless decl.location
168
- next unless assigned?(Pathname(decl.location.buffer.name))
168
+ next unless assigned?(target, Pathname(decl.location.buffer.name))
169
169
 
170
170
  symbols << SymbolInformation.new(
171
171
  name: decl.name.to_s,
@@ -282,6 +282,7 @@ module Steep
282
282
  method_type = factory.method_type(type_def.type)
283
283
  method_type = replace_primitive_method(method_name, type_def, method_type)
284
284
  method_type = replace_kernel_class(method_name, type_def, method_type) { AST::Builtin::Class.instance_type }
285
+ method_type = add_implicitly_returns_nil(type_def.annotations, method_type)
285
286
  Shape::MethodOverload.new(method_type, [type_def])
286
287
  end
287
288
 
@@ -315,6 +316,7 @@ module Steep
315
316
  if type_name.class?
316
317
  method_type = replace_kernel_class(method_name, type_def, method_type) { AST::Types::Name::Singleton.new(name: type_name) }
317
318
  end
319
+ method_type = add_implicitly_returns_nil(type_def.annotations, method_type)
318
320
  Shape::MethodOverload.new(method_type, [type_def])
319
321
  end
320
322
 
@@ -586,7 +588,7 @@ module Steep
586
588
 
587
589
  def record_shape(record)
588
590
  all_key_type = AST::Types::Union.build(
589
- types: record.elements.each_key.map {|value| AST::Types::Literal.new(value: value) }
591
+ types: record.elements.each_key.map {|value| AST::Types::Literal.new(value: value).back_type }
590
592
  )
591
593
  all_value_type = AST::Types::Union.build(types: record.elements.values)
592
594
  hash_type = AST::Builtin::Hash.instance_type(all_key_type, all_value_type)
@@ -822,6 +824,17 @@ module Steep
822
824
 
823
825
  method_type
824
826
  end
827
+
828
+ def add_implicitly_returns_nil(annotations, method_type)
829
+ if annotations.find { _1.string == "implicitly-returns-nil" }
830
+ return_type = method_type.type.return_type
831
+ method_type = method_type.with(
832
+ type: method_type.type.with(return_type: AST::Types::Union.build(types: [return_type, AST::Builtin.nil_type]))
833
+ )
834
+ else
835
+ method_type
836
+ end
837
+ end
825
838
  end
826
839
  end
827
840
  end
@@ -926,10 +926,10 @@ module Steep
926
926
  def to_s
927
927
  required = self.required.map {|ty| ty.to_s }
928
928
  optional = self.optional.map {|ty| "?#{ty}" }
929
- rest = self.rest ? ["*#{self.rest}"] : []
929
+ rest = self.rest ? ["*#{self.rest}"] : [] #: Array[String]
930
930
  required_keywords = keyword_params.requireds.map {|name, type| "#{name}: #{type}" }
931
931
  optional_keywords = keyword_params.optionals.map {|name, type| "?#{name}: #{type}"}
932
- rest_keywords = keyword_params.rest ? ["**#{keyword_params.rest}"] : []
932
+ rest_keywords = keyword_params.rest ? ["**#{keyword_params.rest}"] : [] #: Array[String]
933
933
  "(#{(required + optional + rest + required_keywords + optional_keywords + rest_keywords).join(", ")})"
934
934
  end
935
935
 
@@ -2,12 +2,14 @@ module Steep
2
2
  module PathHelper
3
3
  module_function
4
4
 
5
+ URIParser = URI::RFC2396_Parser.new()
6
+
5
7
  def to_pathname(uri, dosish: Gem.win_platform?)
6
8
  uri = URI.parse(uri)
7
9
  if uri.scheme == "file"
8
10
  path = uri.path or raise
9
11
  path.sub!(%r{^/([a-zA-Z])(:|%3A)//?}i, '\1:/') if dosish
10
- path = URI::DEFAULT_PARSER.unescape(path)
12
+ path = URIParser.unescape(path)
11
13
  Pathname(path)
12
14
  end
13
15
  end
@@ -21,7 +23,7 @@ module Steep
21
23
  if dosish
22
24
  str_path.insert(0, "/") if str_path[0] != "/"
23
25
  end
24
- str_path = URI::DEFAULT_PARSER.escape(str_path)
26
+ str_path = URIParser.escape(str_path)
25
27
  URI::File.build(path: str_path)
26
28
  end
27
29
  end