From c5b433d05febfa7ac65ce3c82ad6a3c66bd42cfc Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Fri, 10 Apr 2026 14:57:02 +0200 Subject: [PATCH 1/4] Fix issue #230 (I hope) + MolComponentFactory source code generation refactoring + test setup refactoring Can now execute the test testSubOverrideOfContractAddingProvidedServices --- .../MolCompleteComponentOverloadImpl.class.st | 34 ------- .../MolComponentFactoryTest.class.st | 97 ++++++------------- src/Molecule/MolComponentFactory.class.st | 80 +++++++++------ 3 files changed, 80 insertions(+), 131 deletions(-) diff --git a/src/Molecule-Tests/MolCompleteComponentOverloadImpl.class.st b/src/Molecule-Tests/MolCompleteComponentOverloadImpl.class.st index 14ef01a..9bc9e86 100644 --- a/src/Molecule-Tests/MolCompleteComponentOverloadImpl.class.st +++ b/src/Molecule-Tests/MolCompleteComponentOverloadImpl.class.st @@ -69,17 +69,6 @@ MolCompleteComponentOverloadImpl class >> usedComponentServices [ MolUsedServices2 } ] -{ #category : #events } -MolCompleteComponentOverloadImpl >> event [ - ^ #event -] - -{ #category : #events } -MolCompleteComponentOverloadImpl >> event2 [ - - ^ #event2 -] - { #category : #'component accessing' } MolCompleteComponentOverloadImpl >> getMolUsedEvents2Notifier [ ^self eventsNotifiers at: MolUsedEvents2 ifAbsent: [^MolNotFoundEventsNotifier new interface: MolUsedEvents2 name: nil]. @@ -161,26 +150,3 @@ MolCompleteComponentOverloadImpl >> getMolUsedServicesProvider [ servicesProvider := MolComponentManager default locatorServices searchServicesProviderFor: MolUsedServices named: servicesSymbol. ^servicesProvider ] - -{ #category : #parameters } -MolCompleteComponentOverloadImpl >> parameter [ - ^ #parameter -] - -{ #category : #'parameters 2' } -MolCompleteComponentOverloadImpl >> parameter2 [ - - ^ #parameter2 -] - -{ #category : #service } -MolCompleteComponentOverloadImpl >> service [ -"test" - ^ #service -] - -{ #category : #'service 2' } -MolCompleteComponentOverloadImpl >> service2 [ -"test2" - ^ #service2 -] diff --git a/src/Molecule-Tests/MolComponentFactoryTest.class.st b/src/Molecule-Tests/MolComponentFactoryTest.class.st index 3c12550..1221cf2 100644 --- a/src/Molecule-Tests/MolComponentFactoryTest.class.st +++ b/src/Molecule-Tests/MolComponentFactoryTest.class.st @@ -9,9 +9,8 @@ Class { { #category : #running } MolComponentFactoryTest >> cleanGeneratedClassesAndTraits [ - - | edited | + edited := false. self class environment at: self generatedComponentClassSymbolName ifPresent: [ :c | c removeFromSystem. edited := true]. @@ -32,13 +31,12 @@ MolComponentFactoryTest >> cleanGeneratedClassesAndTraits [ { #category : #running } MolComponentFactoryTest >> cleanGeneratedMethods [ "Restore methods added by tests" - MolCompleteSubComponentOverloadImpl class removeSelector: #usedComponentServices. MolCompleteSubComponentOverloadImpl class removeSelector: #providedComponentServices ] -{ #category : #utils } +{ #category : #utilities } MolComponentFactoryTest >> createInstanceMethod: aSelector in: aClassOrTrait protocol: aProtocolString withCode: aSourceCodeString [ "Create an instance method in a Class or Trait with source code implementation" | sourceCode | @@ -118,11 +116,25 @@ MolComponentFactoryTest >> generationTag [ ^'Molecule-Tests-Resources - Generated' ] +{ #category : #utilities } +MolComponentFactoryTest >> resetSourceCode [ + + + MolComponentFactory default deactivateDynamicContractUpdate: false. + + self cleanGeneratedClassesAndTraits. + self restoreRemovedThings. + self cleanGeneratedMethods. + + MolComponentFactory default activateDynamicContractUpdate: false. + + "Issue #230: Define the component manually after source code modification without dynamic update" + MolCompleteComponentOverloadImpl defineComponent. +] + { #category : #running } MolComponentFactoryTest >> restoreRemovedThings [ "Restore things removed in tests" - - "Simulate an overridden contract by the user VV" self createInstanceMethod: 'usedComponentServices' in: (MolCompleteComponentOverloadImpl class) protocol: 'accessing - services' withCode: '"Warning: dont edit this method, it is generated in MolComponentFactoryTest>>restoreRemovedThings" @@ -171,72 +183,23 @@ MolComponentFactoryTest >> restoreRemovedThings [ { #category : #running } MolComponentFactoryTest >> setUp [ + super setUp. + self timeLimit: 30 seconds. MolComponentManager cleanUp. - MolComponentFactory default deactivateDynamicContractUpdate: false. - self cleanGeneratedClassesAndTraits. - self restoreRemovedThings. - self cleanGeneratedMethods. - MolComponentFactory default activateDynamicContractUpdate: false. - -] - -{ #category : #running } -MolComponentFactoryTest >> setUpComponentAndType [ - - | edited | - edited := false. - - "Prepare or reset required test classes" - self class environment at: self generatedComponentTypeSymbolName ifAbsent: [ - MolComponentFactory createComponentType: self generatedComponentTypeSymbolName in: self generationTag. - edited := true. - ]. - - self class environment at: self generatedComponentServicesSymbolName ifAbsent: [ - MolComponentFactory createComponentServices: self generatedComponentServicesSymbolName in: self generationTag. - edited := true. - ]. - - self class environment at: self generatedComponentParametersSymbolName ifAbsent: [ - MolComponentFactory createComponentParameters: self generatedComponentParametersSymbolName in: self generationTag. - edited := true. - ]. - - self class environment at: self generatedComponentEventsSymbolName ifAbsent: [ - MolComponentFactory createComponentEvents: self generatedComponentEventsSymbolName in: self generationTag. - edited := true. - ]. - - self class environment at: self generatedComponentClassSymbolName ifAbsent: [ - MolComponentFactory createComponentForType: MolCompleteComponent named: self generatedComponentClassSymbolName in: self generationTag. - edited := true. - ]. - - self class environment at: self generatedComponentClass2SymbolName ifAbsent: [ - MolComponentFactory createComponentForType: MolCompleteComponent named: self generatedComponentClass2SymbolName in: self generationTag. - edited := true. - ]. - - (MolCompleteComponent usedComponentServices includes: MolUsedServices) ifFalse:[ - MolComponentFactory addUsedServices: MolUsedServices in: MolCompleteComponent. - edited := true. - ]. + self resetSourceCode - edited ifTrue:[(Delay forMilliseconds: 100) wait]. ] { #category : #running } MolComponentFactoryTest >> tearDown [ - MolComponentFactory default deactivateDynamicContractUpdate: false. - self cleanGeneratedClassesAndTraits. - self restoreRemovedThings. - self cleanGeneratedMethods. - MolComponentFactory default activateDynamicContractUpdate: false. + self resetSourceCode. MolComponentManager cleanUp. + + super tearDown. ] { #category : #tests } @@ -1450,9 +1413,6 @@ MolComponentFactoryTest >> testSubOverrideOfContractAddingProvidedServices [ | usedServices | - self flag:'Pharo doesnt support this use cases (see #210)'. - true ifTrue:[ ^ self ]. - "initial state" usedServices := MolCompleteSubComponentOverloadImpl providedComponentServices. self assert: usedServices size equals: 2. @@ -1485,11 +1445,11 @@ MolComponentFactoryTest >> testSubOverrideOfContractAddingProvidedServices [ "Clean generated method" MolCompleteSubComponentOverloadImpl class removeSelector: #providedComponentServices. - "test initial state again" - self flag:'Need to rewrite dynamic contract system to wait all system modifications before define components'. - 100 milliSeconds wait. + "Waiting for system modifications notifications for defining components (as IDE usage)" + 0.5 seconds wait. + "test initial state again" usedServices := MolCompleteSubComponentOverloadImpl providedComponentServices. self assert: usedServices size equals: 2. self deny: (usedServices includes: MolUsedServices3). @@ -1538,6 +1498,9 @@ MolComponentFactoryTest >> testSubOverrideOfContractAddingUsedServices [ "Clean generated method" MolCompleteSubComponentOverloadImpl class removeSelector: #usedComponentServices. + "Waiting for system modifications notifications for defining components (as IDE usage)" + 0.5 seconds wait. + "initial state" usedServices := MolCompleteSubComponentOverloadImpl usedComponentServices. self assert: usedServices size equals: 2. diff --git a/src/Molecule/MolComponentFactory.class.st b/src/Molecule/MolComponentFactory.class.st index 5bfd66e..11e5595 100644 --- a/src/Molecule/MolComponentFactory.class.st +++ b/src/Molecule/MolComponentFactory.class.st @@ -623,36 +623,38 @@ MolComponentFactory >> dirtyComponents [ ] { #category : #'private - code generation' } -MolComponentFactory >> generateComponentAccessorsFor: aSymbol withList: aCollection in: aComponent suffix: suffix [ - | selector sourceCode sourceMethod method | - - aCollection copy do: [ :e | | trait | - - "e can be another thing that a Trait, need to check nature of e before generate" - ( e notNil and:[ e isTrait and:[( e isComponentServices or:[ e isComponentParameters or:[ e isComponentEvents ]])]]) ifTrue:[ - - trait := e. - - selector := ('get' , trait printString , suffix) asSymbol. - sourceCode := self getSourceCodeFor: aSymbol trait: trait selector: selector. - (aComponent allSelectors includes: selector) ifFalse: [ - aComponent compile: sourceCode contents classified: self class protocolForComponentAccess - ] ifTrue: [ - "if the method exist inspect this source code for search any difference between the existing required services and the requested required services" - "the selector must not be a parent" - (aComponent selectors includes: selector) ifTrue: [ - method := aComponent >> selector. - sourceMethod := method sourceCode. - sourceMethod ifNotNil: [ - sourceMethod ~= sourceCode contents ifTrue: [ - "rewrite the method" - aComponent compile: sourceCode contents classified: self class protocolForComponentAccess - ]. - ]. - ]. - ]. - ]. - ] +MolComponentFactory >> generateComponentAccessorsFor: aSymbol traits: aTraits in: aComponent suffix: suffix [ + | selector sourceCode protocol | + + (aTraits notNil and: [ + aTraits isTrait and: [ + aTraits isComponentServices or: [ + aTraits isComponentParameters or: [ aTraits isComponentEvents ] ] ] ]) + ifFalse: [ ^ self ]. + + selector := ('get' , aTraits printString , suffix) asSymbol. + protocol := self class protocolForComponentAccess. + sourceCode := self + getSourceCodeFor: aSymbol + trait: aTraits + selector: selector. + + self + generateMethod: sourceCode + selector: selector + protocol: protocol + inComponent: aComponent +] + +{ #category : #'private - code generation' } +MolComponentFactory >> generateComponentAccessorsFor: aSymbol withList: aListOfTraits in: aComponent suffix: suffix [ + + aListOfTraits copy do: [ :traits | + self + generateComponentAccessorsFor: aSymbol + traits: traits + in: aComponent + suffix: suffix ] ] { #category : #'code generation' } @@ -674,6 +676,24 @@ MolComponentFactory >> generateConsumedEventsComponentAccessorsFor: aComponentCl suffix: 'Subscriber' ] +{ #category : #'private - code generation' } +MolComponentFactory >> generateMethod: aSourceCode selector: aSelector protocol: aProtocol inComponent: aComponent [ + + | method sourceMethod | + (aComponent allSelectors includes: aSelector) + ifFalse: [ + aComponent compile: aSourceCode contents classified: aProtocol ] + ifTrue: [ + (aComponent selectors includes: aSelector) ifFalse: [ ^ self ]. + "if the method exist inspect this source code for search any difference between the existing required services and the requested required services""the selector must not be a parent" + method := aComponent >> aSelector. + sourceMethod := method sourceCode. + sourceMethod ifNotNil: [ "if the source code of the method is different than the existing one, recreate it. Same thing for the procotol." + (sourceMethod ~= aSourceCode contents or: [ + method protocol name ~= aProtocol ]) ifTrue: [ "rewrite the method" + aComponent compile: aSourceCode contents classified: aProtocol ] ] ] +] + { #category : #'code generation' } MolComponentFactory >> generateProducedEventsComponentAccessorsFor: aComponentClass [ From 0929c082264625aad70c087659f2c69b8c278961 Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Fri, 10 Apr 2026 15:08:43 +0200 Subject: [PATCH 2/4] Finish to fix issue #248 --- src/Molecule/MolComponentFactory.class.st | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Molecule/MolComponentFactory.class.st b/src/Molecule/MolComponentFactory.class.st index 11e5595..fcdcf93 100644 --- a/src/Molecule/MolComponentFactory.class.st +++ b/src/Molecule/MolComponentFactory.class.st @@ -679,7 +679,7 @@ MolComponentFactory >> generateConsumedEventsComponentAccessorsFor: aComponentCl { #category : #'private - code generation' } MolComponentFactory >> generateMethod: aSourceCode selector: aSelector protocol: aProtocol inComponent: aComponent [ - | method sourceMethod | + | method sourceMethod methodProtocol | (aComponent allSelectors includes: aSelector) ifFalse: [ aComponent compile: aSourceCode contents classified: aProtocol ] @@ -687,10 +687,11 @@ MolComponentFactory >> generateMethod: aSourceCode selector: aSelector protocol: (aComponent selectors includes: aSelector) ifFalse: [ ^ self ]. "if the method exist inspect this source code for search any difference between the existing required services and the requested required services""the selector must not be a parent" method := aComponent >> aSelector. + methodProtocol := method protocol ifNil: [ nil ] ifNotNil: [ :p | p name ]. sourceMethod := method sourceCode. sourceMethod ifNotNil: [ "if the source code of the method is different than the existing one, recreate it. Same thing for the procotol." (sourceMethod ~= aSourceCode contents or: [ - method protocol name ~= aProtocol ]) ifTrue: [ "rewrite the method" + methodProtocol ~= aProtocol ]) ifTrue: [ "rewrite the method" aComponent compile: aSourceCode contents classified: aProtocol ] ] ] ] From ab3561b5dc31bd417e3937abafd1c80b9ddafbcf Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Fri, 10 Apr 2026 15:16:25 +0200 Subject: [PATCH 3/4] Fix Pharo11 reflective API support --- src/Molecule/MolComponentFactory.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Molecule/MolComponentFactory.class.st b/src/Molecule/MolComponentFactory.class.st index fcdcf93..6b260a6 100644 --- a/src/Molecule/MolComponentFactory.class.st +++ b/src/Molecule/MolComponentFactory.class.st @@ -687,7 +687,7 @@ MolComponentFactory >> generateMethod: aSourceCode selector: aSelector protocol: (aComponent selectors includes: aSelector) ifFalse: [ ^ self ]. "if the method exist inspect this source code for search any difference between the existing required services and the requested required services""the selector must not be a parent" method := aComponent >> aSelector. - methodProtocol := method protocol ifNil: [ nil ] ifNotNil: [ :p | p name ]. + methodProtocol := method protocol ifNil: [ nil ] ifNotNil: [ :p | "Pharo 11 reflective API support: protocol is directly a string" p isByteString ifTrue:[ p ] ifFalse:[ p name ] ]. sourceMethod := method sourceCode. sourceMethod ifNotNil: [ "if the source code of the method is different than the existing one, recreate it. Same thing for the procotol." (sourceMethod ~= aSourceCode contents or: [ From 64929c9e90e6cfa892c56d341869995e5fa0ee98 Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Fri, 10 Apr 2026 15:19:28 +0200 Subject: [PATCH 4/4] Pharo11 support --- src/Molecule/MolComponentFactory.class.st | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Molecule/MolComponentFactory.class.st b/src/Molecule/MolComponentFactory.class.st index 6b260a6..45af43a 100644 --- a/src/Molecule/MolComponentFactory.class.st +++ b/src/Molecule/MolComponentFactory.class.st @@ -687,7 +687,8 @@ MolComponentFactory >> generateMethod: aSourceCode selector: aSelector protocol: (aComponent selectors includes: aSelector) ifFalse: [ ^ self ]. "if the method exist inspect this source code for search any difference between the existing required services and the requested required services""the selector must not be a parent" method := aComponent >> aSelector. - methodProtocol := method protocol ifNil: [ nil ] ifNotNil: [ :p | "Pharo 11 reflective API support: protocol is directly a string" p isByteString ifTrue:[ p ] ifFalse:[ p name ] ]. + methodProtocol := method protocol ifNil: [ nil ] ifNotNil: [ :p | + "Pharo 11 reflective API support: protocol is directly a ByteString" p class = Protocol ifTrue:[ p name ] ifFalse:[ p ] ]. sourceMethod := method sourceCode. sourceMethod ifNotNil: [ "if the source code of the method is different than the existing one, recreate it. Same thing for the procotol." (sourceMethod ~= aSourceCode contents or: [