tapioca 0.9.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 590bba5b82c68fea817819ca7d749a8527cc74915df1989639d6b9a23792f4ac
4
- data.tar.gz: 1209c829aca470f50a86db985e3764827c679fca90c312f39daac81b3389d196
3
+ metadata.gz: e53d805bebee7cb1898eb795daebc5c7f586b5dea934a6056dfc2e1037168e83
4
+ data.tar.gz: a9baac8cc53cc87f24495961dfe738603900e9818542c722b79211da2aaea84a
5
5
  SHA512:
6
- metadata.gz: 151fb7e27e03ffaab3a9f7723333154bff8b773d5d98c24e048c726966a2e8c37ea59781d9fc2d445e2b0dd98bbb932461ad525aeb262b97ff56468da693e515
7
- data.tar.gz: 81e6a1f2f25ccd489ae50076a3b07082e53cf279375d5dbd5219916591a3c49b743bc022aea97cef5ff7f88fe919e2d5d1a632892a8596fcb5e888032096e7de
6
+ metadata.gz: 101e3405eb9fbc011e3ddb8ad33a622c74f18cde1d3b6be204aa7840fc7d1f89b1c61e61585cfe9d26c397e67839d09fcd85671c5c0b92f2c8b78741ee4e5ded
7
+ data.tar.gz: 0a7ad144e5860f72158c194de01213809a5294b69eb2a6de12288d827ca574efdc8937c967ec5c578b1a7c429d4f9e29bd5a1ee6738a373cf4cc57ca2ed8163a
data/README.md CHANGED
@@ -152,6 +152,8 @@ Options:
152
152
  [--verify], [--no-verify] # Verify RBIs are up-to-date
