Skip to content

Use T::Utils.unwrap_nilable for scalar coerce_input return types#2625

Merged
KaanOzkan merged 1 commit into
mainfrom
ko-uwrap-nilable
May 15, 2026
Merged

Use T::Utils.unwrap_nilable for scalar coerce_input return types#2625
KaanOzkan merged 1 commit into
mainfrom
ko-uwrap-nilable

Conversation

@KaanOzkan
Copy link
Copy Markdown
Contributor

@KaanOzkan KaanOzkan commented May 15, 2026

Motivation

type_for was stripping the T.nilable(...) wrapper from a custom scalar's coerce_input return type by formatting the runtime type to a string and matching a regex. Operating on the runtime type tree is both simpler and ~8x faster on the common T.nilable(SimpleType) case (measured: 15.3ms vs 1.8ms across 1000 calls).

Output is identical for every input the spec covers, including the new nilable-scalar case added in #2618.

Implementation

Use T::Utils.unwrap_nilable || return_type instead. unwrap_nilable returns nil if the type isn't nilable.

Tests

`type_for` was stripping the `T.nilable(...)` wrapper from a custom
scalar's `coerce_input` return type by formatting the runtime type to a
string and matching a regex. Operating on the runtime type tree is both
simpler and ~8x faster on the common `T.nilable(SimpleType)` case
(measured: 15.3ms vs 1.8ms across 1000 calls).

Output is identical for every input the spec covers, including the new
nilable-scalar case added in #2618.
@KaanOzkan KaanOzkan requested a review from a team as a code owner May 15, 2026 18:44
@KaanOzkan KaanOzkan added the refactor Code refactor with no behaviour change label May 15, 2026
Copy link
Copy Markdown
Member

@paracycle paracycle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't even realized that return_type was already a runtime type. This makes a lot of sense!

@KaanOzkan KaanOzkan merged commit ab86175 into main May 15, 2026
19 of 20 checks passed
@KaanOzkan KaanOzkan deleted the ko-uwrap-nilable branch May 15, 2026 19:32
@amomchilov
Copy link
Copy Markdown
Contributor

@paracycle There's actually several places where we to_s a runtime type, only to later try to regex our way into figuring out things back out about it.

e.g.

#: (untyped type_value) -> String
def type_for(type_value)
return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(type_value)
type = lookup_tapioca_type(type_value) ||
lookup_return_type_of_method(type_value, :deserialize) ||
lookup_return_type_of_method(type_value, :cast) ||
lookup_return_type_of_method(type_value, :cast_value) ||
lookup_arg_type_of_method(type_value, :serialize) ||
T.untyped
type.to_s
end

type = Helpers::ActiveModelTypeHelper.type_for(attribute_type_value)
type = as_nilable_type(type) if Helpers::ActiveModelTypeHelper.assume_nilable?(attribute_type_value)

I considered changing it, but the repo deals with types as strings a fair bit. I think it would more clear (and generally faster) to operate on type values, and stringify them later.

What do you think?

@paracycle
Copy link
Copy Markdown
Member

What do you think?

Overall, this is what I think: #2489

If we can move to our own type builder syntax, then we can also start generating RBS syntax if we wanted to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code refactor with no behaviour change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants