|
16 | 16 | */ |
17 | 17 | package org.sonar.python.semantic.v2.types.typecalculator; |
18 | 18 |
|
| 19 | +import java.util.List; |
| 20 | +import java.util.Set; |
19 | 21 | import org.junit.jupiter.api.Test; |
20 | 22 | import org.sonar.plugins.python.api.tree.CallExpression; |
21 | 23 | import org.sonar.plugins.python.api.tree.ClassDef; |
|
33 | 35 | import org.sonar.python.types.v2.matchers.TypePredicateContext; |
34 | 36 |
|
35 | 37 | import static org.assertj.core.api.Assertions.assertThat; |
| 38 | +import static org.assertj.core.api.InstanceOfAssertFactories.type; |
36 | 39 | import static org.sonar.python.PythonTestUtils.getFirstDescendant; |
37 | 40 | import static org.sonar.python.PythonTestUtils.getLastDescendant; |
| 41 | +import static org.sonar.python.types.v2.TypesTestUtils.INT_TYPE; |
| 42 | +import static org.sonar.python.types.v2.TypesTestUtils.LIST_TYPE; |
38 | 43 | import static org.sonar.python.types.v2.TypesTestUtils.PROJECT_LEVEL_TYPE_TABLE; |
39 | 44 | import static org.sonar.python.types.v2.TypesTestUtils.parseAndInferTypes; |
40 | 45 |
|
@@ -181,6 +186,96 @@ def foo(self) -> Self: ... |
181 | 186 | assertThat(result).isSameAs(PythonType.UNKNOWN); |
182 | 187 | } |
183 | 188 |
|
| 189 | + @Test |
| 190 | + void computeCallExpressionType_selfType_unionWithSelfType() { |
| 191 | + FileInput fileInput = parseAndInferTypes(""" |
| 192 | + from typing import Self |
| 193 | + class A: |
| 194 | + def foo(self) -> Self | int: ... |
| 195 | +
|
| 196 | + a = A() |
| 197 | + a.foo() |
| 198 | + """); |
| 199 | + |
| 200 | + ClassDef classDef = getFirstDescendant(fileInput, ClassDef.class::isInstance); |
| 201 | + ClassType classType = (ClassType) classDef.name().typeV2(); |
| 202 | + |
| 203 | + CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance); |
| 204 | + |
| 205 | + PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext); |
| 206 | + |
| 207 | + Set<PythonType> candidates = assertThat(result) |
| 208 | + .isInstanceOf(ObjectType.class) |
| 209 | + .extracting(PythonType::unwrappedType) |
| 210 | + .asInstanceOf(type(UnionType.class)) |
| 211 | + .extracting(UnionType::candidates) |
| 212 | + .actual(); |
| 213 | + |
| 214 | + assertThat(candidates) |
| 215 | + .satisfiesOnlyOnce(type -> assertThat(type).is(TypesTestUtils.objectTypeOf(classType))) |
| 216 | + .satisfiesOnlyOnce(type -> assertThat(type).is(TypesTestUtils.objectTypeOf(INT_TYPE))) |
| 217 | + .hasSize(2); |
| 218 | + } |
| 219 | + |
| 220 | + @Test |
| 221 | + void computeCallExpressionType_selfType_unionWithSelfTypeInList() { |
| 222 | + FileInput fileInput = parseAndInferTypes(""" |
| 223 | + from typing import Self |
| 224 | + class A: |
| 225 | + def foo(self) -> list[Self] | int: ... |
| 226 | +
|
| 227 | + a = A() |
| 228 | + a.foo() |
| 229 | + """); |
| 230 | + |
| 231 | + ClassDef classDef = getFirstDescendant(fileInput, ClassDef.class::isInstance); |
| 232 | + ClassType classType = (ClassType) classDef.name().typeV2(); |
| 233 | + |
| 234 | + CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance); |
| 235 | + |
| 236 | + PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext); |
| 237 | + |
| 238 | + Set<PythonType> candidates = assertThat(result) |
| 239 | + .isInstanceOf(ObjectType.class) |
| 240 | + .extracting(PythonType::unwrappedType) |
| 241 | + .asInstanceOf(type(UnionType.class)) |
| 242 | + .extracting(UnionType::candidates) |
| 243 | + .actual(); |
| 244 | + assertThat(candidates) |
| 245 | + .satisfiesOnlyOnce(type -> assertThat(type).is(TypesTestUtils.objectTypeOf(INT_TYPE))) |
| 246 | + .satisfiesOnlyOnce(type -> assertThat(type) |
| 247 | + .is(TypesTestUtils.objectTypeOf(LIST_TYPE)) |
| 248 | + .asInstanceOf(type(ObjectType.class)) |
| 249 | + .extracting(ObjectType::attributes) |
| 250 | + .isEqualTo(List.of(ObjectType.fromType(classType)))) |
| 251 | + .hasSize(2); |
| 252 | + } |
| 253 | + |
| 254 | + @Test |
| 255 | + void computeCallExpressionType_selfType_listWithSelfType() { |
| 256 | + FileInput fileInput = parseAndInferTypes(""" |
| 257 | + from typing import Self |
| 258 | + class A: |
| 259 | + def foo(self) -> list[Self]: ... |
| 260 | +
|
| 261 | + a = A() |
| 262 | + a.foo() |
| 263 | + """); |
| 264 | + |
| 265 | + ClassDef classDef = getFirstDescendant(fileInput, ClassDef.class::isInstance); |
| 266 | + ClassType classType = (ClassType) classDef.name().typeV2(); |
| 267 | + |
| 268 | + CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance); |
| 269 | + |
| 270 | + PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext); |
| 271 | + |
| 272 | + assertThat(result) |
| 273 | + .asInstanceOf(type(ObjectType.class)) |
| 274 | + .is(TypesTestUtils.objectTypeOf(LIST_TYPE)) |
| 275 | + .extracting(ObjectType::attributes) |
| 276 | + .isEqualTo(List.of(ObjectType.fromType(classType))); |
| 277 | + } |
| 278 | + |
184 | 279 | @Test |
185 | 280 | void computeCallExpressionType_selfTypeInGenerator_typeshedFunction() { |
186 | 281 | PythonType generatorType = PROJECT_LEVEL_TYPE_TABLE.getType("typing.Generator"); |
@@ -224,6 +319,23 @@ void computeCallExpressionType_selfType_typeshedFunction() { |
224 | 319 | assertThat(objectType.attributes()).isEmpty(); |
225 | 320 | } |
226 | 321 |
|
| 322 | + @Test |
| 323 | + void computeCallExpressionType_selfTypeInOverloadedFunction_typeshedFunction() { |
| 324 | + PythonType moduleType = PROJECT_LEVEL_TYPE_TABLE.getType("torch.nn.modules.Module"); |
| 325 | + assertThat(moduleType).isNotEqualTo(PythonType.UNKNOWN); |
| 326 | + |
| 327 | + FileInput fileInput = parseAndInferTypes(""" |
| 328 | + from torch.nn.modules import Module |
| 329 | + module = Module() |
| 330 | + toModule = module.to(...) |
| 331 | + """); |
| 332 | + |
| 333 | + Name toModuleName = PythonTestUtils.getLastDescendant(fileInput, tree -> tree instanceof Name name && "toModule".equals(name.name())); |
| 334 | + PythonType toModuleType = toModuleName.typeV2(); |
| 335 | + |
| 336 | + assertThat(toModuleType).is(TypesTestUtils.objectTypeOf(moduleType)); |
| 337 | + } |
| 338 | + |
227 | 339 | @Test |
228 | 340 | void computeCallExpressionType_methodCall() { |
229 | 341 | FileInput fileInput = parseAndInferTypes(""" |
|
0 commit comments