153
153
  [--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
154
154
  # Default: true
155
+ [--loc], [--no-loc] # Include comments with source location when generating RBIs
156
+ # Default: true
155
157
  [--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
156
158
  # Default: true
157
159
  -w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
@@ -740,6 +742,8 @@ Options:
740
742
  # Default: sorbet/rbi/todo.rbi
741
743
  [--payload], [--no-payload] # Check shims against Sorbet's payload
742
744
  # Default: true
745
+ -w, [--workers=N] # EXPERIMENTAL: Number of parallel workers
746
+ # Default: 1
743
747
  -c, [--config=<config file path>] # Path to the Tapioca configuration file
744
748
  # Default: sorbet/tapioca/config.yml
745
749
  -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
@@ -802,6 +806,7 @@ gem:
802
806
  activesupport: 'false'
803
807
  verify: false
804
808
  doc: true
809
+ loc: true
805
810
  exported_gem_rbis: true
806
811
  workers: 1
807
812
  auto_strictness: true
@@ -815,6 +820,7 @@ check_shims:
815
820
  annotations_rbi_dir: sorbet/rbi/annotations
816
821
  todo_rbi_file: sorbet/rbi/todo.rbi
817
822
  payload: true
823
+ workers: 1
818
824
  annotations:
819
825
  sources:
820
826
  - https://raw.githubusercontent.com/Shopify/rbi-central/main
data/lib/tapioca/cli.rb CHANGED
@@ -190,6 +190,10 @@ module Tapioca
190
190
  type: :boolean,
191
191
  desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow",
192
192
  default: true
193
+ option :loc,
194
+ type: :boolean,
195
+ desc: "Include comments with source location when generating RBIs",
196
+ default: true
193
197
  option :exported_gem_rbis,
194
198
  type: :boolean,
195
199
  desc: "Include RBIs found in the `rbi/` directory of the gem",
@@ -233,6 +237,7 @@ module Tapioca
233
237
  outpath: Pathname.new(options[:outdir]),
234
238
  file_header: options[:file_header],
235
239
  include_doc: options[:doc],
240
+ include_loc: options[:loc],
236
241
  include_exported_rbis: options[:exported_gem_rbis],
237
242
  number_of_workers: options[:workers],
238
243
  auto_strictness: options[:auto_strictness],
@@ -255,7 +260,7 @@ module Tapioca
255
260
  end
256
261
 
257
262
  if gems.empty? && !all
258
- command.sync(should_verify: verify)
263
+ command.sync(should_verify: verify, exclude: options[:exclude])
259
264
  else
260
265
  command.execute
261
266
  end
@@ -270,6 +275,7 @@ module Tapioca
270
275
  option :annotations_rbi_dir, type: :string, desc: "Path to annotations RBIs", default: DEFAULT_ANNOTATIONS_DIR
271
276
  option :todo_rbi_file, type: :string, desc: "Path to the generated todo RBI file", default: DEFAULT_TODO_FILE
272
277
  option :payload, type: :boolean, desc: "Check shims against Sorbet's payload", default: true
278
+ option :workers, aliases: ["-w"], type: :numeric, desc: "EXPERIMENTAL: Number of parallel workers", default: 1
273
279
  def check_shims
274
280
  command = Commands::CheckShims.new(
275
281
  gem_rbi_dir: options[:gem_rbi_dir],
@@ -277,7 +283,8 @@ module Tapioca
277
283
  shim_rbi_dir: options[:shim_rbi_dir],
278
284
  annotations_rbi_dir: options[:annotations_rbi_dir],
279
285
  todo_rbi_file: options[:todo_rbi_file],
280
- payload: options[:payload]
286
+ payload: options[:payload],
287
+ number_of_workers: options[:workers]
281
288
  )
282
289
  command.execute
283
290
  end
@@ -193,7 +193,8 @@ module Tapioca
193
193
  # Please run `#{default_command(:annotations)}` to update it.
194
194
  COMMENT
195
195
 
196
- contents = content.split("\n")
196
+ # Split contents into newlines and ensure trailing empty lines are included
197
+ contents = content.split("\n", -1)
197
198
  if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
198
199
  contents.insert(2, header).join("\n")
199
200
  else
@@ -15,10 +15,19 @@ module Tapioca
15
15
  annotations_rbi_dir: String,
16
16
  shim_rbi_dir: String,
17
17
  todo_rbi_file: String,
18
- payload: T::Boolean
18
+ payload: T::Boolean,
19
+ number_of_workers: T.nilable(Integer)
19
20
  ).void
20
21
  end
21
- def initialize(gem_rbi_dir:, dsl_rbi_dir:, annotations_rbi_dir:, shim_rbi_dir:, todo_rbi_file:, payload:)
22
+ def initialize(
23
+ gem_rbi_dir:,
24
+ dsl_rbi_dir:,
25
+ annotations_rbi_dir:,
26
+ shim_rbi_dir:,
27
+ todo_rbi_file:,
28
+ payload:,
29
+ number_of_workers:
30
+ )
22
31
  super()
23
32
  @gem_rbi_dir = gem_rbi_dir
24
33
  @dsl_rbi_dir = dsl_rbi_dir
@@ -26,6 +35,7 @@ module Tapioca
26
35
  @shim_rbi_dir = shim_rbi_dir
27
36
  @todo_rbi_file = todo_rbi_file
28
37
  @payload = payload
38
+ @number_of_workers = number_of_workers
29
39
  end
30
40
 
31
41
  sig { override.void }
@@ -51,7 +61,7 @@ module Tapioca
51
61
  exit(1)
52
62
  end
53
63
 
54
- index_payload(index, payload_path)
64
+ index_rbis(index, "payload", payload_path, number_of_workers: @number_of_workers)
55
65
  end
56
66
  else
57
67
  say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
@@ -62,10 +72,10 @@ module Tapioca
62
72
  end
63
73
 
64
74
  index_rbi(index, "todo", @todo_rbi_file)
65
- index_rbis(index, "shim", @shim_rbi_dir)
66
- index_rbis(index, "gem", @gem_rbi_dir)
67
- index_rbis(index, "dsl", @dsl_rbi_dir)
68
- index_rbis(index, "annotation", @annotations_rbi_dir)
75
+ index_rbis(index, "shim", @shim_rbi_dir, number_of_workers: @number_of_workers)
76
+ index_rbis(index, "gem", @gem_rbi_dir, number_of_workers: @number_of_workers)
77
+ index_rbis(index, "dsl", @dsl_rbi_dir, number_of_workers: @number_of_workers)
78
+ index_rbis(index, "annotation", @annotations_rbi_dir, number_of_workers: @number_of_workers)
69
79
 
70
80
  duplicates = duplicated_nodes_from_index(index, shim_rbi_dir: @shim_rbi_dir, todo_rbi_file: @todo_rbi_file)
71
81
  unless duplicates.empty?
@@ -17,6 +17,7 @@ module Tapioca
17
17
  outpath: Pathname,
18
18
  file_header: T::Boolean,
19
19
  include_doc: T::Boolean,
20
+ include_loc: T::Boolean,
20
21
  include_exported_rbis: T::Boolean,
21
22
  number_of_workers: T.nilable(Integer),
22
23
  auto_strictness: T::Boolean,
@@ -33,6 +34,7 @@ module Tapioca
33
34
  outpath:,
34
35
  file_header:,
35
36
  include_doc:,
37
+ include_loc:,
36
38
  include_exported_rbis:,
37
39
  number_of_workers: nil,
38
40
  auto_strictness: true,
@@ -58,6 +60,7 @@ module Tapioca
58
60
  @existing_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
59
61
  @expected_rbis = T.let(nil, T.nilable(T::Hash[String, String]))
60
62
  @include_doc = T.let(include_doc, T::Boolean)
63
+ @include_loc = T.let(include_loc, T::Boolean)
61
64
  @include_exported_rbis = include_exported_rbis
62
65
  end
63
66
 
@@ -94,12 +97,12 @@ module Tapioca
94
97
  end
95
98
  end
96
99
 
97
- sig { params(should_verify: T::Boolean).void }
98
- def sync(should_verify: false)
100
+ sig { params(should_verify: T::Boolean, exclude: T::Array[String]).void }
101
+ def sync(should_verify: false, exclude: [])
99
102
  if should_verify
100
103
  say("Checking for out-of-date RBIs...")
101
104
  say("")
102
- perform_sync_verification
105
+ perform_sync_verification(exclude: exclude)
103
106
  return
104
107
  end
105
108
 
@@ -182,7 +185,7 @@ module Tapioca
182
185
  default_command(:gem, gem.name),
183
186
  reason: "types exported from the `#{gem.name}` gem",) if @file_header
