Skip to content

Commit 8bf1fe5

Browse files
marc-jasper-sonarsourcesonartech
authored andcommitted
SONARPY-1865 Resolve the "self" argument of methods (#673)
GitOrigin-RevId: 147e2650f80a5c938108408d4b93dfbd0cd8b42a
1 parent 1ad23a3 commit 8bf1fe5

File tree

5 files changed

+168
-5
lines changed

5 files changed

+168
-5
lines changed

python-checks/src/test/resources/checks/hotspots/unsafeHttpMethods/django/views_and_urls_same_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
class ClassWithViews:
5-
def view_method(self): # FN SONARPY-2322
5+
def view_method(self): # Noncompliant
66
...
77

88
def get_urlpatterns(self):

python-checks/src/test/resources/checks/localVariableAndParameterNameIncompatibility.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ def django_models():
106106

107107
class RockNRollConfig(AppConfig):
108108
def ready(self):
109-
# FP due to self not being resolved; will be fixed by SONARPY-1865
110-
MyModel = self.get_model("MyModel") # Noncompliant
109+
MyModel = self.get_model("MyModel")
111110

112111
MySecondModel = RockNRollConfig().get_model("MySecondModel")
113112

python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TrivialTypeInferenceVisitor.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import org.sonar.plugins.python.api.types.v2.ModuleType;
7272
import org.sonar.plugins.python.api.types.v2.ObjectType;
7373
import org.sonar.plugins.python.api.types.v2.PythonType;
74+
import org.sonar.plugins.python.api.types.v2.SelfType;
7475
import org.sonar.plugins.python.api.types.v2.TypeOrigin;
7576
import org.sonar.plugins.python.api.types.v2.TypeSource;
7677
import org.sonar.plugins.python.api.types.v2.TypeWrapper;
@@ -336,6 +337,7 @@ public void visitFunctionDef(FunctionDef functionDef) {
336337
// TODO: check scope accuracy
337338
scan(functionDef.typeParams());
338339
scan(functionDef.parameters());
340+
setSelfParameterType(functionDef);
339341
scan(functionDef.body());
340342
});
341343
}
@@ -370,6 +372,38 @@ private FunctionType buildFunctionType(FunctionDef functionDef) {
370372
return functionType;
371373
}
372374

375+
private void setSelfParameterType(FunctionDef functionDef) {
376+
// currentType() is always FunctionType here since we are called from inTypeScope(functionType, ...)
377+
var functionType = (FunctionType) currentType();
378+
// defensive code, should never be true during normal operation
379+
if (!(functionType.owner() instanceof ClassType classType)) {
380+
return;
381+
}
382+
383+
if (!functionType.isInstanceMethod()) {
384+
return;
385+
}
386+
387+
var parameterList = functionDef.parameters();
388+
if (parameterList == null) {
389+
return;
390+
}
391+
392+
var parameters = parameterList.nonTuple();
393+
if (parameters.isEmpty()) {
394+
return;
395+
}
396+
397+
var firstParam = parameters.get(0);
398+
var paramName = firstParam.name();
399+
// Set the type to ObjectType[SelfType[ClassType]]
400+
if (paramName != null) {
401+
var classObjectType = ObjectType.fromType(classType);
402+
var selfType = SelfType.of(classObjectType);
403+
setTypeToName(paramName, selfType);
404+
}
405+
}
406+
373407
@Override
374408
public void visitImportName(ImportName importName) {
375409
importName.modules()

python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,8 +1123,7 @@ def get_urlpatterns(self):
11231123
""";
11241124
ProjectLevelSymbolTable projectSymbolTable = empty();
11251125
projectSymbolTable.addModule(parseWithoutSymbols(content), "my_package", pythonFile("mod.py"));
1126-
// SONARPY-2322: should be true
1127-
assertThat(projectSymbolTable.isDjangoView("my_package.mod.ClassWithViews.view_method")).isFalse();
1126+
assertThat(projectSymbolTable.isDjangoView("my_package.mod.ClassWithViews.view_method")).isTrue();
11281127
}
11291128

11301129
/**

python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import org.sonar.plugins.python.api.types.v2.ObjectType;
6363
import org.sonar.plugins.python.api.types.v2.ParameterV2;
6464
import org.sonar.plugins.python.api.types.v2.PythonType;
65+
import org.sonar.plugins.python.api.types.v2.SelfType;
6566
import org.sonar.plugins.python.api.types.v2.TypeOrigin;
6667
import org.sonar.plugins.python.api.types.v2.TypeSource;
6768
import org.sonar.plugins.python.api.types.v2.TypeWrapper;
@@ -3938,4 +3939,134 @@ private static Statement lastStatement(StatementList statementList) {
39383939
List<Statement> statements = statementList.statements();
39393940
return statements.get(statements.size() - 1);
39403941
}
3942+
3943+
@Test
3944+
void selfParameterTypeInMethod() {
3945+
FileInput root = inferTypes("""
3946+
class MyClass:
3947+
def foo(self, x):
3948+
self
3949+
def bar(self):
3950+
...
3951+
""");
3952+
3953+
var classDef = (ClassDef) root.statements().statements().get(0);
3954+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
3955+
var selfExpressionStatement = (ExpressionStatement) fooMethodDef.body().statements().get(0);
3956+
var selfExpression = selfExpressionStatement.expressions().get(0);
3957+
var selfType = selfExpression.typeV2();
3958+
3959+
// The self parameter should have type ObjectType[SelfType[MyClass]]
3960+
assertThat(selfType).isInstanceOf(ObjectType.class);
3961+
var objectType = (ObjectType) selfType;
3962+
assertThat(objectType.unwrappedType()).isInstanceOf(SelfType.class);
3963+
3964+
var selfTypeInner = (SelfType) objectType.unwrappedType();
3965+
var myClassType = classDef.name().typeV2();
3966+
assertThat(selfTypeInner.innerType()).isEqualTo(myClassType);
3967+
}
3968+
3969+
@Test
3970+
void selfParameterTypeInMethodWithNonStandardName() {
3971+
FileInput root = inferTypes("""
3972+
class MyClass:
3973+
def foo(this):
3974+
this
3975+
""");
3976+
3977+
var classDef = (ClassDef) root.statements().statements().get(0);
3978+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
3979+
var thisExpressionStatement = (ExpressionStatement) fooMethodDef.body().statements().get(0);
3980+
var thisExpression = thisExpressionStatement.expressions().get(0);
3981+
3982+
assertThat(thisExpression.typeV2()).isInstanceOf(ObjectType.class);
3983+
var objectType = (ObjectType) thisExpression.typeV2();
3984+
assertThat(objectType.unwrappedType()).isInstanceOf(SelfType.class);
3985+
}
3986+
3987+
@Test
3988+
void selfParameterTypeOverwritesExplicitAnnotation() {
3989+
FileInput root = inferTypes("""
3990+
class MyClass:
3991+
def foo(self: int):
3992+
self
3993+
""");
3994+
3995+
var classDef = (ClassDef) root.statements().statements().get(0);
3996+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
3997+
var selfExpressionStatement = (ExpressionStatement) fooMethodDef.body().statements().get(0);
3998+
var selfExpression = selfExpressionStatement.expressions().get(0);
3999+
4000+
// SelfType overwrites the explicit type annotation
4001+
assertThat(selfExpression.typeV2()).isInstanceOf(ObjectType.class);
4002+
var objectType = (ObjectType) selfExpression.typeV2();
4003+
assertThat(objectType.unwrappedType()).isInstanceOf(SelfType.class);
4004+
}
4005+
4006+
@Test
4007+
void classMethodFirstParameterType() {
4008+
FileInput root = inferTypes("""
4009+
class MyClass:
4010+
@classmethod
4011+
def foo(cls):
4012+
cls
4013+
""");
4014+
4015+
var classDef = (ClassDef) root.statements().statements().get(0);
4016+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
4017+
var clsExpressionStatement = (ExpressionStatement) fooMethodDef.body().statements().get(0);
4018+
var clsExpression = clsExpressionStatement.expressions().get(0);
4019+
4020+
assertThat(clsExpression.typeV2()).isNotInstanceOf(SelfType.class);
4021+
assertThat(clsExpression.typeV2())
4022+
.extracting(PythonType::unwrappedType)
4023+
.isNotInstanceOf(SelfType.class);
4024+
}
4025+
4026+
@Test
4027+
void staticMethodHasNoImplicitSelfParameter() {
4028+
FileInput root = inferTypes("""
4029+
class MyClass:
4030+
@staticmethod
4031+
def foo(x):
4032+
x
4033+
""");
4034+
4035+
var classDef = (ClassDef) root.statements().statements().get(0);
4036+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
4037+
var xExpressionStatement = (ExpressionStatement) fooMethodDef.body().statements().get(0);
4038+
var xExpression = xExpressionStatement.expressions().get(0);
4039+
4040+
assertThat(xExpression.typeV2()).isNotInstanceOf(SelfType.class);
4041+
assertThat(xExpression.typeV2())
4042+
.extracting(PythonType::unwrappedType)
4043+
.isNotInstanceOf(SelfType.class);
4044+
}
4045+
4046+
@Test
4047+
void setSelfParameterTypeWithNoParameters() {
4048+
FileInput root = inferTypes("""
4049+
class MyClass:
4050+
def foo():
4051+
pass
4052+
""");
4053+
4054+
var classDef = (ClassDef) root.statements().statements().get(0);
4055+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
4056+
assertThat(fooMethodDef.name().typeV2()).isInstanceOf(FunctionType.class);
4057+
}
4058+
4059+
@Test
4060+
void setSelfParameterTypeWithOnlyTupleParameters() {
4061+
// Python 2 syntax: tuple parameters are allowed
4062+
FileInput root = inferTypes("""
4063+
class MyClass:
4064+
def foo((a, b)):
4065+
pass
4066+
""");
4067+
4068+
var classDef = (ClassDef) root.statements().statements().get(0);
4069+
var fooMethodDef = (FunctionDef) classDef.body().statements().get(0);
4070+
assertThat(fooMethodDef.name().typeV2()).isInstanceOf(FunctionType.class);
4071+
}
39414072
}

0 commit comments

Comments
 (0)