tapioca 0.7.3 → 0.8.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +491 -73
- data/lib/tapioca/cli.rb +40 -3
- data/lib/tapioca/commands/annotations.rb +154 -0
- data/lib/tapioca/commands/dsl.rb +20 -1
- data/lib/tapioca/commands/gem.rb +17 -57
- data/lib/tapioca/commands/init.rb +1 -0
- data/lib/tapioca/commands.rb +1 -0
- data/lib/tapioca/dsl/compilers/protobuf.rb +14 -0
- data/lib/tapioca/dsl/pipeline.rb +4 -0
- data/lib/tapioca/dsl.rb +6 -0
- data/lib/tapioca/executor.rb +4 -46
- data/lib/tapioca/gem/listeners/methods.rb +26 -1
- data/lib/tapioca/gem/listeners/sorbet_props.rb +1 -1
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +1 -0
- data/lib/tapioca/gem/pipeline.rb +4 -0
- data/lib/tapioca/gemfile.rb +50 -3
- data/lib/tapioca/helpers/config_helper.rb +13 -0
- data/lib/tapioca/helpers/rbi_helper.rb +124 -0
- data/lib/tapioca/helpers/shims_helper.rb +36 -8
- data/lib/tapioca/helpers/sorbet_helper.rb +3 -9
- data/lib/tapioca/helpers/test/content.rb +1 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -0
- data/lib/tapioca/helpers/test/template.rb +1 -0
- data/lib/tapioca/internal.rb +2 -0
- data/lib/tapioca/rbi_ext/model.rb +2 -0
- data/lib/tapioca/repo_index.rb +41 -0
- data/lib/tapioca/runtime/loader.rb +3 -0
- data/lib/tapioca/runtime/reflection.rb +12 -12
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -46
- data/lib/tapioca/static/symbol_table_parser.rb +2 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +5 -0
- metadata +22 -18
data/lib/tapioca/gemfile.rb
CHANGED
@@ -16,6 +16,50 @@ module Tapioca
|
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
|
+
# This is a module that gets prepended to `Bundler::Dependency` and
|
20
|
+
# makes sure even gems marked as `require: false` are required during
|
21
|
+
# `Bundler.require`.
|
22
|
+
module AutoRequireHook
|
23
|
+
extend T::Sig
|
24
|
+
extend T::Helpers
|
25
|
+
|
26
|
+
requires_ancestor { ::Bundler::Dependency }
|
27
|
+
|
28
|
+
@exclude = T.let([], T::Array[String])
|
29
|
+
|
30
|
+
class << self
|
31
|
+
extend T::Sig
|
32
|
+
|
33
|
+
sig { params(exclude: T::Array[String]).returns(T::Array[String]) }
|
34
|
+
attr_writer :exclude
|
35
|
+
|
36
|
+
sig { params(name: T.untyped).returns(T::Boolean) }
|
37
|
+
def excluded?(name)
|
38
|
+
@exclude.include?(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(T.untyped).checked(:never) }
|
43
|
+
def autorequire
|
44
|
+
value = super
|
45
|
+
|
46
|
+
# If the gem is excluded, we don't want to force require it, in case
|
47
|
+
# it has side-effects users don't want. For example, `fakefs` gem, if
|
48
|
+
# loaded, takes over filesystem operations.
|
49
|
+
return value if AutoRequireHook.excluded?(name)
|
50
|
+
|
51
|
+
# If a gem is marked as `require: false`, then its `autorequire`
|
52
|
+
# value will be `[]`. But, we want those gems to be loaded for our
|
53
|
+
# purposes as well, so we return `nil` in those cases, instead, which
|
54
|
+
# means `require: true`.
|
55
|
+
return nil if value == []
|
56
|
+
|
57
|
+
value
|
58
|
+
end
|
59
|
+
|
60
|
+
::Bundler::Dependency.prepend(self)
|
61
|
+
end
|
62
|
+
|
19
63
|
sig { returns(Bundler::Definition) }
|
20
64
|
attr_reader(:definition)
|
21
65
|
|
@@ -25,8 +69,9 @@ module Tapioca
|
|
25
69
|
sig { returns(T::Array[String]) }
|
26
70
|
attr_reader(:missing_specs)
|
27
71
|
|
28
|
-
sig { void }
|
29
|
-
def initialize
|
72
|
+
sig { params(exclude: T::Array[String]).void }
|
73
|
+
def initialize(exclude)
|
74
|
+
AutoRequireHook.exclude = exclude
|
30
75
|
@gemfile = T.let(File.new(Bundler.default_gemfile), File)
|
31
76
|
@lockfile = T.let(File.new(Bundler.default_lockfile), File)
|
32
77
|
@definition = T.let(Bundler::Dsl.evaluate(gemfile, lockfile, {}), Bundler::Definition)
|
@@ -94,7 +139,8 @@ module Tapioca
|
|
94
139
|
class GemSpec
|
95
140
|
extend(T::Sig)
|
96
141
|
|
97
|
-
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime"].freeze,
|
142
|
+
IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime", "sorbet-static-and-runtime"].freeze,
|
143
|
+
T::Array[String])
|
98
144
|
|
99
145
|
sig { returns(String) }
|
100
146
|
attr_reader :full_gem_path, :version
|
@@ -227,6 +273,7 @@ module Tapioca
|
|
227
273
|
# one of those folders to see if the path really belongs in the given gem
|
228
274
|
# or not.
|
229
275
|
return false unless Bundler::Source::Git === @spec.source
|
276
|
+
|
230
277
|
parent = Pathname.new(path)
|
231
278
|
|
232
279
|
until parent.root?
|
@@ -72,6 +72,7 @@ module Tapioca
|
|
72
72
|
def validate_config!(config_file, config)
|
73
73
|
# To ensure that this is not re-entered, we mark during validation
|
74
74
|
return if @validating_config
|
75
|
+
|
75
76
|
@validating_config = T.let(true, T.nilable(T::Boolean))
|
76
77
|
|
77
78
|
commands = T.cast(self, Thor).class.commands
|
@@ -125,6 +126,18 @@ module Tapioca
|
|
125
126
|
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
126
127
|
"`#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}"
|
127
128
|
next build_error(error_msg) unless config_option_value_type == command_option.type
|
129
|
+
|
130
|
+
case config_option_value_type
|
131
|
+
when :array
|
132
|
+
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
133
|
+
"`Array[String]` but found `#{config_option_value}`"
|
134
|
+
next build_error(error_msg) unless config_option_value.all? { |v| v.is_a?(String) }
|
135
|
+
when :hash
|
136
|
+
error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
|
137
|
+
"`Hash[String, String]` but found `#{config_option_value}`"
|
138
|
+
all_strings = (config_option_value.keys + config_option_value.values).all? { |v| v.is_a?(String) }
|
139
|
+
next build_error(error_msg) unless all_strings
|
140
|
+
end
|
128
141
|
end.compact
|
129
142
|
end
|
130
143
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module RBIHelper
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
requires_ancestor { Thor::Shell }
|
10
|
+
requires_ancestor { SorbetHelper }
|
11
|
+
|
12
|
+
sig do
|
13
|
+
params(
|
14
|
+
command: String,
|
15
|
+
gem_dir: String,
|
16
|
+
dsl_dir: String,
|
17
|
+
auto_strictness: T::Boolean,
|
18
|
+
gems: T::Array[Gemfile::GemSpec],
|
19
|
+
compilers: T::Enumerable[Class]
|
20
|
+
).void
|
21
|
+
end
|
22
|
+
def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
|
23
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
24
|
+
|
25
|
+
say("Checking generated RBI files... ")
|
26
|
+
res = sorbet(
|
27
|
+
"--no-config",
|
28
|
+
"--error-url-base=#{error_url_base}",
|
29
|
+
"--stop-after namer",
|
30
|
+
dsl_dir,
|
31
|
+
gem_dir
|
32
|
+
)
|
33
|
+
say(" Done", :green)
|
34
|
+
|
35
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
|
36
|
+
|
37
|
+
if errors.empty?
|
38
|
+
say(" No errors found\n\n", [:green, :bold])
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
parse_errors = errors.select { |error| error.code < 4000 }
|
43
|
+
|
44
|
+
if parse_errors.any?
|
45
|
+
say_error(<<~ERR, :red)
|
46
|
+
|
47
|
+
##### INTERNAL ERROR #####
|
48
|
+
|
49
|
+
There are parse errors in the generated RBI files.
|
50
|
+
|
51
|
+
This seems related to a bug in Tapioca.
|
52
|
+
Please open an issue at https://github.com/Shopify/tapioca/issues/new with the following information:
|
53
|
+
|
54
|
+
Tapioca v#{Tapioca::VERSION}
|
55
|
+
|
56
|
+
Command:
|
57
|
+
#{command}
|
58
|
+
|
59
|
+
ERR
|
60
|
+
|
61
|
+
say_error(<<~ERR, :red) if gems.any?
|
62
|
+
Gems:
|
63
|
+
#{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
|
64
|
+
|
65
|
+
ERR
|
66
|
+
|
67
|
+
say_error(<<~ERR, :red) if compilers.any?
|
68
|
+
Compilers:
|
69
|
+
#{compilers.map { |compiler| " #{compiler.name}" }.join("\n")}
|
70
|
+
|
71
|
+
ERR
|
72
|
+
|
73
|
+
say_error(<<~ERR, :red)
|
74
|
+
Errors:
|
75
|
+
#{parse_errors.map { |error| " #{error}" }.join("\n")}
|
76
|
+
|
77
|
+
##########################
|
78
|
+
|
79
|
+
ERR
|
80
|
+
end
|
81
|
+
|
82
|
+
if auto_strictness
|
83
|
+
redef_errors = errors.select { |error| error.code == 4010 }
|
84
|
+
update_gem_rbis_strictnesses(redef_errors, gem_dir)
|
85
|
+
end
|
86
|
+
|
87
|
+
Kernel.exit(1) if parse_errors.any?
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
sig { params(errors: T::Array[Spoom::Sorbet::Errors::Error], gem_dir: String).void }
|
93
|
+
def update_gem_rbis_strictnesses(errors, gem_dir)
|
94
|
+
files = []
|
95
|
+
|
96
|
+
errors.each do |error|
|
97
|
+
# Collect the file with error
|
98
|
+
files << error.file
|
99
|
+
error.more.each do |line|
|
100
|
+
# Also collect the conflicting definition file paths
|
101
|
+
next unless line.include?("Previous definition")
|
102
|
+
|
103
|
+
files << line.split(":").first&.strip
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
files
|
108
|
+
.uniq
|
109
|
+
.sort
|
110
|
+
.select { |file| file.start_with?(gem_dir) }
|
111
|
+
.each do |file|
|
112
|
+
Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
|
113
|
+
say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
|
114
|
+
end
|
115
|
+
|
116
|
+
say("\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { params(path: String).returns(String) }
|
120
|
+
def gem_name_from_rbi_path(path)
|
121
|
+
T.must(File.basename(path, ".rbi").split("@").first)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -8,20 +8,25 @@ module Tapioca
|
|
8
8
|
|
9
9
|
requires_ancestor { Thor::Shell }
|
10
10
|
|
11
|
+
SORBET_PAYLOAD_URL = "https://github.com/sorbet/sorbet/tree/master/rbi"
|
12
|
+
|
13
|
+
sig { params(index: RBI::Index, dir: String).void }
|
14
|
+
def index_payload(index, dir)
|
15
|
+
return unless Dir.exist?(dir)
|
16
|
+
|
17
|
+
say("Loading Sorbet payload... ")
|
18
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
19
|
+
parse_and_index_files(index, files)
|
20
|
+
say(" Done", :green)
|
21
|
+
end
|
22
|
+
|
11
23
|
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
12
24
|
def index_rbis(index, kind, dir)
|
13
25
|
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
14
26
|
|
15
27
|
say("Loading #{kind} RBIs from #{dir}... ")
|
16
28
|
files = Dir.glob("#{dir}/**/*.rbi").sort
|
17
|
-
|
18
|
-
trees = files.map do |file|
|
19
|
-
RBI::Parser.parse_file(file)
|
20
|
-
rescue RBI::ParseError => e
|
21
|
-
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
22
|
-
end.compact
|
23
|
-
|
24
|
-
index.visit_all(trees)
|
29
|
+
parse_and_index_files(index, files)
|
25
30
|
say(" Done", :green)
|
26
31
|
end
|
27
32
|
|
@@ -32,14 +37,37 @@ module Tapioca
|
|
32
37
|
index.keys.each do |key|
|
33
38
|
nodes = index[key]
|
34
39
|
next unless shims_have_duplicates?(nodes, shim_rbi_dir)
|
40
|
+
|
35
41
|
duplicates[key] = nodes
|
36
42
|
end
|
37
43
|
say(" Done", :green)
|
38
44
|
duplicates
|
39
45
|
end
|
40
46
|
|
47
|
+
sig { params(loc: RBI::Loc, path_prefix: T.nilable(String)).returns(String) }
|
48
|
+
def location_to_payload_url(loc, path_prefix:)
|
49
|
+
return loc.to_s unless path_prefix
|
50
|
+
|
51
|
+
url = loc.file || ""
|
52
|
+
return loc.to_s unless url.start_with?(path_prefix)
|
53
|
+
|
54
|
+
url = url.sub(path_prefix, SORBET_PAYLOAD_URL)
|
55
|
+
url = "#{url}#L#{loc.begin_line}"
|
56
|
+
url
|
57
|
+
end
|
58
|
+
|
41
59
|
private
|
42
60
|
|
61
|
+
sig { params(index: RBI::Index, files: T::Array[String]).void }
|
62
|
+
def parse_and_index_files(index, files)
|
63
|
+
trees = files.map do |file|
|
64
|
+
RBI::Parser.parse_file(file)
|
65
|
+
rescue RBI::ParseError => e
|
66
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
67
|
+
end.compact
|
68
|
+
index.visit_all(trees)
|
69
|
+
end
|
70
|
+
|
43
71
|
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Boolean) }
|
44
72
|
def shims_have_duplicates?(nodes, shim_rbi_dir)
|
45
73
|
return false if nodes.size == 1
|
@@ -22,19 +22,13 @@ module Tapioca
|
|
22
22
|
|
23
23
|
FEATURE_REQUIREMENTS = T.let({
|
24
24
|
to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"), # https://github.com/sorbet/sorbet/pull/4706
|
25
|
+
print_payload_sources: ::Gem::Requirement.new(">= 0.5.9818"), # https://github.com/sorbet/sorbet/pull/5504
|
25
26
|
type_variable_block_syntax: ::Gem::Requirement.new(">= 0.5.9892"), # https://github.com/sorbet/sorbet/pull/5639
|
26
27
|
}.freeze, T::Hash[Symbol, ::Gem::Requirement])
|
27
28
|
|
28
|
-
|
29
|
-
const :out, String
|
30
|
-
const :err, String
|
31
|
-
const :status, T::Boolean
|
32
|
-
end
|
33
|
-
|
34
|
-
sig { params(sorbet_args: String).returns(CmdResult) }
|
29
|
+
sig { params(sorbet_args: String).returns(Spoom::ExecResult) }
|
35
30
|
def sorbet(*sorbet_args)
|
36
|
-
|
37
|
-
CmdResult.new(out: out, err: err, status: status.success? || false)
|
31
|
+
Spoom::Sorbet.srb(sorbet_args.join(" "), sorbet_bin: sorbet_path, capture_err: true)
|
38
32
|
end
|
39
33
|
|
40
34
|
sig { returns(String) }
|
@@ -41,6 +41,7 @@ module Tapioca
|
|
41
41
|
def add_content_file(name, content)
|
42
42
|
file_name = tmp_path("lib/#{name}")
|
43
43
|
raise ArgumentError, "a file named '#{name}' was already added; cannot overwrite." if File.exist?(file_name)
|
44
|
+
|
44
45
|
FileUtils.mkdir_p(File.dirname(file_name))
|
45
46
|
File.write(file_name, content)
|
46
47
|
file_name
|
data/lib/tapioca/internal.rb
CHANGED
@@ -14,7 +14,9 @@ require "tapioca/runtime/generic_type_registry"
|
|
14
14
|
require "tapioca/helpers/cli_helper"
|
15
15
|
require "tapioca/helpers/config_helper"
|
16
16
|
require "tapioca/helpers/signatures_helper"
|
17
|
+
require "tapioca/helpers/rbi_helper"
|
17
18
|
require "tapioca/helpers/shims_helper"
|
19
|
+
require "tapioca/repo_index"
|
18
20
|
require "tapioca/commands"
|
19
21
|
require "tapioca/cli"
|
20
22
|
require "tapioca/gemfile"
|
@@ -108,6 +108,7 @@ module RBI
|
|
108
108
|
sig { params(name: String).returns(T::Boolean) }
|
109
109
|
def valid_method_name?(name)
|
110
110
|
return true if SPECIAL_METHOD_NAMES.include?(name)
|
111
|
+
|
111
112
|
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
112
113
|
end
|
113
114
|
|
@@ -120,6 +121,7 @@ module RBI
|
|
120
121
|
def create_node(node)
|
121
122
|
cached = nodes_cache[node.to_s]
|
122
123
|
return cached if cached
|
124
|
+
|
123
125
|
nodes_cache[node.to_s] = node
|
124
126
|
self << node
|
125
127
|
node
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
class RepoIndex
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Generic
|
8
|
+
|
9
|
+
sig { params(json: String).returns(RepoIndex) }
|
10
|
+
def self.from_json(json)
|
11
|
+
RepoIndex.from_hash(JSON.parse(json))
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { params(hash: T::Hash[String, T::Hash[T.untyped, T.untyped]]).returns(RepoIndex) }
|
15
|
+
def self.from_hash(hash)
|
16
|
+
hash.each_with_object(RepoIndex.new) do |(name, _), index|
|
17
|
+
index << name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { void }
|
22
|
+
def initialize
|
23
|
+
@entries = T.let(Set.new, T::Set[String])
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(gem_name: String).void }
|
27
|
+
def <<(gem_name)
|
28
|
+
@entries.add(gem_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(T::Enumerable[String]) }
|
32
|
+
def gems
|
33
|
+
@entries.sort
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(gem_name: String).returns(T::Boolean) }
|
37
|
+
def has_gem?(gem_name)
|
38
|
+
@entries.include?(gem_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -41,6 +41,7 @@ module Tapioca
|
|
41
41
|
sig { params(file: T.nilable(String)).void }
|
42
42
|
def require_helper(file)
|
43
43
|
return unless file
|
44
|
+
|
44
45
|
file = File.absolute_path(file)
|
45
46
|
return unless File.exist?(file)
|
46
47
|
|
@@ -51,6 +52,8 @@ module Tapioca
|
|
51
52
|
def rails_engines
|
52
53
|
return [] unless Object.const_defined?("Rails::Engine")
|
53
54
|
|
55
|
+
safe_require("active_support/core_ext/class/subclasses")
|
56
|
+
|
54
57
|
# We can use `Class#descendants` here, since we know Rails is loaded
|
55
58
|
Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
|
56
59
|
end
|
@@ -35,58 +35,58 @@ module Tapioca
|
|
35
35
|
|
36
36
|
sig { params(object: BasicObject).returns(Class).checked(:never) }
|
37
37
|
def class_of(object)
|
38
|
-
CLASS_METHOD.
|
38
|
+
CLASS_METHOD.bind_call(object)
|
39
39
|
end
|
40
40
|
|
41
41
|
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
42
42
|
def constants_of(constant)
|
43
|
-
CONSTANTS_METHOD.
|
43
|
+
CONSTANTS_METHOD.bind_call(constant, false)
|
44
44
|
end
|
45
45
|
|
46
46
|
sig { params(constant: Module).returns(T.nilable(String)) }
|
47
47
|
def name_of(constant)
|
48
|
-
name = NAME_METHOD.
|
48
|
+
name = NAME_METHOD.bind_call(constant)
|
49
49
|
name&.start_with?("#<") ? nil : name
|
50
50
|
end
|
51
51
|
|
52
52
|
sig { params(constant: Module).returns(Class) }
|
53
53
|
def singleton_class_of(constant)
|
54
|
-
SINGLETON_CLASS_METHOD.
|
54
|
+
SINGLETON_CLASS_METHOD.bind_call(constant)
|
55
55
|
end
|
56
56
|
|
57
57
|
sig { params(constant: Module).returns(T::Array[Module]) }
|
58
58
|
def ancestors_of(constant)
|
59
|
-
ANCESTORS_METHOD.
|
59
|
+
ANCESTORS_METHOD.bind_call(constant)
|
60
60
|
end
|
61
61
|
|
62
62
|
sig { params(constant: Class).returns(T.nilable(Class)) }
|
63
63
|
def superclass_of(constant)
|
64
|
-
SUPERCLASS_METHOD.
|
64
|
+
SUPERCLASS_METHOD.bind_call(constant)
|
65
65
|
end
|
66
66
|
|
67
67
|
sig { params(object: BasicObject).returns(Integer).checked(:never) }
|
68
68
|
def object_id_of(object)
|
69
|
-
OBJECT_ID_METHOD.
|
69
|
+
OBJECT_ID_METHOD.bind_call(object)
|
70
70
|
end
|
71
71
|
|
72
72
|
sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
|
73
73
|
def are_equal?(object, other)
|
74
|
-
EQUAL_METHOD.
|
74
|
+
EQUAL_METHOD.bind_call(object, other)
|
75
75
|
end
|
76
76
|
|
77
77
|
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
78
78
|
def public_instance_methods_of(constant)
|
79
|
-
PUBLIC_INSTANCE_METHODS_METHOD.
|
79
|
+
PUBLIC_INSTANCE_METHODS_METHOD.bind_call(constant)
|
80
80
|
end
|
81
81
|
|
82
82
|
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
83
83
|
def protected_instance_methods_of(constant)
|
84
|
-
PROTECTED_INSTANCE_METHODS_METHOD.
|
84
|
+
PROTECTED_INSTANCE_METHODS_METHOD.bind_call(constant)
|
85
85
|
end
|
86
86
|
|
87
87
|
sig { params(constant: Module).returns(T::Array[Symbol]) }
|
88
88
|
def private_instance_methods_of(constant)
|
89
|
-
PRIVATE_INSTANCE_METHODS_METHOD.
|
89
|
+
PRIVATE_INSTANCE_METHODS_METHOD.bind_call(constant)
|
90
90
|
end
|
91
91
|
|
92
92
|
sig { params(constant: Module).returns(T::Array[Module]) }
|
@@ -124,7 +124,7 @@ module Tapioca
|
|
124
124
|
|
125
125
|
sig { params(constant: Module, method: Symbol).returns(Method) }
|
126
126
|
def method_of(constant, method)
|
127
|
-
METHOD_METHOD.
|
127
|
+
METHOD_METHOD.bind_call(constant, method)
|
128
128
|
end
|
129
129
|
|
130
130
|
# Returns an array with all classes that are < than the supplied class.
|
@@ -21,33 +21,47 @@ module T
|
|
21
21
|
Tapioca::Runtime::GenericTypeRegistry.register_type(constant, types)
|
22
22
|
end
|
23
23
|
|
24
|
-
def type_member(variance = :invariant, fixed: nil, lower: nil, upper: nil, &
|
24
|
+
def type_member(variance = :invariant, fixed: nil, lower: nil, upper: nil, &blk)
|
25
25
|
# `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
|
26
26
|
# We use that when registering the type member and then later return it from this method.
|
27
|
+
hash = if blk
|
28
|
+
blk.call
|
29
|
+
else
|
30
|
+
{
|
31
|
+
fixed: fixed,
|
32
|
+
lower: lower,
|
33
|
+
upper: upper,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
27
37
|
Tapioca::TypeVariableModule.new(
|
28
38
|
T.cast(self, Module),
|
29
39
|
Tapioca::TypeVariableModule::Type::Member,
|
30
40
|
variance,
|
31
|
-
|
32
|
-
lower,
|
33
|
-
upper,
|
34
|
-
bounds_proc
|
41
|
+
**hash,
|
35
42
|
).tap do |type_variable|
|
36
43
|
Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
40
|
-
def type_template(variance = :invariant, fixed: nil, lower: nil, upper: nil, &
|
47
|
+
def type_template(variance = :invariant, fixed: nil, lower: nil, upper: nil, &blk)
|
41
48
|
# `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
|
42
49
|
# We use that when registering the type template and then later return it from this method.
|
50
|
+
hash = if blk
|
51
|
+
blk.call
|
52
|
+
else
|
53
|
+
{
|
54
|
+
fixed: fixed,
|
55
|
+
lower: lower,
|
56
|
+
upper: upper,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
43
60
|
Tapioca::TypeVariableModule.new(
|
44
61
|
T.cast(self, Module),
|
45
62
|
Tapioca::TypeVariableModule::Type::Template,
|
46
63
|
variance,
|
47
|
-
|
48
|
-
lower,
|
49
|
-
upper,
|
50
|
-
bounds_proc
|
64
|
+
**hash,
|
51
65
|
).tap do |type_variable|
|
52
66
|
Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
|
53
67
|
end
|
@@ -122,38 +136,25 @@ module Tapioca
|
|
122
136
|
end
|
123
137
|
end
|
124
138
|
|
125
|
-
# rubocop:disable Metrics/ParameterLists
|
126
139
|
sig do
|
127
|
-
params(
|
128
|
-
context: Module,
|
129
|
-
type: Type,
|
130
|
-
variance: Symbol,
|
131
|
-
fixed: T.untyped,
|
132
|
-
lower: T.untyped,
|
133
|
-
upper: T.untyped,
|
134
|
-
bounds_proc: T.nilable(T.proc.returns(T::Hash[Symbol, T.untyped]))
|
135
|
-
).void
|
140
|
+
params(context: Module, type: Type, variance: Symbol, fixed: T.untyped, lower: T.untyped, upper: T.untyped).void
|
136
141
|
end
|
137
|
-
def initialize(context, type, variance, fixed, lower, upper
|
142
|
+
def initialize(context, type, variance, fixed: nil, lower: nil, upper: nil)
|
138
143
|
@context = context
|
139
144
|
@type = type
|
140
145
|
@variance = variance
|
141
|
-
@
|
142
|
-
|
143
|
-
|
144
|
-
build_bounds_proc(fixed, lower, upper)
|
145
|
-
end
|
146
|
-
|
146
|
+
@fixed = fixed
|
147
|
+
@lower = lower
|
148
|
+
@upper = upper
|
147
149
|
super()
|
148
150
|
end
|
149
|
-
# rubocop:enable Metrics/ParameterLists
|
150
151
|
|
151
152
|
sig { returns(T.nilable(String)) }
|
152
153
|
def name
|
153
154
|
constant_name = super
|
154
155
|
|
155
156
|
# This is a hack to work around modules under anonymous modules not having
|
156
|
-
# names in 2.
|
157
|
+
# names in 2.7: https://bugs.ruby-lang.org/issues/14895
|
157
158
|
#
|
158
159
|
# This happens when a type variable is declared under `class << self`, for
|
159
160
|
# example.
|
@@ -169,10 +170,9 @@ module Tapioca
|
|
169
170
|
|
170
171
|
sig { returns(String) }
|
171
172
|
def serialize
|
172
|
-
|
173
|
-
|
174
|
-
lower =
|
175
|
-
upper = bounds[:upper].to_s if bounds.key?(:upper)
|
173
|
+
fixed = @fixed.to_s if @fixed
|
174
|
+
upper = @upper.to_s if @upper
|
175
|
+
lower = @lower.to_s if @lower
|
176
176
|
|
177
177
|
TypeVariableHelper.serialize_type_variable(
|
178
178
|
@type.serialize,
|
@@ -190,19 +190,6 @@ module Tapioca
|
|
190
190
|
|
191
191
|
private
|
192
192
|
|
193
|
-
sig do
|
194
|
-
params(fixed: T.untyped, lower: T.untyped, upper: T.untyped)
|
195
|
-
.returns(T.proc.returns(T::Hash[Symbol, T.untyped]))
|
196
|
-
end
|
197
|
-
def build_bounds_proc(fixed, lower, upper)
|
198
|
-
bounds = {}
|
199
|
-
bounds[:fixed] = fixed unless fixed.nil?
|
200
|
-
bounds[:lower] = lower unless lower.nil?
|
201
|
-
bounds[:upper] = upper unless upper.nil?
|
202
|
-
|
203
|
-
-> { bounds }
|
204
|
-
end
|
205
|
-
|
206
193
|
sig do
|
207
194
|
type_parameters(:Result)
|
208
195
|
.params(block: T.proc.returns(T.type_parameter(:Result)))
|
data/lib/tapioca/version.rb
CHANGED