Fix #19936: Forbid removing temporary variables with usages unless shadowed - #19937
Fix #19936: Forbid removing temporary variables with usages unless shadowed#19937PrasannaBhavan2005 wants to merge 10 commits into
Conversation
…ges unless shadowed
1b21766 to
a12fd64
Compare
|
Thanks I looks like a good addition. I will have a look later. |
balsa-sarenac
left a comment
There was a problem hiding this comment.
Thanks! Mostly looks ok, can be merged as is, but there are small improvements we can make (first comment).
| (RBCondition | ||
| withBlock: [ | ||
| | sequence hasShadowedVar hasUsages | | ||
| sequence := (self definingMethod allChildren select: [ :each | each isSequence ]) | ||
| detect: [ :each | each defines: variableName ] | ||
| ifNone: [ nil ]. | ||
| hasUsages := sequence isNotNil and: [ | ||
| sequence allChildren anySatisfy: [ :node | | ||
| node isVariable and: [ node name = variableName ] ] ]. | ||
| hasShadowedVar := self definingClass definesInstanceVariable: variableName. | ||
| hasUsages not or: [ hasShadowedVar ] ] | ||
| errorString: 'Variable named ' , variableName | ||
| , ' cannot be removed because it is used in the method') } |
There was a problem hiding this comment.
I'd prefer for this to be standalone precondition, but it's good like this as well, it's important we are fixing these.
I'm now thinking that this delegation to instnace variable is behavior-preserving precondition, but this is a tricky scenario where applicability preconditions will fail (variable has usages) and then behavior can still be fixed. I think we didn't have this scenario so far. So nothing to fix now, rather I have to re-think some scenarios and how to support this.
There was a problem hiding this comment.
thanks for the review @balsa-sarenac!
I've applied your suggestion for ReDefinesSelectorsCondition, happy to hear that the precondition logic helps address the issue, let me know if any further changes needed
…formation.class.st Co-authored-by: Балша Шаренац <34557616+balsa-sarenac@users.noreply.github.com>
|
There are a bunch of failing tests after touching this code, can you take a look at them? Before next iteration, be sure to run refactoring tests in your pharo image, things get to mergeable state faster |
|
Thanks @balsa-sarenac! I see the test failures across the composite refactorings ( |
|
Hi @balsa-sarenac, I identified the cause of the test failures: the usage check was previously checking all children of the sequence, including the declaration of the temporary variable itself, leading to false positives across composite refactorings. I've updated the precondition to inspect statement nodes specifically: hasRealUsages := targetSequence isNotNil and: [
targetSequence statements anySatisfy: [ :statement |
statement allChildren anySatisfy: [ :node |
node isVariable and: [ node name = variableName ] ] ] ]. |
|
@PrasannaBhavan2005 there are still failing tests: https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-19937/6/testReport/ Check the image, maybe there's already a method/way to check if temp is used? It might be better to re-use an existing good solution vs.creating a new one. |
|
Hi @balsa-sarenac, The previous failures were caused by the usage check traversing nested sequence scopes (such as inner blocks that declare their own temporary variable with the same name), which broke I have updated the precondition check to ignore variable references occurring inside child sequences that define their own local variable: usages := definingSeq
ifNil: [ #() ]
ifNotNil: [
definingSeq statements flatCollect: [ :stmt |
stmt allChildren select: [ :node |
node isVariable and: [
node name = variableName and: [
| innerSeq |
innerSeq := node parent.
[ innerSeq isNotNil and: [ innerSeq isSequence not ] ]
whileTrue: [ innerSeq := innerSeq parent ].
innerSeq == definingSeq or: [
innerSeq isNil or: [ (innerSeq exactNodeDefines: variableName) not ] ] ] ] ] ] ]. |
|
@PrasannaBhavan2005 can you check: |
|
Hi @balsa-sarenac, I investigated using
To address the complexity concern, I have decomposed the logic by extracting two dedicated private helper methods ( |
| { #category : #'private - accessing' } | ||
| RBRemoveTemporaryVariableTransformation >> definingSequenceNode [ | ||
|
|
||
| ^ (self definingMethod allChildren select: [ :each | each isSequence ]) | ||
| detect: [ :each | each exactNodeDefines: variableName ] | ||
| ifNone: [ nil ] | ||
| ] | ||
|
|
||
| { #category : #'private - testing' } | ||
| RBRemoveTemporaryVariableTransformation >> hasUsagesInScope [ | ||
|
|
||
| | definingSeq | | ||
| definingSeq := self definingSequenceNode. | ||
| definingSeq ifNil: [ ^ false ]. | ||
|
|
||
| ^ definingSeq statements anySatisfy: [ :stmt | | ||
| stmt allChildren anySatisfy: [ :node | | ||
| node isVariable and: [ | ||
| node name = variableName and: [ | ||
| | innerSeq | | ||
| innerSeq := node parent. | ||
| [ innerSeq isNotNil and: [ innerSeq isSequence not ] ] | ||
| whileTrue: [ innerSeq := innerSeq parent ]. | ||
| innerSeq == definingSeq or: [ | ||
| innerSeq isNil or: [ (innerSeq exactNodeDefines: variableName) not ] ] ] ] ] ] | ||
| ] |
There was a problem hiding this comment.
Would it be possible to write some tests for this new code as well? In the future we might want to move this code to RB metamodel, so having tests is really useful in that case.
Thanks for cleaning it up, we're closing in on the merge side
There was a problem hiding this comment.
Can you add these test cases:
- usage like so
foo
| temp |
temp
- nested usage
foo
| temp |
[ :temp | temp ] value: 1.
^42
…oraryVariableTransformationTest
|
Hi @balsa-sarenac, I've added the unit tests in
All 8 tests in the transformation test suite (and the broader refactoring suite) pass cleanly. Let me know if any changes needed |
|
@balsa-sarenac let me know if any issues |
|
Hey @balsa-sarenac it would be great if we can close this PR, has been a long time still no updates, let me know if anything else you need |
| { #category : #'private - accessing' } | ||
| RBRemoveTemporaryVariableTransformation >> definingSequenceNode [ | ||
|
|
||
| ^ (self definingMethod allChildren select: [ :each | each isSequence ]) | ||
| detect: [ :each | each exactNodeDefines: variableName ] | ||
| ifNone: [ nil ] | ||
| ] | ||
|
|
||
| { #category : #'private - testing' } | ||
| RBRemoveTemporaryVariableTransformation >> hasUsagesInScope [ | ||
|
|
||
| | definingSeq | | ||
| definingSeq := self definingSequenceNode. | ||
| definingSeq ifNil: [ ^ false ]. | ||
|
|
||
| ^ definingSeq statements anySatisfy: [ :stmt | | ||
| stmt allChildren anySatisfy: [ :node | | ||
| node isVariable and: [ | ||
| node name = variableName and: [ | ||
| | innerSeq | | ||
| innerSeq := node parent. | ||
| [ innerSeq isNotNil and: [ innerSeq isSequence not ] ] | ||
| whileTrue: [ innerSeq := innerSeq parent ]. | ||
| innerSeq == definingSeq or: [ | ||
| innerSeq isNil or: [ (innerSeq exactNodeDefines: variableName) not ] ] ] ] ] ] | ||
| ] |
There was a problem hiding this comment.
Can you add these test cases:
- usage like so
foo
| temp |
temp
- nested usage
foo
| temp |
[ :temp | temp ] value: 1.
^42
…guments from preconditions
|
@balsa-sarenac I have updated the implementation and pushed the changes:
All tests are passing locally. |
| ] | ||
|
|
||
| { #category : 'tests' } | ||
| RBRemoveTemporaryVariableTransformationTest >> testDefiningSequenceNodeAndHasUsagesInScope [ |
There was a problem hiding this comment.
nit: we usually prefer to test one thing in a test, so this would've been two tests
There was a problem hiding this comment.
So should I split testDefiningSequenceNodeAndHasUsagesInScope into two separate tests (testDefiningSequenceNode and testHasUsagesInScope) to keep each test focused on a single responsibility.?
There was a problem hiding this comment.
it's nitpick, you don't have to, we can merge like this. I'm just mentioning it to get familiar with team preferences and to potentially have it in mind for future changes
There was a problem hiding this comment.
Understood, thanks for the feedback! I'll keep that in mind for future contributions. Looking forward to make more and more contris, thanks!
Fixes #19936
Summary of changes
RBRemoveTemporaryVariableTransformationpreventing removal of temporary variables when they are still referenced in the method body.