PY-80195 Literals of Enums broken after import (2025.1 EAP)

Consider enum attribute of an unknown type an enum member.

(cherry picked from commit 72c2fd37f18e4dde65d735f1032495df5aeda791)

GitOrigin-RevId: 7e56afe6a2d2efe0ea84ac20672aaf7712d8ed23
This commit is contained in:
Petr
2025-04-10 14:25:09 +02:00
committed by intellij-monorepo-bot
parent ed52e34951
commit c52f4bbadf
7 changed files with 131 additions and 20 deletions

View File

@@ -57,6 +57,11 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase {
return null;
}
@Override
public @Nullable PyType getCallableType(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
return Ref.deref(getTransformedEnumAttributeType(callable, context));
}
@Override
public @Nullable PyType getReferenceExpressionType(@NotNull PyReferenceExpression referenceExpression, @NotNull TypeEvalContext context) {
if (!referenceExpression.isQualified()) {
@@ -86,12 +91,12 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase {
*/
@ApiStatus.Internal
public static @Nullable PyLiteralType getEnumMemberType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
return ObjectUtils.tryCast(Ref.deref(getEnumAttributeType(element, context)), PyLiteralType.class);
return ObjectUtils.tryCast(Ref.deref(getTransformedEnumAttributeType(element, context)), PyLiteralType.class);
}
private static @Nullable Ref<PyType> getEnumType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context,
@Nullable PsiElement anchor) {
@Nullable Ref<PyType> enumAttributeType = getEnumAttributeType(referenceTarget, context);
@Nullable Ref<PyType> enumAttributeType = getTransformedEnumAttributeType(referenceTarget, context);
if (enumAttributeType != null) {
return enumAttributeType;
}
@@ -135,11 +140,12 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase {
return null;
}
private static @Nullable Ref<PyType> getEnumAttributeType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
return RecursionManager.doPreventingRecursion(element, false, () -> getEnumAttributeTypeImpl(element, context));
// Returns the type of enum attribute value transformed by 'EnumType' metaclass or null, if the attribute value is not transformed
private static @Nullable Ref<PyType> getTransformedEnumAttributeType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
return RecursionManager.doPreventingRecursion(element, false, () -> getTransformedEnumAttributeTypeImpl(element, context));
}
private static @Nullable Ref<PyType> getEnumAttributeTypeImpl(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
private static @Nullable Ref<PyType> getTransformedEnumAttributeTypeImpl(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
if (!(element instanceof PyTargetExpression) && !(element instanceof PyDecoratable)) return null;
if (!(ScopeUtil.getScopeOwner(element) instanceof PyClass cls && isCustomEnum(cls, context))) return null;
@@ -216,6 +222,7 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase {
else {
if (!targetExpression.hasAssignedValue()) return null;
// Handle enum.auto(), enum.member(), enum.nonmember()
PyTargetExpressionStub stub = targetExpression.getStub();
PyEnumAttributeStub attributeStub = stub != null
? stub.getCustomStub(PyEnumAttributeStub.class)
@@ -228,29 +235,22 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase {
QualifiedName assignedQName = targetExpression.getAssignedQName();
if (assignedQName != null) {
PsiElement value = ContainerUtil.getFirstItem(PyResolveUtil.resolveQualifiedNameInScope(assignedQName, enumClass, context));
if (value != null) {
Ref<PyType> type = getEnumAttributeType(value, context);
return type == null ? null : getEnumAttributeInfo(enumClass, type.get(), context);
}
PsiElement resolved = ContainerUtil.getFirstItem(PyResolveUtil.resolveQualifiedNameInScope(assignedQName, enumClass, context));
PyType type = resolved instanceof PyTypedElement ? context.getType((PyTypedElement)resolved) : null;
return getEnumAttributeInfo(enumClass, type, context);
}
PyLiteralKind literalKind = stub != null
? stub.getAssignedLiteralKind()
: PyLiteralKind.fromExpression(targetExpression.findAssignedValue());
if (literalKind == null) {
return EnumAttributeInfo.nonMember(null);
}
else {
PyType type = PyUtil.convertToType(literalKind, PyBuiltinCache.getInstance(targetExpression));
return new EnumAttributeInfo(type, EnumAttributeKind.MEMBER);
}
PyType type = literalKind != null ? PyUtil.convertToType(literalKind, PyBuiltinCache.getInstance(targetExpression)) : null;
return new EnumAttributeInfo(type, EnumAttributeKind.MEMBER);
}
}
private static @NotNull EnumAttributeInfo getEnumAttributeInfo(@NotNull PyClass enumClass, @Nullable PyType type, @NotNull TypeEvalContext context) {
if (type == null) {
return EnumAttributeInfo.nonMember(null);
return new EnumAttributeInfo(null, EnumAttributeKind.MEMBER);
}
PyQualifiedNameOwner typeDeclarationElement = type.getDeclarationElement();
if (typeDeclarationElement != null) {

View File

@@ -0,0 +1,5 @@
from typing import Literal
from m import SimpleEnum, SuperEnum
p: Literal[SuperEnum.PINK] = SuperEnum.PINK
q: Literal[SimpleEnum.FOO] = <warning descr="Expected type 'Literal[SimpleEnum.FOO]', got 'Literal[SuperEnum.PINK]' instead">SuperEnum.PINK</warning>

View File

@@ -0,0 +1,10 @@
import enum
class SuperEnum(enum.Enum):
PINK = "PINK", "hot"
FLOSS = "FLOSS", "sweet"
class SimpleEnum(enum.Enum):
FOO = "FOO"

View File

@@ -0,0 +1,21 @@
from typing import Literal
from m import *
v1: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">A.X</warning>]
X = Color.R
v2: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">X</warning>]
v3: Literal[Color.G]
v4: Literal[Color.RED]
v5: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">Color.foo</warning>]
v6: Literal[Color.bar]
v7: Literal[SuperEnum.PINK]
v8: Literal[E.FOO]
v9: Literal[E.BAR]
v10: Literal[E.BUZ]
v11: Literal[E.QUX]
v12: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">E.meth2</warning>]

View File

@@ -0,0 +1,38 @@
from enum import Enum, member, nonmember
from typing import Any
class Color(Enum):
R = 1
G = 2
RED = R
foo = nonmember(3)
@member
def bar(self): ...
class A:
X = Color.R
class SuperEnum(Enum):
PINK = "PINK", "hot"
FLOSS = "FLOSS", "sweet"
tuple = 1, "ab"
o = object()
def get_object() -> object: ...
def get_any() -> Any: ...
class E(Enum):
FOO = tuple
BAR = o
BUZ = get_object()
QUX = get_any()
def meth(self): ...
meth2 = meth

View File

@@ -623,6 +623,11 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
);
}
// PY-80195
public void testMultiValueEnum() {
doMultiFileTest();
}
// PY-42418
public void testParametrizedBuiltinCollectionsAndTheirTypingAliasesAreEquivalent() {
doTest();

View File

@@ -1236,7 +1236,7 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
public void testEnumLiteral() {
doTestByText("""
from enum import Enum, member, nonmember
from typing import Literal
from typing import Literal, Any
class Color(Enum):
R = 1
@@ -1251,6 +1251,25 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
class A:
X = Color.R
class SuperEnum(Enum):
PINK = "PINK", "hot"
FLOSS = "FLOSS", "sweet"
tuple = 1, "ab"
o = object()
def get_object() -> object: ...
def get_any() -> Any: ...
class E(Enum):
FOO = tuple
BAR = o
BUZ = get_object()
QUX = get_any()
def meth(self): ...
meth2 = meth
v1: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">A.X</warning>]
X = Color.R
@@ -1259,7 +1278,20 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
v3: Literal[Color.G]
v4: Literal[Color.RED]
v5: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">Color.foo</warning>]
v6: Literal[Color.bar]""");
v6: Literal[Color.bar]
v7: Literal[SuperEnum.PINK]
v8: Literal[E.FOO]
v9: Literal[E.BAR]
v10: Literal[E.BUZ]
v11: Literal[E.QUX]
v12: Literal[<warning descr="'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types">E.meth2</warning>]""");
}
// PY-79227
public void testEnumLiteralMultiFile() {
doMultiFileTest();
}
// PY-35235