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
dobto a variable (despite not having any parentheses,dobis in fact a call to a method in this context, because there has not been an assignment to a variable calleddobin this scope).In your case, the easiest fix is to write
dob = self.dobat the top of the method, which calls thedobmethod once and assigns it to a variable:→ View full example on sorbet.run
All references of
dobafter 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.