tapioca 0.10.3 → 0.10.5

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/exe/tapioca +15 -13
  3. data/lib/tapioca/cli.rb +9 -4
  4. data/lib/tapioca/commands/configure.rb +2 -2
  5. data/lib/tapioca/commands/gem.rb +4 -2
  6. data/lib/tapioca/dsl/compiler.rb +4 -4
  7. data/lib/tapioca/dsl/compilers/aasm.rb +11 -2
  8. data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +163 -0
  9. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +8 -5
  10. data/lib/tapioca/dsl/compilers/active_record_relations.rb +48 -16
  11. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +14 -11
  12. data/lib/tapioca/dsl/compilers/active_resource.rb +22 -15
  13. data/lib/tapioca/dsl/compilers/active_storage.rb +4 -2
  14. data/lib/tapioca/dsl/compilers/protobuf.rb +5 -0
  15. data/lib/tapioca/dsl/compilers/smart_properties.rb +7 -4
  16. data/lib/tapioca/dsl/compilers/url_helpers.rb +7 -4
  17. data/lib/tapioca/dsl/extensions/active_record.rb +29 -0
  18. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +37 -27
  19. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +1 -0
  20. data/lib/tapioca/dsl/pipeline.rb +3 -1
  21. data/lib/tapioca/gem/listeners/yard_doc.rb +13 -10
  22. data/lib/tapioca/gem/pipeline.rb +1 -2
  23. data/lib/tapioca/gemfile.rb +6 -2
  24. data/lib/tapioca/helpers/gem_helper.rb +1 -1
  25. data/lib/tapioca/helpers/rbi_files_helper.rb +14 -7
  26. data/lib/tapioca/helpers/sorbet_helper.rb +7 -3
  27. data/lib/tapioca/helpers/source_uri.rb +10 -7
  28. data/lib/tapioca/loaders/gem.rb +4 -2
  29. data/lib/tapioca/loaders/loader.rb +6 -3
  30. data/lib/tapioca/rbi_ext/model.rb +7 -2
  31. data/lib/tapioca/rbi_formatter.rb +11 -8
  32. data/lib/tapioca/runtime/trackers.rb +17 -0
  33. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +35 -13
  34. data/lib/tapioca/version.rb +1 -1
  35. data/lib/tapioca.rb +8 -5
  36. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c5b81fee880c497fca1e34c644d6da53e4f155e45f564c95eb886f0f6c30d69
4
- data.tar.gz: d0973ab750b577064e5891c0fbd9dd71ddeb82b54d7d8dfcabad4841052e8d6d
3
+ metadata.gz: 9df4814fb4f17b9f1d53aab7a9b9b23498cfea0330fb48b47d909af119ac6ea0
4
+ data.tar.gz: 7f2d233a47a7ceb2b4a1f4894c794d65c2cca9c9e1547022630d7b395de0cd47
5
5
  SHA512:
6
- metadata.gz: 105ab9194d3e62657fedff6d0fd78b187980b5084af80be294690c00a86ac1544b29bd9d86951f50072c895516ce1541251ac2ff05cbb3a2acbbee5d0e103b51
7
- data.tar.gz: 612f856fd4b36301dbfee4cdd80615ee4a684447ea077037c5a72e1c7c216be7c465aae13a5b4462a8db9889929765703293d2935036d7ac1e94b8d21d4ee5fb
6
+ metadata.gz: f65d462c261005c62b31f0864df29b550d5a07551433203f485f8f66c57a8e100fccdb1deb1b7f29e185a1f6e8bf5b4e9f59387a1ba24ea8b4cff168b1b4ca12
7
+ data.tar.gz: 721c40a48f379eda0e6302ff59b2a3228f0262c5d2b2bac1111016d92ec02597e9d35c28a38a6fcc903a8ac13b0b8c4dddcc7e1d747907390762f47601d19e68
data/exe/tapioca CHANGED
@@ -3,19 +3,21 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
 
6
- begin
7
- T::Configuration.default_checked_level = :never
8
- # Suppresses call validation errors
9
- T::Configuration.call_validation_error_handler = ->(*) {}
10
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
11
- T::Configuration.inline_type_error_handler = ->(*) {}
12
- # Suppresses errors caused by incorrect parameter ordering
13
- T::Configuration.sig_validation_error_handler = ->(*) {}
14
- rescue
15
- # Need this rescue so that if another gem has
16
- # already set the checked level by the time we
17
- # get to it, we don't fail outright.
18
- nil
6
+ unless ENV["ENFORCE_TYPECHECKING"] == "1"
7
+ begin
8
+ T::Configuration.default_checked_level = :never
9
+ # Suppresses call validation errors
10
+ T::Configuration.call_validation_error_handler = ->(*) {}
11
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
12
+ T::Configuration.inline_type_error_handler = ->(*) {}
13
+ # Suppresses errors caused by incorrect parameter ordering
14
+ T::Configuration.sig_validation_error_handler = ->(*) {}
15
+ rescue
16
+ # Need this rescue so that if another gem has
17
+ # already set the checked level by the time we
18
+ # get to it, we don't fail outright.
19
+ nil
20
+ end
19
21
  end
20
22
 
21
23
  require_relative "../lib/tapioca/internal"
data/lib/tapioca/cli.rb CHANGED
@@ -25,9 +25,12 @@ module Tapioca
25
25
 
26
26
  desc "init", "get project ready for type checking"
27
27
  def init
28
- invoke(:configure)
29
- invoke(:annotations)
30
- invoke(:gem)
28
+ # We need to make sure that trackers stay enabled until the `gem` command is invoked
29
+ Runtime::Trackers.with_trackers_enabled do
30
+ invoke(:configure)
31
+ invoke(:annotations)
32
+ invoke(:gem)
33
+ end
31
34
  invoke(:todo)
32
35
 
33
36
  print_init_next_steps
@@ -293,7 +296,9 @@ module Tapioca
293
296
  end
294
297
 
295
298
  desc "annotations", "Pull gem RBI annotations from remote sources"
296
- option :sources, type: :array, default: [CENTRAL_REPO_ROOT_URI],
299
+ option :sources,
300
+ type: :array,
301
+ default: [CENTRAL_REPO_ROOT_URI],
297
302
  desc: "URIs of the sources to pull gem RBI annotations from"
298
303
  option :netrc, type: :boolean, default: true, desc: "Use .netrc to authenticate to private sources"
299
304
  option :netrc_file, type: :string, desc: "Path to .netrc file"
@@ -70,7 +70,7 @@ module Tapioca
70
70
  # typed: true
71
71
  # frozen_string_literal: true
72
72
 
73
- # Add your extra requires here (`#{default_command(:require)}` can be used to boostrap this list)
73
+ # Add your extra requires here (`#{default_command(:require)}` can be used to bootstrap this list)
74
74
  CONTENT
75
75
  end
76
76
 
@@ -92,7 +92,7 @@ module Tapioca
92
92
  @installer ||= Bundler::Installer.new(Bundler.root, Bundler.definition)
93
93
  end
94
94
 
95
- sig { returns(Bundler::StubSpecification) }
95
+ sig { returns(T.any(Bundler::StubSpecification, ::Gem::Specification)) }
96
96
  def spec
97
97
  @spec ||= Bundler.definition.specs.find { |s| s.name == "tapioca" }
98
98
  end
@@ -156,9 +156,11 @@ module Tapioca
156
156
 
157
157
  rbi = RBI::File.new(strictness: @typed_overrides[gem.name] || "true")
158
158
 
159
- @rbi_formatter.write_header!(rbi,
159
+ @rbi_formatter.write_header!(
160
+ rbi,
160
161
  default_command(:gem, gem.name),
161
- reason: "types exported from the `#{gem.name}` gem") if @file_header
162
+ reason: "types exported from the `#{gem.name}` gem",
163
+ ) if @file_header
162
164
 
163
165
  rbi.root = Tapioca::Gem::Pipeline.new(gem, include_doc: @include_doc, include_loc: @include_loc).compile
164
166
 
@@ -101,6 +101,9 @@ module Tapioca
101
101
  # rest parameter type
102
102
  params << signature.rest_type.to_s if signature.has_rest
103
103
 
104
+ # keyrest parameter type
105
+ params << signature.keyrest_type.to_s if signature.has_keyrest
106
+
104
107
  # special case `.void` in a proc
105
108
  unless signature.block_name.nil?
106
109
  params << signature.block_type.to_s.gsub("returns(<VOID>)", "void")
@@ -159,10 +162,7 @@ module Tapioca
159
162
  def compile_method_return_type_to_rbi(method_def)
160
163
  signature = signature_of(method_def)
161
164
  return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
162
- return_type = "void" if return_type == "<VOID>"
163
- # Map <NOT-TYPED> to `T.untyped`
164
- return_type = "T.untyped" if return_type == "<NOT-TYPED>"
165
- return_type
165
+ sanitize_signature_types(return_type)
166
166
  end
167
167
  end
168
168
  end
@@ -44,8 +44,17 @@ module Tapioca
44
44
  # https://github.com/aasm/aasm/blob/0e03746/lib/aasm/core/event.rb#L21-L29
45
45
  EVENT_CALLBACKS =
46
46
  T.let(
47
- ["after", "after_commit", "after_transaction", "before", "before_transaction", "ensure", "error",
48
- "before_success", "success",].freeze,
47
+ [
48
+ "after",
49
+ "after_commit",
50
+ "after_transaction",
51
+ "before",
52
+ "before_transaction",
53
+ "ensure",
54
+ "error",
55
+ "before_success",
56
+ "success",
57
+ ].freeze,
49
58
  T::Array[String],
50
59
  )
51
60
 
@@ -0,0 +1,163 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_record"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ require "tapioca/dsl/helpers/active_record_column_type_helper"
11
+ require "tapioca/dsl/helpers/active_record_constants_helper"
12
+
13
+ module Tapioca
14
+ module Dsl
15
+ module Compilers
16
+ # `Tapioca::Dsl::Compilers::DelegatedTypes` defines RBI files for subclasses of
17
+ # [`ActiveRecord::Base`](https://api.rubyonrails.org/classes/ActiveRecord/Base.html).
18
+ # This compiler is only responsible for defining the methods that would be created for delegated_types that
19
+ # are defined in the Active Record model.
20
+ #
21
+ # For example, with the following model class:
22
+ #
23
+ # ~~~rb
24
+ # class Entry < ActiveRecord::Base
25
+ # delegated_type :entryable, types: %w[ Message Comment ]
26
+ # end
27
+ # ~~~
28
+ #
29
+ # this compiler will produce the following methods in the RBI file
30
+ # `entry.rbi`:
31
+ #
32
+ # ~~~rbi
33
+ # # entry.rbi
34
+ # # typed: true
35
+ #
36
+ # class Entry
37
+ # include GeneratedDelegatedTypeMethods
38
+ #
39
+ # module GeneratedDelegatedTypeMethods
40
+ # sig { params(args: T.untyped).returns(T.any(Message, Comment)) }
41
+ # def build_entryable(*args); end
42
+ #
43
+ # sig { returns(Class) }
44
+ # def entryable_class; end
45
+ #
46
+ # sig { returns(ActiveSupport::StringInquirer) }
47
+ # def entryable_name; end
48
+ #
49
+ # sig { returns(T::Boolean) }
50
+ # def message?; end
51
+ #
52
+ # sig { returns(T.nilable(Message)) }
53
+ # def message; end
54
+ #
55
+ # sig { returns(T.nilable(Integer)) }
56
+ # def message_id; end
57
+ #
58
+ # sig { returns(T::Boolean) }
59
+ # def comment?; end
60
+ #
61
+ # sig { returns(T.nilable(Comment)) }
62
+ # def comment; end
63
+ #
64
+ # sig { returns(T.nilable(Integer)) }
65
+ # def comment_id; end
66
+ # end
67
+ # end
68
+ #
69
+ # ~~~
70
+ class ActiveRecordDelegatedTypes < Compiler
71
+ extend T::Sig
72
+ include Helpers::ActiveRecordConstantsHelper
73
+
74
+ ConstantType = type_member { { fixed: T.all(T.class_of(ActiveRecord::Base), Extensions::ActiveRecord) } }
75
+
76
+ sig { override.void }
77
+ def decorate
78
+ return if constant.__tapioca_delegated_types.nil?
79
+
80
+ root.create_path(constant) do |model|
81
+ model.create_module(DelegatedTypesModuleName) do |mod|
82
+ constant.__tapioca_delegated_types.each do |role, data|
83
+ types = data.fetch(:types)
84
+ options = data.fetch(:options, {})
85
+ populate_role_accessors(mod, role, types)
86
+ populate_type_helpers(mod, role, types, options)
87
+ end
88
+ end
89
+
90
+ model.create_include(DelegatedTypesModuleName)
91
+ end
92
+ end
93
+
94
+ class << self
95
+ extend T::Sig
96
+
97
+ sig { override.returns(T::Enumerable[Module]) }
98
+ def gather_constants
99
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ sig { params(mod: RBI::Scope, role: Symbol, types: T::Array[String]).void }
106
+ def populate_role_accessors(mod, role, types)
107
+ mod.create_method(
108
+ "#{role}_name",
109
+ parameters: [],
110
+ return_type: "ActiveSupport::StringInquirer",
111
+ )
112
+
113
+ mod.create_method(
114
+ "#{role}_class",
115
+ parameters: [],
116
+ return_type: "Class",
117
+ )
118
+
119
+ mod.create_method(
120
+ "build_#{role}",
121
+ parameters: [create_rest_param("args", type: "T.untyped")],
122
+ return_type: "T.any(#{types.join(", ")})",
123
+ )
124
+ end
125
+
126
+ sig { params(mod: RBI::Scope, role: Symbol, types: T::Array[String], options: T::Hash[Symbol, T.untyped]).void }
127
+ def populate_type_helpers(mod, role, types, options)
128
+ types.each do |type|
129
+ populate_type_helper(mod, role, type, options)
130
+ end
131
+ end
132
+
133
+ sig { params(mod: RBI::Scope, role: Symbol, type: String, options: T::Hash[Symbol, T.untyped]).void }
134
+ def populate_type_helper(mod, role, type, options)
135
+ singular = type.tableize.tr("/", "_").singularize
136
+ query = "#{singular}?"
137
+ primary_key = options[:primary_key] || "id"
138
+ role_id = options[:foreign_key] || "#{role}_id"
139
+
140
+ getter_type, _ = Helpers::ActiveRecordColumnTypeHelper.new(constant).type_for(role_id.to_s)
141
+
142
+ mod.create_method(
143
+ query,
144
+ parameters: [],
145
+ return_type: "T::Boolean",
146
+ )
147
+
148
+ mod.create_method(
149
+ singular,
150
+ parameters: [],
151
+ return_type: "T.nilable(#{type})",
152
+ )
153
+
154
+ mod.create_method(
155
+ "#{singular}_#{primary_key}",
156
+ parameters: [],
157
+ return_type: as_nilable_type(getter_type),
158
+ )
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -72,11 +72,14 @@ module Tapioca
72
72
 
73
73
  sig { returns(Class) }
74
74
  def fixture_loader
75
- @fixture_loader ||= T.let(Class.new do
76
- T.unsafe(self).include(ActiveRecord::TestFixtures)
77
- T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
78
- T.unsafe(self).fixtures(:all)
79
- end, T.nilable(Class))
75
+ @fixture_loader ||= T.let(
76
+ Class.new do
77
+ T.unsafe(self).include(ActiveRecord::TestFixtures)
78
+ T.unsafe(self).fixture_path = Rails.root.join("test", "fixtures")
79
+ T.unsafe(self).fixtures(:all)
80
+ end,
81
+ T.nilable(Class),
82
+ )
80
83
  end
81
84
 
82
85
  sig { returns(T::Array[String]) }
@@ -196,27 +196,34 @@ module Tapioca
196
196
  T::Array[Symbol],
197
197
  )
198
198
 
199
- QUERY_METHODS = T.let(begin
200
- # Grab all Query methods
201
- query_methods = ActiveRecord::QueryMethods.instance_methods(false)
202
- # Grab all Spawn methods
203
- query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
204
- # Remove the ones we know are private API
205
- query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
206
- # Remove "where" which needs a custom return type for WhereChains
207
- query_methods -= [:where]
208
- # Remove the methods that ...
209
- query_methods
210
- .grep_v(/_clause$/) # end with "_clause"
211
- .grep_v(/_values?$/) # end with "_value" or "_values"
212
- .grep_v(/=$/) # end with "=""
213
- .grep_v(/(?<!uniq)!$/) # end with "!" except for "uniq!"
214
- end, T::Array[Symbol])
199
+ QUERY_METHODS = T.let(
200
+ begin
201
+ # Grab all Query methods
202
+ query_methods = ActiveRecord::QueryMethods.instance_methods(false)
203
+ # Grab all Spawn methods
204
+ query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
205
+ # Remove the ones we know are private API
206
+ query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
207
+ # Remove "where" which needs a custom return type for WhereChains
208
+ query_methods -= [:where]
209
+ # Remove the methods that ...
210
+ query_methods
211
+ .grep_v(/_clause$/) # end with "_clause"
212
+ .grep_v(/_values?$/) # end with "_value" or "_values"
213
+ .grep_v(/=$/) # end with "=""
214
+ .grep_v(/(?<!uniq)!$/) # end with "!" except for "uniq!"
215
+ end,
216
+ T::Array[Symbol],
217
+ )
215
218
  WHERE_CHAIN_QUERY_METHODS = T.let(
216
219
  ActiveRecord::QueryMethods::WhereChain.instance_methods(false),
217
220
  T::Array[Symbol],
218
221
  )
219
222
  FINDER_METHODS = T.let(ActiveRecord::FinderMethods.instance_methods(false), T::Array[Symbol])
223
+ SIGNED_FINDER_METHODS = T.let(
224
+ defined?(ActiveRecord::SignedId) ? ActiveRecord::SignedId::ClassMethods.instance_methods(false) : [],
225
+ T::Array[Symbol],
226
+ )
220
227
  CALCULATION_METHODS = T.let(ActiveRecord::Calculations.instance_methods(false), T::Array[Symbol])
221
228
  ENUMERABLE_QUERY_METHODS = T.let([:any?, :many?, :none?, :one?], T::Array[Symbol])
222
229
  FIND_OR_CREATE_METHODS = T.let(
@@ -593,6 +600,31 @@ module Tapioca
593
600
  end
594
601
  end
595
602
 
603
+ SIGNED_FINDER_METHODS.each do |method_name|
604
+ case method_name
605
+ when :find_signed
606
+ create_common_method(
607
+ "find_signed",
608
+ common_relation_methods_module,
609
+ parameters: [
610
+ create_param("signed_id", type: "T.untyped"),
611
+ create_kw_opt_param("purpose", type: "T.untyped", default: "nil"),
612
+ ],
613
+ return_type: as_nilable_type(constant_name),
614
+ )
615
+ when :find_signed!
616
+ create_common_method(
617
+ "find_signed!",
618
+ common_relation_methods_module,
619
+ parameters: [
620
+ create_param("signed_id", type: "T.untyped"),
621
+ create_kw_opt_param("purpose", type: "T.untyped", default: "nil"),
622
+ ],
623
+ return_type: constant_name,
624
+ )
625
+ end
626
+ end
627
+
596
628
  CALCULATION_METHODS.each do |method_name|
597
629
  case method_name
598
630
  when :average, :maximum, :minimum
@@ -126,17 +126,20 @@ module Tapioca
126
126
 
127
127
  private
128
128
 
129
- TYPES = T.let({
130
- boolean: "T::Boolean",
131
- integer: "Integer",
132
- string: "String",
133
- float: "Float",
134
- date: "Date",
135
- time: "Time",
136
- datetime: "DateTime",
137
- decimal: "BigDecimal",
138
- any: "T.untyped",
139
- }.freeze, T::Hash[Symbol, String])
129
+ TYPES = T.let(
130
+ {
131
+ boolean: "T::Boolean",
132
+ integer: "Integer",
133
+ string: "String",
134
+ float: "Float",
135
+ date: "Date",
136
+ time: "Time",
137
+ datetime: "DateTime",
138
+ decimal: "BigDecimal",
139
+ any: "T.untyped",
140
+ }.freeze,
141
+ T::Hash[Symbol, String],
142
+ )
140
143
 
141
144
  sig { params(attr_type: Symbol).returns(String) }
142
145
  def type_for(attr_type)
@@ -85,18 +85,21 @@ module Tapioca
85
85
 
86
86
  private
87
87
 
88
- TYPES = T.let({
89
- boolean: "T::Boolean",
90
- integer: "Integer",
91
- string: "String",
92
- float: "Float",
93
- date: "Date",
94
- time: "Time",
95
- datetime: "DateTime",
96
- decimal: "BigDecimal",
97
- binary: "String",
98
- text: "String",
99
- }.freeze, T::Hash[Symbol, String])
88
+ TYPES = T.let(
89
+ {
90
+ boolean: "T::Boolean",
91
+ integer: "Integer",
92
+ string: "String",
93
+ float: "Float",
94
+ date: "Date",
95
+ time: "Time",
96
+ datetime: "DateTime",
97
+ decimal: "BigDecimal",
98
+ binary: "String",
99
+ text: "String",
100
+ }.freeze,
101
+ T::Hash[Symbol, String],
102
+ )
100
103
 
101
104
  sig { params(attr_type: Symbol).returns(String) }
102
105
  def type_for(attr_type)
@@ -109,9 +112,13 @@ module Tapioca
109
112
 
110
113
  klass.create_method(attribute, return_type: return_type)
111
114
  klass.create_method("#{attribute}?", return_type: "T::Boolean")
112
- klass.create_method("#{attribute}=", parameters: [
113
- create_param("value", type: return_type),
114
- ], return_type: return_type)
115
+ klass.create_method(
116
+ "#{attribute}=",
117
+ parameters: [
118
+ create_param("value", type: return_type),
119
+ ],
120
+ return_type: return_type,
121
+ )
115
122
  end
116
123
  end
117
124
  end
@@ -48,8 +48,10 @@ module Tapioca
48
48
 
49
49
  ConstantType = type_member do
50
50
  {
51
- fixed: T.all(Module,
52
- ::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods),
51
+ fixed: T.all(
52
+ Module,
53
+ ::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods,
54
+ ),
53
55
  }
54
56
  end
55
57
 
@@ -284,6 +284,11 @@ module Tapioca
284
284
  return_type: "void",
285
285
  )
286
286
 
287
+ klass.create_method(
288
+ "clear_#{field.name}",
289
+ return_type: "void",
290
+ )
291
+
287
292
  field
288
293
  end
289
294
 
@@ -119,10 +119,13 @@ module Tapioca
119
119
  mod.create_method(property.reader.to_s, return_type: type)
120
120
  end
121
121
 
122
- BOOLEANS = T.let([
123
- [true, false],
124
- [false, true],
125
- ].freeze, T::Array[[T::Boolean, T::Boolean]])
122
+ BOOLEANS = T.let(
123
+ [
124
+ [true, false],
125
+ [false, true],
126
+ ].freeze,
127
+ T::Array[[T::Boolean, T::Boolean]],
128
+ )
126
129
 
127
130
  sig { params(property: ::SmartProperties::Property).returns(String) }
128
131
  def type_for(property)
@@ -102,10 +102,13 @@ module Tapioca
102
102
  end
103
103
  end
104
104
 
105
- NON_DISCOVERABLE_INCLUDERS = T.let([
106
- ActionDispatch::IntegrationTest,
107
- ActionView::Helpers,
108
- ], T::Array[Module])
105
+ NON_DISCOVERABLE_INCLUDERS = T.let(
106
+ [
107
+ ActionDispatch::IntegrationTest,
108
+ ActionView::Helpers,
109
+ ],
110
+ T::Array[Module],
111
+ )
109
112
 
110
113
  class << self
111
114
  extend T::Sig
@@ -0,0 +1,29 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_record"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Dsl
12
+ module Compilers
13
+ module Extensions
14
+ module ActiveRecord
15
+ attr_reader :__tapioca_delegated_types
16
+
17
+ def delegated_type(role, types:, **options)
18
+ @__tapioca_delegated_types ||= {}
19
+ @__tapioca_delegated_types[role] = { types: types, options: options }
20
+
21
+ super
22
+ end
23
+
24
+ ::ActiveRecord::Base.singleton_class.prepend(self)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -19,33 +19,7 @@ module Tapioca
19
19
 
20
20
  column_type = @constant.attribute_types[column_name]
21
21
 
22
- getter_type =
23
- case column_type
24
- when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
25
- "::Money"
26
- when ActiveRecord::Type::Integer
27
- "::Integer"
28
- when ActiveRecord::Type::String
29
- "::String"
30
- when ActiveRecord::Type::Date
31
- "::Date"
32
- when ActiveRecord::Type::Decimal
33
- "::BigDecimal"
34
- when ActiveRecord::Type::Float
35
- "::Float"
36
- when ActiveRecord::Type::Boolean
37
- "T::Boolean"
38
- when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
39
- "::Time"
40
- when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
41
- "::ActiveSupport::TimeWithZone"
42
- when ActiveRecord::Enum::EnumType
43
- "::String"
44
- when ActiveRecord::Type::Serialized
45
- serialized_column_type(column_type)
46
- else
47
- handle_unknown_type(column_type)
48
- end
22
+ getter_type = type_for_activerecord_value(column_type)
49
23
 
50
24
  column = @constant.columns_hash[column_name]
51
25
  setter_type =
@@ -71,6 +45,42 @@ module Tapioca
71
45
 
72
46
  private
73
47
 
48
+ sig { params(column_type: T.untyped).returns(String) }
49
+ def type_for_activerecord_value(column_type)
50
+ case column_type
51
+ when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
52
+ "::Money"
53
+ when ActiveRecord::Type::Integer
54
+ "::Integer"
55
+ when ActiveRecord::Type::String
56
+ "::String"
57
+ when ActiveRecord::Type::Date
58
+ "::Date"
59
+ when ActiveRecord::Type::Decimal
60
+ "::BigDecimal"
61
+ when ActiveRecord::Type::Float
62
+ "::Float"
63
+ when ActiveRecord::Type::Boolean
64
+ "T::Boolean"
65
+ when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
66
+ "::Time"
67
+ when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
68
+ "::ActiveSupport::TimeWithZone"
69
+ when ActiveRecord::Enum::EnumType
70
+ "::String"
71
+ when ActiveRecord::Type::Serialized
72
+ serialized_column_type(column_type)
73
+ when defined?(ActiveRecord::ConnectionAdapters::PostgreSQL) &&
74
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore
75
+ "T::Hash[::String, ::String]"
76
+ when defined?(ActiveRecord::ConnectionAdapters::PostgreSQL) &&
77
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
78
+ "T::Array[#{type_for_activerecord_value(column_type.subtype)}]"
79
+ else
80
+ handle_unknown_type(column_type)
81
+ end
82
+ end
83
+
74
84
  sig { params(constant: Module).returns(T::Boolean) }
