rbs 1.3.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +10 -0
  3. data/CHANGELOG.md +69 -0
  4. data/Gemfile +2 -0
  5. data/Rakefile +4 -0
  6. data/Steepfile +9 -1
  7. data/core/array.rbs +8 -7
  8. data/core/builtin.rbs +1 -1
  9. data/core/enumerable.rbs +11 -10
  10. data/core/enumerator.rbs +2 -2
  11. data/core/exception.rbs +1 -0
  12. data/core/false_class.rbs +4 -4
  13. data/core/file.rbs +3 -1
  14. data/core/float.rbs +1 -1
  15. data/core/global_variables.rbs +180 -0
  16. data/core/hash.rbs +7 -7
  17. data/core/integer.rbs +1 -2
  18. data/core/io/wait.rbs +37 -0
  19. data/core/io.rbs +11 -5
  20. data/core/kernel.rbs +25 -2
  21. data/core/object.rbs +1 -1
  22. data/core/ractor.rbs +779 -0
  23. data/core/range.rbs +11 -9
  24. data/core/string_io.rbs +3 -5
  25. data/core/true_class.rbs +4 -4
  26. data/docs/collection.md +116 -0
  27. data/lib/rbs/builtin_names.rb +1 -0
  28. data/lib/rbs/cli.rb +94 -2
  29. data/lib/rbs/collection/cleaner.rb +29 -0
  30. data/lib/rbs/collection/config/lockfile_generator.rb +95 -0
  31. data/lib/rbs/collection/config.rb +85 -0
  32. data/lib/rbs/collection/installer.rb +27 -0
  33. data/lib/rbs/collection/sources/git.rb +147 -0
  34. data/lib/rbs/collection/sources/rubygems.rb +40 -0
  35. data/lib/rbs/collection/sources/stdlib.rb +38 -0
  36. data/lib/rbs/collection/sources.rb +22 -0
  37. data/lib/rbs/collection.rb +13 -0
  38. data/lib/rbs/environment_loader.rb +12 -0
  39. data/lib/rbs/errors.rb +18 -0
  40. data/lib/rbs/parser.rb +1 -1
  41. data/lib/rbs/parser.y +1 -1
  42. data/lib/rbs/prototype/rb.rb +8 -1
  43. data/lib/rbs/prototype/runtime.rb +1 -1
  44. data/lib/rbs/repository.rb +13 -7
  45. data/lib/rbs/type_alias_dependency.rb +88 -0
  46. data/lib/rbs/validator.rb +8 -0
  47. data/lib/rbs/version.rb +1 -1
  48. data/lib/rbs.rb +2 -0
  49. data/sig/builtin_names.rbs +1 -0
  50. data/sig/cli.rbs +5 -0
  51. data/sig/collection/cleaner.rbs +13 -0
  52. data/sig/collection/collections.rbs +112 -0
  53. data/sig/collection/config.rbs +69 -0
  54. data/sig/collection/installer.rbs +15 -0
  55. data/sig/collection.rbs +4 -0
  56. data/sig/environment_loader.rbs +3 -0
  57. data/sig/errors.rbs +9 -0
  58. data/sig/polyfill.rbs +12 -3
  59. data/sig/repository.rbs +4 -0
  60. data/sig/type_alias_dependency.rbs +22 -0
  61. data/sig/validator.rbs +2 -0
  62. data/stdlib/digest/0/digest.rbs +418 -0
  63. data/stdlib/objspace/0/objspace.rbs +406 -0
  64. data/stdlib/openssl/0/openssl.rbs +3711 -0
  65. data/stdlib/pathname/0/pathname.rbs +2 -2
  66. data/stdlib/rubygems/0/rubygems.rbs +1 -1
  67. data/stdlib/securerandom/0/securerandom.rbs +3 -1
  68. data/stdlib/tempfile/0/tempfile.rbs +270 -0
  69. data/stdlib/uri/0/generic.rbs +3 -3
  70. data/steep/Gemfile.lock +10 -10
  71. metadata +28 -3
data/core/range.rbs CHANGED
@@ -101,7 +101,9 @@ class Range[out Elem] < Object
101
101
  # ```
