Skip to content

Commit 72556e0

Browse files
sambostockbfadclaude
committed
Fully qualify T and NilClass references in generated RBI output
When a class defines a type member named `T` (e.g., via `#: [T]` in RBS), Tapioca's generated RBI files contained unqualified `T::Boolean`, `T.nilable(...)`, etc. These resolve to the type member instead of Sorbet's `::T` module, causing type-checking errors. This commit qualifies all `T` references as `::T` and `NilClass` as `::NilClass` in generated RBI output. These types come from RBS translations (e.g., `bool` → `T::Boolean`, `nil` → `NilClass`) and unambiguously always refer to the top-level constants. Changes: - Add qualification regex to `sanitize_signature_types` as a catch-all for type strings produced by Sorbet's `.to_s` - Update all hardcoded type string literals across DSL compilers/helpers - Qualify `.to_s` output on Sorbet types in gem pipeline listeners - Update `as_nilable_type` to produce `::T.nilable(...)` - Update all test expectations to match qualified output Co-Authored-By: Brad Lindsay <brad.lindsay@shopify.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent daa4f71 commit 72556e0

89 files changed

Lines changed: 2148 additions & 2129 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/tapioca/dsl/compiler.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def add_error(error)
115115
def parameters_types_from_signature(method_def, signature)
116116
params = [] #: Array[String]
117117

118-
return method_def.parameters.map { "T.untyped" } unless signature
118+
return method_def.parameters.map { "::T.untyped" } unless signature
119119

120120
# parameters types
121121
signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
@@ -166,13 +166,13 @@ def compile_method_parameters_to_rbi(method_def)
166166
when :req
167167
create_param(name, type: method_type)
168168
when :opt
169-
create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
169+
create_opt_param(name, type: method_type, default: "::T.unsafe(nil)")
170170
when :rest
171171
create_rest_param(name, type: method_type)
172172
when :keyreq
173173
create_kw_param(name, type: method_type)
174174
when :key
175-
create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
175+
create_kw_opt_param(name, type: method_type, default: "::T.unsafe(nil)")
176176
when :keyrest
177177
create_kw_rest_param(name, type: method_type)
178178
when :block
@@ -186,7 +186,7 @@ def compile_method_parameters_to_rbi(method_def)
186186
#: ((Method | UnboundMethod) method_def) -> String
187187
def compile_method_return_type_to_rbi(method_def)
188188
signature = signature_of(method_def)
189-
return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
189+
return_type = signature.nil? ? "::T.untyped" : name_of_type(signature.return_type)
190190
sanitize_signature_types(return_type)
191191
end
192192
end

lib/tapioca/dsl/compilers/aasm.rb

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ def decorate
8585
name = state.name
8686
name = "#{namespace}_#{name}" if namespace
8787

88-
model.create_constant("STATE_#{name.upcase}", value: "T.let(T.unsafe(nil), Symbol)")
89-
model.create_method("#{name}?", return_type: "T::Boolean")
88+
model.create_constant("STATE_#{name.upcase}", value: "::T.let(::T.unsafe(nil), Symbol)")
89+
model.create_method("#{name}?", return_type: "::T::Boolean")
9090
end
9191

9292
# Create all of the methods for each event
93-
parameters = [create_rest_param("opts", type: "T.untyped")]
93+
parameters = [create_rest_param("opts", type: "::T.untyped")]
9494
state_machine.events.each do |event|
9595
model.create_method(event.name.to_s, parameters: parameters)
9696
model.create_method("#{event.name}!", parameters: parameters)
9797
model.create_method("#{event.name}_without_validation!", parameters: parameters)
98-
model.create_method("may_#{event.name}?", return_type: "T::Boolean")
98+
model.create_method("may_#{event.name}?", return_type: "::T::Boolean")
9999

100100
# For events, if there's a namespace the default methods are created in addition to
101101
# namespaced ones.
@@ -105,7 +105,7 @@ def decorate
105105