184
187
 
185
- rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc).compile
188
+ rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
186
189
 
187
190
  merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
188
191
 
@@ -201,11 +204,13 @@ module Tapioca
201
204
  end
202
205
  end
203
206
 
204
- sig { void }
205
- def perform_sync_verification
207
+ sig { params(exclude: T::Array[String]).void }
208
+ def perform_sync_verification(exclude: [])
206
209
  diff = {}
207
210
 
208
211
  removed_rbis.each do |gem_name|
212
+ next if exclude.include?(gem_name)
213
+
209
214
  filename = existing_rbi(gem_name)
210
215
  diff[filename] = :removed
211
216
  end
@@ -65,7 +65,7 @@ module Tapioca
65
65
  class FrozenRecord < Compiler
66
66
  extend T::Sig
67
67
 
68
- ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
68
+ ConstantType = type_member { { fixed: T.all(T.class_of(::FrozenRecord::Base), Extensions::FrozenRecord) } }
69
69
 
70
70
  sig { override.void }
71
71
  def decorate
@@ -97,7 +97,7 @@ module Tapioca
97
97
 
98
98
  sig { params(record: RBI::Scope).void }
99
99
  def decorate_scopes(record)
100
- scopes = T.unsafe(constant).__tapioca_scope_names
100
+ scopes = constant.__tapioca_scope_names
101
101
  return if scopes.nil?
102
102
 
103
103
  module_name = "GeneratedRelationMethods"
@@ -38,12 +38,20 @@ module Tapioca
38
38
  "::Time"
39
39
  when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
40
40
  "::ActiveSupport::TimeWithZone"
41
+ when ActiveRecord::Enum::EnumType
42
+ "::String"
41
43
  else
42
44
  handle_unknown_type(column_type)
43
45
  end
44
46
 
45
47
  column = @constant.columns_hash[column_name]
46
- setter_type = getter_type
48
+ setter_type =
49
+ case column_type
50
+ when ActiveRecord::Enum::EnumType
51
+ enum_setter_type(column_type)
52
+ else
53
+ getter_type
54
+ end
47
55
 
48
56
  if column&.null
49
57
  return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
@@ -108,6 +116,16 @@ module Tapioca
108
116
 
109
117
  first_argument_type.to_s
110
118
  end
