Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,26 @@ RBRemoveTemporaryVariableTransformationTest >> testTransform [

| transformation class |
transformation := RBAddMethodTransformation
sourceCode: 'foo
| temp bar |
bar := 5.
temp := bar * bar.
Transcript show: temp printString; cr.
^temp * temp'
in: self changeMockClass name
withProtocol: #accessing.
sourceCode: 'foo
| temp bar unused |
bar := 5.
temp := bar * bar.
Transcript show: temp printString; cr.
^temp * temp'
in: self changeMockClass name
withProtocol: #accessing.
transformation generateChanges.

transformation := RBRemoveTemporaryVariableTransformation
model: transformation model
variable: 'temp'
inMethod: #foo
inClass: self changeMockClass name.
model: transformation model
variable: 'unused'
inMethod: #foo
inClass: self changeMockClass name.
transformation generateChanges.

self assert: transformation model changes changes size equals: 2.

class := transformation model classNamed: self changeMockClass name.
self assert: (class directlyDefinesMethod: #foo).
self
assert: (class parseTreeForSelector: #foo) temporaries size
equals: 1
self assert: (class parseTreeForSelector: #foo) temporaries size equals: 2
]

{ #category : 'tests - failures' }
Expand All @@ -63,3 +59,57 @@ RBRemoveTemporaryVariableTransformationTest >> testVariableDoesNotExist [
inMethod: #foo
inClass: #RBRemoveTemporaryVariableTransformationTest)
]

{ #category : 'tests' }
RBRemoveTemporaryVariableTransformationTest >> testVariableHasUsagesWithoutShadowing [

| transformation |
transformation := RBAddMethodTransformation
sourceCode: 'foo
| temp bar |
bar := 5.
temp := bar * bar.
^temp'
in: self changeMockClass name
withProtocol: #accessing.
transformation generateChanges.

transformation := RBRemoveTemporaryVariableTransformation
model: transformation model
variable: 'temp'
inMethod: #foo
inClass: self changeMockClass name.

self should: [ transformation generateChanges ] raise: RBRefactoringError
]

{ #category : 'tests' }
RBRemoveTemporaryVariableTransformationTest >> testVariableHasUsagesWithShadowing [

| transformation class |
transformation := RBAddVariableTransformation
instanceVariable: 'temp'
class: self changeMockClass name.
transformation generateChanges.

transformation := RBAddMethodTransformation
model: transformation model
sourceCode: 'foo
| temp bar |
bar := 5.
temp := bar * bar.
^temp'
in: self changeMockClass name
withProtocol: #accessing.
transformation generateChanges.

transformation := RBRemoveTemporaryVariableTransformation
model: transformation model
variable: 'temp'
inMethod: #foo
inClass: self changeMockClass name.
transformation generateChanges.

class := transformation model classNamed: self changeMockClass name.
self assert: (class parseTreeForSelector: #foo) temporaries size equals: 1
]
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,56 @@ RBRemoveTemporaryVariableTransformation class >> variable: aString inMethod: aSe
yourself
]

{ #category : 'preconditions' }
{ #category : #preconditions }
RBRemoveTemporaryVariableTransformation >> applicabilityPreconditions [

^ {
self classExist.
(ReDefinesSelectorsCondition new definesSelectors: (Array with: selector) in: self definingClass).
(RBCondition
withBlock: [
self definingMethod allTemporaryVariables includes: variableName ]
errorString: 'Method named ' , selector
, ' does not define a temporary variable named ' , variableName).
(RBCondition
withBlock: [
(self definingMethod allArgumentVariables includes:
variableName) not ]
errorString: 'Variable named ' , variableName
, ' cannot be removed because it is an argument in this method') }
^ {
self classExist.
(ReDefinesSelectorsCondition new definesSelectors: { selector } in: self definingClass).
(RBCondition
withBlock: [
self definingMethod allTemporaryVariables includes: variableName ]
errorString: 'Method named ' , selector
, ' does not define a temporary variable named ' , variableName).
(RBCondition
withBlock: [
(self definingMethod allArgumentVariables includes: variableName) not ]
errorString: 'Variable named ' , variableName
, ' cannot be removed because it is an argument in this method').
(RBCondition
withBlock: [
self hasUsagesInScope not or: [
self definingClass definesInstanceVariable: variableName ] ]
errorString: 'Variable named ' , variableName
, ' cannot be removed because it is used in the method') }
Comment on lines +69 to +74

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

]

{ #category : #'private - accessing' }
RBRemoveTemporaryVariableTransformation >> definingSequenceNode [

^ (self definingMethod allChildren select: [ :each | each isSequence ])
detect: [ :each | each exactNodeDefines: variableName ]
ifNone: [ nil ]
]

{ #category : #'private - testing' }
RBRemoveTemporaryVariableTransformation >> hasUsagesInScope [
Comment thread
PrasannaBhavan2005 marked this conversation as resolved.
Outdated

| 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 ] ] ] ] ] ]
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add these test cases:

  1. usage like so
    foo
        | temp |
        temp
  1. nested usage
    foo
        | temp |
        [ :temp | temp ] value: 1.
        ^42

{ #category : 'scripting api - conditions' }
RBRemoveTemporaryVariableTransformation >> checkPreconditions [

Expand Down