106106
model.create_method(name.to_s, parameters: parameters)
107107
model.create_method("#{name}!", parameters: parameters)
108-
model.create_method("may_#{name}?", return_type: "T::Boolean")
108+
model.create_method("may_#{name}?", return_type: "::T::Boolean")
109109

110110
# There's no namespaced method created for `_without_validation`, so skip
111111
# defining a method for:
@@ -118,8 +118,8 @@ def decorate
118118
model.create_method(
119119
"aasm",
120120
parameters: [
121-
create_rest_param("args", type: "T.untyped"),
122-
create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMMachine).void)"),
121+
create_rest_param("args", type: "::T.untyped"),
122+
create_block_param("block", type: "::T.nilable(::T.proc.bind(PrivateAASMMachine).void)"),
123123
],
124124
return_type: "PrivateAASMMachine",
125125
class_method: true,
@@ -132,9 +132,9 @@ def decorate
132132
machine.create_method(
133133
"event",
134134
parameters: [
135-
create_param("name", type: "T.untyped"),
136-
create_opt_param("options", default: "nil", type: "T.untyped"),
137-
create_block_param("block", type: "T.proc.bind(PrivateAASMEvent).void"),
135+
create_param("name", type: "::T.untyped"),
136+
create_opt_param("options", default: "nil", type: "::T.untyped"),
137+
create_block_param("block", type: "::T.proc.bind(PrivateAASMEvent).void"),
138138
],
139139
)
140140

@@ -144,8 +144,8 @@ def decorate
144144
machine.create_method(
145145
method,
146146
parameters: [
147-
create_rest_param("callbacks", type: "T.any(String, Symbol, T::Class[T.anything], Proc)"),
148-
create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
147+
create_rest_param("callbacks", type: "::T.any(String, Symbol, ::T::Class[::T.anything], Proc)"),
148+
create_block_param("block", type: "::T.nilable(::T.proc.bind(#{constant_name}).void)"),
149149
],
150150
)
151151
end
@@ -158,10 +158,10 @@ def decorate
158158
event.create_method(
159159
method,
160160
parameters: [
161-
create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
161+
create_opt_param("symbol", type: "::T.nilable(Symbol)", default: "nil"),
162162
create_block_param(
163163
"block",
164-
type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
164+
type: "::T.nilable(::T.proc.bind(#{constant_name}).params(opts: ::T.untyped).void)",
165165
),
166166
],
167167
)
@@ -170,23 +170,23 @@ def decorate
170170
event.create_method(
171171
"transitions",
172172
parameters: [
173-
create_opt_param("definitions", default: "nil", type: "T.untyped"),
174-
create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMTransition).void)"),
173+
create_opt_param("definitions", default: "nil", type: "::T.untyped"),
174+
create_block_param("block", type: "::T.nilable(::T.proc.bind(PrivateAASMTransition).void)"),
175175
],
176176
)
177177
end
178178

179179
machine.create_class("PrivateAASMTransition", superclass_name: "AASM::Core::Transition") do |transition|
180180
TRANSITION_CALLBACKS.each do |method|
181-
return_type = "T.untyped"
182-
return_type = "T::Boolean" if method == "guard"
181+
return_type = "::T.untyped"
182+
return_type = "::T::Boolean" if method == "guard"
183183

