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