Skip to content

Commit

Permalink
Manages shadowed classes and add test of a class shadowing a class
Browse files Browse the repository at this point in the history
  • Loading branch information
jecisc committed Nov 13, 2024
1 parent e9b1434 commit 763c799
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 32 deletions.
6 changes: 4 additions & 2 deletions src/Famix-Python-Entities/FamixPythonClass.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions src/Famix-Python-Generator/FamixPythonGenerator.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ FamixPythonGenerator >> defineHierarchy [
class --|> #TImportable.
class --|> #TWithAnnotationInstances.
class --|> #TWithLambdas.
class --|> #TShadowable.
class --|> #TShadower.

containerEntity --|> namedEntity.
containerEntity --|> #TWithClasses.
Expand Down
26 changes: 26 additions & 0 deletions src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
79 changes: 49 additions & 30 deletions src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down Expand Up @@ -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 [

Expand Down Expand Up @@ -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 [

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ PyFunctionDefinitionNode >> name [
^ self fname value
]

{ #category : '*Famix-Python-Importer' }
PyFunctionDefinitionNode >> pythonFunctionName [

^ fname value
]

{ #category : '*Famix-Python-Importer' }
PyFunctionDefinitionNode >> signatureString [

Expand Down

0 comments on commit 763c799

Please sign in to comment.