184184
transition.create_method(
185185
method,
186186
parameters: [
187187
create_block_param(
188188
"block",
189-
type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
189+
type: "::T.nilable(::T.proc.bind(#{constant_name}).params(opts: ::T.untyped).void)",
190190
),
191191
],
192192
return_type: return_type,

lib/tapioca/dsl/compilers/action_controller_helpers.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ def create_unknown_proxy_method(helper_methods, method_name)
140140
helper_methods.create_method(
141141
method_name.to_s,
142142
parameters: [
143-
create_rest_param("args", type: "T.untyped"),
144-
create_kw_rest_param("kwargs", type: "T.untyped"),
145-
create_block_param("blk", type: "T.untyped"),
143+
create_rest_param("args", type: "::T.untyped"),
144+
create_kw_rest_param("kwargs", type: "::T.untyped"),
145+
create_block_param("blk", type: "::T.untyped"),
146146
],
147-
return_type: "T.untyped",
147+
return_type: "::T.untyped",
148148
)
149149
end
150150

lib/tapioca/dsl/compilers/action_text.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ def decorate
5959
)
6060
scope.create_method(
6161
"#{name}?",
62-
return_type: "T::Boolean",
62+
return_type: "::T::Boolean",
6363
)
6464
scope.create_method(
6565
"#{name}=",
66-
parameters: [create_param("value", type: "T.nilable(T.any(#{type}, String))")],
67-
return_type: "T.untyped",
66+
parameters: [create_param("value", type: "::T.nilable(::T.any(#{type}, String))")],
67+
return_type: "::T.untyped",
6868
)
6969
end
7070
end

lib/tapioca/dsl/compilers/active_job.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def decorate
5454
job.create_method(
5555
"perform_later",
5656
parameters: perform_later_parameters(parameters, constant_name),
57-
return_type: "T.any(#{constant_name}, FalseClass)",
57+
return_type: "::T.any(#{constant_name}, FalseClass)",
5858
class_method: true,
5959
)
6060

@@ -75,7 +75,7 @@ def perform_later_parameters(parameters, constant_name)
7575
parameters.reject! { |typed_param| RBI::BlockParam === typed_param.param }
7676
parameters + [create_block_param(
7777
"block",
78-
type: "T.nilable(T.proc.params(job: #{constant_name}).void)",
78+
type: "::T.nilable(::T.proc.params(job: #{constant_name}).void)",
7979
)]
8080
else
8181
parameters

lib/tapioca/dsl/compilers/active_model_attributes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def handle_method_pattern?(pattern)
106106
def type_for(attribute_type_value)
107107
case attribute_type_value
108108
when ActiveModel::Type::Boolean
109-
as_nilable_type("T::Boolean")
109+
as_nilable_type("::T::Boolean")
110110
when ActiveModel::Type::Date
111111
as_nilable_type("::Date")
112112
when ActiveModel::Type::DateTime, ActiveModel::Type::Time

lib/tapioca/dsl/compilers/active_model_secure_password.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def decorate
7777
if method == :authenticate || method.start_with?("authenticate_")
7878
klass.create_method(
7979
method.to_s,
80-
parameters: [create_param("unencrypted_password", type: "T.untyped")],
81-
return_type: "T.any(#{constant}, FalseClass)",
80+
parameters: [create_param("unencrypted_password", type: "::T.untyped")],
81+
return_type: "::T.any(#{constant}, FalseClass)",
8282
)
8383
else
8484
create_method_from_def(klass, constant.instance_method(method))

lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ def decorate
6363
# Create RBI definitions for all the attributes that use confirmation validation
6464
confirmation_validators.each do |validator|
6565
validator.attributes.each do |attr_name|
66-
klass.create_method("#{attr_name}_confirmation", return_type: "T.untyped")
66+
klass.create_method("#{attr_name}_confirmation", return_type: "::T.untyped")
6767
klass.create_method(
6868
"#{attr_name}_confirmation=",
69-
parameters: [create_param("#{attr_name}_confirmation", type: "T.untyped")],
70-
return_type: "T.untyped",
69+
parameters: [create_param("#{attr_name}_confirmation", type: "::T.untyped")],
70+
return_type: "::T.untyped",
7171
)
7272
end
7373
end