102
102
  def begin: () -> Elem # Begin-less ranges have type of Range[Integer?]
103
103
 
104
- def bsearch: [U] () { (Elem) -> boolish } -> U?
104
+ def bsearch: () -> ::Enumerator[Elem, Elem?]
105
+ | () { (Elem) -> (true | false) } -> Elem?
106
+ | () { (Elem) -> ::Integer } -> Elem?
105
107
 
106
108
  def cover?: (untyped obj) -> bool
107
109
 
@@ -132,7 +134,7 @@ class Range[out Elem] < Object
132
134
  # (10..20).first(3) #=> [10, 11, 12]
133
135
  # ```
134
136
  def first: () -> Elem
135
- | (?Integer n) -> ::Array[Elem]
137
+ | (Integer n) -> ::Array[Elem]
136
138
 
137
139
  # Compute a hash-code for this range. Two ranges with equal begin and end
138
140
  # points (using `eql?` ), and the same
@@ -165,7 +167,7 @@ class Range[out Elem] < Object
165
167
  # (10...20).last(3) #=> [17, 18, 19]
166
168
  # ```
167
169
  def last: () -> Elem
168
- | (?Integer n) -> ::Array[Elem]
170
+ | (Integer n) -> ::Array[Elem]
169
171
 
170
172
  # Returns the maximum value in the range. Returns `nil` if the begin value
171
173
  # of the range larger than the end value. Returns `nil` if the begin value
@@ -178,9 +180,9 @@ class Range[out Elem] < Object
178
180
  # (10..20).max #=> 20
179
181
  # ```
180
182
  def max: () -> Elem
181
- | () { (Elem arg0, Elem arg1) -> Integer } -> Elem
182
- | (?Integer n) -> ::Array[Elem]
183
- | (?Integer n) { (Elem arg0, Elem arg1) -> Integer } -> ::Array[Elem]
183
+ | () { (Elem a, Elem b) -> Integer } -> Elem
184
+ | (Integer n) -> ::Array[Elem]
185
+ | (Integer n) { (Elem a, Elem b) -> Integer } -> ::Array[Elem]
184
186
 
185
187
  # Returns the minimum value in the range. Returns `nil` if the begin value
186
188
  # of the range is larger than the end value. Returns `nil` if the begin
@@ -193,9 +195,9 @@ class Range[out Elem] < Object
193
195
  # (10..20).min #=> 10
194
196
  # ```
195
197
  def min: () -> Elem
196
- | () { (Elem arg0, Elem arg1) -> Integer } -> Elem
197
- | (?Integer n) -> ::Array[Elem]
198
- | (?Integer n) { (Elem arg0, Elem arg1) -> Integer } -> ::Array[Elem]
198
+ | () { (Elem a, Elem b) -> Integer } -> Elem
199
+ | (Integer n) -> ::Array[Elem]
200
+ | (Integer n) { (Elem a, Elem b) -> Integer } -> ::Array[Elem]
199
201
 
200
202
  # Returns the number of elements in the range. Both the begin and the end
201
203
  # of the [Range](Range.downloaded.ruby_doc) must be
data/core/string_io.rbs CHANGED
@@ -164,10 +164,9 @@ class StringIO
164
164
 
165
165
  # See IO#read.
166
166
  #
167
- def read: (?Integer length, ?String outbuf) -> String?
167
+ def read: (?int? length, ?string outbuf) -> String?
168
168
 
169
- def read_nonblock: (Integer len) -> String
170
- | (Integer len, ?String buf) -> String
169
+ def read_nonblock: (int len, ?string buf) -> String
171
170
 
172
171
  def readbyte: () -> Integer
173
172
 
@@ -179,8 +178,7 @@ class StringIO
179
178
  #
180
179
  def readlines: (?String sep, ?Integer limit, ?chomp: boolish) -> ::Array[String]
181
180
 
182
- def readpartial: (Integer maxlen) -> String
183
- | (Integer maxlen, ?String outbuf) -> String
181
+ def readpartial: (int maxlen, ?string outbuf) -> String
184
182
 
