From 763c79998e51eb84ac541fa4bf2729a5b9f0e75d Mon Sep 17 00:00:00 2001 From: CyrilFerlicot Date: Wed, 13 Nov 2024 16:46:26 +0100 Subject: [PATCH] Manages shadowed classes and add test of a class shadowing a class --- .../FamixPythonClass.class.st | 6 +- .../FamixPythonGenerator.class.st | 2 + .../FamixPythonProject1Test.class.st | 26 ++++++ .../FamixPythonImporterVisitor.class.st | 79 ++++++++++++------- .../PyFunctionDefinitionNode.extension.st | 6 ++ 5 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/Famix-Python-Entities/FamixPythonClass.class.st b/src/Famix-Python-Entities/FamixPythonClass.class.st index 53aa538..94ea553 100644 --- a/src/Famix-Python-Entities/FamixPythonClass.class.st +++ b/src/Famix-Python-Entities/FamixPythonClass.class.st @@ -34,6 +34,8 @@ | `instancedClasses` | `FamixPythonClass` | `metaclass` | `FamixPythonClass` | | | `metaclass` | `FamixPythonClass` | `instancedClasses` | `FamixPythonClass` | | | `receivingInvocations` | `FamixTInvocationsReceiver` | `receiver` | `FamixTInvocation` | List of invocations performed on this entity (considered as the receiver)| +| `shadowedEntity` | `FamixTShadower` | `shadowingEntity` | `FamixTShadowable` | Entity that is been shadowed by myself in my defining scope.| +| `shadowingEntity` | `FamixTShadowable` | `shadowedEntity` | `FamixTShadower` | Entity shadowing me in my defining scope.| | `sourceAnchor` | `FamixTSourceEntity` | `element` | `FamixTSourceAnchor` | SourceAnchor entity linking to the original source code for this entity| | `typedEntities` | `FamixTType` | `declaredType` | `FamixTTypedEntity` | Entities that have this type as declaredType| @@ -51,8 +53,8 @@ Class { #name : 'FamixPythonClass', #superclass : 'FamixPythonType', - #traits : 'FamixTClass + FamixTImportable + FamixTWithAnnotationInstances + FamixTWithLambdas', - #classTraits : 'FamixTClass classTrait + FamixTImportable classTrait + FamixTWithAnnotationInstances classTrait + FamixTWithLambdas classTrait', + #traits : 'FamixTClass + FamixTImportable + FamixTShadowable + FamixTShadower + FamixTWithAnnotationInstances + FamixTWithLambdas', + #classTraits : 'FamixTClass classTrait + FamixTImportable classTrait + FamixTShadowable classTrait + FamixTShadower classTrait + FamixTWithAnnotationInstances classTrait + FamixTWithLambdas classTrait', #instVars : [ '#instancedClasses => FMMany type: #FamixPythonClass opposite: #metaclass', '#isMetaclass => FMProperty', diff --git a/src/Famix-Python-Generator/FamixPythonGenerator.class.st b/src/Famix-Python-Generator/FamixPythonGenerator.class.st index 24e8b76..411a979 100644 --- a/src/Famix-Python-Generator/FamixPythonGenerator.class.st +++ b/src/Famix-Python-Generator/FamixPythonGenerator.class.st @@ -86,6 +86,8 @@ FamixPythonGenerator >> defineHierarchy [ class --|> #TImportable. class --|> #TWithAnnotationInstances. class --|> #TWithLambdas. + class --|> #TShadowable. + class --|> #TShadower. containerEntity --|> namedEntity. containerEntity --|> #TWithClasses. diff --git a/src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st b/src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st index dc08769..1999f27 100644 --- a/src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st +++ b/src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st @@ -170,6 +170,32 @@ FamixPythonProject1Test >> testClassInPackage [ self assert: class typeContainer equals: (self packageNamed: 'subpackage1') ] +{ #category : 'tests - classes' } +FamixPythonProject1Test >> testClassShadowedByOtherClass [ + + | shadower shadowed | + shadower := (self model allClasses select: [ :funct | funct name = 'ClassShadowedByOtherClass' ]) asOrderedCollection detectMax: [ :funct | + funct sourceAnchor startPos ]. + shadowed := (self model allClasses select: [ :funct | funct name = 'ClassShadowedByOtherClass' ]) asOrderedCollection detectMin: [ :funct | + funct sourceAnchor startPos ]. + + self assert: shadower name equals: 'ClassShadowedByOtherClass'. + self deny: shadower isShadowed. + self assert: shadower typeContainer equals: (self moduleNamed: 'moduleWithShadowing'). + self assert: shadower shadowedEntity equals: shadowed. + self assert: shadower sourceText equals: ('class ClassShadowedByOtherClass: + + def __init__(self): + self.i_var_of_shadowed_class = 3' copyReplaceAll: String cr with: String lf). + + self assert: shadowed name equals: 'ClassShadowedByOtherClass'. + self assert: shadowed isShadowed. + self assert: shadowed typeContainer equals: (self moduleNamed: 'moduleWithShadowing'). + self assert: shadowed shadowingEntity equals: shadower. + self assert: shadowed sourceText equals: ('class ClassShadowedByOtherClass: + c_var_of_shadowed_class = True' copyReplaceAll: String cr with: String lf) +] + { #category : 'tests - attributes' } FamixPythonProject1Test >> testClassVariableAreDefinedOnlyOnce [ "We should have only one ivar even if it is assigned multiple times." diff --git a/src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st b/src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st index 755d909..c074fea 100644 --- a/src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st +++ b/src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st @@ -58,44 +58,31 @@ FamixPythonImporterVisitor >> classNamed: aName [ { #category : 'private-entity-creation' } FamixPythonImporterVisitor >> createClass: classDefinitionNode [ - | famixClass | - famixClass := model newClassNamed: classDefinitionNode pythonClassName. - - (classDefinitionNode superClasses reject: [ :class | class class = PyMetaclassNode ]) - ifEmpty: [ - model newInheritance - superclass: (self ensureStubClassNamed: 'object'); - subclass: famixClass ] - ifNotEmpty: [ :superclasses | - superclasses do: [ :superclass | - | inheritance | - inheritance := model newInheritance. - inheritance subclass: famixClass. - self - resolve: ((SRIdentifierWithNode identifier: superclass name) - expectedKind: FamixPythonClass; - notFoundReplacementEntity: [ :unresolvedSuperclass :currentEntity | self ensureStubClassNamed: unresolvedSuperclass identifier ]; - yourself) - foundAction: [ :entity :currentEntity | inheritance superclass: entity ] ] ]. - - famixClass typeContainer: self currentEntity. - - ^ self setSourceAnchor: famixClass from: classDefinitionNode + | class shadowedEntity | + "If we are shadowing an entity, we need to mark is as shadowed." + shadowedEntity := self findSadowedEntityNamed: classDefinitionNode pythonClassName. + + class := model newClassNamed: classDefinitionNode pythonClassName. + + self setSuperclassesOf: classDefinitionNode from: class. + + shadowedEntity ifNotNil: [ class shadowedEntity: shadowedEntity ]. + class typeContainer: self currentEntity. + + ^ self setSourceAnchor: class from: classDefinitionNode ] { #category : 'private-entity-creation' } FamixPythonImporterVisitor >> createFunction: aFunctionNode [ - | function shadowedFunction | - "If the same element already has a function of the same name, we select the last one defined and we mark it as shadowed." - self withCurrentEntityDo: [ :entity | - ((entity query descendants ofType: FamixTFunction) select: [ :child | child name = aFunctionNode fname value ]) ifNotEmpty: [ :functions | - shadowedFunction := functions detectMax: [ :aFunction | aFunction sourceAnchor startPos ] ] ]. + | function shadowedEntity | + "If we are shadowing an entity, we need to mark is as shadowed." + shadowedEntity := self findSadowedEntityNamed: aFunctionNode pythonFunctionName. - function := self basicCreateFunction: aFunctionNode fname value withSignature: aFunctionNode signatureString. + function := self basicCreateFunction: aFunctionNode pythonFunctionName withSignature: aFunctionNode signatureString. function functionOwner: self currentEntity. - shadowedFunction ifNotNil: [ function shadowedEntity: shadowedFunction ]. + shadowedEntity ifNotNil: [ function shadowedEntity: shadowedEntity ]. ^ self setSourceAnchor: function from: aFunctionNode ] @@ -418,6 +405,17 @@ FamixPythonImporterVisitor >> extractInvocationInformation: anInvocationNode [ ^ invocationDict ] +{ #category : 'accessing' } +FamixPythonImporterVisitor >> findSadowedEntityNamed: name [ + "If the same element already has a shadowable entity of the same name, we select the last one defined. If there is none, we return nil" + + self withCurrentEntityDo: [ :entity | + ((entity query descendants ofType: FamixTShadowable) select: [ :child | child name = name ]) ifNotEmpty: [ :entities | + ^ entities detectMax: [ :anEntity | anEntity sourceAnchor startPos ] ] ]. + + ^ nil +] + { #category : 'accessing - methods' } FamixPythonImporterVisitor >> functions [ @@ -635,6 +633,27 @@ FamixPythonImporterVisitor >> setSourceAnchor: aFamixEntity from: aSmaccNode [ ^ aFamixEntity ] +{ #category : 'as yet unclassified' } +FamixPythonImporterVisitor >> setSuperclassesOf: classDefinitionNode from: class [ + + ^ (classDefinitionNode superClasses reject: [ :aClass | aClass class = PyMetaclassNode ]) + ifEmpty: [ + model newInheritance + superclass: (self ensureStubClassNamed: 'object'); + subclass: class ] + ifNotEmpty: [ :superclasses | + superclasses do: [ :superclass | + | inheritance | + inheritance := model newInheritance. + inheritance subclass: class. + self + resolve: ((SRIdentifierWithNode identifier: superclass name) + expectedKind: FamixPythonClass; + notFoundReplacementEntity: [ :unresolvedSuperclass :currentEntity | self ensureStubClassNamed: unresolvedSuperclass identifier ]; + yourself) + foundAction: [ :entity :currentEntity | inheritance superclass: entity ] ] ] +] + { #category : 'accessing - methods' } FamixPythonImporterVisitor >> signatureFromInvocation: anInvocationNode [ diff --git a/src/Famix-Python-Importer/PyFunctionDefinitionNode.extension.st b/src/Famix-Python-Importer/PyFunctionDefinitionNode.extension.st index a871846..d36c042 100644 --- a/src/Famix-Python-Importer/PyFunctionDefinitionNode.extension.st +++ b/src/Famix-Python-Importer/PyFunctionDefinitionNode.extension.st @@ -6,6 +6,12 @@ PyFunctionDefinitionNode >> name [ ^ self fname value ] +{ #category : '*Famix-Python-Importer' } +PyFunctionDefinitionNode >> pythonFunctionName [ + + ^ fname value +] + { #category : '*Famix-Python-Importer' } PyFunctionDefinitionNode >> signatureString [