rbs 1.4.0 → 1.6.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +10 -0
  3. data/CHANGELOG.md +52 -0
  4. data/Gemfile +2 -0
  5. data/Steepfile +9 -1
  6. data/core/builtin.rbs +1 -1
  7. data/core/file.rbs +3 -1
  8. data/core/global_variables.rbs +3 -3
  9. data/core/io/wait.rbs +37 -0
  10. data/core/io.rbs +6 -4
  11. data/core/ractor.rbs +779 -0
  12. data/core/string_io.rbs +3 -5
  13. data/docs/collection.md +116 -0
  14. data/lib/rbs/builtin_names.rb +1 -0
  15. data/lib/rbs/cli.rb +93 -2
  16. data/lib/rbs/collection/cleaner.rb +29 -0
  17. data/lib/rbs/collection/config/lockfile_generator.rb +95 -0
  18. data/lib/rbs/collection/config.rb +85 -0
  19. data/lib/rbs/collection/installer.rb +27 -0
  20. data/lib/rbs/collection/sources/git.rb +147 -0
  21. data/lib/rbs/collection/sources/rubygems.rb +40 -0
  22. data/lib/rbs/collection/sources/stdlib.rb +38 -0
  23. data/lib/rbs/collection/sources.rb +22 -0
  24. data/lib/rbs/collection.rb +13 -0
  25. data/lib/rbs/environment_loader.rb +12 -0
  26. data/lib/rbs/errors.rb +2 -0
  27. data/lib/rbs/repository.rb +13 -7
  28. data/lib/rbs/validator.rb +4 -1
  29. data/lib/rbs/version.rb +1 -1
  30. data/lib/rbs.rb +1 -0
  31. data/sig/builtin_names.rbs +1 -0
  32. data/sig/cli.rbs +5 -0
  33. data/sig/collection/cleaner.rbs +13 -0
  34. data/sig/collection/collections.rbs +112 -0
  35. data/sig/collection/config.rbs +69 -0
  36. data/sig/collection/installer.rbs +15 -0
  37. data/sig/collection.rbs +4 -0
  38. data/sig/environment_loader.rbs +3 -0
  39. data/sig/polyfill.rbs +12 -3
  40. data/sig/repository.rbs +4 -0
  41. data/stdlib/digest/0/digest.rbs +418 -0
  42. data/stdlib/objspace/0/objspace.rbs +406 -0
  43. data/stdlib/openssl/0/openssl.rbs +1 -1
  44. data/stdlib/tempfile/0/tempfile.rbs +270 -0
  45. data/steep/Gemfile.lock +10 -10
  46. metadata +24 -3
@@ -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,6 +383,8 @@ 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
@@ -55,13 +55,8 @@ module RBS
55
55
  end
56
56
 
57
57
  def find_best_version(version)
58
- return latest_version unless version
59
-
60
- if v = version_names.reverse.bsearch {|v| v <= version ? true : false }
61
- versions[v]
62
- else
63
- oldest_version
64
- end
58
+ best_version = Repository.find_best_version(version, version_names)
59
+ versions[best_version]
65
60
  end
66
61
 
67
62
  def empty?
@@ -89,6 +84,17 @@ module RBS
89
84
  end
90
85
  end
91
86
 
87
+ def self.find_best_version(version, candidates)
88
+ candidates = candidates.sort
89
+ return candidates.last || raise unless version
90
+
91
+ if v = candidates.reverse.bsearch {|v| v <= version ? true : false }
92
+ v
93
+ else
94
+ candidates.first or raise
95
+ end
96
+ end
97
+
92
98
  def add(dir)
93
99
  dirs << dir
94
100
 
data/lib/rbs/validator.rb CHANGED
@@ -56,7 +56,10 @@ module RBS
56
56
 
57
57
  def validate_type_alias(entry:)
58
58
  @type_alias_dependency ||= TypeAliasDependency.new(env: env)
59
- raise RecursiveTypeAliasError.new(alias_names: [entry.decl.name], location: entry.decl.location) if @type_alias_dependency.circular_definition?(entry.decl.name)
59
+ if @type_alias_dependency.circular_definition?(entry.decl.name)
60
+ location = entry.decl.location or raise
61
+ raise RecursiveTypeAliasError.new(alias_names: [entry.decl.name], location: location)
62
+ end
60
63
  end
61
64
  end
62
65
  end
data/lib/rbs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RBS
2
- VERSION = "1.4.0"
2
+ VERSION = "1.6.1"
3
3
  end
data/lib/rbs.rb CHANGED
@@ -45,6 +45,7 @@ require "rbs/repository"
45
45
  require "rbs/ancestor_graph"
46
46
  require "rbs/locator"
47
47
  require "rbs/type_alias_dependency"
48
+ require "rbs/collection"
48
49
 
49
50
  begin
50
51
  require "rbs/parser"
@@ -37,5 +37,6 @@ module RBS
37
37
  Regexp: Name
38
38
  TrueClass: Name
39
39
  FalseClass: Name
