tapioca 0.11.8 → 0.11.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -49
  3. data/lib/tapioca/cli.rb +76 -67
  4. data/lib/tapioca/commands/{dsl.rb → abstract_dsl.rb} +32 -78
  5. data/lib/tapioca/commands/{gem.rb → abstract_gem.rb} +26 -93
  6. data/lib/tapioca/commands/annotations.rb +9 -7
  7. data/lib/tapioca/commands/check_shims.rb +2 -0
  8. data/lib/tapioca/commands/command.rb +9 -2
  9. data/lib/tapioca/commands/configure.rb +2 -2
  10. data/lib/tapioca/commands/dsl_compiler_list.rb +31 -0
  11. data/lib/tapioca/commands/dsl_generate.rb +40 -0
  12. data/lib/tapioca/commands/dsl_verify.rb +25 -0
  13. data/lib/tapioca/commands/gem_generate.rb +51 -0
  14. data/lib/tapioca/commands/gem_sync.rb +37 -0
  15. data/lib/tapioca/commands/gem_verify.rb +36 -0
  16. data/lib/tapioca/commands/require.rb +2 -0
  17. data/lib/tapioca/commands/todo.rb +21 -2
  18. data/lib/tapioca/commands.rb +8 -2
  19. data/lib/tapioca/dsl/compiler.rb +8 -4
  20. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +3 -1
  21. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +94 -0
  22. data/lib/tapioca/dsl/compilers/active_record_columns.rb +19 -9
  23. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +3 -2
  24. data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
  25. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +1 -1
  26. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +9 -2
  27. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +208 -0
  28. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +20 -4
  29. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +20 -3
  30. data/lib/tapioca/dsl/pipeline.rb +6 -4
  31. data/lib/tapioca/gem/pipeline.rb +103 -36
  32. data/lib/tapioca/gemfile.rb +13 -7
  33. data/lib/tapioca/helpers/git_attributes.rb +34 -0
  34. data/lib/tapioca/helpers/test/template.rb +4 -4
  35. data/lib/tapioca/internal.rb +1 -0
  36. data/lib/tapioca/loaders/dsl.rb +11 -1
  37. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +0 -27
  38. data/lib/tapioca/static/symbol_loader.rb +9 -8
  39. data/lib/tapioca/version.rb +1 -1
  40. metadata +19 -10
@@ -6,6 +6,17 @@ module Tapioca
6
6
  class Todo < CommandWithoutTracker
7
7
  include SorbetHelper
8
8
 
9
+ DEPRECATION_MESSAGE = T.let(<<~DEPRECATION, String)
10
+ The `todo` command is deprecated and will be removed in a future release.
11
+
12
+ If your project is still missing type definitions for constants, try the following:
13
+ 1. Regenerate gem RBIs by running `bin/tapioca gem --all` and `bin/tapioca annotations`
14
+ 2. Generate RBIs for DSLs by running `bin/tapioca dsl`
15
+ 3. If the missing constants are defined in files that a gem does not load by default,
16
+ manually require those files in `sorbet/tapioca/require.rb` and regenerate gem RBIs
17
+ 4. Manually create an RBI shim defining the missing constants
18
+ DEPRECATION
19
+
9
20
  sig do