185
183
  # Reinitializes the stream with the given *other_StrIO* or *string* and *mode*
186
184
  # (see StringIO#new).
data/core/true_class.rbs CHANGED
@@ -5,13 +5,13 @@
5
5
  class TrueClass
6
6
  public
7
7
 
8
- def !: () -> bool
8
+ def !: () -> false
9
9
 
10
10
  # And---Returns `false` if *obj* is `nil` or `false`, `true` otherwise.
11
11
  #
12
12
  def &: (nil) -> false
13
13
  | (false) -> false
14
- | (untyped obj) -> bool
14
+ | (untyped obj) -> true
15
15
 
16
16
  # Case Equality -- For class Object, effectively the same as calling `#==`, but
17
17
  # typically overridden by descendants to provide meaningful semantics in `case`
@@ -24,7 +24,7 @@ class TrueClass
24
24
  #
25
25
  def ^: (nil) -> true
26
26
  | (false) -> true
27
- | (untyped obj) -> bool
27
+ | (untyped obj) -> false
28
28
 
29
29
  alias inspect to_s
30
30
 
@@ -42,5 +42,5 @@ class TrueClass
42
42
  #
43
43
  # or
44
44
  #
45
- def |: (boolish obj) -> bool
45
+ def |: (untyped obj) -> true
46
46
  end
@@ -0,0 +1,116 @@
1
+ # RBS Collection manager
2
+
3
+ `rbs collection` sub command manages third party gems' RBS. In short, it is `bundler` for RBS.
4
+
5
+ ## Requirements
6
+
7
+ * `git(1)`
8
+ * `Gemfile.lock`
9
+
10
+
11
+ ## Usage
12
+
13
+ ### Setup
14
+
15
+ First, generate the configuration file, `rbs_collection.yaml`, with `rbs collection init`.
16
+
17
+ ```console
18
+ $ rbs collection init
19
+ created: rbs_collection.yaml
20
+
21
+ $ cat rbs_collection.yaml
22
+ # Download sources
23
+ sources:
24
+ - name: ruby/gem_rbs_collection
25
+ remote: https://github.com/ruby/gem_rbs_collection.git
26
+ revision: main
27
+ repo_dir: gems
28
+
29
+ # A directory to install the downloaded RBSs
30
+ path: .gem_rbs_collection
31
+
32
+ gems:
33
+ # Skip loading rbs gem's RBS.
34
+ # It's unnecessary if you don't use rbs as a library.
35
+ - name: rbs
36
+ ignore: true
37
+ ```
38
+
39
+ I also recommend updating `.gitignore`.
40
+
41
+ ```console
42
+ $ echo /.gem_rbs_collection/ >> .gitignore
43
+ ```
44
+
45
+ ### Install dependencies
46
+
47
+ Then, install gems' RBS with `rbs collection install`! It copies RBS from [the gem RBS repository](https://github.com/ruby/gem_rbs_collection) to `.gem_rbs_collection/` directory by default.
48
+ I recommend to ignore `.gem_rbs_collection/` from version control system, such as Git.
49
+
50
+ ```console
51
+ $ rbs collection install
52
+ Installing ast:2.4 (ruby/gem_rbs_collection@4b1a2a2f64c)
53
+ ...
54
+ It's done! 42 gems's RBSs now installed.
55
+ ```
56
+
57
+ Finally the third party RBSs are available! `rbs` commands, such as `rbs validate`, automatically load the third party RBSs.
58
+
59
+ ### Other commands
60
+
61
+ `rbs collection` has two more commands.
62
+
63
+ * `rbs collection update` updates `rbs_collection.lock.yaml`.
64
+ * `rbs collection clean` removes unnecessary rbs from `.gem_rbs_collection` directory.
65
+
66
+ ## Configuration
67
+
68
+ Configure `rbs collection` with editing `rbs_collection.yaml`.
69
+
70
+ ```yaml
71
+ # Download sources.
72
+ # You can add own collection git repository.
73
+ sources:
74
+ - name: ruby/gem_rbs_collection
75
+ remote: https://github.com/ruby/gem_rbs_collection.git
76
+ revision: main
77
+ repo_dir: gems
78
+
79
+ # A directory to install the downloaded RBSs
80
+ path: .gem_rbs_collection
81
+
82
+ gems:
83
+ # If the Gemfile.lock doesn't contain csv gem but you use csv gem,
84
+ # you can write the gem name explicitly to install RBS of the gem.
85
+ - name: csv
86
+
87
+ # If the Gemfile.lock contains nokogiri gem but you don't want to use the RBS,
88
+ # you can ignore the gem.
89
+ # `rbs collection` avoids to install nokogiri gem's RBS by this change.
90
+ # It is useful if the nokogiri RBS has a problem, such as compatibility issue with other RBS.
91
+ - name: nokogiri
92
+ ignore: true
93
+ ```
94
+
95
+ ## Files / Directories
96
+
97
+ * `rbs_collection.yaml`
98
+ * The configuration file.
99
+ * You need to edit it if:
100
+ * You don't want to ignore gem's RBS.
101
+ * You want to add gem's RBS explicitly.
102
+ * You can change the file path with `--collection` option. e.g. `rbs --collection another_conf.yaml collection install`.
103
+ * `rbs_collection.lock.yaml`
104
+ * RBS installs and loads RBS files with this file.
105
+ * It is auto-generated file. Do not edit this file.
106
+ * I recommend to manage it with VCS such as git.
107
+ * `.gem_rbs_collection/`
108
+ * RBS installs third party RBS files to the directory.
109
+ * I recommend to ignore it from VCS.
110
+ * You can change the path with `path` option of `rbs_collection.yaml` file.
111
+
112
+
113
+ ## How it works
114
+
115
+ `rbs collection` is integrated with Bundler.
116
+ `rbs collection install` command generates `gem_rbs_collection.lock.yaml` from `gem_rbs_collection.yaml` and `Gemfile.lock`. It uses `Gemfile.lock` to detects dependencies.
@@ -51,5 +51,6 @@ module RBS
51
51
  Regexp = Name.define(:Regexp)
