Skip to content
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

New driver for the push up method refactoring (P13) #16637

Open
wants to merge 15 commits into
base: Pharo13
Choose a base branch
from

Conversation

carolahp
Copy link
Contributor

@carolahp carolahp commented May 14, 2024

Summary

Implements driver for handling the user interaction logic when applying the push up method refactoring.
Tests are created for the new ReConditions
Replaces PR#16438
Depends on Spec PR#1548

About the architecture

  • In this Video I explain two proposed concepts to be considered in the architecture of refactorings in general.

@Ducasse
Copy link
Member

Ducasse commented May 14, 2024

Hi caro this is nice to have you back on that.
@balsa-sarenac will certainly have a look. let us when you need feedback

@Ducasse
Copy link
Member

Ducasse commented May 31, 2024

Hi @carolahp let us know when you want a review.

@carolahp carolahp marked this pull request as ready for review June 6, 2024 10:44
@carolahp
Copy link
Contributor Author

carolahp commented Jun 6, 2024

Hi @Ducasse and @balsa-sarenac , I would appreciate your review on this PR.
In particular I would like to discuss the 'fixable' and 'unfixable' breaking conditions that I explain in the video referred in the PR description

@MarcusDenker
Copy link
Member

There are lots of failing tests:

[osx-64 / Tests-osx-64 / MacOSX64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.PharoDocComment.Tests/CommentTestCase/osx_64___Tests_osx_64___nil/)
[osx-64 / Tests-osx-64 / MacOSX64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.PharoDocComment.Tests/CommentTestCase/osx_64___Tests_osx_64___nil_2/)
[osx-64 / Tests-osx-64 / MacOSX64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.PharoDocComment.Tests/CommentTestCase/osx_64___Tests_osx_64___nil_3/)
[osx-64 / Tests-osx-64 / MacOSX64.UnifiedFFI.Tests.FFIAutoReleaseOptionCalloutTest.testExternalStructure](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.UnifiedFFI.Tests/FFIAutoReleaseOptionCalloutTest/osx_64___Tests_osx_64___testExternalStructure/)
[osx-64 / Tests-osx-64 / MacOSX64.UnifiedFFI.Tests.FFIAutoReleaseOptionCalloutTest.testVoidPointer](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.UnifiedFFI.Tests/FFIAutoReleaseOptionCalloutTest/osx_64___Tests_osx_64___testVoidPointer/)
[unix-64 / Tests-unix-64 / Unix64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/Unix64.PharoDocComment.Tests/CommentTestCase/unix_64___Tests_unix_64___nil/)
[unix-64 / Tests-unix-64 / Unix64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/Unix64.PharoDocComment.Tests/CommentTestCase/unix_64___Tests_unix_64___nil_2/)
[unix-64 / Tests-unix-64 / Unix64.PharoDocComment.Tests.CommentTestCase.nil](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/Unix64.PharoDocComment.Tests/CommentTestCase/unix_64___Tests_unix_64___nil_3/)
[osx-64 / Tests-osx-64 / MacOSX64.Collections.Sequenceable.Tests.ArrayTest.testShuffleBy](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Collections.Sequenceable.Tests/ArrayTest/osx_64___Tests_osx_64___testShuffleBy/)
[osx-64 / Tests-osx-64 / MacOSX64.Collections.Support.Tests.CollectionTest.testAtRandomWeighting](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Collections.Support.Tests/CollectionTest/osx_64___Tests_osx_64___testAtRandomWeighting/)
[osx-64 / Tests-osx-64 / MacOSX64.Collections.Support.Tests.CollectionTest.testAtRandomWeightingMultiple](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Collections.Support.Tests/CollectionTest/osx_64___Tests_osx_64___testAtRandomWeightingMultiple/)
[osx-64 / Tests-osx-64 / MacOSX64.Kernel.Extended.Tests.UnicodeTest.testRNG](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Kernel.Extended.Tests/UnicodeTest/osx_64___Tests_osx_64___testRNG/)
[osx-64 / Tests-osx-64 / MacOSX64.Network.Tests.UUIDPrimitivesTest.testCreationNodeBased](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Network.Tests/UUIDPrimitivesTest/osx_64___Tests_osx_64___testCreationNodeBased/)
[osx-64 / Tests-osx-64 / MacOSX64.RTree.Tests.RTreeTest.testRemoveLeaf1](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.RTree.Tests/RTreeTest/osx_64___Tests_osx_64___testRemoveLeaf1/)
[osx-64 / Tests-osx-64 / MacOSX64.Random.Tests.RandomTest.testDistribution](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Random.Tests/RandomTest/osx_64___Tests_osx_64___testDistribution/)
[osx-64 / Tests-osx-64 / MacOSX64.Random.Tests.RandomTest.testPrimitiveRandomGeneration1](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Random.Tests/RandomTest/osx_64___Tests_osx_64___testPrimitiveRandomGeneration1/)
[osx-64 / Tests-osx-64 / MacOSX64.Random.Tests.RandomTest.testPrimitiveRandomGeneration2](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Random.Tests/RandomTest/osx_64___Tests_osx_64___testPrimitiveRandomGeneration2/)
[osx-64 / Tests-osx-64 / MacOSX64.Random.Tests.RandomTest.testPrimitiveRandomGeneration3](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Random.Tests/RandomTest/osx_64___Tests_osx_64___testPrimitiveRandomGeneration3/)
[osx-64 / Tests-osx-64 / MacOSX64.Roassal.Global.Tests.RSForceBasedLayoutTest.testAddNodesAndEdges](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Roassal.Global.Tests/RSForceBasedLayoutTest/osx_64___Tests_osx_64___testAddNodesAndEdges/)
[osx-64 / Tests-osx-64 / MacOSX64.Roassal.Global.Tests.RSRTreeTest.testRemoveLeaf1](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/job/PR-16637/6/testReport/junit/MacOSX64.Roassal.Global.Tests/RSRTreeTest/osx_64___Tests_osx_64___testRemoveLeaf1/)

