Skip to content

Commit

Permalink
Merge pull request pharo-project#6781 from MarcusDenker/UndefinedSlot
Browse files Browse the repository at this point in the history
UndefinedSlot
  • Loading branch information
MarcusDenker committed Jul 8, 2020
2 parents 770c9a0 + e611471 commit 4b07b2a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 11 deletions.
67 changes: 67 additions & 0 deletions src/Kernel/UndefinedSlot.class.st
@@ -0,0 +1,67 @@
"
When loading a class where a Slot is used that is not present in the System, we need to model
this slot somehow.
The UndefinedSlot ingores reads and write (it returns nil like undeclared variables). But on
access, it checks if the Slot has been loaded and if yes, it rebuilds the class definition.
"
Class {
#name : #UndefinedSlot,
#superclass : #Slot,
#instVars : [
'ast',
'classIsRebuild'
],
#classInstVars : [
'undeclaredSlots'
],
#category : #'Kernel-Variables'
}

{ #category : #'instance creation' }
UndefinedSlot class >> named: aName ast: aSlotClassName [
^ (self named: aName) ast: aSlotClassName
]

{ #category : #accessing }
UndefinedSlot >> ast: aMessageNode [
classIsRebuild := false.
ast := aMessageNode
]

{ #category : #private }
UndefinedSlot >> checkClassRebuild [
"break the recursion while rebuilding"
classIsRebuild ifTrue: [ ^ self].
(self definingClass environment hasClassNamed: self slotClassName) ifFalse: [ ^ self ].
classIsRebuild := true.
"we rebuild the class, this triggers instance migration"
self definingClass compiler evaluate: self definingClass definitionWithSlots.
"recompile all methods that access me to generat code for the loaded definition"
self usingMethods do: [:each | each recompile].
]

{ #category : #printing }
UndefinedSlot >> printOn: aStream [
"we print as the definition that could not be loaded"
aStream nextPutAll: ast formattedCode
]

{ #category : #'meta-object-protocol' }
UndefinedSlot >> read: anObject [
"Undeclared slots read nil always, but check if they can repair the class"
self checkClassRebuild.
^ nil
]

{ #category : #accessing }
UndefinedSlot >> slotClassName [
^ast arguments first variable name
]

{ #category : #'meta-object-protocol' }
UndefinedSlot >> write: aValue to: anObject [
"Undeclared slots ignore writes, but check if they can repair the class"
self checkClassRebuild.
^ aValue
]
4 changes: 3 additions & 1 deletion src/Shift-ClassBuilder/ShiftClassBuilder.class.st
Expand Up @@ -83,14 +83,16 @@ ShiftClassBuilder >> build [

self createSharedVariables.

self installSlotsAndVariables.

self oldClass ifNotNil: [
self newClass basicCategory: self oldClass basicCategory.
self copyOrganization.
self builderEnhancer compileMethodsFor: self. ].

self builderEnhancer afterMethodsCompiled: self.

self installSlotsAndVariables.


^ newClass
]
Expand Down
7 changes: 4 additions & 3 deletions src/Slot-Core/Symbol.extension.st
Expand Up @@ -2,8 +2,9 @@ Extension { #name : #Symbol }

{ #category : #'*Slot-Core' }
Symbol >> => aVariable [
"If the slot we give as argument is not present in the image, we will get a nil. In that case we should throw an explicit error to the user saying a slot is missing."
"If the slot we give as argument is not present in the image, we create an UndefinedSlot with the AST of the slot definition"

aVariable ifNil: [ SlotNotFound signalForName: self ].
^ aVariable named: self
^ aVariable
ifNil: [ UndefinedSlot named: self ast: thisContext sender sourceNodeExecuted]
ifNotNil: [ aVariable named: self ]
]
58 changes: 58 additions & 0 deletions src/Slot-Tests/SlotErrorsTest.class.st
Expand Up @@ -196,6 +196,64 @@ SlotErrorsTest >> testSlotWithReservedName [
raise: InvalidSlotName ]
]

{ #category : #tests }
SlotErrorsTest >> testUndeclareSlot [
| slot instance |

aClass := self make: [ :builder |
builder slots: { UndefinedSlot named: #a ast: ((RBParser parseExpression: '#a => MySlot') doSemanticAnalysis;yourself) } ].

slot := aClass slotNamed: #a.

self assert: slot class equals: UndefinedSlot.
self assert: slot name equals: #a.
self assert: slot slotClassName equals: #MySlot.

instance := aClass new.

self assert: (slot read: instance) equals: nil.
slot write: 1 to: instance.
self assert: (slot read: instance) equals: nil.

]

{ #category : #tests }
SlotErrorsTest >> testUndeclareSlotFixWhenSlotIsLoaded [
| slot instance mySlot |

aClass := self make: [ :builder |
builder slots: { UndefinedSlot named: #a ast: ((RBParser parseExpression: '#a => ', self anotherClassName) doSemanticAnalysis;yourself) } ].

slot := aClass slotNamed: #a.

self assert: slot class equals: UndefinedSlot.
self assert: slot name equals: #a.
self assert: slot slotClassName equals: self anotherClassName.

instance := aClass new.

self assert: (slot read: instance) equals: nil.
slot write: 1 to: instance.
self assert: (slot read: instance) equals: nil.

PropertySlot
subclass: self anotherClassName
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
package: self aCategory.

self assert: (slot read: instance) equals: nil.

mySlot := aClass slotNamed: #a.

self assert: mySlot name equals: #a.

self assert: (mySlot read: instance) equals: nil.
mySlot write: 1 to: instance.
self assert: (mySlot read: instance) equals: 1.
]

{ #category : #tests }
SlotErrorsTest >> testValidateClassName [

Expand Down
7 changes: 0 additions & 7 deletions src/Slot-Tests/SlotTest.class.st
Expand Up @@ -44,13 +44,6 @@ SlotTest >> testIsWrittenInMethod [
self assert: ((self class slotNamed: #ivarForTesting) isWrittenIn: self class >> testSelector)
]

{ #category : #'tests - misc' }
SlotTest >> testNotFoundSlotRaiseExplicitError [
"When a slot is not present in the image, we will try to build the slot with nil. In that case we should return an error."

self should: [ #test => nil ] raise: SlotNotFound
]

{ #category : #'tests - read/write' }
SlotTest >> testNotReadInMethod [

Expand Down

0 comments on commit 4b07b2a

Please sign in to comment.