10
21
  params(
11
22
  todo_file: String,
@@ -19,6 +30,16 @@ module Tapioca
19
30
  super()
20
31
  end
21
32
 
33
+ sig { void }
34
+ def run_with_deprecation
35
+ say(DEPRECATION_MESSAGE, :red)
36
+ say("")
37
+
38
+ run
39
+ end
40
+
41
+ private
42
+
22
43
  sig { override.void }
23
44
  def execute
24
45
  say("Finding all unresolved constants, this may take a few seconds... ")
@@ -43,8 +64,6 @@ module Tapioca
43
64
  say("Please review changes and commit them.", [:green, :bold])
44
65
  end
45
66
 
46
- private
47
-
48
67
  sig { params(constants: T::Array[String], command: String).returns(RBI::File) }
49
68
  def rbi(constants, command:)
50
69
  file = RBI::File.new
@@ -7,9 +7,15 @@ module Tapioca
7
7
  autoload :CommandWithoutTracker, "tapioca/commands/command_without_tracker"
8
8
  autoload :Annotations, "tapioca/commands/annotations"
9
9
  autoload :CheckShims, "tapioca/commands/check_shims"
10
- autoload :Dsl, "tapioca/commands/dsl"
10
+ autoload :AbstractDsl, "tapioca/commands/abstract_dsl"
11
+ autoload :DslCompilerList, "tapioca/commands/dsl_compiler_list"
12
+ autoload :DslGenerate, "tapioca/commands/dsl_generate"
13
+ autoload :DslVerify, "tapioca/commands/dsl_verify"
11
14
  autoload :Configure, "tapioca/commands/configure"
12
- autoload :Gem, "tapioca/commands/gem"
15
+ autoload :AbstractGem, "tapioca/commands/abstract_gem"
16
+ autoload :GemGenerate, "tapioca/commands/gem_generate"
17
+ autoload :GemSync, "tapioca/commands/gem_sync"
18
+ autoload :GemVerify, "tapioca/commands/gem_verify"
13
19
  autoload :Require, "tapioca/commands/require"
14
20
  autoload :Todo, "tapioca/commands/todo"
15
21
  end
@@ -45,14 +45,18 @@ module Tapioca
45
45
 
46
46
  sig { returns(T::Enumerable[T::Class[T.anything]]) }
47
47
  def all_classes
48
- @all_classes = T.let(@all_classes, T.nilable(T::Enumerable[T::Class[T.anything]]))
49
- @all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[T::Class[T.anything]]).each
48
+ @all_classes ||= T.let(
49
+ T.cast(ObjectSpace.each_object(Class), T::Enumerable[T::Class[T.anything]]).each,
50
+ T.nilable(T::Enumerable[T::Class[T.anything]]),
51
+ )
50
52
  end
51
53
 
52
54
  sig { returns(T::Enumerable[Module]) }
53
55
  def all_modules
54
- @all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
55
- @all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
56
+ @all_modules ||= T.let(
57
+ T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each,
58
+ T.nilable(T::Enumerable[Module]),
59
+ )
56
60
  end
57
61
  end
58
62
 
@@ -126,7 +126,9 @@ module Tapioca
126
126
 
127
127
  sig { override.returns(T::Enumerable[Module]) }
128
128
  def gather_constants
129
- descendants_of(::ActionController::Base).reject(&:abstract?).select(&:name)
129
+ descendants_of(::ActionController::Base).select(&:name).select do |klass|
130
+ klass.const_defined?(:HelperMethods, false)
131
+ end
130
132
  end
131
133
  end
132
134
 