40
+ Numeric: Name
40
41
  end
41
42
  end
data/sig/cli.rbs CHANGED
@@ -2,6 +2,7 @@ module RBS
2
2
  class CLI
3
3
  class LibraryOptions
4
4
  attr_accessor core_root: Pathname?
5
+ attr_accessor config_path: Pathname?
5
6
 
6
7
  attr_reader libs: Array[String]
7
8
  attr_reader dirs: Array[String]
@@ -63,6 +64,10 @@ module RBS
63
64
 
64
65
  def run_test: (Array[String], LibraryOptions) -> void
65
66
 
67
+ def run_collection: (Array[String], LibraryOptions) -> void
68
+
66
69
  def test_opt: (LibraryOptions) -> String?
70
+
71
+ def collection_options: (Array[String]) -> OptionParser
67
72
  end
68
73
  end
@@ -0,0 +1,13 @@
1
+ module RBS
2
+ module Collection
3
+ class Cleaner
4
+ attr_reader lock: Config
5
+
6
+ def initialize: (lockfile_path: Pathname) -> void
7
+
8
+ def clean: () -> void
9
+
10
+ def needed?: (String gem_name, String version) -> bool
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,112 @@
1
+ module RBS
2
+ module Collection
3
+ module Sources
4
+ def self.from_config_entry: (source_entry) -> _Source
5
+
6
+ interface _Source
7
+ def has?: (Config::gem_entry) -> boolish
8
+ def versions: (Config::gem_entry) -> Array[String]
9
+ def install: (dest: Pathname, config_entry: Config::gem_entry, stdout: CLI::_IO) -> void
10
+ def to_lockfile: () -> source_entry
11
+ end
12
+
13
+ type source_entry = Git::source_entry
14
+ | Stdlib::source_entry
15
+ | Rubygems::source_entry
16
+
17
+ class Git
18
+ METADATA_FILENAME: String
19
+
20
+ type source_entry = {
21
+ 'type' => 'git',
22
+ 'name' => String,
23
+ 'remote' => String,
24
+ 'revision' => String,
25
+ 'repo_dir' => String?,
26
+ }
27
+
28
+ class CommandError < StandardError
29
+ end
30
+
31
+ attr_reader name: String
32
+ attr_reader remote: String
33
+ attr_reader repo_dir: String
34
+
35
+ def initialize: (name: String, revision: String, remote: String, repo_dir: String?) -> untyped
36
+
37
+ def has?: (Config::gem_entry) -> bool
38
+
39
+ def versions: (Config::gem_entry) -> Array[String]
40
+
41
+ def install: (dest: Pathname, config_entry: Config::gem_entry, stdout: CLI::_IO) -> void
42
+
43
+ def to_lockfile: () -> source_entry
44
+
45
+ private
46
+
47
+ def _install: (dest: Pathname , config_entry: Config::gem_entry) -> void
48
+
49
+ def setup!: (revision: String) -> void
50
+
51
+ def need_to_fetch?: (String revision ) -> bool
52
+
53
+ def git_dir: () -> Pathname
54
+
55
+ def gem_repo_dir: () -> Pathname
56
+
57
+ def with_revision: [T] () { () -> T } -> T
58
+
59
+ def resolved_revision: () -> String
60
+
61
+ def resolve_revision: () -> String
62
+
63
+ def git: (*String cmd) -> String
64
+
65
+ def sh!: (*String cmd) -> String
66
+
67
+ def format_config_entry: (Config::gem_entry) -> String
68
+ end
69
+
70
+ # signatures that are bundled in rbs gem under the stdlib/ directory
71
+ class Stdlib
72
+ type source_entry = {
73
+ 'type' => 'stdlib',
74
+ }
75
+
76
+ # polyfill of singleton module
77
+ def self.instance: () -> instance
78
+
79
+ def has?: (Config::gem_entry) -> bool
80
+
81
+ def versions: (Config::gem_entry) -> Array[String]
82
+
83
+ def install: (dest: Pathname, config_entry: Config::gem_entry, stdout: CLI::_IO) -> void
84
+
85
+ def to_lockfile: () -> source_entry
86
+
87
+ private
88
+
89
+ def gem_dir: (Config::gem_entry) -> Pathname
90
+ end
91
+
92
+ # sig/ directory
93
+ class Rubygems
94
+ type source_entry = {
95
+ 'type' => 'rubygems',
96
+ }
97
+
98
+ # polyfill of singleton module
99
+ def self.instance: () -> instance
100
+
101
+ def has?: (Config::gem_entry) -> boolish
102
+ def versions: (Config::gem_entry) -> Array[String]
103
+ def install: (dest: Pathname, config_entry: Config::gem_entry, stdout: CLI::_IO) -> void
104
+ def to_lockfile: () -> source_entry
105
+
106
+ private
107
+
108
+ def gem_sig_path: (Config::gem_entry) -> [Gem::Specification, Pathname]?
109
+ end
110
+ end
111
+ end
112
+ end