tapioca 0.10.3 → 0.10.5

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