tapioca 0.12.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a3ddc2c5d6e123488c0c4f2d0aad9a5de62a8145f2c3eb2d6b73de6f814afc0
4
- data.tar.gz: 908740542e026711e9ee85d46ce3828533f965c302039b645213adc016d74258
3
+ metadata.gz: f655f69697a5c375a3e04eef733d55213d2ea83b7392f35e3f81ed61ef4f93e4
4
+ data.tar.gz: 1ba60b67539a5034080dd9247f2f005593f25bcb2f01d72b72bccfb9a1f77f73
5
5
  SHA512:
6
- metadata.gz: 0622e47a67918e24c8ec4434892265039b38c0de522b3e2ad1ca156d62b1487317384a7dcc928249c7d5cec62493bee5e36d0cbd2342b97a39011056485d6f30
7
- data.tar.gz: 3082afd8096436fee4ecf4cb46b3a4232f77e34fa53bfb60b3182d12a7fcb5b3df58fe8ce67d1c8dcfb9b4a68dd859429bb6f0cf65eb8708991dfb2ff018720b
6
+ metadata.gz: 99e5157e6d3b82922fb321d9eed18813bd8964cd02a9e253653c73ae6fff56529638e129532bf165dae9ec43e191e9186b4e6a7e4481ea0e4f11d5e2675b2f37
7
+ data.tar.gz: 22e178bf5afe57e2fa348606bf3cdbc8085cb4e8f735cdf1ad06665e27e6b5e08085d1eef74bba823eab87b3e1f76f12c02df5455a4fd182f11455744a5ce27c
data/README.md CHANGED
@@ -40,6 +40,7 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase
40
40
  * [Excluding a gem from RBI generation](#excluding-a-gem-from-rbi-generation)
41
41
  * [Changing the strictness level of the RBI for a gem](#changing-the-strictness-level-of-the-rbi-for-a-gem)
42
42
  * [Keeping RBI files for gems up-to-date](#keeping-rbi-files-for-gems-up-to-date)
43
+ * [Importing hand written signatures from gem's `rbi/` folder](#importing-hand-written-signatures-from-gems-rbi-folder)
43
44
  * [Pulling RBI annotations from remote sources](#pulling-rbi-annotations-from-remote-sources)
44
45
  * [Basic authentication](#basic-authentication)
45
46
  * [Using a .netrc file](#using-a-netrc-file)
@@ -83,10 +84,10 @@ Commands:
83
84
  tapioca todo # Generate the list of unresolved constants
84
85
 
85
86
  Options:
86
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
87
- # Default: sorbet/tapioca/config.yml
88
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
89
- # Default: false
87
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
88
+ # Default: sorbet/tapioca/config.yml
89
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
90
+ # Default: false
90
91
 
91
92
  ```
92
93
  <!-- END_HELP -->
@@ -117,10 +118,10 @@ Usage:
117
118
  tapioca init
118
119
 
119
120
  Options:
120
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
121
- # Default: sorbet/tapioca/config.yml
122
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
123
- # Default: false
121
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
122
+ # Default: sorbet/tapioca/config.yml
123
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
124
+ # Default: false
124
125
 
125
126
  Get project ready for type checking
126
127
  ```
@@ -164,43 +165,43 @@ Usage:
164
165
  tapioca gem [gem...]
165
166
 
166
167
  Options:
167
- --out, -o, [--outdir=directory] # The output directory for generated gem RBI files
168
- # Default: sorbet/rbi/gems
169
- [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
170
- # Default: true
171
- [--all], [--no-all] # Regenerate RBI files for all gems
172
- # Default: false
173
- --pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
174
- --post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
175
- # Default: sorbet/tapioca/require.rb
176
- -x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
177
- [--include-dependencies], [--no-include-dependencies] # Generate RBI files for dependencies of the given gem(s)
178
- # Default: false
179
- --typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
180
- # Default: {"activesupport"=>"false"}
181
- [--verify], [--no-verify] # Verify RBIs are up-to-date
182
- # Default: false
183
- [--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
184
- # Default: true
185
- [--loc], [--no-loc] # Include comments with source location when generating RBIs
186
- # Default: true
187
- [--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
188
- # Default: true
189
- -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
190
- [--auto-strictness], [--no-auto-strictness] # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
191
- # Default: true
192
- --dsl-dir, [--dsl-dir=directory] # The DSL directory used to correct gems strictnesses
193
- # Default: sorbet/rbi/dsl
194
- [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
195
- # Default: 120
196
- -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
197
- # Default: development
198
- [--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
199
- # Default: true
200
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
201
- # Default: sorbet/tapioca/config.yml
202
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
203
- # Default: false
168
+ --out, -o, [--outdir=directory] # The output directory for generated gem RBI files
169
+ # Default: sorbet/rbi/gems
170
+ [--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file
171
+ # Default: true
172
+ [--all], [--no-all], [--skip-all] # Regenerate RBI files for all gems
173
+ # Default: false
174
+ --pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
175
+ --post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
176
+ # Default: sorbet/tapioca/require.rb
177
+ -x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
178
+ [--include-dependencies], [--no-include-dependencies], [--skip-include-dependencies] # Generate RBI files for dependencies of the given gem(s)
179
+ # Default: false
180
+ --typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
181
+ # Default: {"activesupport"=>"false"}
182
+ [--verify], [--no-verify], [--skip-verify] # Verify RBIs are up-to-date
183
+ # Default: false
184
+ [--doc], [--no-doc], [--skip-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
185
+ # Default: true
186
+ [--loc], [--no-loc], [--skip-loc] # Include comments with source location when generating RBIs
187
+ # Default: true
188
+ [--exported-gem-rbis], [--no-exported-gem-rbis], [--skip-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
189
+ # Default: true
190
+ -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
191
+ [--auto-strictness], [--no-auto-strictness], [--skip-auto-strictness] # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
192
+ # Default: true
193
+ --dsl-dir, [--dsl-dir=directory] # The DSL directory used to correct gems strictnesses
194
+ # Default: sorbet/rbi/dsl
195
+ [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
196
+ # Default: 120
197
+ -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
198
+ # Default: development
199
+ [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
200
+ # Default: true
201
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
202
+ # Default: sorbet/tapioca/config.yml
203
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
204
+ # Default: false
204
205
 
205
206
  Generate RBIs from gems
206
207
  ```
@@ -223,18 +224,18 @@ For gems that have a normal default `require` and that load all of their constan
223
224
  For example, suppose you are using the class `BetterHtml::Parser` exported from the `better_html` gem. Just doing a `require "better_html"` (which is the default require) does not load that type:
224
225
 
225
226
  ```shell
226
- $ bundle exec pry
227
+ $ bundle exec irb
227
228
 
228
- [1] pry(main)> require 'better_html'
229
+ irb(main):001> require 'better_html'
229
230
  => true
230
- [2] pry(main)> BetterHtml
231
+ irb(main):002> BetterHtml
231
232
  => BetterHtml
232
- [3] pry(main)> BetterHtml::Parser
233
- NameError: uninitialized constant BetterHtml::Parser
234
- from (pry):3:in `__pry__`
235
- [4] pry(main)> require 'better_html/parser'
233
+ irb(main):003> BetterHtml::Parser
234
+ (irb):3:in '<main>': uninitialized constant BetterHtml::Parser (NameError)
235
+ Did you mean? BetterHtml::ParserError
236
+ irb(main):004> require 'better_html/parser'
236
237
  => true
237
- [5] pry(main)> BetterHtml::Parser
238
+ irb(main):005> BetterHtml::Parser
238
239
  => BetterHtml::Parser
239
240
  ```
240
241
 
@@ -315,7 +316,7 @@ gem:
315
316
 
316
317
  #### Keeping RBI files for gems up-to-date
317
318
 
318
- To ensure all RBI files for gems are up-to-date with the latest changes in your `Gemfile.lock`, Tapioca provides a `--verify` option:
319
+ To ensure all RBI files for gems are present and have the correct version based on your `Gemfile.lock`, Tapioca provides a `--verify` option:
319
320
 
320
321
  ```shell
321
322
  $ bin/tapioca gems --verify
@@ -325,7 +326,16 @@ Checking for out-of-date RBIs...
325
326
  Nothing to do, all RBIs are up-to-date.
326
327
  ```
327
328
 
328
- This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking. **Warning**: doing so will break your normal Dependabot workflow as every pull-request opened to bump a gem version will fail CI since the RBI will be out-of-date and will require you to manually run `bin/tapioca gems` to update them.
329
+ This option can be used in CI to make sure the RBI files are *up-to-date* and ensure accurate type checking.
330
+
331
+ **Warning**: doing so will break your normal automated dependency update workflow as every pull request opened to bump a gem version will fail CI since the RBI will be out-of-date. You will need to either set up additional automation (eg [Dependabot](https://github.com/dependabot/dependabot-core/issues/5962#issuecomment-1303781931)), or manually run `bin/tapioca gems` and commit the results.
332
+
333
+ **Warning**: Verification ONLY ensures the RBI files are present, used and have the correct version based on the gem version in your `Gemfile.lock`. It's possible for your RBIs to be out-of-date if RBIs were not regenerated following an update to tapioca itself or if a another gem that injects functionality (e.g. `turbo-rails`) was installed/updated/removed. To ensure RBIs are completely up-to-date, you must run `bin/tapioca gems --all` but it's not recommended to do this in CI as it's an expensive operation.
334
+
335
+
336
+ #### Importing hand written signatures from gem's `rbi/` folder
337
+
338
+ Tapioca will import any signatures found in the `rbi/` folder of a given gem and combine them with the RBIs it generates. This is useful when a gem doesn't want to depend on `sorbet-runtime` but still wants to provide type safety to users during static checks. Note that the `rbi/` folder needs to be included in the gem release using the `.gemspec` file. Applications can choose not to import these signatures using the `--no-exported-gem-rbis` flag.
329
339
 
330
340
  ### Pulling RBI annotations from remote sources
331
341
 
@@ -357,14 +367,14 @@ Usage:
357
367
  Options:
358
368
  [--sources=one two three] # URIs of the sources to pull gem RBI annotations from
359
369
  # Default: "https://raw.githubusercontent.com/Shopify/rbi-central/main"
360
- [--netrc], [--no-netrc] # Use .netrc to authenticate to private sources
370
+ [--netrc], [--no-netrc], [--skip-netrc] # Use .netrc to authenticate to private sources
361
371
  # Default: true
362
372
  [--netrc-file=NETRC_FILE] # Path to .netrc file
363
373
  [--auth=AUTH] # HTTP authorization header for private sources
364
374
  --typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for pulled annotations
365
375
  -c, [--config=<config file path>] # Path to the Tapioca configuration file
366
376
  # Default: sorbet/tapioca/config.yml
367
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
377
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
368
378
  # Default: false
369
379
 
370
380
  Pull gem RBI annotations from remote sources
@@ -467,32 +477,32 @@ Usage:
467
477
  tapioca dsl [constant...]
468
478
 
469
479
  Options:
470
- --out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
471
- # Default: sorbet/rbi/dsl
472
- [--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
473
- # Default: true
474
- [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
475
- [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
476
- [--verify], [--no-verify] # Verifies RBIs are up-to-date
477
- # Default: false
478
- -q, [--quiet], [--no-quiet] # Suppresses file creation output
479
- # Default: false
480
- -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: 2)
481
- # Default: 2
482
- [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
483
- # Default: 120
484
- -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
485
- # Default: development
486
- -l, [--list-compilers], [--no-list-compilers] # List all loaded compilers
487
- # Default: false
488
- [--app-root=APP_ROOT] # The path to the Rails application
489
- # Default: .
490
- [--halt-upon-load-error], [--no-halt-upon-load-error] # Halt upon a load error while loading the Rails application
491
- # Default: true
492
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
493
- # Default: sorbet/tapioca/config.yml
494
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
495
- # Default: false
480
+ --out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
481
+ # Default: sorbet/rbi/dsl
482
+ [--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file
483
+ # Default: true
484
+ [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s)
485
+ [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s)
486
+ [--verify], [--no-verify], [--skip-verify] # Verifies RBIs are up-to-date
487
+ # Default: false
488
+ -q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output
489
+ # Default: false
490
+ -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: 2)
491
+ # Default: 2
492
+ [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
493
+ # Default: 120
494
+ -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
495
+ # Default: development
496
+ -l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers
497
+ # Default: false
498
+ [--app-root=APP_ROOT] # The path to the Rails application
499
+ # Default: .
500
+ [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application
501
+ # Default: true
502
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
503
+ # Default: sorbet/tapioca/config.yml
504
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
505
+ # Default: false
496
506
 
497
507
  Generate RBIs for dynamic methods
498
508
  ```
@@ -521,6 +531,18 @@ Reason:
521
531
 
522
532
  This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking.
523
533
 
534
+ If you are using Rails, you can configure `tapioca dsl` to run after each migration:
535
+
536
+ ```ruby
537
+ # Rakefile
538
+ if Rails.env.development?
539
+ namespace :db do
540
+ task :migrate do # Appends to the existing `db:migrate` task
541
+ system("bundle exec tapioca dsl", exception: true)
542
+ end
543
+ end
544
+ ```
545
+
524
546
  #### Writing custom DSL compilers
525
547
 
526
548
  It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.
@@ -840,23 +862,23 @@ Usage:
840
862
  tapioca check-shims
841
863
 
842
864
  Options:
843
- [--gem-rbi-dir=GEM_RBI_DIR] # Path to gem RBIs
844
- # Default: sorbet/rbi/gems
845
- [--dsl-rbi-dir=DSL_RBI_DIR] # Path to DSL RBIs
846
- # Default: sorbet/rbi/dsl
847
- [--shim-rbi-dir=SHIM_RBI_DIR] # Path to shim RBIs
848
- # Default: sorbet/rbi/shims
849
- [--annotations-rbi-dir=ANNOTATIONS_RBI_DIR] # Path to annotations RBIs
850
- # Default: sorbet/rbi/annotations
851
- [--todo-rbi-file=TODO_RBI_FILE] # Path to the generated todo RBI file
852
- # Default: sorbet/rbi/todo.rbi
853
- [--payload], [--no-payload] # Check shims against Sorbet's payload
854
- # Default: true
855
- -w, [--workers=N] # Number of parallel workers (default: auto)
856
- -c, [--config=<config file path>] # Path to the Tapioca configuration file
857
- # Default: sorbet/tapioca/config.yml
858
- -V, [--verbose], [--no-verbose] # Verbose output for debugging purposes
859
- # Default: false
865
+ [--gem-rbi-dir=GEM_RBI_DIR] # Path to gem RBIs
866
+ # Default: sorbet/rbi/gems
867
+ [--dsl-rbi-dir=DSL_RBI_DIR] # Path to DSL RBIs
868
+ # Default: sorbet/rbi/dsl
869
+ [--shim-rbi-dir=SHIM_RBI_DIR] # Path to shim RBIs
870
+ # Default: sorbet/rbi/shims
871
+ [--annotations-rbi-dir=ANNOTATIONS_RBI_DIR] # Path to annotations RBIs
872
+ # Default: sorbet/rbi/annotations
873
+ [--todo-rbi-file=TODO_RBI_FILE] # Path to the generated todo RBI file
874
+ # Default: sorbet/rbi/todo.rbi
875
+ [--payload], [--no-payload], [--skip-payload] # Check shims against Sorbet's payload
876
+ # Default: true
877
+ -w, [--workers=N] # Number of parallel workers (default: auto)
878
+ -c, [--config=<config file path>] # Path to the Tapioca configuration file
879
+ # Default: sorbet/tapioca/config.yml
880
+ -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
881
+ # Default: false
860
882
 
861
883
  Check duplicated definitions in shim RBIs
862
884
  ```
@@ -175,10 +175,11 @@ module Tapioca
175
175
 
176
176
  unless unprocessable_compilers.empty?
177
177
  message = unprocessable_compilers.map do |name, _|
178
- set_color("Error: Cannot find compiler '#{name}'", :red)
178
+ set_color("Warning: Cannot find compiler '#{name}'", :yellow)
179
179
  end.join("\n")
180
180
 
181
- raise Thor::Error, message
181
+ say(message)
182
+ say("")
182
183
  end
183
184
 
184
185
  T.cast(compiler_map.values, T::Array[T.class_of(Tapioca::Dsl::Compiler)])
@@ -118,7 +118,9 @@ module Tapioca
118
118
  reason: "types exported from the `#{gem.name}` gem",
119
119
  ) if @file_header
120
120
 
121
- rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
121
+ rbi.root = Runtime::Trackers::Autoload.with_disabled_exits do
122
+ Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
123
+ end
122
124
 
123
125
  merge_with_exported_rbi(gem, rbi) if @include_exported_rbis
124
126
 
@@ -3,6 +3,8 @@
3
3
 
4
4
  return unless defined?(ActiveModel::Attributes)
5
5
 
6
+ require "tapioca/dsl/helpers/active_model_type_helper"
7
+
6
8
  module Tapioca
7
9
  module Dsl
8
10
  module Compilers
@@ -99,11 +101,6 @@ module Tapioca
99
101
 
100
102
  sig { params(attribute_type_value: T.untyped).returns(::String) }
101
103
  def type_for(attribute_type_value)
102
- # This guarantees that the type will remain as T.untyped for attributes in the following form:
103
- # attribute :name
104
- # This is because for a generic attribute with no specified type, ActiveModel::Type::Value.new is returned
105
- return "T.untyped" if attribute_type_value.instance_of?(ActiveModel::Type::Value)
106
-
107
104
  type = case attribute_type_value
108
105
  when ActiveModel::Type::Boolean
109
106
  "T::Boolean"
@@ -120,7 +117,7 @@ module Tapioca
120
117
  when ActiveModel::Type::String
121
118
  "::String"
122
119
  else
123
- attribute_type_value.class.name.to_s
120
+ Helpers::ActiveModelTypeHelper.type_for(attribute_type_value)
124
121
  end
125
122
 
126
123
  as_nilable_type(type)
@@ -78,7 +78,15 @@ module Tapioca
78
78
 
79
79
  root.create_path(constant) do |klass|
80
80
  methods.each do |method|
81
- create_method_from_def(klass, constant.instance_method(method))
81
+ if method == :authenticate || method.start_with?("authenticate_")
82
+ klass.create_method(
83
+ method.to_s,
84
+ parameters: [create_param("unencrypted_password", type: "T.untyped")],
85
+ return_type: "T.any(#{constant}, FalseClass)",
86
+ )
87
+ else
88
+ create_method_from_def(klass, constant.instance_method(method))
89
+ end
82
90
  end
83
91
  end
84
92
  end
@@ -58,16 +58,6 @@ module Tapioca
58
58
  # `Model::PrivateRelation` modules, so that, for example, `find_by` and `all` can be chained off of the
59
59
  # `Model` class.
60
60
  #
61
- # **A note on find**: `find` is typed as `T.untyped` by default.
62
- #
63
- # While it is often used in the manner of `Model.find(id)`, Rails does support pasing in an array to find, which
64
- # would then return a `T::Enumerable[Model]`. This would force a static cast everywhere find is used to avoid type
65
- # errors. This is not ideal considering very few users of find use the array syntax over a where. With untyped,
66
- # this cast is optional and so it was decided to avoid typing it. If you need runtime guarentees when using `find`
67
- # the best method of doing so is by casting the return value to the model: `T.cast(Model.find(id), Model)`.
68
- # `find_by` does guarentee a return value of `Model`, so find can can be refactored accordingly:
69
- # `Model.find_by!(id: id)`. This will avoid the cast requirement at runtime.
70
- #
71
61
  # **CAUTION**: The generated relation classes are named `PrivateXXX` intentionally to reflect the fact
72
62
  # that they represent private subconstants of the Active Record model. As such, these types do not
73
63
  # exist at runtime, and their counterparts that do exist at runtime are marked `private_constant` anyway.
@@ -200,6 +190,8 @@ module Tapioca
200
190
  query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
201
191
  # Remove the ones we know are private API
202
192
  query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
193
+ # Remove "group" which needs a custom return type for GroupChains
194
+ query_methods -= [:group]
203
195
  # Remove "where" which needs a custom return type for WhereChains
204
196
  query_methods -= [:where]
205
197
  # Remove the methods that ...
@@ -304,6 +296,7 @@ module Tapioca
304
296
  end
305
297
  end
306
298
 
299
+ create_relation_group_chain_class
307
300
  create_relation_where_chain_class
308
301
  end
309
302
 
@@ -322,9 +315,87 @@ module Tapioca
322
315
  end
323
316
  end
324
317
 
318
+ create_association_relation_group_chain_class
325
319
  create_association_relation_where_chain_class
326
320
  end
327
321
 
322
+ sig { void }
323
+ def create_relation_group_chain_class
324
+ model.create_class(RelationGroupChainClassName, superclass_name: RelationClassName) do |klass|
325
+ create_group_chain_methods(klass)
326
+ klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
327
+ end
328
+ end
329
+
330
+ sig { void }
331
+ def create_association_relation_group_chain_class
332
+ model.create_class(
333
+ AssociationRelationGroupChainClassName,
334
+ superclass_name: AssociationRelationClassName,
335
+ ) do |klass|
336
+ create_group_chain_methods(klass)
337
+ klass.create_type_variable("Elem", type: "type_member", fixed: constant_name)
338
+ end
339
+ end
340
+
341
+ sig { params(klass: RBI::Scope).void }
342
+ def create_group_chain_methods(klass)
343
+ # Calculation methods used with `group` return a hash where the keys cannot be typed
344
+ # but the values can. Technically a `group` anywhere in the query chain produces
345
+ # this behavior but to avoid needing to re-type every query method inside this module
346
+ # we make a simplifying assumption that the calculation method is called immediately
347
+ # after the group (e.g. `group().count` and not `group().where().count`). The one
348
+ # exception is `group().having().count` which is fairly idiomatic so that gets handled
349
+ # without breaking the chain.
350
+ klass.create_method(
351
+ "having",
352
+ parameters: [
353
+ create_rest_param("args", type: "T.untyped"),
354
+ create_block_param("blk", type: "T.untyped"),
355
+ ],
356
+ return_type: "T.self_type",
357
+ )
358
+
359
+ CALCULATION_METHODS.each do |method_name|
360
+ case method_name
361
+ when :average, :maximum, :minimum
362
+ klass.create_method(
363
+ method_name.to_s,
364
+ parameters: [
365
+ create_param("column_name", type: "T.any(String, Symbol)"),
366
+ ],
367
+ return_type: "T::Hash[T.untyped, #{method_name == :average ? "Numeric" : "T.untyped"}]",
368
+ )
369
+ when :calculate
370
+ klass.create_method(
371
+ "calculate",
372
+ parameters: [
373
+ create_param("operation", type: "Symbol"),
374
+ create_param("column_name", type: "T.any(String, Symbol)"),
375
+ ],
376
+ return_type: "T::Hash[T.untyped, Numeric]",
377
+ )
378
+ when :count
379
+ klass.create_method(
380
+ "count",
381
+ parameters: [
382
+ create_opt_param("column_name", type: "T.untyped", default: "nil"),
383
+ ],
384
+ return_type: "T::Hash[T.untyped, Integer]",
385
+ )
386
+ when :sum
387
+ klass.create_method(
388
+ "sum",
389
+ parameters: [
390
+ create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
391
+ create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
392
+ ],
393
+ return_type: "T::Hash[T.untyped, Numeric]",
394
+ )
395
+ end
396
+ end
397
+ end
398
+
328
399
  sig { void }
329
400
  def create_relation_where_chain_class
330
401
  model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
@@ -468,6 +539,15 @@ module Tapioca
468
539
  sig { void }
469
540
  def create_relation_methods
470
541
  create_relation_method("all")
542
+ create_relation_method(
543
+ "group",
544
+ parameters: [
545
+ create_rest_param("args", type: "T.untyped"),
546
+ create_block_param("blk", type: "T.untyped"),
547
+ ],
548
+ relation_return_type: RelationGroupChainClassName,
549
+ association_return_type: AssociationRelationGroupChainClassName,
550
+ )
471
551
  create_relation_method(
472
552
  "where",
473
553
  parameters: [
@@ -479,13 +559,29 @@ module Tapioca
479
559
  )
480
560
 
481
561
  QUERY_METHODS.each do |method_name|
482
- create_relation_method(
483
- method_name,
484
- parameters: [
485
- create_rest_param("args", type: "T.untyped"),
486
- create_block_param("blk", type: "T.untyped"),
487
- ],
488
- )
562
+ case method_name
563
+ when :extract_associated
564
+ parameters = [create_param("association", type: "Symbol")]
565
+ return_type = "T::Array[T.untyped]"
566
+ relation_methods_module.create_method(
567
+ method_name.to_s,
568
+ parameters: parameters,
569
+ return_type: return_type,
570
+ )
571
+ association_relation_methods_module.create_method(
572
+ method_name.to_s,
573
+ parameters: parameters,
574
+ return_type: return_type,
575
+ )
576
+ else
577
+ create_relation_method(
578
+ method_name,
579
+ parameters: [
580
+ create_rest_param("args", type: "T.untyped"),
581
+ create_block_param("blk", type: "T.untyped"),
582
+ ],
583
+ )
584
+ end
489
585
  end
490
586
  end
491
587
 
@@ -560,12 +656,29 @@ module Tapioca
560
656
  return_type: "T::Boolean",
561
657
  )
562
658
  when :find
563
- create_common_method(
659
+ # From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
660
+ id_types = "T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, " \
661
+ "::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, " \
662
+ "::ActiveSupport::Duration, T::Class[T.anything])"
663
+ array_type = if constant.try(:composite_primary_key?)
664
+ "T::Array[T::Array[#{id_types}]"
665
+ else
666
+ "T::Array[#{id_types}]"
667
+ end
668
+ sigs = [
669
+ common_relation_methods_module.create_sig(
670
+ parameters: [create_param("args", type: id_types)],
671
+ return_type: constant_name,
672
+ ),
673
+ common_relation_methods_module.create_sig(
674
+ parameters: [create_param("args", type: array_type)],
675
+ return_type: "T::Enumerable[#{constant_name}]",
676
+ ),
677
+ ]
678
+ common_relation_methods_module.create_method_with_sigs(
564
679
  "find",
565
- parameters: [
566
- create_rest_param("args", type: "T.untyped"),
567
- ],
568
- return_type: "T.untyped",
680
+ sigs: sigs,
681
+ parameters: [RBI::ReqParam.new("args")],
569
682
  )
570
683
  when :find_by
571
684
  create_common_method(
@@ -599,12 +712,20 @@ module Tapioca
599
712
  return_type: constant_name,
600
713
  )
601
714
  when :first, :last, :take
602
- create_common_method(
603
- method_name,
604
- parameters: [
605
- create_opt_param("limit", type: "T.untyped", default: "nil"),
606
- ],
607
- return_type: "T.untyped",
715
+ sigs = [
716
+ common_relation_methods_module.create_sig(
717
+ parameters: [create_opt_param("limit", type: "NilClass", default: "nil")],
718
+ return_type: as_nilable_type(constant_name),
719
+ ),
720
+ common_relation_methods_module.create_sig(
721
+ parameters: [create_param("limit", type: "Integer")],
722
+ return_type: "T::Array[#{constant_name}]",
723
+ ),
724
+ ]
725
+ common_relation_methods_module.create_method_with_sigs(
726
+ method_name.to_s,
727
+ sigs: sigs,
728
+ parameters: [RBI::OptParam.new("limit", "nil")],
608
729
  )
609
730
  when :raise_record_not_found_exception!
610
731
  # skip
@@ -653,7 +774,7 @@ module Tapioca
653
774
  parameters: [
654
775
  create_param("column_name", type: "T.any(String, Symbol)"),
655
776
  ],
656
- return_type: "T.untyped",
777
+ return_type: method_name == :average ? "Numeric" : "T.untyped",
657
778
  )
658
779
  when :calculate
659
780
  create_common_method(
@@ -662,7 +783,7 @@ module Tapioca
662
783
  create_param("operation", type: "Symbol"),
663
784
  create_param("column_name", type: "T.any(String, Symbol)"),
664
785
  ],
665
- return_type: "T.untyped",
786
+ return_type: "Numeric",
666
787
  )
667
788
  when :count
668
789
  create_common_method(
@@ -670,7 +791,7 @@ module Tapioca
670
791
  parameters: [
671
792
  create_opt_param("column_name", type: "T.untyped", default: "nil"),
672
793
  ],
673
- return_type: "T.untyped",
794
+ return_type: "Integer",
674
795
  )
675
796
  when :ids
676
797
  create_common_method("ids", return_type: "Array")
@@ -689,7 +810,7 @@ module Tapioca
689
810
  create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
690
811
  create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
691
812
  ],
692
- return_type: "T.untyped",
813
+ return_type: "Numeric",
693
814
  )
694
815
  end
695
816
  end
@@ -698,51 +819,121 @@ module Tapioca
698
819
  case method_name
699
820
  when :find_each
700
821
  order = ActiveRecord::Batches.instance_method(:find_each).parameters.include?([:key, :order])
701
- create_common_method(
822
+ sigs = [
823
+ common_relation_methods_module.create_sig(
824
+ parameters: [
825
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
826
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
827
+ create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
828
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
829
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
830
+ create_block_param("block", type: "T.proc.params(object: #{constant_name}).void"),
831
+ ],
832
+ return_type: "void",
833
+ ),
834
+ common_relation_methods_module.create_sig(
835
+ parameters: [
836
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
837
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
838
+ create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
839
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
840
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
841
+ ],
842
+ return_type: "T::Enumerator[#{constant_name}]",
843
+ ),
844
+ ]
845
+ common_relation_methods_module.create_method_with_sigs(
702
846
  "find_each",
847
+ sigs: sigs,
703
848
  parameters: [
704
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
705
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
706
- create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
707
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
708
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
709
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
849
+ RBI::KwOptParam.new("start", "nil"),
850
+ RBI::KwOptParam.new("finish", "nil"),
851
+ RBI::KwOptParam.new("batch_size", "1000"),
852
+ RBI::KwOptParam.new("error_on_ignore", "nil"),
853
+ *(RBI::KwOptParam.new("order", ":asc") if order),
854
+ RBI::BlockParam.new("block"),
710
855
  ],
711
- return_type: "T.nilable(T::Enumerator[#{constant_name}])",
712
856
  )
713
857
  when :find_in_batches
714
858
  order = ActiveRecord::Batches.instance_method(:find_in_batches).parameters.include?([:key, :order])
715
- create_common_method(
859
+ sigs = [
860
+ common_relation_methods_module.create_sig(
861
+ parameters: [
862
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
863
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
864
+ create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
865
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
866
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
867
+ create_block_param("block", type: "T.proc.params(object: T::Array[#{constant_name}]).void"),
868
+ ],
869
+ return_type: "void",
870
+ ),
871
+ common_relation_methods_module.create_sig(
872
+ parameters: [
873
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
874
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
875
+ create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
876
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
877
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
878
+ ],
879
+ return_type: "T::Enumerator[T::Enumerator[#{constant_name}]]",
880
+ ),
881
+ ]
882
+ common_relation_methods_module.create_method_with_sigs(
716
883
  "find_in_batches",
884
+ sigs: sigs,
717
885
  parameters: [
718
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
719
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
720
- create_kw_opt_param("batch_size", type: "Integer", default: "1000"),
721
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
722
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
723
- create_block_param(
724
- "block",
725
- type: "T.nilable(T.proc.params(object: T::Array[#{constant_name}]).void)",
726
- ),
886
+ RBI::KwOptParam.new("start", "nil"),
887
+ RBI::KwOptParam.new("finish", "nil"),
888
+ RBI::KwOptParam.new("batch_size", "1000"),
889
+ RBI::KwOptParam.new("error_on_ignore", "nil"),
890
+ *(RBI::KwOptParam.new("order", ":asc") if order),
891
+ RBI::BlockParam.new("block"),
727
892
  ],
728
- return_type: "T.nilable(T::Enumerator[T::Enumerator[#{constant_name}]])",
729
893
  )
730
894
  when :in_batches
731
895
  order = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :order])
732
896
  use_ranges = ActiveRecord::Batches.instance_method(:in_batches).parameters.include?([:key, :use_ranges])
733
- create_common_method(
897
+ sigs = [
898
+ common_relation_methods_module.create_sig(
899
+ parameters: [
900
+ create_kw_opt_param("of", type: "Integer", default: "1000"),
901
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
902
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
903
+ create_kw_opt_param("load", type: "T.untyped", default: "false"),
904
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
905
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
906
+ *(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
907
+ create_block_param("block", type: "T.proc.params(object: #{RelationClassName}).void"),
908
+ ],
909
+ return_type: "void",
910
+ ),
911
+ common_relation_methods_module.create_sig(
912
+ parameters: [
913
+ create_kw_opt_param("of", type: "Integer", default: "1000"),
914
+ create_kw_opt_param("start", type: "T.untyped", default: "nil"),
915
+ create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
916
+ create_kw_opt_param("load", type: "T.untyped", default: "false"),
917
+ create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
918
+ *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
919
+ *(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
920
+ ],
921
+ return_type: "::ActiveRecord::Batches::BatchEnumerator",
922
+ ),
923
+ ]
924
+ common_relation_methods_module.create_method_with_sigs(
734
925
  "in_batches",
926
+ sigs: sigs,
735
927
  parameters: [
736
- create_kw_opt_param("of", type: "Integer", default: "1000"),
737
- create_kw_opt_param("start", type: "T.untyped", default: "nil"),
738
- create_kw_opt_param("finish", type: "T.untyped", default: "nil"),
739
- create_kw_opt_param("load", type: "T.untyped", default: "false"),
740
- create_kw_opt_param("error_on_ignore", type: "T.untyped", default: "nil"),
741
- *(create_kw_opt_param("order", type: "Symbol", default: ":asc") if order),
742
- *(create_kw_opt_param("use_ranges", type: "T.untyped", default: "nil") if use_ranges),
743
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{RelationClassName}).void)"),
928
+ RBI::KwOptParam.new("of", "1000"),
929
+ RBI::KwOptParam.new("start", "nil"),
930
+ RBI::KwOptParam.new("finish", "nil"),
931
+ RBI::KwOptParam.new("load", "false"),
932
+ RBI::KwOptParam.new("error_on_ignore", "nil"),
933
+ *(RBI::KwOptParam.new("order", ":asc") if order),
934
+ *(RBI::KwOptParam.new("use_ranges", "nil") if use_ranges),
935
+ RBI::BlockParam.new("block"),
744
936
  ],
745
- return_type: "T.nilable(::ActiveRecord::Batches::BatchEnumerator)",
746
937
  )
747
938
  end
748
939
  end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- return unless defined?(Config)
4
+ return unless defined?(Config) && defined?(Config::VERSION) && defined?(Config.const_name)
5
5
 
6
6
  module Tapioca
7
7
  module Dsl
@@ -302,6 +302,13 @@ module Tapioca
302
302
  return_type: "void",
303
303
  )
304
304
 
305
+ if desc.has_presence?
306
+ klass.create_method(
307
+ "has_#{field.name}?",
308
+ return_type: "Object",
309
+ )
310
+ end
311
+
305
312
  field
306
313
  end
307
314
 
@@ -0,0 +1,70 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Helpers
7
+ module ActiveModelTypeHelper
8
+ class << self
9
+ extend T::Sig
10
+
11
+ # Returns the type indicated by the custom ActiveModel::Type::Value.
12
+ # Accepts subclasses of ActiveModel::Type::Value as well as classes that implement similar methods.
13
+ sig { params(type_value: T.untyped).returns(String) }
14
+ def type_for(type_value)
15
+ return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(type_value)
16
+
17
+ type = lookup_return_type_of_method(type_value, :deserialize) ||
18
+ lookup_return_type_of_method(type_value, :cast) ||
19
+ lookup_return_type_of_method(type_value, :cast_value) ||
20
+ lookup_arg_type_of_method(type_value, :serialize) ||
21
+ T.untyped
22
+ type.to_s
23
+ end
24
+
25
+ private
26
+
27
+ MEANINGLESS_TYPES = T.let(
28
+ [
29
+ T.untyped,
30
+ T.noreturn,
31
+ T::Private::Types::Void,
32
+ T::Private::Types::NotTyped,
33
+ ].freeze,
34
+ T::Array[Object],
35
+ )
36
+
37
+ sig { params(type: T.untyped).returns(T::Boolean) }
38
+ def meaningful_type?(type)
39
+ !MEANINGLESS_TYPES.include?(type)
40
+ end
41
+
42
+ sig { params(obj: T.untyped, method: Symbol).returns(T.nilable(T::Types::Base)) }
43
+ def lookup_return_type_of_method(obj, method)
44
+ return_type = lookup_signature_of_method(obj, method)&.return_type
45
+ return unless return_type && meaningful_type?(return_type)
46
+
47
+ return_type
48
+ end
49
+
50
+ sig { params(obj: T.untyped, method: Symbol).returns(T.nilable(T::Types::Base)) }
51
+ def lookup_arg_type_of_method(obj, method)
52
+ # Arg types is an array of [name, type] entries, so we dig into first entry (index 0)
53
+ # and then into the type which is the last element (index 1)
54
+ first_arg_type = lookup_signature_of_method(obj, method)&.arg_types&.dig(0, 1)
55
+ return unless first_arg_type && meaningful_type?(first_arg_type)
56
+
57
+ first_arg_type
58
+ end
59
+
60
+ sig { params(obj: T.untyped, method: Symbol).returns(T.untyped) }
61
+ def lookup_signature_of_method(obj, method)
62
+ Runtime::Reflection.signature_of(obj.method(method))
63
+ rescue NameError
64
+ nil
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "tapioca/dsl/helpers/active_model_type_helper"
5
+
4
6
  module Tapioca
5
7
  module Dsl
6
8
  module Helpers
@@ -98,7 +100,7 @@ module Tapioca
98
100
  ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
99
101
  "T::Array[#{type_for_activerecord_value(column_type.subtype)}]"
100
102
  else
101
- handle_unknown_type(column_type)
103
+ ActiveModelTypeHelper.type_for(column_type)
102
104
  end
103
105
  end
104
106
 
@@ -108,40 +110,6 @@ module Tapioca
108
110
  !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
109
111
  end
110
112
 
111
- sig { params(column_type: BasicObject).returns(String) }
112
- def handle_unknown_type(column_type)
113
- return "T.untyped" unless ActiveModel::Type::Value === column_type
114
- return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(column_type)
115
-
116
- lookup_return_type_of_method(column_type, :deserialize) ||
117
- lookup_return_type_of_method(column_type, :cast) ||
118
- lookup_arg_type_of_method(column_type, :serialize) ||
119
- "T.untyped"
120
- end
121
-
122
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
123
- def lookup_return_type_of_method(column_type, method)
124
- signature = Runtime::Reflection.signature_of(column_type.method(method))
125
- return unless signature
126
-
127
- return_type = signature.return_type
128
- return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
129
-
130
- return_type.to_s
131
- end
132
-
133
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
134
- def lookup_arg_type_of_method(column_type, method)
135
- signature = Runtime::Reflection.signature_of(column_type.method(method))
136
- return unless signature
137
-
138
- # Arg types is an array [name, type] entries, so we desctructure the type of
139
- # first argument to get the first argument type
140
- _, first_argument_type = signature.arg_types.first
141
-
142
- first_argument_type.to_s
143
- end
144
-
145
113
  sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
146
114
  def enum_setter_type(column_type)
147
115
  # In Rails < 7 this method is private. When support for that is dropped we can call the method directly
@@ -22,8 +22,10 @@ module Tapioca
22
22
  CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
23
23
 
24
24
  RelationClassName = T.let("PrivateRelation", String)
25
+ RelationGroupChainClassName = T.let("PrivateRelationGroupChain", String)
25
26
  RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
26
27
  AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
28
+ AssociationRelationGroupChainClassName = T.let("PrivateAssociationRelationGroupChain", String)
27
29
  AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
28
30
  AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
29
31
  end
@@ -61,7 +61,7 @@ module Tapioca
61
61
  .sort_by! { |c| T.must(Runtime::Reflection.name_of(c)) }
62
62
 
63
63
  # It's OK if there are no constants to process if we received a valid file/path.
64
- if constants_to_process.empty? && requested_paths.select { |p| File.exist?(p) }.empty?
64
+ if constants_to_process.empty? && requested_paths.none? { |p| File.exist?(p) }
65
65
  report_error(<<~ERROR)
66
66
  No classes/modules can be matched for RBI generation.
67
67
  Please check that the requested classes/modules include processable DSL methods.
@@ -178,7 +178,18 @@ module Tapioca
178
178
 
179
179
  sig { void }
180
180
  def parse_yard_docs
181
- files.each { |path| YARD.parse(path.to_s, [], Logger::Severity::FATAL) }
181
+ files.each do |path|
182
+ YARD.parse(path.to_s, [], Logger::Severity::FATAL)
183
+ rescue RangeError
184
+ # In some circumstances, YARD will raise an error when parsing a file
185
+ # that is actually valid Ruby. We don't want tapioca to halt in these
186
+ # cases, so we'll rescue the error, pretend like there was no
187
+ # documentation, and move on.
188
+ #
189
+ # This can be removed when https://github.com/lsegal/yard/issues/1536
190
+ # is resolved and released.
191
+ []
192
+ end
182
193
  end
183
194
 
184
195
  sig { returns(T::Array[String]) }
@@ -87,24 +87,56 @@ module RBI
87
87
  ).void
88
88
  end
89
89
  def create_method(name, parameters: [], return_type: "T.untyped", class_method: false, visibility: RBI::Public.new,
90
+ comments: [])
91
+ sig = create_sig(parameters: parameters, return_type: return_type)
92
+ create_method_with_sigs(
93
+ name,
94
+ sigs: [sig],
95
+ parameters: parameters.map(&:param),
96
+ class_method: class_method,
97
+ visibility: visibility,
98
+ comments: comments,
99
+ )
100
+ end
101
+
102
+ sig do
103
+ params(
104
+ name: String,
105
+ sigs: T::Array[RBI::Sig],
106
+ parameters: T::Array[RBI::Param],
107
+ class_method: T::Boolean,
108
+ visibility: RBI::Visibility,
109
+ comments: T::Array[RBI::Comment],
110
+ ).void
111
+ end
112
+ def create_method_with_sigs(name, sigs:, parameters: [], class_method: false, visibility: RBI::Public.new,
90
113
  comments: [])
91
114
  return unless Tapioca::RBIHelper.valid_method_name?(name)
92
115
 
93
- sig = RBI::Sig.new(return_type: return_type)
94
116
  method = RBI::Method.new(
95
117
  name,
96
- sigs: [sig],
118
+ sigs: sigs,
119
+ params: parameters,
97
120
  is_singleton: class_method,
98
121
  visibility: visibility,
99
122
  comments: comments,
100
123
  )
101
- parameters.each do |param|
102
- method << param.param
103
- sig << RBI::SigParam.new(param.param.name, param.type)
104
- end
105
124
  self << method
106
125
  end
107
126
 
127
+ sig do
128
+ params(
129
+ parameters: T::Array[RBI::TypedParam],
130
+ return_type: String,
131
+ ).returns(RBI::Sig)
132
+ end
133
+ def create_sig(parameters: [], return_type: "T.untyped")
134
+ params = parameters.map do |param|
135
+ RBI::SigParam.new(param.param.name, param.type)
136
+ end
137
+ RBI::Sig.new(params: params, return_type: return_type)
138
+ end
139
+
108
140
  private
109
141
 
110
142
  sig { returns(T::Hash[String, RBI::Node]) }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.12.0"
5
+ VERSION = "0.13.0"
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.12.0
4
+ version: 0.13.0
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: 2024-01-25 00:00:00.000000000 Z
14
+ date: 2024-03-25 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -81,21 +81,18 @@ dependencies:
81
81
  requirements:
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
- version: 0.5.10820
84
+ version: 0.5.11087
85
85
  type: :runtime
86
86
  prerelease: false
87
87
  version_requirements: !ruby/object:Gem::Requirement
88
88
  requirements:
89
89
  - - ">="
90
90
  - !ruby/object:Gem::Version
91
- version: 0.5.10820
91
+ version: 0.5.11087
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: spoom
94
94
  requirement: !ruby/object:Gem::Requirement
95
95
  requirements:
96
- - - "~>"
97
- - !ruby/object:Gem::Version
98
- version: 1.2.0
99
96
  - - ">="
100
97
  - !ruby/object:Gem::Version
101
98
  version: 1.2.0
@@ -103,9 +100,6 @@ dependencies:
103
100
  prerelease: false
104
101
  version_requirements: !ruby/object:Gem::Requirement
105
102
  requirements:
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: 1.2.0
109
103
  - - ">="
110
104
  - !ruby/object:Gem::Version
111
105
  version: 1.2.0
@@ -209,6 +203,7 @@ files:
209
203
  - lib/tapioca/dsl/extensions/active_record.rb
210
204
  - lib/tapioca/dsl/extensions/frozen_record.rb
211
205
  - lib/tapioca/dsl/extensions/kredis.rb
206
+ - lib/tapioca/dsl/helpers/active_model_type_helper.rb
212
207
  - lib/tapioca/dsl/helpers/active_record_column_type_helper.rb
213
208
  - lib/tapioca/dsl/helpers/active_record_constants_helper.rb
214
209
  - lib/tapioca/dsl/helpers/graphql_type_helper.rb
@@ -293,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
293
288
  - !ruby/object:Gem::Version
294
289
  version: '0'
295
290
  requirements: []
296
- rubygems_version: 3.5.5
291
+ rubygems_version: 3.5.6
297
292
  signing_key:
298
293
  specification_version: 4
299
294
  summary: A Ruby Interface file generator for gems, core types and the Ruby standard