119
+
120
+ sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
121
+ def enum_setter_type(column_type)
122
+ case column_type.subtype
123
+ when ActiveRecord::Type::Integer
124
+ "T.any(::String, ::Symbol, ::Integer)"
125
+ else
126
+ "T.any(::String, ::Symbol)"
127
+ end
128
+ end
111
129
  end
112
130
  end
113
131
  end
@@ -105,6 +105,9 @@ module Tapioca
105
105
  class MethodNodeAdded < NodeAdded
106
106
  extend T::Sig
107
107
 
108
+ sig { returns(UnboundMethod) }
109
+ attr_reader :method
110
+
108
111
  sig { returns(RBI::Method) }
109
112
  attr_reader :node
110
113
 
@@ -118,14 +121,16 @@ module Tapioca
118
121
  params(
119
122
  symbol: String,
120
123
  constant: Module,
124
+ method: UnboundMethod,
121
125
  node: RBI::Method,
122
126
  signature: T.untyped,
123
127
  parameters: T::Array[[Symbol, String]]
124
128
  ).void.checked(:never)
125
129
  end
126
- def initialize(symbol, constant, node, signature, parameters)
130
+ def initialize(symbol, constant, method, node, signature, parameters) # rubocop:disable Metrics/ParameterLists
127
131
  super(symbol, constant)
128
132
  @node = node
133
+ @method = method
129
134
  @signature = signature
130
135
  @parameters = parameters
131
136
  end
@@ -134,7 +134,7 @@ module Tapioca
134
134
  end
135
135
  end
136
136
 
137
- @pipeline.push_method(symbol_name, constant, rbi_method, signature, sanitized_parameters)
137
+ @pipeline.push_method(symbol_name, constant, method, rbi_method, signature, sanitized_parameters)
138
138
  tree << rbi_method
139
139
  end
140
140
 
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Gem
6
+ module Listeners
7
+ class SourceLocation < Base
8
+ extend T::Sig
9
+
10
+ private
11
+
12
+ sig { override.params(event: ConstNodeAdded).void }
13
+ def on_const(event)
14
+ file, line = Object.const_source_location(event.symbol)
15
+ add_source_location_comment(event.node, file, line)
16
+ end
17
+
18
+ sig { override.params(event: ScopeNodeAdded).void }
19
+ def on_scope(event)
20
+ # Instead of using `const_source_location`, which always reports the first place where a constant is defined,
21
+ # we filter the locations tracked by ConstantDefinition. This allows us to provide the correct location for
22
+ # constants that are defined by multiple gems.
23
+ locations = Runtime::Trackers::ConstantDefinition.locations_for(event.constant)
24
+ location = locations.find do |loc|
25
+ Pathname.new(loc.path).realpath.to_s.include?(@pipeline.gem.full_gem_path)
26
+ end
27
+
28
+ # The location may still be nil in some situations, like constant aliases (e.g.: MyAlias = OtherConst). These
29
+ # are quite difficult to attribute a correct location, given that the source location points to the original
30
+ # constants and not the alias
31
+ add_source_location_comment(event.node, location.path, location.lineno) unless location.nil?
32
+ end
33
+
34
+ sig { override.params(event: MethodNodeAdded).void }
35
+ def on_method(event)
36
+ file, line = event.method.source_location
37
+ add_source_location_comment(event.node, file, line)
38
+ end
39
+
40
+ sig { params(node: RBI::NodeWithComments, file: T.nilable(String), line: T.nilable(Integer)).void }
41
+ def add_source_location_comment(node, file, line)
42
+ return unless file && line
43
+
44
+ gem = @pipeline.gem
45
+ path = Pathname.new(file)
46
+ return unless File.exist?(path)
47
+
48
+ path = if path.realpath.to_s.start_with?(gem.full_gem_path)
49
+ "#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
50
+ else
51
+ path.sub("#{Bundler.bundle_path}/gems/", "")
52
+ end
53
+
54
+ node.comments << RBI::Comment.new("") if node.comments.any?
55
+ node.comments << RBI::Comment.new("source://#{path}:#{line}")
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -15,3 +15,4 @@ require "tapioca/gem/listeners/sorbet_type_variables"
15
15
  require "tapioca/gem/listeners/subconstants"
16
16
  require "tapioca/gem/listeners/foreign_constants"
17
17
  require "tapioca/gem/listeners/yard_doc"
18
+ require "tapioca/gem/listeners/source_location"
@@ -13,8 +13,8 @@ module Tapioca
13
13
  sig { returns(Gemfile::GemSpec) }
14
14
  attr_reader :gem
15
15
 
16
- sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean).void }
17
- def initialize(gem, include_doc: false)
16
+ sig { params(gem: Gemfile::GemSpec, include_doc: T::Boolean, include_loc: T::Boolean).void }
17
+ def initialize(gem, include_doc: false, include_loc: false)
18
18
  @root = T.let(RBI::Tree.new, RBI::Tree)
19
19
  @gem = gem
20
20
  @seen = T.let(Set.new, T::Set[String])
@@ -40,6 +40,7 @@ module Tapioca
40
40
  @node_listeners << Gem::Listeners::Subconstants.new(self)
41
41
  @node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc
42
42
  @node_listeners << Gem::Listeners::ForeignConstants.new(self)
43
+ @node_listeners << Gem::Listeners::SourceLocation.new(self) if include_loc
43
44
  @node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
44
45
  end
45
46
 
@@ -87,13 +88,14 @@ module Tapioca
87
88
  params(
88
89
  symbol: String,
89
90
  constant: Module,
91
+ method: UnboundMethod,
90
92
  node: RBI::Method,
91
93
  signature: T.untyped,
92
94
  parameters: T::Array[[Symbol, String]]
93
95
  ).void.checked(:never)
94
96
  end
95
- def push_method(symbol, constant, node, signature, parameters)
96
- @events << Gem::MethodNodeAdded.new(symbol, constant, node, signature, parameters)
97
+ def push_method(symbol, constant, method, node, signature, parameters) # rubocop:disable Metrics/ParameterLists
98
+ @events << Gem::MethodNodeAdded.new(symbol, constant, method, node, signature, parameters)
97
99
  end
98
100
 
99
101
  sig { params(symbol_name: String).returns(T::Boolean) }
@@ -9,33 +9,33 @@ module Tapioca
9
9
  requires_ancestor { Thor::Shell }
10
10
  requires_ancestor { SorbetHelper }
11
11
 
12
- sig { params(index: RBI::Index, dir: String).void }
13
- def index_payload(index, dir)
14
- return unless Dir.exist?(dir)
15
-
16
- say("Loading Sorbet payload... ")
17
- files = Dir.glob("#{dir}/**/*.rbi").sort
18
- parse_and_index_files(index, files)
19
- say(" Done", :green)
20
- end
21
-
22
12
  sig { params(index: RBI::Index, kind: String, file: String).void }
23
13
  def index_rbi(index, kind, file)
24
14
  return unless File.exist?(file)
25
15
 
26
16
  say("Loading #{kind} RBIs from #{file}... ")
27
- parse_and_index_file(index, file)
28
- say(" Done", :green)
17
+ time = Benchmark.realtime do
18
+ parse_and_index_files(index, [file], number_of_workers: 1)
19
+ end
20
+ say(" Done ", :green)
21
+ say("(#{time.round(2)}s)")
29
22
  end
30
23
 
31
- sig { params(index: RBI::Index, kind: String, dir: String).void }
32
- def index_rbis(index, kind, dir)
24
+ sig { params(index: RBI::Index, kind: String, dir: String, number_of_workers: T.nilable(Integer)).void }
25
+ def index_rbis(index, kind, dir, number_of_workers:)
33
26
  return unless Dir.exist?(dir) && !Dir.empty?(dir)
34
27
 
35
- say("Loading #{kind} RBIs from #{dir}... ")
36
- files = Dir.glob("#{dir}/**/*.rbi").sort
37
- parse_and_index_files(index, files)
38
- say(" Done", :green)
28
+ if kind == "payload"
29
+ say("Loading Sorbet payload... ")
30
+ else
31
+ say("Loading #{kind} RBIs from #{dir}... ")
32
+ end
33
+ time = Benchmark.realtime do
34
+ files = Dir.glob("#{dir}/**/*.rbi").sort
35
+ parse_and_index_files(index, files, number_of_workers: number_of_workers)
36
+ end
37
+ say(" Done ", :green)
38
+ say("(#{time.round(2)}s)")
39
39
  end
40
40
 
