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 duration methods to Numeric #166

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Conversation

grncdr
Copy link

@grncdr grncdr commented May 30, 2023

See: #137

Type of Change

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

Changes

@grncdr grncdr requested a review from a team as a code owner May 30, 2023 11:02
@grncdr grncdr changed the title Add duration methods to Integer Add duration methods to Numeric May 30, 2023
@andyw8
Copy link
Contributor

andyw8 commented May 30, 2023

I expect it's not commonly used, but let's include fortnight and fortnights for completeness?

And also in_milliseconds.

Copy link
Contributor

@KaanOzkan KaanOzkan left a comment

Choose a reason for hiding this comment

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

I think adding active_support/core_ext/numeric/time to here will fix CI

@grncdr
Copy link
Author

grncdr commented May 31, 2023

Signed the CLA

@KaanOzkan
Copy link
Contributor

The failing methods don't exist on Numeric so they'd have to be removed.

@andyw8
Copy link
Contributor

andyw8 commented May 31, 2023

🤔 Seem they are instead defined here? https://github.com/rails/rails/blob/main/activesupport/lib/active_support/duration.rb

@grncdr
Copy link
Author

grncdr commented Jun 1, 2023

🤔 Seem they are instead defined here? https://github.com/rails/rails/blob/main/activesupport/lib/active_support/duration.rb

That's the definition of ActiveSupport::Duration. The failing methods are defined in https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/integer/time.rb

I originally added all the definitions on Integer, found the definitions on Numeric when filling out the PR template, and now I realize that they're split like this because a partial year/month doesn't make sense (what is Date.today + 3.14.months?).

Fixing it up now.

@grncdr
Copy link
Author

grncdr commented Jun 1, 2023

I think I've got it right now. Thanks for your patience everyone.

Copy link
Contributor

@KaanOzkan KaanOzkan left a comment

Choose a reason for hiding this comment

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

I'm conflicted about this PR because it'll cause a lot of typecheck errors due to the delegation happening in ActiveSupport::Duration. I'm not sure if it'll bring in enough value. Applications are using Duration in place of Numeric.

For example, a common issue now would be while doing operations:
Time.now - 1.second errors with Expected Time but found ActiveSupport::Duration for argument arg0 due to sorbet's internal RBIs.

ActiveSupport::Duration is the correct type for 1.second however it can be used in place of Numeric and it's hard to tell Sorbet that. We'll have to be able to override the internal RBI. Looks like sorbet-typed went this route before but I'm not sure how it was working for the above example.

@grncdr
Copy link
Author

grncdr commented Jun 13, 2023

Hm, those are good points.

I'm conflicted about this PR because it'll cause a lot of typecheck errors due to the delegation happening in ActiveSupport::Duration. I'm not sure if it'll bring in enough value. Applications are using Duration in place of Numeric.

I think this one is a bit less of an issue, as it effects expressions where a duration is the receiver (like 1.second + 1.second) which are less common and/or can be handled by adding more methods to the RBI for ActiveSupport::Duration. (which appears to be what sorbet-typed did before (https://github.com/sorbet/sorbet-typed/blob/440aa126c55570ad35cb5a5d4fe4a392f23d65d3/lib/activesupport/all/activesupport.rbi#L1693)

For example, a common issue now would be while doing operations: Time.now - 1.second errors with Expected Time but found ActiveSupport::Duration for argument arg0 due to sorbet's internal RBIs.

This is definitely annoying. It's also annoying to not be able to write 1.second.ago. I am still pretty new to Sorbet and it's limitations/tradeoffs. Is it technically possible to override the type of Time#-(arg0) (and friends) to make the type of arg0 a union of Numeric and ActiveSupport::Duration?

ActiveSupport::Duration is the correct type for 1.second however it can be used in place of Numeric and it's hard to tell Sorbet that. We'll have to be able to override the internal RBI. Looks like sorbet-typed went this route before but I'm not sure how it was working for the above example.

It looks like there were overrides: https://github.com/sorbet/sorbet-typed/blob/440aa126c55570ad35cb5a5d4fe4a392f23d65d3/lib/activesupport/all/activesupport.rbi#L1252-L1266

(again, very new to Sorbet here, and it seems like the story around overrides might have changed at some point)

@KaanOzkan
Copy link
Contributor

KaanOzkan commented Jun 13, 2023

It looks like there were overrides: https://github.com/sorbet/sorbet-typed/blob/440aa126c55570ad35cb5a5d4fe4a392f23d65d3/lib/activesupport/all/activesupport.rbi#L1252-L1266

Yes but I'm unable to make them work in our monolith. The overrides aren't picked up by Sorbet. I will be discussing this with Stripe and see if sorbet-typed is doing something we are not. Lmk if you have other findings in your application.

Even with the overrides I'm not convinced that typing these as Duration has practical typing benefits since we can't represent that delegation yet 😕

It's also annoying to not be able to write 1.second.ago

Yeah currently the return type for second is coming from Tapioca generated gem RBIs for activesupport which is T.untyped. That's how we're able to write 1.second.ago. I'd love to represent this delegation and add type safety to those call sites.

@Morriar Morriar added the rbi Change related to RBI annotations label Mar 1, 2024
@amomchilov
Copy link
Contributor

since we can't represent that delegation yet

Can we just hand-roll RBI for the methods we know are commonly being delegated?

@KaanOzkan
Copy link
Contributor

Not really. We can't add Duration to sorbet's default RBIs so we'd have to return a Numeric from the methods in this PR which isn't correct.

@ipvalverde ipvalverde mentioned this pull request Jun 28, 2024
3 tasks
@ipvalverde
Copy link
Contributor

Any updates on this one? We've been getting some annoying behaviour when we have something like the following:

one_day_ago = Time.now.utc - 1.day
# one_day_ago is inferred to be a `Float`

@KaanOzkan
Copy link
Contributor

I was able to make overloads for stdlib methods like Time#- work so we'll able to cover most of the practical use cases by adding annotations for each of those methods in this file. However, there haven't been any changes to Sorbet regarding delegation so we still can't represent ActiveSupport::Duration accurately, i.e. 1.second.digits will fail with a method digits does not exist o nActiveSupport::Duration error. So when using methods that are called on the underlying type (Integer in this case) one would still need T.cast.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rbi Change related to RBI annotations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants