/
RBInlineMethodRefactoring.class.st
471 lines (419 loc) · 16.3 KB
/
RBInlineMethodRefactoring.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
"
I am a refactoring for replacing method calls by the method implementation.
You can select a message send in a method and refactoring this message send to inline its code.
Any temporary variable used in the original message send is added into this method and renamed if there are already variables with this name.
My preconditions verify that the inlined method is not a primitive call, the method does not have multiple returns. I'll show a warning if the method is overriden in subclasses.
"
Class {
#name : #RBInlineMethodRefactoring,
#superclass : #RBMethodRefactoring,
#instVars : [
'sourceInterval',
'inlineParseTree',
'sourceParseTree',
'sourceSelector',
'sourceMessage',
'inlineClass',
'checkOverridden'
],
#category : #'Refactoring-Core-Refactorings'
}
{ #category : #'instance creation' }
RBInlineMethodRefactoring class >> inline: anInterval inMethod: aSelector forClass: aClass [
^self new
inline: anInterval
inMethod: aSelector
forClass: aClass
]
{ #category : #'instance creation' }
RBInlineMethodRefactoring class >> model: aRBSmalltalk inline: anInterval inMethod: aSelector forClass: aClass [
^(self new)
model: aRBSmalltalk;
inline: anInterval
inMethod: aSelector
forClass: aClass;
yourself
]
{ #category : #transforming }
RBInlineMethodRefactoring >> addSelfReturn [
inlineParseTree addSelfReturn
]
{ #category : #transforming }
RBInlineMethodRefactoring >> addTemporary: sourceNode assignedTo: replacementNode [
| newName |
newName := self renameConflictingTemporary: sourceNode name.
(inlineParseTree body)
addTemporaryNamed: newName;
addNodeFirst: (RBAssignmentNode variable: (RBVariableNode named: newName)
value: replacementNode)
]
{ #category : #accessing }
RBInlineMethodRefactoring >> checkOverridden [
^ checkOverridden ifNil: [ checkOverridden := true ]
]
{ #category : #accessing }
RBInlineMethodRefactoring >> checkOverridden: aBoolean [
checkOverridden := aBoolean
]
{ #category : #transforming }
RBInlineMethodRefactoring >> checkSuperMessages [
self inlineClass = class
ifTrue: [ ^ self ].
self inlineClass superclass ifNil: [ ^ self ].
inlineParseTree superMessages
do: [ :each |
( self inlineClass superclass whoDefinesMethod: each ) = ( class superclass whoDefinesMethod: each )
ifFalse: [ self
refactoringError:
( 'Cannot inline method since it sends a super message <1s> that is overriden'
expandMacrosWith: each )
]
]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> compileMethod [
class compileTree: sourceParseTree
]
{ #category : #transforming }
RBInlineMethodRefactoring >> findSelectedMessage [
sourceParseTree := class parseTreeFor: sourceSelector.
sourceParseTree ifNil: [ self refactoringFailure: 'Could not parse sources' ].
sourceMessage := sourceParseTree whichNodeIsContainedBy: sourceInterval.
sourceMessage
ifNil: [ self refactoringFailure: 'The selection doesn''t appear to be a message send' ].
sourceMessage isCascade
ifTrue: [ sourceMessage := sourceMessage messages last ].
sourceMessage isMessage
ifFalse: [ self refactoringFailure: 'The selection doesn''t appear to be a message send' ].
(sourceMessage receiver isSelfVariable or: [ sourceMessage receiver isSuperVariable ])
ifFalse: [ self refactoringError: 'Cannot inline non-self messages' ]
]
{ #category : #testing }
RBInlineMethodRefactoring >> hasMultipleReturns [
"Do we have multiple returns? If the last statement isn't a return, then we have an implicit return of self."
| searcher |
searcher := self parseTreeSearcher.
searcher
matches: '^``@object'
do: [ :aNode :hasAReturn |
hasAReturn
ifTrue: [ ^ true ].
true ].
searcher
executeTree: inlineParseTree
initialAnswer: inlineParseTree lastIsReturn not.
^ false
]
{ #category : #initialization }
RBInlineMethodRefactoring >> inline: anInterval inMethod: aSelector forClass: aClass [
sourceSelector := aSelector.
class := self classObjectFor: aClass.
sourceInterval := anInterval
]
{ #category : #transforming }
RBInlineMethodRefactoring >> inlineClass [
^ inlineClass
ifNil: [ inlineClass := ( sourceMessage receiver name = 'super'
ifTrue: [ class superclass ]
ifFalse: [ class ] ) whoDefinesMethod: self inlineSelector
]
ifNotNil: [ inlineClass ]
]
{ #category : #accessing }
RBInlineMethodRefactoring >> inlineParseTree [
^ inlineParseTree
]
{ #category : #transforming }
RBInlineMethodRefactoring >> inlineSelector [
sourceMessage ifNil: [ self findSelectedMessage ].
^ sourceMessage selector
]
{ #category : #transforming }
RBInlineMethodRefactoring >> inlineSourceReplacing: aParseTree [
| statements nodeUnderSequence |
statements := inlineParseTree body statements.
(statements size > 1 and: [ aParseTree isEvaluatedFirst not ]) ifTrue: [
self refactoringWarning:
'To inline this method, we need to move some of its statements before the original message send.<n>This could change the order of execution, which can change the behavior.<n>Do you want to proceed?'
expandMacros ].
nodeUnderSequence := aParseTree.
[ nodeUnderSequence parent isSequence ] whileFalse: [ nodeUnderSequence := nodeUnderSequence parent ].
nodeUnderSequence parent
addNodes: (statements copyFrom: 1 to: (statements size - 1 max: 0)) before: nodeUnderSequence;
addTemporariesNamed: inlineParseTree body temporaryNames.
aParseTree parent replaceNode: aParseTree withNode: (statements isEmpty
ifTrue: [ RBVariableNode selfNode ]
ifFalse: [ statements last ])
]
{ #category : #transforming }
RBInlineMethodRefactoring >> insertInlinedMethod [
| node |
node := sourceMessage.
self moveComments.
node parent isCascade
ifTrue:
[self rewriteCascadedMessage.
node := node parent].
node parent isReturn
ifTrue: [node := node parent]
ifFalse: [self removeReturns].
self replaceArguments.
self inlineSourceReplacing: node.
sourceParseTree removeDeadCode.
self removeEmptyIfTrues.
self removeImmediateBlocks
]
{ #category : #testing }
RBInlineMethodRefactoring >> isOverridden [
| selector|
selector := self inlineSelector.
class allSubclassesDo: [:each |
(each directlyDefinesMethod: selector)
ifTrue: [ ^ true ]].
^ false
]
{ #category : #testing }
RBInlineMethodRefactoring >> isPrimitive [
^inlineParseTree isPrimitive
]
{ #category : #transforming }
RBInlineMethodRefactoring >> moveComments [
inlineParseTree nodesDo:
[:each |
each
comments: (each comments collect:
[:aComment |
| start source |
source := sourceParseTree source.
start := source size + 1.
source := source
, (inlineParseTree source copyFrom: aComment start to: aComment stop).
sourceParseTree source: source.
RBComment with: aComment contents at: start])]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> normalizeIfTrues [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replace: '| `@temps | ``@.s1. ``@boolean ifTrue: [| `@t1 | ``@.Stmts1. ^`@r1]. ``@.s2. ^``@r2'
with: '| `@temps | ``@.s1. ``@boolean ifTrue: [| `@t1 | ``@.Stmts1. ^`@r1] ifFalse: [``@.s2. ^``@r2]';
replace: '| `@temps | ``@.s1. ``@boolean ifFalse: [| `@t1 | ``@.Stmts1. ^`@r1]. ``@.s2. ^``@r2'
with: '| `@temps | ``@.s1. ``@boolean ifTrue: [``@.s2. ^``@r2] ifFalse: [| `@t1 | ``@.Stmts1. ^`@r1]'.
[rewriter executeTree: inlineParseTree]
whileTrue: [inlineParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> normalizeReturns [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replace: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ^``@r1] ifFalse: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ``@r1] ifFalse: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ^``@r1] ifTrue: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ``@r1] ifTrue: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ``@r1] ifFalse: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ``@r1] ifFalse: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ``@r1] ifTrue: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ``@r1] ifTrue: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ^``@r1] ifFalse: [| `@t2 | `@.Stmts2. ``@r2]'
with: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ``@r1] ifFalse: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ^``@r1] ifTrue: [| `@t2 | `@.Stmts2. ``@r2]'
with: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ``@r1] ifTrue: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '``@boolean ifTrue: [| `@t1 | `@.Stmts1. ^``@r1] ifFalse: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifTrue: [| `@t1 | `@.Stmts1. ``@r1] ifFalse: [| `@t2 | `@.Stmts2. ``@r2]';
replace: '``@boolean ifFalse: [| `@t1 | `@.Stmts1. ^``@r1] ifTrue: [| `@t2 | `@.Stmts2. ^``@r2]'
with: '^``@boolean ifFalse: [| `@t1 | `@.Stmts1. ``@r1] ifTrue: [| `@t2 | `@.Stmts2. ``@r2]'.
[rewriter executeTree: inlineParseTree]
whileTrue: [inlineParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> parseInlineMethod [
self inlineClass
ifNil: [ self
refactoringFailure:
( '<1p> or its superclasses don''t contain method <2s>'
expandMacrosWith: class
with: self inlineSelector )
].
inlineParseTree := self inlineClass parseTreeFor: self inlineSelector.
inlineParseTree ifNil: [ self refactoringFailure: 'Could not parse sources' ].
inlineParseTree lastIsReturn
ifFalse: [ inlineParseTree addSelfReturn ]
]
{ #category : #preconditions }
RBInlineMethodRefactoring >> preconditions [
^(RBCondition definesSelector: sourceSelector in: class)
& (RBCondition withBlock:
[self findSelectedMessage.
self checkOverridden ifTrue: [
self isOverridden
ifTrue:
[self
refactoringWarning: ('<1p>>><2s> is overriden. Do you want to inline it anyway?'
expandMacrosWith: self inlineClass
with: self inlineSelector)] ].
self parseInlineMethod.
self isPrimitive
ifTrue: [self refactoringError: 'Cannot inline primitives'].
self checkSuperMessages.
self rewriteInlinedTree.
(sourceMessage parent isReturn or: [self hasMultipleReturns not])
ifFalse:
[self
refactoringError: 'Cannot inline method since it contains multiple returns that cannot be rewritten'].
true])
]
{ #category : #transforming }
RBInlineMethodRefactoring >> removeEmptyIfTrues [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replace: '``@boolean ifTrue: [] ifFalse: [| `@temps | ``@.Stmts]'
with: '``@boolean ifFalse: [|`@temps | ``@.Stmts]';
replace: '``@boolean ifFalse: [] ifTrue: [| `@temps | ``@.Stmts]'
with: '``@boolean ifTrue: [|`@temps | ``@.Stmts]';
replace: '``@boolean ifTrue: [| `@temps | ``@.Stmts] ifFalse: []'
with: '``@boolean ifTrue: [|`@temps | ``@.Stmts]';
replace: '``@boolean ifFalse: [| `@temps | ``@.Stmts] ifTrue: []'
with: '``@boolean ifFalse: [|`@temps | ``@.Stmts]'.
(rewriter executeTree: sourceParseTree)
ifTrue: [sourceParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> removeImmediateBlocks [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replace: '[``.object] value'
with: '``.object'
when: [:aNode | aNode parent isCascade not].
rewriter
replace: '| `@temps | ``@.Stmts1. [| `@bTemps | ``@.bStmts] value. ``@.Stmts2'
with: '| `@temps `@bTemps | ``@.Stmts1. ``@.bStmts. ``@.Stmts2'.
(rewriter executeTree: sourceParseTree)
ifTrue: [sourceParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> removeReturns [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter replace: '^``@object' with: '``@object'.
(rewriter executeTree: inlineParseTree)
ifTrue: [inlineParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> renameConflictingTemporaries [
inlineParseTree allDefinedVariables
do: [:each | self renameConflictingTemporary: each]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> renameConflictingTemporary: aName [
| allNames newName index seqNode |
allNames := (Set new)
addAll: inlineParseTree allDefinedVariables;
yourself.
allNames remove: aName ifAbsent: [].
seqNode := sourceMessage.
[seqNode isSequence] whileFalse: [seqNode := seqNode parent].
allNames addAll: seqNode allDefinedVariables. "Add those variables defined in blocks. This might cause a few
variables to be renamed that don't need to be, but this should be safe."
newName := aName.
index := 0.
[(sourceMessage whoDefines: newName) notNil or:
[(class hierarchyDefinesVariable: newName) or: [allNames includes: newName]]]
whileTrue:
[index := index + 1.
newName := aName , index printString].
newName = aName ifFalse: [self renameTemporary: aName to: newName].
^newName
]
{ #category : #transforming }
RBInlineMethodRefactoring >> renameTemporary: oldName to: newName [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replace: oldName with: newName;
replaceArgument: oldName with: newName.
(rewriter executeTree: inlineParseTree)
ifTrue: [inlineParseTree := rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> replaceArgument: sourceNode with: replacementNode [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter replaceTree: sourceNode withTree: replacementNode.
(rewriter executeTree: inlineParseTree body)
ifTrue: [inlineParseTree body: rewriter tree]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> replaceArguments [
sourceMessage arguments reversed
with: inlineParseTree arguments reversed
do: [ :replacement :source |
(replacement isImmediateNode or: [ self shouldInlineExpression: replacement newSource ])
ifTrue: [ self replaceArgument: source with: replacement ]
ifFalse: [ self addTemporary: source assignedTo: replacement ] ]
]
{ #category : #transforming }
RBInlineMethodRefactoring >> rewriteCascadedMessage [
| index messages |
messages := sourceMessage parent messages.
index := (1 to: messages size)
detect: [:i | sourceMessage == (messages at: i)]
ifNone: [0].
inlineParseTree body addNodesFirst: (messages copyFrom: 1 to: index - 1).
self removeReturns.
inlineParseTree body
addNodes: (messages copyFrom: index + 1 to: messages size).
inlineParseTree addReturn
]
{ #category : #transforming }
RBInlineMethodRefactoring >> rewriteInlinedTree [
sourceMessage parent isReturn
ifTrue:
[(sourceParseTree isLast: sourceMessage parent)
ifFalse: [self addSelfReturn]]
ifFalse:
[self
writeGuardClauses;
normalizeIfTrues;
normalizeReturns;
addSelfReturn]
]
{ #category : #printing }
RBInlineMethodRefactoring >> storeOn: aStream [
aStream nextPut: $(.
self class storeOn: aStream.
aStream nextPutAll: ' inline: '.
sourceInterval storeOn: aStream.
aStream
nextPutAll: ' inMethod: #';
nextPutAll: sourceSelector;
nextPutAll: ' forClass: '.
class storeOn: aStream.
aStream nextPut: $)
]
{ #category : #transforming }
RBInlineMethodRefactoring >> transform [
self
renameConflictingTemporaries;
insertInlinedMethod;
compileMethod
]
{ #category : #transforming }
RBInlineMethodRefactoring >> writeGuardClauses [
| rewriter |
rewriter := self parseTreeRewriter.
rewriter
replaceMethod: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [| `@t1 | `@.Stmts1. ^`@r1]. `@.s2. ^`@r2'
with: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [| `@t1 | `@.Stmts1. ^`@r1] ifFalse: [`@.s2. ^`@r2]';
replaceMethod: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifFalse: [| `@t1 | `@.Stmts1. ^`@r1]. `@.s2. ^`@r2'
with: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [`@.s2. ^`@r2] ifFalse: [| `@t1 | `@.Stmts1. ^`@r1]';
replaceMethod: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [| `@t1 | `@.Stmts1. ^`@r1]. `@.s2'
with: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [| `@t1 | `@.Stmts1. ^`@r1] ifFalse: [`@.s2. ^self]';
replaceMethod: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifFalse: [| `@t1 | `@.Stmts1. ^`@r1]. `@.s2'
with: '`@methodName: `@args | `@temps | `@.s1. `@boolean ifTrue: [`@.s2. ^self] ifFalse: [| `@t1 | `@.Stmts1. ^`@r1]'.
[rewriter executeTree: self inlineParseTree]
whileTrue: [inlineParseTree := rewriter tree]
]