@@ -0,0 +1,94 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_model"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Dsl
12
+ module Compilers
13
+ # `Tapioca::Dsl::Compilers::ActiveModelValidationsConfirmation` decorates RBI files for all
14
+ # classes that use [`ActiveModel::Validates::Confirmation`](https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_confirmation_of).
15
+ #
16
+ # For example, with the following class:
17
+ #
18
+ # ~~~rb
19
+ # class User
20
+ # include ActiveModel::Validations
21
+ #
22
+ # validates_confirmation_of :password
23
+ #
24
+ # validates :email, confirmation: true
25
+ # end
26
+ # ~~~
27
+ #
28
+ # this compiler will produce an RBI file with the following content:
29
+ # ~~~rbi
30
+ # # typed: true
31
+ #
32
+ # class User
33
+ #
34
+ # sig { returns(T.untyped) }
35
+ # def email_confirmation; end
36
+ #
37
+ # sig { params(email_confirmation=: T.untyped).returns(T.untyped) }
38
+ # def email_confirmation=(email_confirmation); end
39
+ #
40
+ # sig { returns(T.untyped) }
41
+ # def password_confirmation; end
42
+ #
43
+ # sig { params(password_confirmation=: T.untyped).returns(T.untyped) }
44
+ # def password_confirmation=(password_confirmation); end
45
+ # end
46
+ # ~~~
47
+ class ActiveModelValidationsConfirmation < Compiler
48
+ extend T::Sig
49
+
50
+ ConstantType = type_member do
51
+ {
52
+ fixed: T.all(
53
+ T::Class[ActiveModel::Validations],
54
+ ActiveModel::Validations::HelperMethods,
55
+ ActiveModel::Validations::ClassMethods,
56
+ ),
57
+ }
58
+ end
59
+
60
+ class << self
61
+ sig { override.returns(T::Enumerable[Module]) }
62
+ def gather_constants
63
+ # Collect all the classes that include ActiveModel::Validations
64
+ all_classes.select do |c|
65
+ c < ActiveModel::Validations
66
+ end
67
+ end
68
+ end
69
+
70
+ sig { override.void }
71
+ def decorate
72
+ confirmation_validators = constant.validators.grep(ActiveModel::Validations::ConfirmationValidator)
73
+
74
+ return if confirmation_validators.empty?
75
+
76
+ # Create a RBI definition for each class that includes Active::Model::Validations
77
+ root.create_path(constant) do |klass|
78
+ # Create RBI definitions for all the attributes that use confirmation validation
79
+ confirmation_validators.each do |validator|
80
+ validator.attributes.each do |attr_name|
81
+ klass.create_method("#{attr_name}_confirmation", return_type: "T.untyped")
82
+ klass.create_method(
83
+ "#{attr_name}_confirmation=",
84
+ parameters: [create_param("#{attr_name}_confirmation", type: "T.untyped")],
85
+ return_type: "T.untyped",
86
+ )
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -110,8 +110,8 @@ module Tapioca
110
110
 
111
111
  root.create_path(constant) do |model|
112
112
  model.create_module(AttributeMethodsModuleName) do |mod|
113
- constant.attribute_names.each do |column_name|
114
- add_methods_for_attribute(mod, column_name)
113
+ (constant.attribute_names + ["id"]).uniq.each do |attribute_name|
114
+ add_methods_for_attribute(mod, attribute_name)
115
115
  end
116
116
 
117
117
  constant.attribute_aliases.each do |attribute_name, column_name|
@@ -127,7 +127,7 @@ module Tapioca
127
127
  old_method_names = patterns.map { |m| m.method_name(column_name) }
128
128
  methods_to_add = new_method_names - old_method_names
129
129
 
130
- add_methods_for_attribute(mod, column_name, attribute_name, methods_to_add)
130
+ add_methods_for_attribute(mod, attribute_name, column_name, methods_to_add)
131
131
  end
132
132
  end
133
133
 
@@ -152,13 +152,13 @@ module Tapioca
152
152
  name: String,
153
153
  methods_to_add: T.nilable(T::Array[String]),
154
154
  return_type: String,
155
- parameters: T::Array[[String, String]],
155
+ parameters: T::Array[RBI::TypedParam],
156
156
  ).void
157
157
  end
158
158
  def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])
159
159
  klass.create_method(
160
160
  name,
161
- parameters: parameters.map { |param, type| create_param(param, type: type) },
161
+ parameters: parameters,
162
162
  return_type: return_type,
163
163
  ) if methods_to_add.nil? || methods_to_add.include?(name)
164
164
  end
@@ -166,13 +166,15 @@ module Tapioca
166
166
  sig do
167
167
  params(
168
168
  klass: RBI::Scope,
169
- column_name: String,
170
169
  attribute_name: String,
170
+ column_name: String,
171
171
  methods_to_add: T.nilable(T::Array[String]),
172
172
  ).void
173
173
  end
174
- def add_methods_for_attribute(klass, column_name, attribute_name = column_name, methods_to_add = nil)
175
- getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper.new(constant).type_for(column_name)
174
+ def add_methods_for_attribute(klass, attribute_name, column_name = attribute_name, methods_to_add = nil)
175
+ getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper
176
+ .new(constant)
177
+ .type_for(attribute_name, column_name)
176
178
 