@carolahp
Copy link
Contributor Author

carolahp commented Jun 6, 2024

Related breaking tests are due to modifications to RBPullUpMethodRefactoring preconditions and breaking changes. I am checking the best way to solve the issues without modifying tests

@Ducasse
Copy link
Member

Ducasse commented Jun 8, 2024

@carolahp I would like to understand the difference between an unfixable case and a precondition that is violated.
To me if the case is unfixable then it means that we should not apply the refactoring.

@carolahp
Copy link
Contributor Author

@carolahp I would like to understand the difference between an unfixable case and a precondition that is violated. To me if the case is unfixable then it means that we should not apply the refactoring.

@Ducasse the only difference is that an unfixable case is associated with a choice, for example "browse overriden method". However, I didn't think before that it may be simpler to just make these "unfixable cases" into preconditions.

I will update the PR to do this

Copy link
Member

@balsa-sarenac balsa-sarenac left a comment

Choose a reason for hiding this comment

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

Hey Caro, great work!
For the question on "fixable" vs "unfixable". I agree with Steph that "unfixable"s are basically applicability preconditions. And for breaking changes, we know this from the beginning that some of them can be fixed by other refactoring (what you refer to as "fixable") and others are just warnings (the refactoring cannot promise behaviour preservation and there are no fixes it can offer either). We didn't had them so far in the architecture since there was not a need to differentiate them. Maybe they can just be methods on preconditions, that if a precondition fail you can ask it what is the refactoring that fixes you? However, I see a drawback here with reusability (multiple refactorings use the same precondition and they want different fixes when they fail, or for one refactoring there can be breaking change and for other is applicability). Let me know how did you planned to integrate this in the current architecture?

