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

Support more recent Sorbet versions #26

Merged
merged 10 commits into from
May 9, 2024
Merged

Support more recent Sorbet versions #26

merged 10 commits into from
May 9, 2024

Conversation

molawson
Copy link
Owner

@molawson molawson commented May 9, 2024

The primary impetus here is to get to a spot where I can actually release a new version with recent changes. To get to a version that I can bundle install locally on an Apple Silicon Mac, I need a more recent Sorbet version, which includes a few changes we need to account for.

I'm also trying hard not to make these breaking changes, which required jumping through a few hoops, again, mostly related to Sorbet things. While there's a lot I like about Sorbet, these sorts of constraints (e.g. tooling nearly forcing Ruby version deprecations–or at least making some support quite difficult) do make me question whether the payoff is worth it. I'd like this gem to be as stable as possible for as long as possible. Hopefully this was the most painful of the transitions 🤞🏼

molawson added 10 commits May 8, 2024 18:17
Get more recent versions that will install on the latest Apple Silicon
machines with the latest OS.
bundle update standard
bundle exec rake standard:fix
Sorbet used to do some of this, now Tapioca is the suggested tool for
generating RBI files for dependencies for more accurate type checking.
I've removed the old sorbet directories and initialized tapioca.
In more recent versions of Sorbet, instantiating a class that's marked
as abstract will raise an exception, so all of our specs that test the
Set class directly are now failing. We can get the same functionality by
moving those to shared examples and including them in the Intersection
and Union specs. This required very little to change in the specs
themselves.
Now that we have more type information for core and stdlib Ruby classes,
Sorbet is complaining that we're potentially sending things that aren't
Strings to Date.parse (whose first argument is said to be a String).

From my POV, this is a problem in the Sorbet type system because marking
the first argument for Date#parse as a String doesn't cover some
built-in functionality for that method.

If you use a non-String as the first argument, you'll get an error
complaining about that type not having an "implicit conversion to
String." This is a big feature of a number of Ruby parsing & conversion
methods. Classes can signal that they can behave and be treated just
like a String by responding to #to_str (this is different from the #to_s
method, which is _explicit_ conversion). But that functionality isn't
captured in the type system or by including a certain class.

This is a feature that I think is worth continuing to support for a
conversion method.

As this branch is the fallback branch and we intend to catch errors
where non-String and non-String-like objects are passed to Date#parse,
we'll use the T.let(object, T.untyped) escape hatch to keep our intended
functionality while appeasing Sorbet.

I hope that one day Sorbet can support interfaces that revolve around
responding to certain methods rather than class hierarchy alone. Until
then, this will do for us.
First, for more recent versions of Sorbet, I had the type wrong for
these splatted arguments (i.e. *elements). The type should be the type
for each of the individual args, not the group of them.

Once that was sorted out, we hit a new problem. Calling super in
Intersection and Union initialize methods seemed to confuse the type
system into thinking that it was passing along the type wrapped in an
extra array.

Normally, we could splat the elements before calling super, but that led
to error 7019[^1], which pretty plainly says that splats aren't very
well supported in Sorbet right now. The suggestion is to have the
methods take an Array instead of splatted arguments.

Since the tests explicitly cover these methods accepting both a single
array and separate individual arguments, I'd rather not break that
functionality for this type issue. Perhaps we'll go that route in the
future, but for now we can fall back on the fact that Set is an abstract
class. We can change it to take only a single Array argument since
nothing should be calling it directly (and if it were, they'd be getting
a type error). But we'll leave the child classes as accepting splatted
args.

Also, because Sorbet doesn't quite understand how Array() works (i.e. if
you pass it an Array, it won't wrap that but return it as-is), we have
to do this Array(object).flatten dance to make sure that Sorbet is clear
on what we're dealing with.

Again, I don't love this, but it works for now. The type system is happy
and all the tests are passing.

[^1]: https://sorbet.org/docs/error-reference#7019
Some gems we're using for development & test require Ruby 3.1 for the
versions in our lockfile. Let's update the default version for all tasks
to 3.1 for now to make a few things easier.
Some gem versions in the lockfile now require newer Ruby versions. To
try and not break support for older versions just yet, let's see if we
can get tests running by removing the lockfile before running bundle
install (if it's there the setup-ruby command will run in deployment
mode).
@molawson molawson merged commit 89b130e into main May 9, 2024
14 checks passed
@molawson molawson deleted the ml/update-dev-tooling branch May 9, 2024 15:04
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.

1 participant