41
41
  sig do
@@ -48,13 +48,16 @@ module Tapioca
48
48
  def duplicated_nodes_from_index(index, shim_rbi_dir:, todo_rbi_file:)
49
49
  duplicates = {}
50
50
  say("Looking for duplicates... ")
51
- index.keys.each do |key|
52
- nodes = index[key]
53
- next unless shims_or_todos_have_duplicates?(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
51
+ time = Benchmark.realtime do
52
+ index.keys.each do |key|
53
+ nodes = index[key]
54
+ next unless shims_or_todos_have_duplicates?(nodes, shim_rbi_dir: shim_rbi_dir, todo_rbi_file: todo_rbi_file)
54
55
 
55
- duplicates[key] = nodes
56
+ duplicates[key] = nodes
57
+ end
56
58
  end
57
- say(" Done", :green)
59
+ say(" Done ", :green)
60
+ say("(#{time.round(2)}s)")
58
61
  duplicates
59
62
  end
60
63
 
@@ -150,19 +153,19 @@ module Tapioca
150
153
 
151
154
  private
152
155
 
153
- sig { params(index: RBI::Index, files: T::Array[String]).void }
154
- def parse_and_index_files(index, files)
155
- files.each do |file|
156
- parse_and_index_file(index, file)
156
+ sig { params(index: RBI::Index, files: T::Array[String], number_of_workers: T.nilable(Integer)).void }
157
+ def parse_and_index_files(index, files, number_of_workers:)
158
+ executor = Executor.new(files, number_of_workers: number_of_workers)
159
+
160
+ trees = executor.run_in_parallel do |file|
161
+ next if Spoom::Sorbet::Sigils.file_strictness(file) == "ignore"
162
+
163
+ RBI::Parser.parse_file(file)
164
+ rescue RBI::ParseError => e
165
+ say_error("\nWarning: #{e} (#{e.location})", :yellow)
157
166
  end
158
- end
159
167
 
160
- sig { params(index: RBI::Index, file: String).void }
161
- def parse_and_index_file(index, file)
162
- tree = RBI::Parser.parse_file(file)
163
- index.visit(tree)
164
- rescue RBI::ParseError => e
165
- say_error("\nWarning: #{e} (#{e.location})", :yellow)
168
+ index.visit_all(trees)
166
169
  end
167
170
 
168
171
  sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String, todo_rbi_file: String).returns(T::Boolean) }
@@ -93,7 +93,10 @@ module Tapioca
93
93
 
94
94
  sig { params(name: String).returns(T::Boolean) }
95
95
  def valid_method_name?(name)
96
- !name.to_sym.inspect.start_with?(':"', ":@", ":$")
96
+ name == "==" || !(
97
+ name.to_sym.inspect.start_with?(':"', ":@", ":$") ||
98
+ name.delete_suffix("=").to_sym.inspect.start_with?(':"', ":@", ":$")
99
+ )
97
100
  end
98
101
 
99
102
  sig { params(name: String).returns(T::Boolean) }
@@ -7,6 +7,7 @@ require "tapioca"
7
7
  require "tapioca/runtime/reflection"
8
8
  require "tapioca/runtime/trackers"
9
9
 
10
+ require "benchmark"
10
11
  require "bundler"
11
12
  require "erb"
12
13
  require "etc"
@@ -158,15 +158,14 @@ module Tapioca
158
158
  # Examines the call stack to identify the closest location where a "require" is performed
159
159
  # by searching for the label "<top (required)>". If none is found, it returns the location
160
160
  # labeled "<main>", which is the original call site.
161
- sig { returns(String) }
162
- def required_from_location
163
- locations = Kernel.caller_locations
161
+ sig { params(locations: T.nilable(T::Array[Thread::Backtrace::Location])).returns(String) }
162
+ def resolve_loc(locations)
164
163
  return "" unless locations
165
164
 
166
- required_location = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
167
- return "" unless required_location
165
+ resolved_loc = locations.find { |loc| REQUIRED_FROM_LABELS.include?(loc.label) }
166
+ return "" unless resolved_loc
168
167
 
169
- required_location.absolute_path || ""
168
+ resolved_loc.absolute_path || ""
170
169
  end
171
170
 
172
171
  sig { params(constant: Module).returns(T.nilable(String)) }
@@ -17,36 +17,54 @@ module Tapioca
17
17
  const :path, String
18
18
  end
19
19
 
20
- @class_files = {}
20
+ @class_files = {}.compare_by_identity
21
21
 
22
22
  # Immediately activated upon load. Observes class/module definition.
23
23
  TracePoint.trace(:class) do |tp|
24
- unless tp.self.singleton_class?
25
- key = name_of(tp.self)
26
- file = tp.path
27
- lineno = tp.lineno
28
-
29
- if file == "(eval)"
30
- caller_location = T.must(caller_locations)
31
- .drop_while { |loc| loc.path == "(eval)" }
32
- .first
33
-
34
- file = caller_location&.path
35
- lineno = caller_location&.lineno
36
- end
37
-
38
- @class_files[key] ||= Set.new
39
- @class_files[key] << ConstantLocation.new(path: T.must(file), lineno: T.must(lineno))
24
+ next if tp.self.singleton_class?
25
+
26
+ key = tp.self
27
+
28
+ if tp.path == "(eval)"
29
+ caller_location = T.must(caller_locations)
30
+ .drop_while { |loc| loc.path == "(eval)" }
31
+ .first
32
+
33
+ next unless caller_location
34
+
35
+ loc = ConstantLocation.new(path: caller_location.absolute_path || "", lineno: caller_location.lineno)
36
+ else
37
+ loc = build_constant_location(tp, caller_locations)
40
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)
41
57
  end
42
58
 
43
59
  # Returns the files in which this class or module was opened. Doesn't know
44
60
  # about situations where the class was opened prior to +require+ing,
45
61
  # or where metaprogramming was used via +eval+, etc.
46
62
  def self.files_for(klass)
47
- name = String === klass ? klass : name_of(klass)
48
- files = @class_files.fetch(name, [])
49
- files.map(&:path).to_set
63
+ locations_for(klass).map(&:path).to_set
64
+ end
65
+
66
+ def self.locations_for(klass)
67
+ @class_files.fetch(klass, Set.new)
50
68
  end
51
69
  end
52
70
  end
@@ -42,7 +42,7 @@ module Tapioca
42
42
  def self.register(constant, mixin, mixin_type)
43
43
  return unless @enabled
44
44
 
45
- location = Reflection.required_from_location
45
+ location = Reflection.resolve_loc(caller_locations)
46
46
 
47
47
  constants = constants_with_mixin(mixin)
48
48
  constants.fetch(mixin_type).store(constant, location)
@@ -1,18 +1,28 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- # We need sorbet to compile the signature for `qualified_name_of` before applying
5
- # the patch to avoid an infinite loop.
6
- T::Utils.signature_for_method(::Tapioca::Runtime::Reflection.method(:qualified_name_of))
7
-
8
4
  module T
9
5
  module Types
10
6
  class Simple
11
7
  module NamePatch
8
+ NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
9
+
12
10
  def name
13
11
  # Sorbet memoizes this method into the `@name` instance variable but
14
12
  # doing so means that types get memoized before this patch is applied
15
- ::Tapioca::Runtime::Reflection.qualified_name_of(@raw_type)
13
+ qualified_name_of(@raw_type)
14
+ end
15
+
16
+ def qualified_name_of(constant)
17
+ name = NAME_METHOD.bind_call(constant)
18
+ name = nil if name&.start_with?("#<")
19
+ return if name.nil?
20
+
21
+ if name.start_with?("::")
22
+ name
23
+ else
24
+ "::#{name}"
25
+ end
16
26
  end
17
27
  end
18
28
 
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.9.0"
5
+ VERSION = "0.9.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2022-07-07 00:00:00.000000000 Z
14
+ date: 2022-07-14 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -224,6 +224,7 @@ files:
224
224
  - lib/tapioca/gem/listeners/sorbet_required_ancestors.rb
225
225
  - lib/tapioca/gem/listeners/sorbet_signatures.rb
226
226
  - lib/tapioca/gem/listeners/sorbet_type_variables.rb
227
+ - lib/tapioca/gem/listeners/source_location.rb
227
228
  - lib/tapioca/gem/listeners/subconstants.rb
228
229
  - lib/tapioca/gem/listeners/yard_doc.rb
229
230
  - lib/tapioca/gem/pipeline.rb