lib/tapioca/dsl/compilers/active_record_associations.rb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ def populate_nested_attribute_writers(mod)
149149
constant.nested_attributes_options.keys.each do |association_name|
150150
mod.create_method(
151151
"#{association_name}_attributes=",
152-
parameters: [create_param("attributes", type: "T.untyped")],
153-
return_type: "T.untyped",
152+
parameters: [create_param("attributes", type: "::T.untyped")],
153+
return_type: "::T.untyped",
154154
)
155155
end
156156
end
@@ -200,37 +200,37 @@ def populate_single_assoc_getter_setter(klass, association_name, reflection)
200200
if association_methods_module.method_defined?("#{association_name}_changed?")
201201
klass.create_method(
202202
"#{association_name}_changed?",
203-
return_type: "T::Boolean",
203+
return_type: "::T::Boolean",
204204
)
205205
end
206206
if association_methods_module.method_defined?("#{association_name}_previously_changed?")
207207
klass.create_method(
208208
"#{association_name}_previously_changed?",
209-
return_type: "T::Boolean",
209+
return_type: "::T::Boolean",
210210
)
211211
end
212212
unless reflection.polymorphic?
213213
klass.create_method(
214214
"build_#{association_name}",
215215
parameters: [
216-
create_rest_param("args", type: "T.untyped"),
217-
create_block_param("blk", type: "T.untyped"),
216+
create_rest_param("args", type: "::T.untyped"),
217+
create_block_param("blk", type: "::T.untyped"),
218218
],
219219
return_type: association_class,
220220
)
221221
klass.create_method(
222222
"create_#{association_name}",
223223
parameters: [
224-
create_rest_param("args", type: "T.untyped"),
225-
create_block_param("blk", type: "T.untyped"),
224+
create_rest_param("args", type: "::T.untyped"),
225+
create_block_param("blk", type: "::T.untyped"),
226226
],
227227
return_type: association_class,
228228
)
229229
klass.create_method(
230230
"create_#{association_name}!",
231231
parameters: [
232-
create_rest_param("args", type: "T.untyped"),
233-
create_block_param("blk", type: "T.untyped"),
232+
create_rest_param("args", type: "::T.untyped"),
233+
create_block_param("blk", type: "::T.untyped"),
234234
],
235235
return_type: association_class,
236236
)
@@ -249,25 +249,25 @@ def populate_collection_assoc_getter_setter(klass, association_name, reflection)
249249
)
250250
klass.create_method(
251251
"#{association_name}=",
252-
parameters: [create_param("value", type: "T::Enumerable[#{association_class}]")],
252+
parameters: [create_param("value", type: "::T::Enumerable[#{association_class}]")],
253253
return_type: "void",
254254
)
255255
klass.create_method(
256256
"#{association_name.to_s.singularize}_ids",
257-
return_type: "T::Array[T.untyped]",
257+
return_type: "::T::Array[::T.untyped]",
258258
)
259259
klass.create_method(
260260
"#{association_name.to_s.singularize}_ids=",
261-
parameters: [create_param("ids", type: "T::Array[T.untyped]")],
262-
return_type: "T::Array[T.untyped]",
261+
parameters: [create_param("ids", type: "::T::Array[::T.untyped]")],
262+
return_type: "::T::Array[::T.untyped]",
263263
)
264264
end
265265

266266
#: (ReflectionType reflection) -> String
267267
def type_for(reflection)
268268
validate_reflection!(reflection)
269269

270-
return "T.untyped" if !constant.table_exists? || polymorphic_association?(reflection)
270+
return "::T.untyped" if !constant.table_exists? || polymorphic_association?(reflection)
271271

272272
T.must(qualified_name_of(reflection.klass))
273273
end
@@ -364,7 +364,7 @@ def relation_type_for(reflection)
364364
"#{qualified_name_of(reflection.klass)}::#{AssociationsCollectionProxyClassName}"
365365
end
366366
elsif polymorphic_association
367-
"ActiveRecord::Associations::CollectionProxy[T.untyped]"
367+
"ActiveRecord::Associations::CollectionProxy[::T.untyped]"
368368
else
369369
"::ActiveRecord::Associations::CollectionProxy[#{qualified_name_of(reflection.klass)}]"
370370
end

0 commit comments

Comments
 (0)