rbs 1.4.0 → 1.6.1

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