52
52
  TrueClass = Name.define(:TrueClass)
53
53
  FalseClass = Name.define(:FalseClass)
54
+ Numeric = Name.define(:Numeric)
54
55
  end
55
56
  end
data/lib/rbs/cli.rb CHANGED
@@ -6,6 +6,7 @@ module RBS
6
6
  class CLI
7
7
  class LibraryOptions
8
8
  attr_accessor :core_root
9
+ attr_accessor :config_path
9
10
  attr_reader :repos
10
11
  attr_reader :libs
11
12
  attr_reader :dirs
@@ -16,6 +17,7 @@ module RBS
16
17
 
17
18
  @libs = []
18
19
  @dirs = []
20
+ @config_path = Collection::Config::PATH
19
21
  end
20
22
 
21
23
  def loader
@@ -25,6 +27,8 @@ module RBS
25
27
  end
26
28
 
27
29
  loader = EnvironmentLoader.new(core_root: core_root, repository: repository)
30
+ lock = config_path&.then { |p| Collection::Config.lockfile_of(p) }
31
+ loader.add_collection(lock) if lock
28
32
 
29
33
  dirs.each do |dir|
30
34
  loader.add(path: Pathname(dir))
@@ -52,6 +56,14 @@ module RBS
52
56
  self.core_root = nil
53
57
  end
54
58
 
59
+ opts.on('--collection PATH', "File path of collection configration (default: #{Collection::Config::PATH})") do |path|
60
+ self.config_path = Pathname(path).expand_path
61
+ end
62
+
63
+ opts.on('--no-collection', 'Ignore collection configration') do
64
+ self.config_path = nil
65
+ end
66
+
55
67
  opts.on("--repo DIR", "Add RBS repository") do |dir|
56
68
  repos << dir
57
69
  end
@@ -68,7 +80,7 @@ module RBS
68
80
  @stderr = stderr
69
81
  end
70
82
 