177
179
  # Added by ActiveRecord::AttributeMethods::Read
178
180
  #
@@ -189,7 +191,7 @@ module Tapioca
189
191
  klass,
190
192
  "#{attribute_name}=",
191
193
  methods_to_add,
192
- parameters: [["value", setter_type]],
194
+ parameters: [create_param("value", type: setter_type)],
193
195
  return_type: setter_type,
194
196
  )
195
197
 
@@ -254,6 +256,10 @@ module Tapioca
254
256
  "#{attribute_name}_changed?",
255
257
  methods_to_add,
256
258
  return_type: "T::Boolean",
259
+ parameters: [
260
+ create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
261
+ create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
262
+ ],
257
263
  )
258
264
  add_method(
259
265
  klass,
@@ -277,6 +283,10 @@ module Tapioca
277
283
  "#{attribute_name}_previously_changed?",
278
284
  methods_to_add,
279
285
  return_type: "T::Boolean",
286
+ parameters: [
287
+ create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
288
+ create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
289
+ ],
280
290
  )
281
291
  add_method(
282
292
  klass,
@@ -98,6 +98,9 @@ module Tapioca
98
98
  return if stores.values.all? { |store| store.accessors.empty? }
99
99
 
100
100
  root.create_path(constant) do |model|
101
+ store_accessors_module = model.create_module("StoreAccessors")
102
+ model.create_include("StoreAccessors")
103
+
101
104
  stores.values.each do |store_data|
102
105
  store_data.accessors.each do |accessor, name|
103
106
  field = store_data.fields.fetch(accessor)
@@ -105,9 +108,7 @@ module Tapioca
105
108
  type = as_nilable_type(type) if field.null
106
109
  name ||= field.name # support < 1.5.0
107
110
 
108
- store_accessors_module = model.create_module("StoreAccessors")
109
111
  generate_methods(store_accessors_module, name.to_s, type)
110
- model.create_include("StoreAccessors")
111
112
  end
112
113
  end
113
114
  end
@@ -85,7 +85,7 @@ module Tapioca
85
85
 
86
86
  sig { params(concern: Module).returns(T::Array[Module]) }
87
87
  def dependencies_of(concern)
88
- concern.instance_variable_get(:@_dependencies)
88
+ concern.instance_variable_get(:@_dependencies) || []
89
89
  end
90
90
  end
91
91
 
@@ -55,7 +55,7 @@ module Tapioca
55
55
  root.create_path(constant) do |input_object|
56
56
  arguments.each do |argument|
57
57
  name = argument.keyword.to_s
58
- input_object.create_method(name, return_type: Helpers::GraphqlTypeHelper.type_for(argument.type))
58
+ input_object.create_method(name, return_type: Helpers::GraphqlTypeHelper.type_for(argument))
59
59
  end
60
60
  end
61
61
  end
@@ -42,7 +42,7 @@ module Tapioca
42
42
  class GraphqlMutation < Compiler
43
43
  extend T::Sig
44
44
 
45
- ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema::InputObject) } }
45
+ ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema::Mutation) } }
46
46
 
47
47
  sig { override.void }
48
48
  def decorate
@@ -59,7 +59,7 @@ module Tapioca
59
59
  params = compile_method_parameters_to_rbi(method_def).map do |param|
60
60
  name = param.param.name
61
61
  argument = arguments_by_name.fetch(name, nil)
62
- create_typed_param(param.param, argument ? Helpers::GraphqlTypeHelper.type_for(argument.type) : "T.untyped")
62
+ create_typed_param(param.param, argument_type(argument))
63
63
  end
64
64
 
65
65
  root.create_path(constant) do |mutation|
@@ -67,6 +67,13 @@ module Tapioca
67
67
  end
68
68
  end
69
69
 
