steep 1.8.3 → 1.9.0.dev.2

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