tapioca 0.8.3 → 0.9.2
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 +5 -2
- data/README.md +188 -36
- data/lib/tapioca/cli.rb +130 -66
- data/lib/tapioca/commands/annotations.rb +167 -34
- data/lib/tapioca/commands/check_shims.rb +101 -0
- data/lib/tapioca/commands/{init.rb → configure.rb} +1 -1
- data/lib/tapioca/commands/dsl.rb +1 -1
- data/lib/tapioca/commands/gem.rb +15 -10
- data/lib/tapioca/commands.rb +2 -1
- data/lib/tapioca/dsl/compiler.rb +1 -13
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +17 -0
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +5 -4
- data/lib/tapioca/dsl/compilers/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/compilers/protobuf.rb +6 -0
- data/lib/tapioca/dsl/compilers.rb +0 -4
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +21 -3
- data/lib/tapioca/dsl/pipeline.rb +0 -2
- data/lib/tapioca/dsl.rb +8 -0
- data/lib/tapioca/executor.rb +0 -3
- data/lib/tapioca/gem/events.rb +22 -3
- data/lib/tapioca/gem/listeners/base.rb +11 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +5 -0
- data/lib/tapioca/gem/listeners/foreign_constants.rb +65 -0
- data/lib/tapioca/gem/listeners/methods.rb +7 -18
- data/lib/tapioca/gem/listeners/mixins.rb +31 -10
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +5 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +6 -1
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +5 -0
- data/lib/tapioca/gem/listeners/source_location.rb +67 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +5 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +5 -0
- data/lib/tapioca/gem/listeners.rb +2 -0
- data/lib/tapioca/gem/pipeline.rb +64 -19
- data/lib/tapioca/gem.rb +6 -0
- data/lib/tapioca/gemfile.rb +7 -6
- data/lib/tapioca/helpers/cli_helper.rb +8 -2
- data/lib/tapioca/helpers/config_helper.rb +0 -2
- data/lib/tapioca/helpers/env_helper.rb +16 -0
- data/lib/tapioca/helpers/rbi_files_helper.rb +255 -0
- data/lib/tapioca/helpers/rbi_helper.rb +98 -94
- data/lib/tapioca/helpers/sorbet_helper.rb +2 -3
- data/lib/tapioca/helpers/test/content.rb +0 -2
- data/lib/tapioca/helpers/test/template.rb +0 -2
- data/lib/tapioca/internal.rb +36 -12
- data/lib/tapioca/rbi_ext/model.rb +2 -15
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +18 -16
- data/lib/tapioca/runtime/reflection.rb +26 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -16
- data/lib/tapioca/runtime/trackers/mixin.rb +49 -14
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +15 -5
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +40 -0
- data/lib/tapioca/static/requires_compiler.rb +0 -2
- data/lib/tapioca/static/symbol_loader.rb +26 -30
- data/lib/tapioca/static/symbol_table_parser.rb +0 -3
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +3 -0
- metadata +24 -7
- data/lib/tapioca/dsl/helpers/param_helper.rb +0 -55
- data/lib/tapioca/helpers/shims_helper.rb +0 -115
- data/lib/tapioca/helpers/signatures_helper.rb +0 -17
- data/lib/tapioca/helpers/type_variable_helper.rb +0 -43
@@ -4,121 +4,125 @@
|
|
4
4
|
module Tapioca
|
5
5
|
module RBIHelper
|
6
6
|
extend T::Sig
|
7
|
-
|
7
|
+
include SorbetHelper
|
8
|
+
extend SorbetHelper
|
9
|
+
extend self
|
8
10
|
|
9
|
-
|
10
|
-
|
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
|
11
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
12
|
+
def create_param(name, type:)
|
13
|
+
create_typed_param(RBI::Param.new(name), type)
|
21
14
|
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
15
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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")}
|
16
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
17
|
+
def create_opt_param(name, type:, default:)
|
18
|
+
create_typed_param(RBI::OptParam.new(name, default), type)
|
19
|
+
end
|
64
20
|
|
65
|
-
|
21
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
22
|
+
def create_rest_param(name, type:)
|
23
|
+
create_typed_param(RBI::RestParam.new(name), type)
|
24
|
+
end
|
66
25
|
|
67
|
-
|
68
|
-
|
69
|
-
|
26
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
27
|
+
def create_kw_param(name, type:)
|
28
|
+
create_typed_param(RBI::KwParam.new(name), type)
|
29
|
+
end
|
70
30
|
|
71
|
-
|
31
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
32
|
+
def create_kw_opt_param(name, type:, default:)
|
33
|
+
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
34
|
+
end
|
72
35
|
|
73
|
-
|
74
|
-
|
75
|
-
|
36
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
37
|
+
def create_kw_rest_param(name, type:)
|
38
|
+
create_typed_param(RBI::KwRestParam.new(name), type)
|
39
|
+
end
|
76
40
|
|
77
|
-
|
41
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
42
|
+
def create_block_param(name, type:)
|
43
|
+
create_typed_param(RBI::BlockParam.new(name), type)
|
44
|
+
end
|
78
45
|
|
79
|
-
|
80
|
-
|
46
|
+
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
47
|
+
def create_typed_param(param, type)
|
48
|
+
RBI::TypedParam.new(param: param, type: sanitize_signature_types(type))
|
49
|
+
end
|
81
50
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
51
|
+
sig { params(sig_string: String).returns(String) }
|
52
|
+
def sanitize_signature_types(sig_string)
|
53
|
+
sig_string
|
54
|
+
.gsub(".returns(<VOID>)", ".void")
|
55
|
+
.gsub("<VOID>", "void")
|
56
|
+
.gsub("<NOT-TYPED>", "T.untyped")
|
57
|
+
.gsub(".params()", "")
|
58
|
+
end
|
86
59
|
|
87
|
-
|
60
|
+
sig do
|
61
|
+
params(
|
62
|
+
type: String,
|
63
|
+
variance: Symbol,
|
64
|
+
fixed: T.nilable(String),
|
65
|
+
upper: T.nilable(String),
|
66
|
+
lower: T.nilable(String)
|
67
|
+
).returns(String)
|
88
68
|
end
|
69
|
+
def self.serialize_type_variable(type, variance, fixed, upper, lower)
|
70
|
+
variance = nil if variance == :invariant
|
89
71
|
|
90
|
-
|
72
|
+
bounds = []
|
73
|
+
bounds << "fixed: #{fixed}" if fixed
|
74
|
+
bounds << "lower: #{lower}" if lower
|
75
|
+
bounds << "upper: #{upper}" if upper
|
91
76
|
|
92
|
-
|
93
|
-
|
94
|
-
files = []
|
77
|
+
parameters = []
|
78
|
+
block = []
|
95
79
|
|
96
|
-
|
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")
|
80
|
+
parameters << ":#{variance}" if variance
|
102
81
|
|
103
|
-
|
104
|
-
|
82
|
+
if sorbet_supports?(:type_variable_block_syntax)
|
83
|
+
block = bounds
|
84
|
+
else
|
85
|
+
parameters.concat(bounds)
|
105
86
|
end
|
106
87
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
88
|
+
serialized = type.dup
|
89
|
+
serialized << "(#{parameters.join(", ")})" unless parameters.empty?
|
90
|
+
serialized << " { { #{block.join(", ")} } }" unless block.empty?
|
91
|
+
serialized
|
92
|
+
end
|
115
93
|
|
116
|
-
|
94
|
+
sig { params(name: String).returns(T::Boolean) }
|
95
|
+
def valid_method_name?(name)
|
96
|
+
# try to parse a method definition with this name
|
97
|
+
iseq = RubyVM::InstructionSequence.compile("def #{name}; end", nil, nil, 0, false)
|
98
|
+
# pull out the first operation in the instruction sequence and its first argument
|
99
|
+
op, arg, _data = iseq.to_a.dig(-1, 0)
|
100
|
+
# make sure that the operation is a method definition and the method that was
|
101
|
+
# defined has the expected name, for example, for `def !foo; end` we don't get
|
102
|
+
# a syntax error but instead get a method defined as `"foo"`
|
103
|
+
op == :definemethod && arg == name.to_sym
|
104
|
+
rescue SyntaxError
|
105
|
+
false
|
117
106
|
end
|
118
107
|
|
119
|
-
sig { params(
|
120
|
-
def
|
121
|
-
|
108
|
+
sig { params(name: String).returns(T::Boolean) }
|
109
|
+
def valid_parameter_name?(name)
|
110
|
+
sentinel_method_name = :sentinel_method_name
|
111
|
+
# try to parse a method definition with this name as the name of a
|
112
|
+
# keyword parameter. If we use a positional parameter, then parameter names
|
113
|
+
# like `&` (and maybe others) will be treated like `def foo(&); end` and will
|
114
|
+
# thus be considered valid. Using a required keyword parameter prevents that
|
115
|
+
# confusion between Ruby syntax and parameter name.
|
116
|
+
iseq = RubyVM::InstructionSequence.compile("def #{sentinel_method_name}(#{name}:); end", nil, nil, 0, false)
|
117
|
+
# pull out the first operation in the instruction sequence and its first argument and data
|
118
|
+
op, arg, data = iseq.to_a.dig(-1, 0)
|
119
|
+
# make sure that:
|
120
|
+
# 1. a method was defined, and
|
121
|
+
# 2. the method has the expected method name, and
|
122
|
+
# 3. the method has a keyword parameter with the expected name
|
123
|
+
op == :definemethod && arg == sentinel_method_name && data.dig(11, :keyword, 0) == name.to_sym
|
124
|
+
rescue SyntaxError
|
125
|
+
false
|
122
126
|
end
|
123
127
|
end
|
124
128
|
end
|
@@ -1,9 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "pathname"
|
5
|
-
require "shellwords"
|
6
|
-
|
7
4
|
module Tapioca
|
8
5
|
module SorbetHelper
|
9
6
|
extend T::Sig
|
@@ -20,6 +17,8 @@ module Tapioca
|
|
20
17
|
|
21
18
|
SORBET_EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
|
22
19
|
|
20
|
+
SORBET_PAYLOAD_URL = "https://github.com/sorbet/sorbet/tree/master/rbi"
|
21
|
+
|
23
22
|
FEATURE_REQUIREMENTS = T.let({
|
24
23
|
to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"), # https://github.com/sorbet/sorbet/pull/4706
|
25
24
|
print_payload_sources: ::Gem::Requirement.new(">= 0.5.9818"), # https://github.com/sorbet/sorbet/pull/5504
|
data/lib/tapioca/internal.rb
CHANGED
@@ -1,32 +1,56 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "set"
|
5
|
+
|
4
6
|
require "tapioca"
|
5
7
|
require "tapioca/runtime/reflection"
|
6
8
|
require "tapioca/runtime/trackers"
|
9
|
+
|
10
|
+
require "benchmark"
|
11
|
+
require "bundler"
|
12
|
+
require "erb"
|
13
|
+
require "etc"
|
14
|
+
require "fileutils"
|
15
|
+
require "json"
|
16
|
+
require "logger"
|
17
|
+
require "net/http"
|
18
|
+
require "netrc"
|
19
|
+
require "parallel"
|
20
|
+
require "pathname"
|
21
|
+
require "shellwords"
|
22
|
+
require "spoom"
|
23
|
+
require "tempfile"
|
24
|
+
require "thor"
|
25
|
+
require "yaml"
|
26
|
+
require "yard-sorbet"
|
27
|
+
|
7
28
|
require "tapioca/runtime/dynamic_mixin_compiler"
|
8
29
|
require "tapioca/helpers/gem_helper"
|
9
30
|
require "tapioca/runtime/loader"
|
31
|
+
|
10
32
|
require "tapioca/helpers/sorbet_helper"
|
11
|
-
require "tapioca/helpers/
|
12
|
-
require "tapioca/sorbet_ext/generic_name_patch"
|
33
|
+
require "tapioca/helpers/rbi_helper"
|
13
34
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
35
|
+
require "tapioca/sorbet_ext/name_patch"
|
36
|
+
require "tapioca/sorbet_ext/generic_name_patch"
|
37
|
+
require "tapioca/sorbet_ext/proc_bind_patch"
|
14
38
|
require "tapioca/runtime/generic_type_registry"
|
39
|
+
|
15
40
|
require "tapioca/helpers/cli_helper"
|
16
41
|
require "tapioca/helpers/config_helper"
|
17
|
-
require "tapioca/helpers/
|
18
|
-
require "tapioca/helpers/
|
19
|
-
|
42
|
+
require "tapioca/helpers/rbi_files_helper"
|
43
|
+
require "tapioca/helpers/env_helper"
|
44
|
+
|
20
45
|
require "tapioca/repo_index"
|
21
|
-
require "tapioca/commands"
|
22
|
-
require "tapioca/cli"
|
23
46
|
require "tapioca/gemfile"
|
24
47
|
require "tapioca/executor"
|
48
|
+
|
25
49
|
require "tapioca/static/symbol_table_parser"
|
26
50
|
require "tapioca/static/symbol_loader"
|
27
|
-
require "tapioca/gem/events"
|
28
|
-
require "tapioca/gem/listeners"
|
29
|
-
require "tapioca/gem/pipeline"
|
30
|
-
require "tapioca/dsl/compiler"
|
31
|
-
require "tapioca/dsl/pipeline"
|
32
51
|
require "tapioca/static/requires_compiler"
|
52
|
+
|
53
|
+
require "tapioca/gem"
|
54
|
+
require "tapioca/dsl"
|
55
|
+
require "tapioca/commands"
|
56
|
+
require "tapioca/cli"
|
@@ -72,7 +72,7 @@ module RBI
|
|
72
72
|
).void
|
73
73
|
end
|
74
74
|
def create_type_variable(name, type:, variance: :invariant, fixed: nil, upper: nil, lower: nil)
|
75
|
-
value = Tapioca::
|
75
|
+
value = Tapioca::RBIHelper.serialize_type_variable(type, variance, fixed, upper, lower)
|
76
76
|
create_node(RBI::TypeMember.new(name, value))
|
77
77
|
end
|
78
78
|
|
@@ -86,7 +86,7 @@ module RBI
|
|
86
86
|
).void
|
87
87
|
end
|
88
88
|
def create_method(name, parameters: [], return_type: "T.untyped", class_method: false, visibility: RBI::Public.new)
|
89
|
-
return unless valid_method_name?(name)
|
89
|
+
return unless Tapioca::RBIHelper.valid_method_name?(name)
|
90
90
|
|
91
91
|
sig = RBI::Sig.new(return_type: return_type)
|
92
92
|
method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method, visibility: visibility)
|
@@ -99,19 +99,6 @@ module RBI
|
|
99
99
|
|
100
100
|
private
|
101
101
|
|
102
|
-
SPECIAL_METHOD_NAMES = T.let(
|
103
|
-
["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
|
104
|
-
"==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`",].freeze,
|
105
|
-
T::Array[String]
|
106
|
-
)
|
107
|
-
|
108
|
-
sig { params(name: String).returns(T::Boolean) }
|
109
|
-
def valid_method_name?(name)
|
110
|
-
return true if SPECIAL_METHOD_NAMES.include?(name)
|
111
|
-
|
112
|
-
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
113
|
-
end
|
114
|
-
|
115
102
|
sig { returns(T::Hash[String, RBI::Node]) }
|
116
103
|
def nodes_cache
|
117
104
|
T.must(@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node])))
|
@@ -35,22 +35,24 @@ module Tapioca
|
|
35
35
|
# before the actual include
|
36
36
|
before = singleton_class.ancestors
|
37
37
|
# Call the actual `include` method with the supplied module
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
38
|
+
::Tapioca::Runtime::Trackers::Mixin.with_disabled_registration do
|
39
|
+
super(mod).tap do
|
40
|
+
# Take a snapshot of the list of singleton class ancestors
|
41
|
+
# after the actual include
|
42
|
+
after = singleton_class.ancestors
|
43
|
+
# The difference is the modules that are added to the list
|
44
|
+
# of ancestors of the singleton class. Those are all the
|
45
|
+
# modules that were `extend`ed due to the `include` call.
|
46
|
+
#
|
47
|
+
# We record those modules on our lookup table keyed by
|
48
|
+
# the included module with the values being all the modules
|
49
|
+
# that that module pulls into the singleton class.
|
50
|
+
#
|
51
|
+
# We need to reverse the order, since the extend order should
|
52
|
+
# be the inverse of the ancestor order. That is, earlier
|
53
|
+
# extended modules would be later in the ancestor chain.
|
54
|
+
mixins_from_modules[mod] = (after - before).reverse!
|
55
|
+
end
|
54
56
|
end
|
55
57
|
rescue Exception # rubocop:disable Lint/RescueException
|
56
58
|
# this is a best effort, bail if we can't perform this
|
@@ -20,6 +20,8 @@ module Tapioca
|
|
20
20
|
PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
|
21
21
|
METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
|
22
22
|
|
23
|
+
REQUIRED_FROM_LABELS = T.let(["<top (required)>", "<main>"].freeze, T::Array[String])
|
24
|
+
|
23
25
|
sig do
|
24
26
|
params(
|
25
27
|
symbol: String,
|
@@ -152,6 +154,30 @@ module Tapioca
|
|
152
154
|
|
153
155
|
T.unsafe(result)
|
154
156
|
end
|
157
|
+
|
158
|
+
# Examines the call stack to identify the closest location where a "require" is performed
|
159
|
+
# by searching for the label "<top (required)>". If none is found, it returns the location
|
160
|
+
# labeled "<main>", which is the original call site.
|
161
|
+
sig { params(locations: T.nilable(T::Array[Thread::Backtrace::Location])).returns(String) }
|
162
|
+
def resolve_loc(locations)
|
163
|
+
return "" unless locations
|
164
|
+
|
165
|
+
resolved_loc = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
|
166
|
+
return "" unless resolved_loc
|
167
|
+
|
168
|
+
resolved_loc.absolute_path || ""
|
169
|
+
end
|
170
|
+
|
171
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
172
|
+
def constant_name_from_singleton_class(constant)
|
173
|
+
constant.to_s.match("#<Class:(.+)>")&.captures&.first
|
174
|
+
end
|
175
|
+
|
176
|
+
sig { params(constant: Module).returns(T.nilable(BasicObject)) }
|
177
|
+
def constant_from_singleton_class(constant)
|
178
|
+
constant_name = constant_name_from_singleton_class(constant)
|
179
|
+
constantize(constant_name) if constant_name
|
180
|
+
end
|
155
181
|
end
|
156
182
|
end
|
157
183
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "set"
|
5
|
-
|
6
4
|
module Tapioca
|
7
5
|
module Runtime
|
8
6
|
module Trackers
|
@@ -12,31 +10,61 @@ module Tapioca
|
|
12
10
|
# available in the ruby runtime without extra accounting.
|
13
11
|
module ConstantDefinition
|
14
12
|
extend Reflection
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
class ConstantLocation < T::Struct
|
16
|
+
const :lineno, Integer
|
17
|
+
const :path, String
|
18
|
+
end
|
15
19
|
|
16
|
-
@class_files = {}
|
20
|
+
@class_files = {}.compare_by_identity
|
17
21
|
|
18
22
|
# Immediately activated upon load. Observes class/module definition.
|
19
23
|
TracePoint.trace(:class) do |tp|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
next if tp.self.singleton_class?
|
25
|
+
|
26
|
+
key = tp.self
|
27
|
+
|
28
|
+
path = tp.path
|
29
|
+
if File.exist?(path)
|
30
|
+
loc = build_constant_location(tp, caller_locations)
|
31
|
+
else
|
32
|
+
caller_location = T.must(caller_locations)
|
33
|
+
.find { |loc| loc.path && File.exist?(loc.path) }
|
34
|
+
|
35
|
+
next unless caller_location
|
36
|
+
|
37
|
+
loc = ConstantLocation.new(path: caller_location.absolute_path || "", lineno: caller_location.lineno)
|
30
38
|
end
|
39
|
+
|
40
|
+
(@class_files[key] ||= Set.new) << loc
|
41
|
+
end
|
42
|
+
|
43
|
+
TracePoint.trace(:c_return) do |tp|
|
44
|
+
next unless tp.method_id == :new
|
45
|
+
next unless Module === tp.return_value
|
46
|
+
|
47
|
+
key = tp.return_value
|
48
|
+
loc = build_constant_location(tp, caller_locations)
|
49
|
+
(@class_files[key] ||= Set.new) << loc
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.build_constant_location(tp, locations)
|
53
|
+
file = resolve_loc(caller_locations)
|
54
|
+
lineno = file == File.realpath(tp.path) ? tp.lineno : 0
|
55
|
+
|
56
|
+
ConstantLocation.new(path: file, lineno: lineno)
|
31
57
|
end
|
32
58
|
|
33
59
|
# Returns the files in which this class or module was opened. Doesn't know
|
34
60
|
# about situations where the class was opened prior to +require+ing,
|
35
61
|
# or where metaprogramming was used via +eval+, etc.
|
36
62
|
def self.files_for(klass)
|
37
|
-
|
38
|
-
|
39
|
-
|
63
|
+
locations_for(klass).map(&:path).to_set
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.locations_for(klass)
|
67
|
+
@class_files.fetch(klass, Set.new)
|
40
68
|
end
|
41
69
|
end
|
42
70
|
end
|
@@ -7,7 +7,9 @@ module Tapioca
|
|
7
7
|
module Mixin
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
@
|
10
|
+
@constants_to_mixin_locations = {}.compare_by_identity
|
11
|
+
@mixins_to_constants = {}.compare_by_identity
|
12
|
+
@enabled = true
|
11
13
|
|
12
14
|
class Type < T::Enum
|
13
15
|
enums do
|
@@ -17,24 +19,38 @@ module Tapioca
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
sig do
|
23
|
+
type_parameters(:Result)
|
24
|
+
.params(block: T.proc.returns(T.type_parameter(:Result)))
|
25
|
+
.returns(T.type_parameter(:Result))
|
26
|
+
end
|
27
|
+
def self.with_disabled_registration(&block)
|
28
|
+
@enabled = false
|
29
|
+
|
30
|
+
block.call
|
31
|
+
ensure
|
32
|
+
@enabled = true
|
33
|
+
end
|
34
|
+
|
20
35
|
sig do
|
21
36
|
params(
|
22
37
|
constant: Module,
|
23
|
-
|
38
|
+
mixin: Module,
|
24
39
|
mixin_type: Type,
|
25
|
-
locations: T.nilable(T::Array[Thread::Backtrace::Location])
|
26
40
|
).void
|
27
41
|
end
|
28
|
-
def self.register(constant,
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
42
|
+
def self.register(constant, mixin, mixin_type)
|
43
|
+
return unless @enabled
|
44
|
+
|
45
|
+
location = Reflection.resolve_loc(caller_locations)
|
46
|
+
|
47
|
+
constants = constants_with_mixin(mixin)
|
48
|
+
constants.fetch(mixin_type).store(constant, location)
|
33
49
|
end
|
34
50
|
|
35
|
-
sig { params(
|
36
|
-
def self.
|
37
|
-
@
|
51
|
+
sig { params(mixin: Module).returns(T::Hash[Type, T::Hash[Module, String]]) }
|
52
|
+
def self.constants_with_mixin(mixin)
|
53
|
+
@mixins_to_constants[mixin] ||= {
|
38
54
|
Type::Prepend => {}.compare_by_identity,
|
39
55
|
Type::Include => {}.compare_by_identity,
|
40
56
|
Type::Extend => {}.compare_by_identity,
|
@@ -52,8 +68,10 @@ class Module
|
|
52
68
|
constant,
|
53
69
|
self,
|
54
70
|
Tapioca::Runtime::Trackers::Mixin::Type::Prepend,
|
55
|
-
caller_locations
|
56
71
|
)
|
72
|
+
|
73
|
+
register_extend_on_attached_class(constant) if constant.singleton_class?
|
74
|
+
|
57
75
|
super
|
58
76
|
end
|
59
77
|
|
@@ -62,8 +80,10 @@ class Module
|
|
62
80
|
constant,
|
63
81
|
self,
|
64
82
|
Tapioca::Runtime::Trackers::Mixin::Type::Include,
|
65
|
-
caller_locations
|
66
83
|
)
|
84
|
+
|
85
|
+
register_extend_on_attached_class(constant) if constant.singleton_class?
|
86
|
+
|
67
87
|
super
|
68
88
|
end
|
69
89
|
|
@@ -72,9 +92,24 @@ class Module
|
|
72
92
|
obj,
|
73
93
|
self,
|
74
94
|
Tapioca::Runtime::Trackers::Mixin::Type::Extend,
|
75
|
-
caller_locations
|
76
95
|
) if Module === obj
|
77
96
|
super
|
78
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Including or prepending on a singleton class is functionally equivalent to extending the
|
102
|
+
# attached class. Registering the mixin as an extend on the attached class ensures that
|
103
|
+
# this mixin can be found whether searching for an include/prepend on the singleton class
|
104
|
+
# or an extend on the attached class.
|
105
|
+
def register_extend_on_attached_class(constant)
|
106
|
+
attached_class = Tapioca::Runtime::Reflection.constant_from_singleton_class(constant)
|
107
|
+
|
108
|
+
Tapioca::Runtime::Trackers::Mixin.register(
|
109
|
+
T.cast(attached_class, Module),
|
110
|
+
self,
|
111
|
+
Tapioca::Runtime::Trackers::Mixin::Type::Extend,
|
112
|
+
) if attached_class
|
113
|
+
end
|
79
114
|
end)
|
80
115
|
end
|
@@ -1,9 +1,6 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "tapioca/sorbet_ext/name_patch"
|
5
|
-
require "tapioca/helpers/sorbet_helper"
|
6
|
-
|
7
4
|
module T
|
8
5
|
module Generic
|
9
6
|
# This module intercepts calls to generic type instantiations and type variable definitions.
|
@@ -174,7 +171,7 @@ module Tapioca
|
|
174
171
|
lower = bounds[:lower].to_s if bounds.key?(:lower)
|
175
172
|
upper = bounds[:upper].to_s if bounds.key?(:upper)
|
176
173
|
|
177
|
-
|
174
|
+
RBIHelper.serialize_type_variable(
|
178
175
|
@type.serialize,
|
179
176
|
@variance,
|
180
177
|
fixed,
|