70
+ sig { params(argument: T.nilable(GraphQL::Schema::Argument)).returns(String) }
71
+ def argument_type(argument)
72
+ return "T.untyped" unless argument
73
+
74
+ Helpers::GraphqlTypeHelper.type_for(argument)
75
+ end
76
+
70
77
  class << self
71
78
  extend T::Sig
72
79
 
@@ -0,0 +1,208 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "json_api_client"
6
+ rescue LoadError
7
+ # means JsonApiClient is not installed,
8
+ # so let's not even define the compiler.
9
+ return
10
+ end
11
+
12
+ module Tapioca
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::JsonApiClientResource` generates RBI files for classes that inherit
16
+ # [`JsonApiClient::Resource`](https://github.com/JsonApiClient/json_api_client).
17
+ #
18
+ # For example, with the following classes that inherits `JsonApiClient::Resource`:
19
+ #
20
+ # ~~~rb
21
+ # # user.rb
22
+ # class User < JsonApiClient::Resource
23
+ # has_many :posts
24
+ #
25
+ # property :name, type: :string
26
+ # property :is_admin, type: :boolean, default: false
27
+ # end
28
+ #
29
+ # # post.rb
30
+ # class Post < JsonApiClient::Resource
31
+ # belongs_to :user
32
+ #
33
+ # property :title, type: :string
34
+ # end
35
+ # ~~~
36
+ #
37
+ # this compiler will produce RBI files with the following content:
38
+ #
39
+ # ~~~rbi
40
+ # # user.rbi
41
+ # # typed: strong
42
+ #
43
+ # class User
44
+ # include JsonApiClientResourceGeneratedMethods
45
+ #
46
+ # module JsonApiClientResourceGeneratedMethods
47
+ # sig { returns(T::Boolean) }
48
+ # def is_admin; end
49
+ #
50
+ # sig { params(is_admin: T::Boolean).returns(T::Boolean) }
51
+ # def is_admin=(is_admin); end
52
+ #
53
+ # sig { returns(T.nilable(::String)) }
54
+ # def name; end
55
+ #
56
+ # sig { params(name: T.nilable(::String)).returns(T.nilable(::String)) }
57
+ # def name=(name); end
58
+ #
59
+ # sig { returns(T.nilable(T::Array[Post])) }
60
+ # def posts; end
61
+ #
62
+ # sig { params(posts: T.nilable(T::Array[Post])).returns(T.nilable(T::Array[Post])) }
63
+ # def posts=(posts); end
64
+ # end
65
+ # end
66
+ #
67
+ # # post.rbi
68
+ # # typed: strong
69
+ #
70
+ # class Post
71
+ # include JsonApiClientResourceGeneratedMethods
72
+ #
73
+ # module JsonApiClientResourceGeneratedMethods
74
+ # sig { returns(T.nilable(::String)) }
75
+ # def title; end
76
+ #
77
+ # sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
78
+ # def title=(title); end
79
+ #
80
+ # sig { returns(T.nilable(::String)) }
81
+ # def user_id; end
82
+ #
83
+ # sig { params(user_id: T.nilable(::String)).returns(T.nilable(::String)) }
84
+ # def user_id=(user_id); end
85
+ # end
86
+ # end
87
+ # ~~~
88
+ class JsonApiClientResource < Compiler
89
+ extend T::Sig
90
+
91
+ ConstantType = type_member { { fixed: T.class_of(::JsonApiClient::Resource) } }
92
+
93
+ sig { override.void }
94
+ def decorate
95
+ schema = resource_schema
96
+ return if schema.nil? && constant.associations.empty?
97
+
98
+ root.create_path(constant) do |k|
99
+ module_name = "JsonApiClientResourceGeneratedMethods"
100
+ k.create_module(module_name) do |mod|
101
+ schema&.each_property do |property|
102
+ generate_methods_for_property(mod, property)
103
+ end
104
+
105
+ constant.associations.each do |association|
106
+ generate_methods_for_association(mod, association)
107
+ end
108
+ end
109
+
110
+ k.create_include(module_name)
111
+ end
112
+ end
113
+
114
+ class << self
115
+ extend T::Sig
116
+
117
+ sig { override.returns(T::Enumerable[Module]) }
118
+ def gather_constants
119
+ all_modules.select do |c|
120
+ name_of(c) && c < ::JsonApiClient::Resource
121
+ end
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ sig { returns(T.nilable(::JsonApiClient::Schema)) }
128
+ def resource_schema
129
+ schema = constant.schema
130
+
131
+ # empty? does not exist on JsonApiClient::Schema
132
+ schema if schema.size > 0 # rubocop:disable Style/ZeroLengthPredicate
133
+ end
134
+
135
+ sig do
136
+ params(
137
+ mod: RBI::Scope,
138
+ property: ::JsonApiClient::Schema::Property,
139
+ ).void
140
+ end
141
+ def generate_methods_for_property(mod, property)
142
+ type = type_for(property)
143
+
144
+ name = property.name.to_s
145
+
146
+ mod.create_method(name, return_type: type)
147
+ mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
148
+ end
149
+
150
+ sig { params(property: ::JsonApiClient::Schema::Property).returns(String) }
151
+ def type_for(property)
152
+ type = ::JsonApiClient::Schema::TypeFactory.type_for(property.type)
153
+ return "T.untyped" if type.nil?
154
+
155
+ sorbet_type = if type.respond_to?(:sorbet_type)
156
+ type.sorbet_type
157
+ elsif type == ::JsonApiClient::Schema::Types::Integer
158
+ "::Integer"
159
+ elsif type == ::JsonApiClient::Schema::Types::String
160
+ "::String"
161
+ elsif type == ::JsonApiClient::Schema::Types::Float
162
+ "::Float"
163
+ elsif type == ::JsonApiClient::Schema::Types::Time
164
+ "::Time"
165
+ elsif type == ::JsonApiClient::Schema::Types::Decimal
166
+ "::BigDecimal"
167
+ elsif type == ::JsonApiClient::Schema::Types::Boolean
168
+ "T::Boolean"
169
+ else
170
+ "T.untyped"
171
+ end
172
+
173
+ if property.default.nil?
174
+ as_nilable_type(sorbet_type)
175
+ else
176
+ sorbet_type
177
+ end
178
+ end
179
+
180
+ sig do
181
+ params(
182
+ mod: RBI::Scope,
183
+ association: JsonApiClient::Associations::BaseAssociation,
184
+ ).void
185
+ end
186
+ def generate_methods_for_association(mod, association)
187
+ # If the association is broken, it will raise a NameError when trying to access the association_class
188
+ klass = association.association_class
189
+
190
+ name, type = case association
191
+ when ::JsonApiClient::Associations::BelongsTo::Association
192
+ # id must be a string: # https://jsonapi.org/format/#document-resource-object-identification
193
+ [association.param.to_s, "T.nilable(::String)"]
194
+ when ::JsonApiClient::Associations::HasOne::Association
195
+ [association.attr_name.to_s, "T.nilable(#{klass})"]
196
+ when ::JsonApiClient::Associations::HasMany::Association
197
+ [association.attr_name.to_s, "T.nilable(T::Array[#{klass}])"]
198
+ else
199
+ return # Unsupported association type
200
+ end
201
+
202
+ mod.create_method(name, return_type: type)
203
+ mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -13,8 +13,26 @@ module Tapioca
13
13
  @constant = constant
14
14
  end
15
15
 
16
+ sig { params(attribute_name: String, column_name: String).returns([String, String]) }
17
+ def type_for(attribute_name, column_name = attribute_name)
18
+ return id_type if attribute_name == "id"
19
+
20
+ column_type_for(column_name)
21
+ end
22
+
23
+ private
24
+
25
+ sig { returns([String, String]) }
26
+ def id_type
27
+ if @constant.respond_to?(:composite_primary_key?) && T.unsafe(@constant).composite_primary_key?
28
+ @constant.primary_key.map(&method(:column_type_for)).map { |tuple| "[#{tuple.join(", ")}]" }
29
+ else
30
+ column_type_for(@constant.primary_key)
31
+ end
32
+ end
33
+
16
34
  sig { params(column_name: String).returns([String, String]) }
17
- def type_for(column_name)
35
+ def column_type_for(column_name)
18
36
  return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
19
37
 
20
38
  column = @constant.columns_hash[column_name]
@@ -33,7 +51,7 @@ module Tapioca
33
51
  return [getter_type, as_nilable_type(setter_type)]
34
52
  end
35
53
 
36
- if column_name == @constant.primary_key ||
54
+ if Array(@constant.primary_key).include?(column_name) ||
37
55
  column_name == "created_at" ||
38
56
  column_name == "updated_at"
39
57
  getter_type = as_nilable_type(getter_type)
@@ -42,8 +60,6 @@ module Tapioca
42
60
  [getter_type, setter_type]
43
61
  end
44
62
 
45
- private
46
-
47
63
  sig { params(column_type: T.untyped).returns(String) }
48
64
  def type_for_activerecord_value(column_type)
49
65
  case column_type
@@ -9,8 +9,16 @@ module Tapioca
9
9
 
10
10
  extend T::Sig
11
11
 
12
- sig { params(type: GraphQL::Schema::Wrapper).returns(String) }
13
- def type_for(type)
12
+ sig { params(argument: GraphQL::Schema::Argument).returns(String) }
13
+ def type_for(argument)
14
+ type = if argument.loads
15
+ loads_type = ::GraphQL::Schema::Wrapper.new(argument.loads)
16
+ loads_type = loads_type.to_list_type if argument.type.list?
17
+ loads_type = loads_type.to_non_null_type if argument.type.non_null?
18
+ loads_type
19
+ else
20
+ argument.type
21
+ end
14
22
  unwrapped_type = type.unwrap
15
23
 
16
24
  parsed_type = case unwrapped_type
@@ -39,6 +47,10 @@ module Tapioca
39
47
  end
40
48
  when GraphQL::Schema::InputObject.singleton_class
41
49
  type_for_constant(unwrapped_type)
50
+ when GraphQL::Schema::NonNull.singleton_class
51
+ type_for(unwrapped_type.of_type)
52
+ when Module
53
+ Runtime::Reflection.qualified_name_of(unwrapped_type) || "T.untyped"
42
54
  else
43
55
  "T.untyped"
44
56
  end
@@ -47,7 +59,7 @@ module Tapioca
47
59
  parsed_type = "T::Array[#{parsed_type}]"
48
60
  end
49
61
 
50
- unless type.non_null?
62
+ unless type.non_null? || has_replaceable_default?(argument)
51
63
  parsed_type = RBIHelper.as_nilable_type(parsed_type)
52
64
  end
53
65
 
@@ -60,6 +72,11 @@ module Tapioca
60
72
  def type_for_constant(constant)
61
73
  Runtime::Reflection.qualified_name_of(constant) || "T.untyped"
62
74
  end
75
+
76
+ sig { params(argument: GraphQL::Schema::Argument).returns(T::Boolean) }
77
+ def has_replaceable_default?(argument)
78
+ !!argument.replace_null_with_default? && !argument.default_value.nil?
79
+ end
63
80
  end
64
81
  end
65
82
  end
@@ -105,10 +105,12 @@ module Tapioca
105
105
 
106
106
  sig { returns(T::Array[T.class_of(Compiler)]) }
107
107
  def compilers
108
- @compilers = T.let(@compilers, T.nilable(T::Array[T.class_of(Compiler)]))
109
- @compilers ||= Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
110
- T.must(compiler.name)
111
- end
108
+ @compilers ||= T.let(
109
+ Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
110
+ T.must(compiler.name)
111
+ end,
112
+ T.nilable(T::Array[T.class_of(Compiler)]),
113
+ )
112
114
  end
113
115
 
114
116
  private