rbs 1.3.3 → 1.6.0

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 (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
@@ -0,0 +1,85 @@
1
+ module RBS
2
+ module Collection
3
+
4
+ # This class represent the configration file.
5
+ class Config
6
+ class CollectionNotAvailable < StandardError
7
+ def initialize
8
+ super <<~MSG
9
+ rbs collection is not initialized.
10
+ Run `rbs collection install` to install RBSs from collection.
11
+ MSG
12
+ end
13
+ end
14
+
15
+ PATH = Pathname('rbs_collection.yaml')
16
+
17
+ # Generate a rbs lockfile from Gemfile.lock to `config_path`.
18
+ # If `with_lockfile` is true, it respects existing rbs lockfile.
19
+ def self.generate_lockfile(config_path:, gemfile_lock_path:, with_lockfile: true)
20
+ LockfileGenerator.generate(config_path: config_path, gemfile_lock_path: gemfile_lock_path, with_lockfile: with_lockfile)
21
+ end
22
+
23
+ def self.from_path(path)
24
+ new(YAML.load(path.read), config_path: path)
25
+ end
26
+
27
+ def self.lockfile_of(config_path)
28
+ lock_path = to_lockfile_path(config_path)
29
+ from_path lock_path if lock_path.exist?
30
+ end
31
+
32
+ def self.to_lockfile_path(config_path)
33
+ config_path.sub_ext('.lock' + config_path.extname)
34
+ end
35
+
36
+ def initialize(data, config_path:)
37
+ @data = data
38
+ @config_path = config_path
39
+ end
40
+
41
+ def add_gem(gem)
42
+ gems << gem
43
+ end
44
+
45
+ def gem(gem_name)
46
+ gems.find { |gem| gem['name'] == gem_name }
47
+ end
48
+
49
+ def repo_path
50
+ @config_path.dirname.join @data['path']
51
+ end
52
+
53
+ def sources
54
+ @sources ||= (
55
+ @data['sources']
56
+ .map { |c| Sources.from_config_entry(c) }
57
+ .push(Sources::Stdlib.instance)
58
+ .push(Sources::Rubygems.instance)
59
+ )
60
+ end
61
+
62
+ def dump_to(io)
63
+ YAML.dump(@data, io)
64
+ end
65
+
66
+ def gems
67
+ @data['gems'] ||= []
68
+ end
69
+
70
+ # It raises an error when there are non-available libraries
71
+ def check_rbs_availability!
72
+ raise CollectionNotAvailable unless repo_path.exist?
73
+
74
+ gems.each do |gem|
75
+ case gem['source']['type']
76
+ when 'git'
77
+ meta_path = repo_path.join(gem['name'], gem['version'], Sources::Git::METADATA_FILENAME)
78
+ raise CollectionNotAvailable unless meta_path.exist?
79
+ raise CollectionNotAvailable unless gem == YAML.load(meta_path.read)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,27 @@
1
+ module RBS
2
+ module Collection
3
+ class Installer
4
+ attr_reader :lockfile
5
+ attr_reader :stdout
6
+
7
+ def initialize(lockfile_path:, stdout: $stdout)
8
+ @lockfile = Config.from_path(lockfile_path)
9
+ @stdout = stdout
10
+ end
11
+
12
+ def install_from_lockfile
13
+ install_to = lockfile.repo_path
14
+ lockfile.gems.each do |config_entry|
15
+ source_for(config_entry).install(dest: install_to, config_entry: config_entry, stdout: stdout)
16
+ end
17
+ stdout.puts "It's done! #{lockfile.gems.size} gems' RBSs now installed."
18
+ end
19
+
20
+ private def source_for(config_entry)
21
+ @source_for ||= {}
22
+ key = config_entry['source'] or raise
23
+ @source_for[key] ||= Sources.from_config_entry(key)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,147 @@
1
+ require 'digest/sha2'
2
+ require 'open3'
3
+
4
+ module RBS
5
+ module Collection
6
+ module Sources
7
+ class Git
8
+ METADATA_FILENAME = '.rbs_meta.yaml'
9
+
10
+ class CommandError < StandardError; end
11
+
12
+ attr_reader :name, :remote, :repo_dir
13
+
14
+ def initialize(name:, revision:, remote:, repo_dir:)
15
+ @name = name
16
+ @remote = remote
17
+ @repo_dir = repo_dir || 'gems'
18
+
19
+ setup!(revision: revision)
20
+ end
21
+
22
+ def has?(config_entry)
23
+ gem_name = config_entry['name']
24
+ gem_repo_dir.join(gem_name).directory?
25
+ end
26
+
27
+ def versions(config_entry)
28
+ gem_name = config_entry['name']
29
+ gem_repo_dir.join(gem_name).glob('*/').map { |path| path.basename.to_s }
30
+ end
31
+
32
+ def install(dest:, config_entry:, stdout:)
33
+ gem_name = config_entry['name']
34
+ version = config_entry['version'] or raise
35
+ gem_dir = dest.join(gem_name, version)
36
+
37
+ if gem_dir.directory?
38
+ if (prev = YAML.load_file(gem_dir.join(METADATA_FILENAME))) == config_entry
39
+ stdout.puts "Using #{format_config_entry(config_entry)}"
40
+ else
41
+ # @type var prev: RBS::Collection::Config::gem_entry
42
+ stdout.puts "Updating to #{format_config_entry(config_entry)} from #{format_config_entry(prev)}"
43
+ FileUtils.remove_entry_secure(gem_dir.to_s)
44
+ _install(dest: dest, config_entry: config_entry)
45
+ end
46
+ else
47
+ stdout.puts "Installing #{format_config_entry(config_entry)}"
48
+ _install(dest: dest, config_entry: config_entry)
49
+ end
50
+ end
51
+
52
+ private def _install(dest:, config_entry:)
53
+ gem_name = config_entry['name']
54
+ version = config_entry['version'] or raise
55
+ dest = dest.join(gem_name)
56
+ dest.mkpath
57
+ src = gem_repo_dir.join(gem_name, version)
58
+
59
+ FileUtils.cp_r(src, dest)
60
+ dest.join(version, METADATA_FILENAME).write(YAML.dump(config_entry))
61
+ end
62
+
63
+ def to_lockfile
64
+ {
65
+ 'type' => 'git',
66
+ 'name' => name,
67
+ 'revision' => resolved_revision,
68
+ 'remote' => remote,
69
+ 'repo_dir' => repo_dir,
70
+ }
71
+ end
72
+
73
+ private def format_config_entry(config_entry)
74
+ name = config_entry['name']
75
+ v = config_entry['version']
76
+
77
+ rev = resolved_revision[0..10]
78
+ desc = "#{name}@#{rev}"
79
+
80
+ "#{name}:#{v} (#{desc})"
81
+ end
82
+
83
+ private def setup!(revision:)
84
+ git_dir.mkpath
85
+ if git_dir.join('.git').directory?
86
+ if need_to_fetch?(revision)
87
+ git 'fetch', 'origin'
88
+ end
89
+ else
90
+ git 'clone', remote, git_dir.to_s
91
+ end
92
+
93
+ begin
94
+ git 'checkout', "origin/#{revision}" # for branch name as `revision`
95
+ rescue CommandError
96
+ git 'checkout', revision
97
+ end
98
+ end
99
+
100
+ private def need_to_fetch?(revision)
101
+ return true unless revision.match?(/\A[a-f0-9]{40}\z/)
102
+
103
+ begin
104
+ git('cat-file', '-e', revision)
105
+ false
106
+ rescue CommandError
107
+ true
108
+ end
109
+ end
110
+
111
+ private def git_dir
112
+ @git_dir ||= (
113
+ base = Pathname(ENV['XDG_CACHE_HOME'] || File.expand_path("~/.cache"))
114
+ dir = base.join('rbs', Digest::SHA256.hexdigest(remote))
115
+ dir.mkpath
116
+ dir
117
+ )
118
+ end
119
+
120
+ private def gem_repo_dir
121
+ git_dir.join @repo_dir
122
+ end
123
+
124
+ private def resolved_revision
125
+ @resolved_revision ||= resolve_revision
126
+ end
127
+
128
+ private def resolve_revision
129
+ git('rev-parse', 'HEAD').chomp
130
+ end
131
+
132
+ private def git(*cmd)
133
+ sh! 'git', *cmd
134
+ end
135
+
136
+ private def sh!(*cmd)
137
+ RBS.logger.debug "$ #{cmd.join(' ')}"
138
+ (__skip__ = Open3.capture3(*cmd, chdir: git_dir)).then do |out, err, status|
139
+ raise CommandError, "Unexpected status #{status.exitstatus}\n\n#{err}" unless status.success?
140
+
141
+ out
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,40 @@
1
+ require 'singleton'
2
+
3
+ module RBS
4
+ module Collection
5
+ module Sources
6
+ # Signatures that are inclduded in gem package as sig/ directory.
7
+ class Rubygems
8
+ include Singleton
9
+
10
+ def has?(config_entry)
11
+ gem_sig_path(config_entry)
12
+ end
13
+
14
+ def versions(config_entry)
15
+ spec, _ = gem_sig_path(config_entry)
16
+ spec or raise
17
+ [spec.version.to_s]
18
+ end
19
+
20
+ def install(dest:, config_entry:, stdout:)
21
+ # Do nothing because stdlib RBS is available by default
22
+ name = config_entry['name']
23
+ version = config_entry['version'] or raise
24
+ _, from = gem_sig_path(config_entry)
25
+ stdout.puts "Using #{name}:#{version} (#{from})"
26
+ end
27
+
28
+ def to_lockfile
29
+ {
30
+ 'type' => 'rubygems',
31
+ }
32
+ end
33
+
34
+ private def gem_sig_path(config_entry)
35
+ RBS::EnvironmentLoader.gem_sig_path(config_entry['name'], config_entry['version'])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ require 'singleton'
2
+
3
+ module RBS
4
+ module Collection
5
+ module Sources
6
+ # signatures that are bundled in rbs gem under the stdlib/ directory
7
+ class Stdlib
8
+ include Singleton
9
+
10
+ def has?(config_entry)
11
+ gem_dir(config_entry).exist?
12
+ end
13
+
14
+ def versions(config_entry)
15
+ gem_dir(config_entry).glob('*/').map { |path| path.basename.to_s }
16
+ end
17
+
18
+ def install(dest:, config_entry:, stdout:)
19
+ # Do nothing because stdlib RBS is available by default
20
+ name = config_entry['name']
21
+ version = config_entry['version'] or raise
22
+ from = gem_dir(config_entry) / version
23
+ stdout.puts "Using #{name}:#{version} (#{from})"
24
+ end
25
+
26
+ def to_lockfile
27
+ {
28
+ 'type' => 'stdlib',
29
+ }
30
+ end
31
+
32
+ private def gem_dir(config_entry)
33
+ Repository::DEFAULT_STDLIB_ROOT.join(config_entry['name'])
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ require_relative './sources/git'
2
+ require_relative './sources/stdlib'
3
+ require_relative './sources/rubygems'
4
+
5
+ module RBS
6
+ module Collection
7
+ module Sources
8
+ def self.from_config_entry(source_entry)
9
+ case source_entry['type']
10
+ when 'git', nil # git source by default
11
+ __skip__ = Git.new(**source_entry.slice('name', 'revision', 'remote', 'repo_dir').transform_keys(&:to_sym))
12
+ when 'stdlib'
13
+ Stdlib.instance
14
+ when 'rubygems'
15
+ Rubygems.instance
16
+ else
17
+ raise
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ require 'bundler'
3
+
4
+ require_relative './collection/sources'
5
+ require_relative './collection/config'
6
+ require_relative './collection/config/lockfile_generator'
7
+ require_relative './collection/installer'
8
+ require_relative './collection/cleaner'
9
+
10
+ module RBS
11
+ module Collection
12
+ end
13
+ end
@@ -47,6 +47,18 @@ module RBS
47
47
  end
48
48
  end
49
49
 
50
+ def add_collection(collection_config)
51
+ warn "warning: rbs collection is experimental, and the behavior may change until RBS v2.0"
52
+
53
+ collection_config.check_rbs_availability!
54
+
55
+ repository.add(collection_config.repo_path)
56
+
57
+ collection_config.gems.each do |gem|
58
+ add(library: gem['name'], version: gem['version'])
59
+ end
60
+ end
61
+
50
62
  def has_library?(library:, version:)
51
63
  if self.class.gem_sig_path(library, version) || repository.lookup(library, version)
52
64
  true
data/lib/rbs/errors.rb CHANGED
@@ -383,7 +383,25 @@ module RBS
383
383
  "include"
384
384
  when AST::Members::Extend
385
385
  "extend"
386
+ else
387
+ raise
386
388
  end
387
389
  end
388
390
  end
391
+
392
+ class RecursiveTypeAliasError < LoadingError
393
+ attr_reader :alias_names
394
+ attr_reader :location
395
+
396
+ def initialize(alias_names:, location:)
397
+ @alias_names = alias_names
398
+ @location = location
399
+
400
+ super "#{Location.to_string location}: Recursive type alias definition found for: #{name}"
401
+ end
402
+
403
+ def name
404
+ @alias_names.map(&:name).join(', ')
405
+ end
406
+ end
389
407
  end
data/lib/rbs/parser.rb CHANGED
@@ -326,7 +326,7 @@ def next_token
326
326
  new_token(:tUKEYWORD, input.matched.chop.to_sym)
327
327
  when input.scan(/[A-Z]\w*[?!]:/)
328
328
  new_token(:tUKEYWORD_Q_E, input.matched.chop.to_sym)
329
- when input.scan(/\$[A-Za-z_]\w*/)
329
+ when input.scan(/\$([A-Za-z_]\w*|[~*$?!@\/\\;,.=:<>"&`'+]|\d+|-[0-9_A-Za-z])/)
330
330
  new_token(:tGLOBALIDENT)
331
331
  when input.scan(/@[a-zA-Z_]\w*/)
332
332
  new_token(:tIVAR, input.matched.to_sym)
data/lib/rbs/parser.y CHANGED
@@ -1708,7 +1708,7 @@ def next_token
1708
1708
  new_token(:tUKEYWORD, input.matched.chop.to_sym)
1709
1709
  when input.scan(/[A-Z]\w*[?!]:/)
1710
1710
  new_token(:tUKEYWORD_Q_E, input.matched.chop.to_sym)
1711
- when input.scan(/\$[A-Za-z_]\w*/)
1711
+ when input.scan(/\$([A-Za-z_]\w*|[~*$?!@\/\\;,.=:<>"&`'+]|\d+|-[0-9_A-Za-z])/)
1712
1712
  new_token(:tGLOBALIDENT)
1713
1713
  when input.scan(/@[a-zA-Z_]\w*/)
1714
1714
  new_token(:tIVAR, input.matched.to_sym)