75
85
  def do_not_generate_strong_types?(constant)
76
86
  Object.const_defined?(:StrongTypeGeneration) &&
@@ -13,6 +13,7 @@ module Tapioca
13
13
 
14
14
  AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
15
15
  AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
16
+ DelegatedTypesModuleName = T.let("GeneratedDelegatedTypeMethods", String)
16
17
 
17
18
  RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
18
19
  AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
@@ -128,9 +128,11 @@ module Tapioca
128
128
  def filter_anonymous_and_reloaded_constants(constants)
129
129
  # Group constants by their names
130
130
  constants_by_name = constants
131
- .group_by { |c| T.must(Runtime::Reflection.name_of(c)) }
131
+ .group_by { |c| Runtime::Reflection.name_of(c) }
132
132
  .select { |name, _| !name.nil? }
133
133
 
134
+ constants_by_name = T.cast(constants_by_name, T::Hash[String, T::Array[Module]])
135
+
134
136
  # Find the constants that have been reloaded
135
137
  reloaded_constants = constants_by_name.select { |_, constants| constants.size > 1 }.keys
136
138
 
@@ -7,16 +7,19 @@ module Tapioca
7
7
  class YardDoc < Base
8
8
  extend T::Sig
9
9
 
10
- IGNORED_COMMENTS = T.let([
11
- ":doc:",
12
- ":nodoc:",
13
- "typed:",
14
- "frozen_string_literal:",
15
- "encoding:",
16
- "warn_indent:",
17
- "shareable_constant_value:",
18
- "rubocop:",
19
- ], T::Array[String])
10
+ IGNORED_COMMENTS = T.let(
11
+ [
12
+ ":doc:",
13
+ ":nodoc:",
14
+ "typed:",
15
+ "frozen_string_literal:",
16
+ "encoding:",
17
+ "warn_indent:",
18
+ "shareable_constant_value:",
19
+ "rubocop:",
20
+ ],
21
+ T::Array[String],
22
+ )
20
23
 
21
24
  IGNORED_SIG_TAGS = T.let(["param", "return"], T::Array[String])
22
25
 
@@ -241,8 +241,7 @@ module Tapioca
241
241
  klass = class_of(value)
242
242
 
243
243
  klass_name = if klass == ObjectSpace::WeakMap
244
- # WeakMap is an implicit generic with one type variable
245
- "ObjectSpace::WeakMap[T.untyped]"
244
+ sorbet_supports?(:non_generic_weak_map) ? "ObjectSpace::WeakMap" : "ObjectSpace::WeakMap[T.untyped]"
246
245
  elsif T::Generic === klass
247
246
  generic_name_of(klass)
248
247
  else
@@ -154,8 +154,12 @@ module Tapioca
154
154
 
155
155
  IGNORED_GEMS = T.let(
156
156
  [
157
- "sorbet", "sorbet-static", "sorbet-runtime", "sorbet-static-and-runtime",
158
- "debug", "fakefs",
157
+ "sorbet",
158
+ "sorbet-static",
159
+ "sorbet-runtime",
160
+ "sorbet-static-and-runtime",
161
+ "debug",
162
+ "fakefs",
159
163
  ].freeze,
160
164
  T::Array[String],
161
165
  )
@@ -5,7 +5,7 @@ module Tapioca
5
5
  module GemHelper
6
6
  extend T::Sig
7
7
 
8
- sig { params(app_dir: String, full_gem_path: String).returns(T::Boolean) }
8
+ sig { params(app_dir: T.any(String, Pathname), full_gem_path: String).returns(T::Boolean) }
9
9
  def gem_in_app_dir?(app_dir, full_gem_path)
10
10
  app_dir = to_realpath(app_dir)
11
11
  full_gem_path = to_realpath(full_gem_path)
@@ -161,7 +161,8 @@ module Tapioca
161
161
  RBI::Parser.parse_file(file)
162
162
  rescue RBI::ParseError => e
163
163
  say_error("\nWarning: #{e} (#{e.location})", :yellow)
164
- end
164
+ nil
165
+ end.compact
165
166
 
166
167
  index.visit_all(trees)
167
168
  end
@@ -211,16 +212,22 @@ module Tapioca
211
212
 
212
213
  sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
213
214
  def extract_methods_and_attrs(nodes)
214
- T.cast(nodes.select do |node|
215
- node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
216
- end, T::Array[T.any(RBI::Method, RBI::Attr)])
215
+ T.cast(
216
+ nodes.select do |node|
217
+ node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
218
+ end,
219
+ T::Array[T.any(RBI::Method, RBI::Attr)],
220
+ )
217
221
  end
218
222
 
219
223
  sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Mixin, RBI::RequiresAncestor)]) }
220
224
  def extract_mixins(nodes)
221
- T.cast(nodes.select do |node|
222
- node.is_a?(RBI::Mixin) || node.is_a?(RBI::RequiresAncestor)
223
- end, T::Array[T.all(RBI::Mixin, RBI::RequiresAncestor)])
225
+ T.cast(
226
+ nodes.select do |node|
227
+ node.is_a?(RBI::Mixin) || node.is_a?(RBI::RequiresAncestor)
228
+ end,
229
+ T::Array[T.all(RBI::Mixin, RBI::RequiresAncestor)],
230
+ )
224
231
  end
225
232
 
226
233
  sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
@@ -19,9 +19,13 @@ module Tapioca
19
19
 
20
20
  SORBET_PAYLOAD_URL = "https://github.com/sorbet/sorbet/tree/master/rbi"
21
21
 
22
- FEATURE_REQUIREMENTS = T.let({
23
- # feature_name: ::Gem::Requirement.new(">= ___"), # https://github.com/sorbet/sorbet/pull/___
24
- }.freeze, T::Hash[Symbol, ::Gem::Requirement])
22
+ FEATURE_REQUIREMENTS = T.let(
23
+ {
24
+ # feature_name: ::Gem::Requirement.new(">= ___"), # https://github.com/sorbet/sorbet/pull/___
25
+ non_generic_weak_map: ::Gem::Requirement.new(">= 0.5.10587"), # https://github.com/sorbet/sorbet/pull/6610
26
+ }.freeze,
27
+ T::Hash[Symbol, ::Gem::Requirement],
28
+ )
25
29
 
26
30
  sig { params(sorbet_args: String).returns(Spoom::ExecResult) }
27
31
  def sorbet(*sorbet_args)
@@ -7,13 +7,16 @@ module URI
7
7
  class Source < URI::File
8
8
  extend T::Sig
9
9
 
10
- COMPONENT = T.let([
11
- :scheme,
12
- :gem_name,
13
- :gem_version,
14
- :path,
15
- :line_number,
16
- ].freeze, T::Array[Symbol])
10
+ COMPONENT = T.let(
11
+ [
12
+ :scheme,
13
+ :gem_name,
14
+ :gem_version,
15
+ :path,
16
+ :line_number,
17
+ ].freeze,
18
+ T::Array[Symbol],
19
+ )
17
20
 
18
21
  alias_method(:gem_name, :host)
19
22
  alias_method(:line_number, :fragment)
@@ -18,10 +18,12 @@ module Tapioca
18
18
  ).void
19
19
  end
20
20
  def load_application(bundle:, prerequire:, postrequire:, default_command:)
21
- loader = new(bundle: bundle,
21
+ loader = new(
22
+ bundle: bundle,
22
23
  prerequire: prerequire,
23
24
  postrequire: postrequire,
24
- default_command: default_command)
25
+ default_command: default_command,
26
+ )
25
27
  loader.load
26
28
  end
27
29
  end
@@ -47,9 +47,12 @@ module Tapioca
47
47
 
48
48
  eager_load_rails_app if eager_load
49
49
  rescue LoadError, StandardError => e
50
- say("Tapioca attempted to load the Rails application after encountering a `config/application.rb` file, " \
51
- "but it failed. If your application uses Rails please ensure it can be loaded correctly before generating " \
52
- "RBIs.\n#{e}", :yellow)
50
+ say(
51
+ "Tapioca attempted to load the Rails application after encountering a `config/application.rb` file, " \
52
+ "but it failed. If your application uses Rails please ensure it can be loaded correctly before " \
53
+ "generating RBIs.\n#{e}",
54
+ :yellow,
55
+ )
53
56
  say("Continuing RBI generation without loading the Rails application.")
54
57
  end
55
58
 
@@ -91,8 +91,13 @@ module RBI
91
91
  return unless Tapioca::RBIHelper.valid_method_name?(name)
92
92
 
93
93
  sig = RBI::Sig.new(return_type: return_type)
94
- method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method, visibility: visibility,
95
- comments: comments)
94
+ method = RBI::Method.new(
95
+ name,
96
+ sigs: [sig],
97
+ is_singleton: class_method,
98
+ visibility: visibility,
99
+ comments: comments,
100
+ )
96
101
  parameters.each do |param|
97
102
  method << param.param
98
103
  sig << RBI::SigParam.new(param.param.name, param.type)
@@ -26,12 +26,15 @@ module Tapioca
26
26
  end
27
27
  end
28
28
 
29
- DEFAULT_RBI_FORMATTER = T.let(RBIFormatter.new(
30
- add_sig_templates: false,
31
- group_nodes: true,
32
- max_line_length: nil,
33
- nest_singleton_methods: true,
34
- nest_non_public_methods: true,
35
- sort_nodes: true,
36
- ), RBIFormatter)
29
+ DEFAULT_RBI_FORMATTER = T.let(
30
+ RBIFormatter.new(
31
+ add_sig_templates: false,
32
+ group_nodes: true,
33
+ max_line_length: nil,
34
+ nest_singleton_methods: true,
35
+ nest_non_public_methods: true,
36
+ sort_nodes: true,
37
+ ),
38
+ RBIFormatter,
39
+ )
37
40
  end
@@ -13,6 +13,23 @@ module Tapioca
13
13
  class << self
14
14
  extend T::Sig
15
15
 
16
+ sig do
17
+ type_parameters(:Return)
18
+ .params(blk: T.proc.returns(T.type_parameter(:Return)))
19
+ .returns(T.type_parameter(:Return))
20
+ end
21
+ def with_trackers_enabled(&blk)
22
+ # Currently this is a dirty hack to ensure disabling trackers
23
+ # doesn't work while in the block passed to this method.
24
+ disable_all_method = method(:disable_all!)
25
+ define_singleton_method(:disable_all!) {}
26
+ blk.call
27
+ ensure
28
+ if disable_all_method
29
+ define_singleton_method(:disable_all!, disable_all_method)
30
+ end
31
+ end
32
+
16
33
  sig { void }