71
- COMMANDS = [:ast, :list, :ancestors, :methods, :method, :validate, :constant, :paths, :prototype, :vendor, :parse, :test]
83
+ COMMANDS = [:ast, :list, :ancestors, :methods, :method, :validate, :constant, :paths, :prototype, :vendor, :parse, :test, :collection]
72
84
 
73
85
  def parse_logging_options(opts)
74
86
  opts.on("--log-level LEVEL", "Specify log level (defaults to `warn`)") do |level|
@@ -451,6 +463,7 @@ EOU
451
463
  builder.expand_alias(name).tap do |type|
452
464
  validator.validate_type type, context: [Namespace.root]
453
465
  end
466
+ validator.validate_type_alias(entry: decl)
454
467
  end
455
468
  end
456
469
 
@@ -818,11 +831,90 @@ EOB
818
831
 
819
832
  # @type var out: String
820
833
  # @type var err: String
821
- out, err, status = Open3.capture3(env_hash, *args)
834
+ out, err, status = __skip__ = Open3.capture3(env_hash, *args)
822
835
  stdout.print(out)
823
836
  stderr.print(err)
824
837
 
825
838
  status
826
839
  end
840
+
841
+ def run_collection(args, options)
842
+ warn "warning: rbs collection is experimental, and the behavior may change until RBS v2.0"
843
+
844
+ opts = collection_options(args)
845
+ params = {}
846
+ opts.order args.drop(1), into: params
847
+ config_path = options.config_path or raise
848
+ lock_path = Collection::Config.to_lockfile_path(config_path)
849
+
850
+ case args[0]
851
+ when 'install'
852
+ unless params[:frozen]
853
+ Collection::Config.generate_lockfile(config_path: config_path, gemfile_lock_path: Pathname('./Gemfile.lock'))
854
+ end
855
+ Collection::Installer.new(lockfile_path: lock_path, stdout: stdout).install_from_lockfile
856
+ when 'update'
857
+ # TODO: Be aware of argv to update only specified gem
858
+ Collection::Config.generate_lockfile(config_path: config_path, gemfile_lock_path: Pathname('./Gemfile.lock'), with_lockfile: false)
859
+ Collection::Installer.new(lockfile_path: lock_path, stdout: stdout).install_from_lockfile
860
+ when 'init'
861
+ if config_path.exist?
862
+ puts "#{config_path} already exists"
863
+ exit 1
864
+ end
865
+
866
+ config_path.write(<<~'YAML')
867
+ # Download sources
868
+ sources:
869
+ - name: ruby/gem_rbs_collection
870
+ remote: https://github.com/ruby/gem_rbs_collection.git
871
+ revision: main
872
+ repo_dir: gems
873
+
874
+ # A directory to install the downloaded RBSs
875
+ path: .gem_rbs_collection
876
+
877
+ gems:
878
+ # Skip loading rbs gem's RBS.
879
+ # It's unnecessary if you don't use rbs as a library.
880
+ - name: rbs
881
+ ignore: true
882
+ YAML
883
+ stdout.puts "created: #{config_path}"
884
+ when 'clean'
885
+ unless lock_path.exist?
886
+ puts "#{lock_path} should exist to clean"
887
+ exit 1
888
+ end
889
+ Collection::Cleaner.new(lockfile_path: lock_path)
890
+ when 'help'
891
+ puts opts.help
892
+ else
893
+ puts opts.help
894
+ exit 1
895
+ end
896
+ end
897
+
898
+ def collection_options(args)
899
+ OptionParser.new do |opts|
900
+ opts.banner = <<~HELP
901
+ Usage: rbs collection [install|update|init|clean|help]
902
+
903
+ Manage RBS collection, which contains third party RBS.
904
+
905
+ Examples:
906
+
907
+ # Initialize the configration file
908
+ $ rbs collection init
909
+
910
+ # Generate the lock file and install RBSs from the lock file
911
+ $ rbs collection install
912
+
913
+ # Update the RBSs
914
+ $ rbs collection update
915
+ HELP
916
+ opts.on('--frozen') if args[0] == 'install'
917
+ end
918
+ end
827
919
  end
828
920
  end
@@ -0,0 +1,29 @@
1
+ module RBS
2
+ module Collection
3
+ class Cleaner
4
+ attr_reader :lock
5
+
6
+ def initialize(lockfile_path:)
7
+ @lock = Config.from_path(lockfile_path)
8
+ end
9
+
10
+ def clean
11
+ lock.repo_path.glob('*/*') do |dir|
12
+ *_, gem_name, version = dir.to_s.split('/')
13
+ gem_name or raise
14
+ version or raise
15
+ next if needed? gem_name, version
16
+
17
+ FileUtils.remove_entry_secure(dir.to_s)
18
+ end
19
+ end
20
+
21
+ def needed?(gem_name, version)
22
+ gem = lock.gem(gem_name)
23
+ return false unless gem
24
+
25
+ gem['version'] == version
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,95 @@
1
+ module RBS
2
+ module Collection
3
+
4
+ # This class represent the configration file.
5
+ class Config
6
+ class LockfileGenerator
7
+ attr_reader :config, :lock, :gemfile_lock, :lock_path
8
+
9
+ def self.generate(config_path:, gemfile_lock_path:, with_lockfile: true)
10
+ new(config_path: config_path, gemfile_lock_path: gemfile_lock_path, with_lockfile: with_lockfile).generate
11
+ end
12
+
13
+ def initialize(config_path:, gemfile_lock_path:, with_lockfile:)
14
+ @config = Config.from_path config_path
15
+ @lock_path = Config.to_lockfile_path(config_path)
16
+ @lock = Config.from_path(lock_path) if lock_path.exist? && with_lockfile
17
+ @gemfile_lock = Bundler::LockfileParser.new(gemfile_lock_path.read)
18
+ end
19
+
20
+ def generate
21
+ config.gems.each do |gem|
22
+ assign_gem(gem_name: gem['name'], version: gem['version'])
23
+ end
24
+
25
+ gemfile_lock_gems do |spec|
26
+ assign_gem(gem_name: spec.name, version: spec.version)
27
+ end
28
+ remove_ignored_gems!
29
+
30
+ config.dump_to(lock_path)
31
+ config
32
+ end
33
+
34
+ private def assign_gem(gem_name:, version:)
35
+ locked = lock&.gem(gem_name)
36
+ specified = config.gem(gem_name)
37
+
38
+ return if specified&.dig('ignore')
39
+ return if specified&.dig('source') # skip if the source is already filled
40
+
41
+ if locked
42
+ # If rbs_collection.lock.yaml contain the gem, use it.
43
+ upsert_gem specified, locked
44
+ else
45
+ # Find the gem from gem_collection.
46
+ source = find_source(gem_name: gem_name)
47
+ return unless source
48
+
49
+ installed_version = version
50
+ best_version = find_best_version(version: installed_version, versions: source.versions({ 'name' => gem_name }))
51
+ # @type var new_content: RBS::Collection::Config::gem_entry
52
+ new_content = {
53
+ 'name' => gem_name,
54
+ 'version' => best_version.to_s,
55
+ 'source' => source.to_lockfile,
56
+ }
57
+ upsert_gem specified, new_content
58
+ end
59
+ end
60
+
61
+ private def upsert_gem(old, new)
62
+ if old
63
+ old.merge! new
64
+ else
65
+ config.add_gem new
66
+ end
67
+ end
68
+
69
+ private def remove_ignored_gems!
70
+ config.gems.reject! { |gem| gem['ignore'] }
71
+ end
72
+
73
+ private def gemfile_lock_gems(&block)
74
+ gemfile_lock.specs.each do |spec|
75
+ yield spec
76
+ end
77
+ end
78
+
79
+ private def find_source(gem_name:)
80
+ sources = config.sources
81
+
82
+ sources.find { |c| c.has?({ 'name' => gem_name, 'revision' => nil } ) }
83
+ end
84
+
85
+ private def find_best_version(version:, versions:)
86
+ candidates = versions.map { |v| Gem::Version.create(v) or raise }
87
+ return candidates.max || raise unless version
88
+
89
+ v = Gem::Version.create(version) or raise
90
+ Repository.find_best_version(v, candidates)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end