I'm using Sorbet on a Rails project and I have a method that does a calculation on a nilable property.
def age
return unless dob && dob.year > 1900
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
On that code, dob
is nilable. If I run a type check on it I get a bunch of complains asking to wrap dob
inside T.must(dob)
:
app/models/person.rb:18: Method year does not exist on NilClass component of T.nilable(Date) https://srb.help/7003
18 | return unless dob && dob.year > 1900
^^^^
Got T.nilable(Date) originating from:
app/models/person.rb:18:
18 | return unless dob && dob.year > 1900
^^^
Autocorrect: Use `-a` to autocorrect
app/models/person.rb:18: Replace with T.must(dob)
18 | return unless dob && dob.year > 1900
^^^
app/models/person.rb:21: Method year does not exist on NilClass component of T.nilable(Date) https://srb.help/7003
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^^
Got T.nilable(Date) originating from:
app/models/person.rb:21:
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
Autocorrect: Use `-a` to autocorrect
app/models/person.rb:21: Replace with T.must(dob)
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
app/models/person.rb:21: Method month does not exist on NilClass component of T.nilable(Date) https://srb.help/7003
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^^^
Got T.nilable(Date) originating from:
app/models/person.rb:21:
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
Autocorrect: Use `-a` to autocorrect
app/models/person.rb:21: Replace with T.must(dob)
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
app/models/person.rb:21: Method month does not exist on NilClass component of T.nilable(Date) https://srb.help/7003
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^^^
Got T.nilable(Date) originating from:
app/models/person.rb:21:
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
Autocorrect: Use `-a` to autocorrect
app/models/person.rb:21: Replace with T.must(dob)
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
app/models/person.rb:21: Method day does not exist on NilClass component of T.nilable(Date) https://srb.help/7003
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
Got T.nilable(Date) originating from:
app/models/person.rb:21:
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
^^^
Autocorrect: Use `-a` to autocorrect
app/models/person.rb:21: Replace with T.must(dob)
21 | now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
What is the best way to tell Sorbet that dob
won't be nil since I'm already returning if it is.
This is Sorbet's most-commonly asked question, and it appears at the top of the Sorbet FAQ page.
The short answer is that you need to assign dob
to a variable (despite not having any parentheses, dob
is in fact a call to a method in this context, because there has not been an assignment to a variable called dob
in this scope).
In your case, the easiest fix is to write dob = self.dob
at the top of the method, which calls the dob
method once and assigns it to a variable:
def age
dob = self.dob
return unless dob && dob.year > 1900
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
ā View full example on sorbet.run
All references of dob
after the first assignment are now reads from the local variable, instead of calls to the method, which allows Sorbet to remember any flow-sensitive type analyses it has performed.