17
34
  def disable_all!
18
35
  @trackers.each(&:disable!)
@@ -62,8 +62,8 @@ module T
62
62
  # we've created a clone of that type with the `name` method returning the
63
63
  # appropriate name for that specific concrete type.
64
64
  def name
65
- if T::Generic === @raw_type || Tapioca::TypeVariableModule === @raw_type
66
- # for types that are generic or are type variables, use the name
65
+ if T::Generic === @raw_type
66
+ # for types that are generic, use the name
67
67
  # returned by the "name" method of this instance
68
68
  @name ||= T.unsafe(@raw_type).name.freeze
69
69
  else
@@ -78,20 +78,42 @@ module T
78
78
  end
79
79
 
80
80
  module Utils
81
- module CoercePatch
82
- def coerce(val)
83
- if val.is_a?(Tapioca::TypeVariableModule)
84
- val.coerce_to_type_variable
85
- elsif val.respond_to?(:__tapioca_override_type)
86
- val.__tapioca_override_type
87
- else
88
- super
81
+ # This duplication is required to preserve backwards compatibility with sorbet-runtime versions prior to the
82
+ # introduction of the `Private` module in https://github.com/sorbet/sorbet/pull/6559.
83
+ if defined?(T::Utils::Private)
84
+ module Private
85
+ module PrivateCoercePatch
86
+ def coerce_and_check_module_types(val, check_val, check_module_type)
87
+ if val.is_a?(Tapioca::TypeVariableModule)
88
+ val.coerce_to_type_variable
89
+ elsif val.respond_to?(:__tapioca_override_type)
90
+ val.__tapioca_override_type
91
+ else
92
+ super
93
+ end
94
+ end
95
+ end
96
+
97
+ class << self
98
+ prepend(PrivateCoercePatch)
99
+ end
100
+ end
101
+ else
102
+ module CoercePatch
103
+ def coerce(val)
104
+ if val.is_a?(Tapioca::TypeVariableModule)
105
+ val.coerce_to_type_variable
106
+ elsif val.respond_to?(:__tapioca_override_type)
107
+ val.__tapioca_override_type
108
+ else
109
+ super
110
+ end
89
111
  end
90
112
  end
91
- end
92
113
 
93
- class << self
94
- prepend(CoercePatch)
114
+ class << self
115
+ prepend(CoercePatch)
116
+ end
95
117
  end
96
118
  end
97
119
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.10.3"
5
+ VERSION = "0.10.5"
6
6
  end
data/lib/tapioca.rb CHANGED
@@ -43,11 +43,14 @@ module Tapioca
43
43
  DEFAULT_TODO_FILE = T.let("#{DEFAULT_RBI_DIR}/todo.rbi", String)
44
44
  DEFAULT_ANNOTATIONS_DIR = T.let("#{DEFAULT_RBI_DIR}/annotations", String)
45
45
 
46
- DEFAULT_OVERRIDES = T.let({
47
- # ActiveSupport overrides some core methods with different signatures
48
- # so we generate a typed: false RBI for it to suppress errors
49
- "activesupport" => "false",
50
- }.freeze, T::Hash[String, String])
46
+ DEFAULT_OVERRIDES = T.let(
47
+ {
48
+ # ActiveSupport overrides some core methods with different signatures
49
+ # so we generate a typed: false RBI for it to suppress errors
50
+ "activesupport" => "false",
51
+ }.freeze,
52
+ T::Hash[String, String],
53
+ )
51
54
 
52
55
  DEFAULT_RBI_MAX_LINE_LENGTH = 120
53
56
  DEFAULT_ENVIRONMENT = "development"
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.10.3
4
+ version: 0.10.5
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-11-10 00:00:00.000000000 Z
14
+ date: 2023-01-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -81,14 +81,14 @@ dependencies:
81
81
  requirements:
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
- version: 0.5.9892
84
+ version: 0.5.10187
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.9892
91
+ version: 0.5.10187
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: spoom
94
94
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +171,7 @@ files:
171
171
  - lib/tapioca/dsl/compilers/active_model_secure_password.rb
172
172
  - lib/tapioca/dsl/compilers/active_record_associations.rb
173
173
  - lib/tapioca/dsl/compilers/active_record_columns.rb
174
+ - lib/tapioca/dsl/compilers/active_record_delegated_types.rb
174
175
  - lib/tapioca/dsl/compilers/active_record_enum.rb
175
176
  - lib/tapioca/dsl/compilers/active_record_fixtures.rb
176
177
  - lib/tapioca/dsl/compilers/active_record_relations.rb
@@ -192,6 +193,7 @@ files:
192
193
  - lib/tapioca/dsl/compilers/smart_properties.rb
193
194
  - lib/tapioca/dsl/compilers/state_machines.rb
194
195
  - lib/tapioca/dsl/compilers/url_helpers.rb
196
+ - lib/tapioca/dsl/extensions/active_record.rb
195
197
  - lib/tapioca/dsl/extensions/frozen_record.rb
196
198
  - lib/tapioca/dsl/helpers/active_record_column_type_helper.rb
197
199
  - lib/tapioca/dsl/helpers/active_record_constants_helper.rb