Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActiveSupport signatures for time methods #304

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

olivier-thatch
Copy link
Contributor

Type of Change

  • Add RBI for a new gem
  • Modify RBI for an existing gem
  • Other:

Changes

This PR adds a bunch of signatures for time-related ActiveSupport methods. I wrote these signatures almost a year ago and we've been using them in our codebase without issues, so I'm fairly confident that they are correct.

@olivier-thatch olivier-thatch requested a review from a team as a code owner February 12, 2025 04:02
@KaanOzkan
Copy link
Contributor

Thanks! I think this is similar to this PR where I did some testing and left comments around usability #166 (comment). Does it apply? Curious what your experience has been.

@olivier-thatch
Copy link
Contributor Author

This hasn't been an issue for us in practice -- I don't think we've often needed to call methods delegated to the underlying numeric value on ActiveSupport::Duration instances or the underlying Time value on ActiveSupport::TimeWithZone instances.

For ActiveSupport::Duration, I would advise using #to_i (or #to_f, #to_r, etc.), e.g. 1.second.to_i.digits. This plays nicely with Sorbet and is still very readable (and maybe even preferable IMHO).

For ActiveSupport::TimeWithZone, it's a little trickier because the return value of methods delegated to the underlying Time instance is wrapped back into an ActiveSupport::TimeWithZone instance. So there is a difference between Time.zone.now.floor (which returns an ActiveSupport::TimeWithZone) and Time.zone.now.to_time.floor (which returns a Time).

I suppose we could add signatures for delegated methods with @method_missing? There aren't that many methods on Time and a bunch of them are explicitly defined on ActiveSupport::TimeWithZone and already have signatures.

@KaanOzkan
Copy link
Contributor

Thanks. Similar to my other comment I'm wary about the toll this would introduce to large codebases. In our monolith we already can't enable this annotation due to high number of errors and this doubles it (~2k). Unfortunately users don't have a way of being more selective so if they run into too many issues they'll mark it as "ignore" and move on.

Right now we can't express the runtime delegation with annotations so I'm leaning towards these annotations being too premature.

suppose we could add signatures for delegated methods with @method_missing? There aren't that many methods on Time and a bunch of them are explicitly defined on ActiveSupport::TimeWithZone and already have signatures.

It's possible, although it's another hard coded list of methods to maintain since we can't express delegation. How would it look in practice? I'm seeing errors such as Expected Time but found ActiveSupport::TimeWithZone due to the signature on ago for example. Would you just change the return type to be Time? I don't know if that can lead to other issues or not.

@olivier-thatch
Copy link
Contributor Author

That's fair. As is often the case with Rails, the very dynamic behavior of those classes is hard or impossible to properly codify as Sorbet signatures 😞

Going on a bit of a tangent here, but would Tapioca annotations benefit from more granularity? My understanding of the current behavior is: if a gem is present in a project's Gemfile and there are annotations for this gem in the rbi-central repo, then the annotations are pulled into sorbet/rbi/annotations.

Do you think it would be useful to have the ability of have more than one set of annotations per gem, and for each set to come with a description and recommendations? E.g. ActiveSupport could have:

  • a default set that's safe for all codebases and is just installed, no questions asked
  • a separate set for time-related methods that might cause errors in existing codebases and has to be opted in
  • etc.

Ideally Tapioca would be smart enough to try applying each set separately and report if any Sorbet type errors are raised when the signatures from the set are included.

Such a feature would make the annotations system significantly more complex, but the current state of things is kind of a bummer: we can't add these signatures to the rbi-central repo because they risk breaking existing codebases, so users are left to either forego types or spend time writing signatures themselves directly in their own codebase like I did.

@KaanOzkan
Copy link
Contributor

Yes I think from the user's perspective it'd be good to have for the reasons you listed.

sig { params(years: Integer).returns(T.self_type) }
def next_year(years = 1); end

sig { returns(::ActiveSupport::TimeWithZone) }
Copy link
Contributor

Choose a reason for hiding this comment

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

https://github.com/Shopify/tapioca/blob/main/lib/tapioca/dsl/compilers/active_support_time_ext.rb already narrows this down btw, I think this signature should be removed completely

@olivier-thatch olivier-thatch force-pushed the olivier-activesupport-time-methods branch from 8352f72 to e379da2 Compare February 26, 2025 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants