Skip to content

Commit

Permalink
Big refactoring to start to manage namespace resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
jecisc committed Feb 5, 2025
1 parent b1c2bb9 commit 729e4ec
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 79 deletions.
17 changes: 17 additions & 0 deletions src/Famix-Python-Importer-Tests/FamixPythonProject1Test.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -3748,6 +3748,23 @@ FamixPythonProject1Test >> testSimpleFunction [
self assert: function functionOwner equals: (self moduleNamed: 'moduleAtRoot')
]

{ #category : 'tests - invocations' }
FamixPythonProject1Test >> testSimpleFunctionInvocationWithNamespace [

| module function invocation |
function := self functionNamed: 'sort_list'.
module := self moduleNamed: 'moduleAtRoot2'.

invocation := function incomingInvocations detect: [ :aReference | aReference sender = module ].

self assert: invocation class equals: FamixPythonInvocation.
self assert: invocation source equals: module.
self assert: invocation sender equals: module.
self assertCollection: invocation target hasSameElements: { function }.
self assert: invocation invokedEntity equals: function.
self assert: (module outgoingInvocations anySatisfy: [ :aReference | aReference invokedEntity = function ])
]

{ #category : 'tests - inheritances' }
FamixPythonProject1Test >> testSimpleInheritance [

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Class {
#name : 'FamixPythonAbstractInvocationOrInstantiationResolvable',
#superclass : 'SRResolvable',
#instVars : [
'identifier',
'entity'
],
#category : 'Famix-Python-Importer-SymbolResolution',
#package : 'Famix-Python-Importer',
#tag : 'SymbolResolution'
}

{ #category : 'accessing' }
FamixPythonAbstractInvocationOrInstantiationResolvable class >> identifier: anIdentifierString [

^ self new
identifier: anIdentifierString;
yourself
]

{ #category : 'hooks' }
FamixPythonAbstractInvocationOrInstantiationResolvable >> applyReplacementStrategyWithCurrentEntity: aCurrentEntity [

self entity: (self notFoundReplacementEntity cull: self cull: aCurrentEntity)
]

{ #category : 'accessing' }
FamixPythonAbstractInvocationOrInstantiationResolvable >> entity [

^ entity
]

{ #category : 'accessing' }
FamixPythonAbstractInvocationOrInstantiationResolvable >> entity: anObject [

entity := anObject
]

{ #category : 'accessing' }
FamixPythonAbstractInvocationOrInstantiationResolvable >> identifier [

^ identifier
]

{ #category : 'accessing' }
FamixPythonAbstractInvocationOrInstantiationResolvable >> identifier: anObject [

identifier := anObject
]
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ FamixPythonFunctionCallReceiverVariableNode >> acceptVisitor: aRootVisitor [

^ aRootVisitor visitFunctionCallReceiverVariable: self
]

{ #category : 'as yet unclassified' }
FamixPythonFunctionCallReceiverVariableNode >> hasReceiver [

^ false
]
147 changes: 112 additions & 35 deletions src/Famix-Python-Importer/FamixPythonImporterVisitor.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,117 @@ FamixPythonImporterVisitor >> relativeFilePath: aNode [
^(parentFileName relativeTo: rootFilePath asPath) pathString
]

{ #category : 'visiting' }
FamixPythonImporterVisitor >> resolveInvocationOrInstantiationFrom: aFunctionCallExpression [
"When we end up here we might have multiple cases to manage:
- x() => This is either a function invocation or instantiation. There should be no stub.
- package.module.x() => Function invocation of instantiation comming from one of the import. Stub is an unknown entity.
- self.x() => Method invocation. There should be no stub.
- c.x() => Method invocation. Can be a stub method
- x()() => Invocation or Instantiation of the result of another invocation. This we cannot really treat it here so for now I'll just ignore this case. We can discuss in a Moose meeting if we want to do something else."

| source |
aFunctionCallExpression source traceCr.
self flag: #todo. "Once we managed all cases we should refactor."

self flag: #todo. "Discuss in meeting"
" => x()()
This is happening when we invoke the result of an invocation. The parser cannot know what is invoked. For now I'm ignoring it"
(aFunctionCallExpression receiver isKindOf: PyFunctionCallExpressionNode) ifTrue: [ ^ self ].

"=> x()"
self flag: #todo. "Stubs should not be functions."
self flag: #todo. "See if we can simplify the code since there is only 1 possibility?"
aFunctionCallExpression hasReceiver ifFalse: [
self
resolve: ((FamixPythonInvocationOrInstantiationResolvable identifier: aFunctionCallExpression receiver name)
expectedKind: {
FamixPythonFunction.
FamixPythonClass };
notFoundReplacementEntity: [ :unresolved :currentEntity |
self flag: #todo. "This can be something else than a function"
{ (self ensureStubFunctionNamed: unresolved identifier) } ];
yourself)
foundAction: [ :result :currentEntity |
self flag: #todo. "Improve code"
(result isCollection not and: [ result isClass ])
ifTrue: [
| reference invocation |
reference := result createAccessOrReferenceFrom: currentEntity node: aFunctionCallExpression.
self setSourceAnchor: reference from: aFunctionCallExpression.

invocation := self model newInvocation
candidates: { (self getConstructorOf: result) };
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ]
ifFalse: [
| invocation |
invocation := self model newInvocation
candidates: result;
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ] ] ].

"=> package.module.x()"
self flag: #todo.
source := aFunctionCallExpression receiver source.
self flag: #todo. "duplication with visitFieldAccessExpression."
self flag: #todo. "duplacation with what is just above."
self flag: #todo. "Stubs should not be functions."
self flag: #todo. "Add tests in case of alias + imports with spaces + multiple levels"
self flag: #todo. "See if we can simplify the code since there is only 1 possibility?"
self currentEntity allEffectiveSimpleImports
detect: [ :import |
| importPath |
importPath := import hasAlias
ifTrue: [ import alias ]
ifFalse: [ importPaths at: import ].
source = importPath or: [
importPath := importPath , '.'.
(source beginsWith: importPath) and: [ ((source withoutPrefix: importPath) includes: $.) not ] ] ]
ifFound: [ :import |
self
resolve: ((FamixPythonInvocationOrInstantiationWithNamespaceResolvable identifier: aFunctionCallExpression receiver name import: import)
expectedKind: {
FamixPythonFunction.
FamixPythonClass };
notFoundReplacementEntity: [ :unresolved :currentEntity |
self flag: #todo. "This can be something else than a function"
{ (self ensureStubFunctionNamed: unresolved identifier) } ];
yourself)
foundAction: [ :result :currentEntity |
self flag: #todo. "Improve code"
(result isCollection not and: [ result isClass ])
ifTrue: [
| reference invocation |
reference := result createAccessOrReferenceFrom: currentEntity node: aFunctionCallExpression.
self setSourceAnchor: reference from: aFunctionCallExpression.

invocation := self model newInvocation
candidates: { (self getConstructorOf: result) };
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ]
ifFalse: [
| invocation |
invocation := self model newInvocation
candidates: result;
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ] ] ].
self flag: #todo.

"=> self.x()"

"=> c.x()"
self flag: #todo
]

{ #category : 'accessing - modules' }
FamixPythonImporterVisitor >> rootFilePath [

Expand Down Expand Up @@ -590,41 +701,7 @@ FamixPythonImporterVisitor >> visitFile: aFileNode [
{ #category : 'visiting' }
FamixPythonImporterVisitor >> visitFunctionCallExpression: aFunctionCallExpression [

aFunctionCallExpression source traceCr.
self flag: #todo. "Temporary hack"
(aFunctionCallExpression receiver isKindOf: PyFunctionCallExpressionNode) ifFalse: [
self
resolve: ((FamixPythonInvocationOrInstantiationResolvable identifier: aFunctionCallExpression receiver name)
expectedKind: {
FamixPythonFunction.
FamixPythonClass };
notFoundReplacementEntity: [ :unresolved :currentEntity |
self flag: #todo. "This can be something else than a function"
{ (self ensureStubFunctionNamed: unresolved identifier) } ];
yourself)
foundAction: [ :result :currentEntity |
self flag: #todo. "Improve code"
(result isCollection not and: [ result isClass ])
ifTrue: [
| reference invocation |
reference := result createAccessOrReferenceFrom: currentEntity node: aFunctionCallExpression.
self setSourceAnchor: reference from: aFunctionCallExpression.

invocation := self model newInvocation
candidates: { (self getConstructorOf: result) };
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ]
ifFalse: [
| invocation |
invocation := self model newInvocation
candidates: result;
sender: currentEntity;
signature: aFunctionCallExpression source;
yourself.
self setSourceAnchor: invocation from: aFunctionCallExpression ] ] ].

self resolveInvocationOrInstantiationFrom: aFunctionCallExpression.

^ super visitFunctionCallExpression: aFunctionCallExpression
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,11 @@
Class {
#name : 'FamixPythonInvocationOrInstantiationResolvable',
#superclass : 'SRResolvable',
#instVars : [
'identifier',
'entity'
],
#superclass : 'FamixPythonAbstractInvocationOrInstantiationResolvable',
#category : 'Famix-Python-Importer-SymbolResolution',
#package : 'Famix-Python-Importer',
#tag : 'SymbolResolution'
}

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationResolvable class >> identifier: anIdentifierString [

^ self new
identifier: anIdentifierString;
yourself
]

{ #category : 'hooks' }
FamixPythonInvocationOrInstantiationResolvable >> applyReplacementStrategyWithCurrentEntity: aCurrentEntity [

self entity: (self notFoundReplacementEntity cull: self cull: aCurrentEntity)
]

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationResolvable >> entity [

^ entity
]

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationResolvable >> entity: anObject [

entity := anObject
]

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationResolvable >> identifier [

^ identifier
]

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationResolvable >> identifier: anObject [

identifier := anObject
]

{ #category : 'resolution' }
FamixPythonInvocationOrInstantiationResolvable >> resolveInScope: aScope currentEntity: currentEntity [

Expand All @@ -56,7 +14,7 @@ FamixPythonInvocationOrInstantiationResolvable >> resolveInScope: aScope current

matchingEntities := (aScope reachableEntitiesNamed: self identifier ofKinds: self expectedKinds) ifEmpty: [ NotFound signal ].

matchingEntities := matchingEntities sorted: [ :entity | entity sourceAnchor startPos ] ascending.
matchingEntities := matchingEntities sorted: [ :anEntity | anEntity sourceAnchor startPos ] ascending.
(matchingEntities collectAsSet: #class) size = 1
ifTrue: [ "This is an instantiation"
matchingEntities last isClass ifTrue: [ ^ self entity: matchingEntities last ].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Class {
#name : 'FamixPythonInvocationOrInstantiationWithNamespaceResolvable',
#superclass : 'FamixPythonAbstractInvocationOrInstantiationResolvable',
#instVars : [
'import'
],
#category : 'Famix-Python-Importer-SymbolResolution',
#package : 'Famix-Python-Importer',
#tag : 'SymbolResolution'
}

{ #category : 'instance creation' }
FamixPythonInvocationOrInstantiationWithNamespaceResolvable class >> identifier: aString import: anImport [

^ (self identifier: aString)
import: anImport;
yourself
]

{ #category : 'accessing' }
FamixPythonInvocationOrInstantiationWithNamespaceResolvable >> import: anObject [
import := anObject
]

{ #category : 'resolution' }
FamixPythonInvocationOrInstantiationWithNamespaceResolvable >> resolveInScope: aScope currentEntity: currentEntity [

import importedEntity name = identifier ifTrue: [ ^ self entity: import importedEntity ].

^ (import importedEntity definedEntitiesNamed: identifier ofKinds: self expectedKinds) ifEmpty: [ SRNoResolutionPossible signal ] ifNotEmpty: [ :entities |
| matchingEntities |
self flag: #todo. "This could probably be simplified? Or at least remove duplication with sibling classes.."
matchingEntities := entities sorted: [ :anEntity | anEntity sourceAnchor startPos ] ascending.
(matchingEntities collectAsSet: #class) size = 1
ifTrue: [ "This is an instantiation"
matchingEntities last isClass ifTrue: [ ^ self entity: matchingEntities last ].

self entity: matchingEntities ]
ifFalse: [
self entity: (matchingEntities last isClass
ifTrue: [ matchingEntities last ]
ifFalse: [ { matchingEntities last } ]) ] ]
]
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Extension { #name : 'PyFieldAccessExpressionNode' }

{ #category : '*Famix-Python-Importer' }
PyFieldAccessExpressionNode >> hasReceiver [

^ self receiver isNotNil
]

{ #category : '*Famix-Python-Importer' }
PyFieldAccessExpressionNode >> isInstanceVariableAssignation [

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Extension { #name : 'PyFunctionCallExpressionNode' }

{ #category : '*Famix-Python-Importer' }
PyFunctionCallExpressionNode >> hasReceiver [
"I return true if my `receiver` has a receiver. (Because my receiver is not really a receiver but what is been invoked or referenced."

^ self receiver hasReceiver
]

{ #category : '*Famix-Python-Importer' }
PyFunctionCallExpressionNode >> isFunctionCallExpression [

Expand Down

0 comments on commit 729e4ec

Please sign in to comment.