Skip to content

Commit 330a21d

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3608 Represent multiple attributes on parameters correctly (#750)
GitOrigin-RevId: 575735ac04d5e210d2d9333f7b3ad8afb31608ed
1 parent cf148b2 commit 330a21d

File tree

2 files changed

+70
-7
lines changed

2 files changed

+70
-7
lines changed

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Optional;
2929
import java.util.Set;
3030
import java.util.function.Predicate;
31+
import java.util.stream.Collectors;
3132
import java.util.stream.Stream;
3233
import javax.annotation.CheckForNull;
3334
import javax.annotation.Nullable;
@@ -774,17 +775,13 @@ private PythonType resolveTypeAnnotationExpressionType(Expression expression, @N
774775
PythonType resolvedType = resolveSelfType(expression.typeV2(), enclosingClassType);
775776
return ObjectType.Builder.fromType(resolvedType).withTypeSource(TypeSource.TYPE_HINT).build();
776777
} else if (expression instanceof SubscriptionExpression subscriptionExpression && subscriptionExpression.object().typeV2() != PythonType.UNKNOWN) {
777-
var candidateTypes = subscriptionExpression.subscripts()
778+
List<PythonType> attributes = subscriptionExpression.subscripts()
778779
.expressions()
779780
.stream()
780781
.map(Expression::typeV2)
781-
.distinct()
782-
.toList();
783-
784-
var elementsType = UnionType.or(candidateTypes);
782+
.map(type -> ObjectType.Builder.fromType(type).withTypeSource(TypeSource.TYPE_HINT).build())
783+
.collect(Collectors.toList());
785784

786-
var attributes = new ArrayList<PythonType>();
787-
attributes.add(ObjectType.Builder.fromType(elementsType).withTypeSource(TypeSource.TYPE_HINT).build());
788785
return ObjectType.Builder.fromType(subscriptionExpression.object().typeV2())
789786
.withAttributes(attributes)
790787
.withTypeSource(TypeSource.TYPE_HINT)

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Set;
2626
import java.util.function.Function;
2727
import java.util.stream.Stream;
28+
import org.assertj.core.api.Condition;
2829
import org.assertj.core.api.InstanceOfAssertFactories;
2930
import org.assertj.core.data.Index;
3031
import org.assertj.core.groups.Tuple;
@@ -4906,4 +4907,69 @@ class AClass(SuperClass):
49064907
.isInstanceOf(LazyTypeWrapper.class), Index.atIndex(0));
49074908
});
49084909
}
4910+
4911+
@Test
4912+
void testParametersWithAttributes() {
4913+
FileInput root = inferTypes("""
4914+
from typing import Annotated
4915+
def test(a: Annotated[int, "foo"]) -> Annotated[int, "bar"]:
4916+
pass
4917+
""");
4918+
4919+
FunctionDef functionDef = PythonTestUtils.getFirstChild(root, FunctionDef.class::isInstance);
4920+
FunctionType functionType = (FunctionType) functionDef.name().typeV2();
4921+
4922+
assertThat(functionType.parameters()).hasSize(1);
4923+
4924+
ParameterV2 parameter = functionType.parameters().get(0);
4925+
assertThat(parameter.declaredType().type())
4926+
.isInstanceOfSatisfying(ObjectType.class, objType -> assertThat(objType.attributes())
4927+
.satisfies(intAttribute -> assertThat(intAttribute).is(objectTypeOf(INT_TYPE)), Index.atIndex(0))
4928+
.satisfies(strAttribute -> assertThat(strAttribute).is(objectTypeOf(STR_TYPE)), Index.atIndex(1)));
4929+
4930+
PythonType returnType = functionType.returnType();
4931+
assertThat(returnType)
4932+
.isInstanceOfSatisfying(ObjectType.class, objType -> assertThat(objType.attributes())
4933+
.satisfies(intAttribute -> assertThat(intAttribute).is(objectTypeOf(INT_TYPE)), Index.atIndex(0))
4934+
.satisfies(strAttribute -> assertThat(strAttribute).is(objectTypeOf(STR_TYPE)), Index.atIndex(1)));
4935+
}
4936+
4937+
@Test
4938+
void testParametersWithAttributesWithLocalClassTypes() {
4939+
FileInput root = inferTypes("""
4940+
from typing import Annotated
4941+
class MyClass: ...
4942+
def test(a: Annotated[int, MyClass()]) -> Annotated[int, MyClass()]:
4943+
pass
4944+
""");
4945+
4946+
FunctionDef functionDef = PythonTestUtils.getFirstChild(root, FunctionDef.class::isInstance);
4947+
FunctionType functionType = (FunctionType) functionDef.name().typeV2();
4948+
4949+
ClassType myClass = (ClassType) PythonTestUtils.<ClassDef>getFirstChild(root, ClassDef.class::isInstance).name().typeV2();
4950+
4951+
assertThat(functionType.parameters()).hasSize(1);
4952+
4953+
ParameterV2 parameter = functionType.parameters().get(0);
4954+
assertThat(parameter.declaredType().type())
4955+
.isInstanceOfSatisfying(ObjectType.class, objType -> assertThat(objType.attributes())
4956+
.satisfies(intAttribute -> assertThat(intAttribute).is(objectTypeOf(INT_TYPE)), Index.atIndex(0))
4957+
.satisfies(myClassAttribute -> assertThat(myClassAttribute).is(objectTypeOf(myClass)), Index.atIndex(1)));
4958+
4959+
PythonType returnType = functionType.returnType();
4960+
assertThat(returnType)
4961+
.isInstanceOfSatisfying(ObjectType.class, objType -> assertThat(objType.attributes())
4962+
.satisfies(intAttribute -> assertThat(intAttribute).is(objectTypeOf(INT_TYPE)), Index.atIndex(0))
4963+
.satisfies(myClassAttribute -> assertThat(myClassAttribute).is(objectTypeOf(myClass)), Index.atIndex(1)));
4964+
}
4965+
4966+
private static Condition<PythonType> objectTypeOf(PythonType type) {
4967+
return new Condition<PythonType>("is object type of " + type) {
4968+
@Override
4969+
public boolean matches(PythonType value) {
4970+
return value instanceof ObjectType objectType && objectType.unwrappedType().equals(type);
4971+
}
4972+
};
4973+
}
4974+
49094975
}

0 commit comments

Comments
 (0)