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
Extract new BuggyObsoleteStrictMemoization
cop
#175
Conversation
“specs” made it sound like it would include all gem specs, including Sorbet.
191faa1
to
4063f7e
Compare
expect_offense(<<~RUBY) | ||
def foo | ||
@foo = T.let(nil, T.nilable(Foo)) | ||
^^^ This might be a mistaken variant of the two-stage workaround that used to be needed for memoization in `#typed: strict` files. See https://sorbet.org/docs/type-assertions#put-type-assertions-behind-memoization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda nice, the ^^^
is now only under the nil
part of this, rather than the whole line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an easier reviewing experience, take a look at the commit list.
I made this by first copying over the ObsoleteStrictMemoization
as-is, then making changes to it in a second commit. YoSo you can just review the diff to go from ObsoleteStrictMemoization
to BuggyObsoleteStrictMemoization
I wonder if it should be named |
@andyw8 I'm torn on it. It's a bit wordier, but I think it helped with clarity and connecting the two cops. That was the name I originally had, but I liked being able to reference the phrase "obsolete memoization pattern" in docs to help explain it. "strict memoization pattern" wouldn't be as clear, since there's the old 2-line one, and the new one-liner. |
$(const {nil? cbase} :T) :let | ||
{(ivar _ivar) nil} | ||
(send (const {nil? cbase} :T) :nilable $_ivar_type))) # T.nilable(_ivar_type) | ||
$(or-asgn (ivasgn _ivar) $_initialization_expr)) # Second line: @_ivar ||= _initialization_expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a test showing that the cop doesn't trigger if the lines are separated:
@foo = T.let(@foo, T.nilable(Foo))
bar
@foo ||= Foo.new
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Done.
4063f7e
to
0ab5e8c
Compare
expect_no_offenses(<<~RUBY) | ||
def foo | ||
@foo = T.let(@foo, T.nilable(Foo)) | ||
some_other_computation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
The
Sorbet/ObsoleteStrictMemoization
cop introduced in #162 was initially meant to detect this kind of pattern:@Morriar had the fantastic idea to also detect a mistaken variant:
This was a great idea and found several such bugs in our program, but wasn't implemented quite right.
Auto-correcting this is dangerous. If the computation being memoized (
Foo.new
, in this case) had side effects, calling it only once (instead of once on every call tofoo
) can be observed, and might be a breaking change. This is problematic becauseSorbet/ObsoleteStrictMemoization
is markedSafe: true
andSafeAutoCorrect: true
.This PR splits off this behaviour into a new
BuggyObsoleteStrictMemoization
cop. The output of this cop is the correct (but still obsolete) memoization pattern. From there,ObsoleteStrictMemoization
can be applied to bring it to the modern standard. This cop is markedSafe: true, SafeAutoCorrect: false
.