Comment on lines +175 to +183
{ #category : 'accessing' }
RBClass >> methodNamed: aSymbol [

| allMethods |
allMethods := IdentitySet new.
self methods do: [ :each | each selector = aSymbol ifTrue: [^ each]].
^ nil
]

Copy link
Member

Choose a reason for hiding this comment

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

There already exists a method for this: methodFor:. Could you remove this one?

Comment on lines +75 to +84
{ #category : 'accessing - methods' }
RBMetaclass >> methodNamed: aSymbol [

| allMethods |

allMethods := IdentitySet new.
self allMethods do: [ :each | each selector = aSymbol ifTrue: [^ each]].
^ nil
]

Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above.

Comment on lines +54 to +58
{ #category : 'accessing' }
RBMetaclass >> classVariableNames [
^ self instanceSide classVariableNames
]

Copy link
Member

Choose a reason for hiding this comment

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

I'm curious, what is the use case when you use this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only use case is in the RBPullUpMethodParametrizedTest>>testFailurePullUpClassMethod , where the refactoring should fail

image

Comment on lines +376 to +384
{ #category : 'preconditions' }
RBPullUpMethodRefactoring >> pushUpSharedVariable: aVariable [
| refactoring |
refactoring := RBPullUpClassVariableRefactoring
model: self model
variable: aVariable
class: targetSuperclass.
self generateChangesFor: refactoring
]
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to rename this method to be inline with the rest of the methods like: pullUpSharedVariable:?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think yes, do you think we should also rename the method RBPullUpMethodRefactoring >> pushUpVariable:?

Copy link
Member

Choose a reason for hiding this comment

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

yes go for it :)

Comment on lines 402 to +418
RBPullUpMethodRefactoring >> removeDuplicatesOf: aSelector [

| tree |
tree := targetSuperclass parseTreeForSelector: aSelector.
targetSuperclass allSubclasses do:
[:each |
((each directlyDefinesMethod: aSelector) and:
[(tree equalTo: (each parseTreeForSelector: aSelector) exceptForVariables: #())
and: [(each superclass whoDefinesMethod: aSelector) == targetSuperclass]])
ifTrue:
[removeDuplicates
ifFalse:
[removeDuplicates :=
self
refactoringConfirmWarning: 'Do you want to remove duplicate subclass methods?'].
removeDuplicates ifTrue:[
self generateChangesFor:
(RBRemoveMethodTransformation
selector: aSelector from: each)]]]
targetSuperclass allSubclasses do: [ :each |
((each directlyDefinesMethod: aSelector) and: [
(tree
equalTo: (each parseTreeForSelector: aSelector)
exceptForVariables: #( )) and: [
(each superclass whichClassIncludesSelector: aSelector)
== targetSuperclass ] ]) ifTrue: [
removeDuplicates ifFalse: [
removeDuplicates := self refactoringConfirmWarning:
'Do you want to remove duplicate subclass methods?' ].
removeDuplicates ifTrue: [
self generateChangesFor:
(RBRemoveMethodTransformation selector: aSelector from: each) ] ] ]
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking that this method should be moved to the driver?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, I created a new condition called ReMethodsHaveNoDuplicatesCondition that encapsulates this functionality

Copy link
Member

Choose a reason for hiding this comment

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

I like the suggestion too.

Copy link
Member

Choose a reason for hiding this comment

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

But this is not just suggestion, this is a cleanup action that will remove methods with duplicate body in all siblings. So you may need a condition, but you also need to be able to call this transformation as well. This can be done along with "Push up referenced instance variables"?

Comment on lines 52 to 66
RBPullUpMethodRefactoring >> applicabilityPreconditions [
" Check that all selectors are defined in `class`,
and that `class` is in the hierarchy of subclasses of `targetSuperclass` "

^ {
(RBCondition hasSuperclass: class).
(ReDefinesSelectorsCondition new
definesSelectors: selectors
in: class).
(RBCondition withBlock: [
self checkClassVars.
self checkSuperclass.
self checkSuperMessages.
true ]) }
(ReClassHasSubclassesCondition new
class: targetSuperclass;
subclassesList: { class name }).
self preconditionNoOverrides.
self preconditionNoSupersendsSent.
self preconditionNoSupersendsReceived }
]
Copy link
Member

Choose a reason for hiding this comment

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

I think that we here miss calls to preconditionsNoReferencesToInstVars and preconditionsNoReferencesToSharedVars? Can you double check that all previous preconditions are still called in the new version after the refactoring as well?

Comment on lines +51 to +56
{ #category : 'execution' }
RePushUpMethodDriver >> breakingChoices [

^ refactoring failedBreakingChangePreconditions collect: [ :each |
each class strategyChoiceClass new driver: self ]
]
Copy link
Member

Choose a reason for hiding this comment

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

This is cool! However, we should support multiple choices per breaking change preconditions. This can be dealt with in future PR, not this one.

Copy link
Member

Choose a reason for hiding this comment

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

Yes in another step :)

| presenter changes |

"Display previous transformations that will fix the fixable breaking conditions"
changes := self breakingChoices.
Copy link
Member

Choose a reason for hiding this comment

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

The variable name should be choices right?

Comment on lines +184 to +190
{ #category : 'action' }
RePushUpMethodDriver >> pushUpReferencedInstVars [

notInstVarRefs violators do: [ :violator |
"We add the pushUpVariable transformation to the refactoring previous transformations"
refactoring pushUpVariable: (violator at: 2) ]
]
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense here to encapsulate this violator at: 2? Something like notInstVarRefs referencedInstanceVariables do: [ :iv | refactoring pushUpVariable: iv ]

Comment on lines +193 to +198
RePushUpMethodDriver >> pushUpReferencedSharedVars [

notSharedVarRefs violators do: [ :violator |
"We add the pushUpVariable transformation to the refactoring previous transformations"
refactoring pushUpSharedVariable: (violator at: 2) ]
]
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as for inst vars

@Ducasse Ducasse added the Status: Need more work The issue is nearly ready. Waiting some last bits. label Jun 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Need more work The issue is nearly ready. Waiting some last bits.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants