diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java index 6ef4486ab011..e4d980543327 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java @@ -21,19 +21,17 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.stubs.StubInputStream; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.*; -import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.ast.impl.PyUtilCore; -import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub.InitializerType; import com.jetbrains.python.psi.stubs.PyTypingAliasStub; -import org.jetbrains.annotations.ApiStatus; import one.util.streamex.StreamEx; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -167,19 +165,44 @@ public final class PyTypingAliasStubType extends CustomTargetExpressionStubType< if (expression instanceof PyBinaryExpression) { return looksLikeTypeHint(expression); } - return PsiTreeUtil.processElements(expression, element -> { - // Check only composite elements - if (element instanceof ASTDelegatePsiElement) { - if (!VALID_TYPE_ANNOTATION_ELEMENTS.contains(element.getNode().getElementType())) { - return false; + boolean[] illegal = {false}; + expression.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPySubscriptionExpression(@NotNull PySubscriptionExpression node) { + if (node.getOperand() instanceof PyReferenceExpression refExpr && + "Annotated".equals(refExpr.getName()) && + node.getIndexExpression() instanceof PyTupleExpression tupleExpr) { + refExpr.accept(this); + tupleExpr.getElements()[0].accept(this); } - if (element instanceof PyReferenceExpression) { - // too complex reference expression, e.g. foo[bar].baz - return ((PyReferenceExpression)element).asQualifiedName() != null; + else if (node.getOperand() instanceof PyReferenceExpression refExpr && + "Literal".equals(refExpr.getName())) { + refExpr.accept(this); + } + else { + super.visitPySubscriptionExpression(node); + } + } + + @Override + public void visitPyReferenceExpression(@NotNull PyReferenceExpression node) { + if (node.asQualifiedName() == null) { + illegal[0] = true; + } + } + + @Override + public void visitElement(@NotNull PsiElement element) { + if (element instanceof ASTDelegatePsiElement) { + if (!VALID_TYPE_ANNOTATION_ELEMENTS.contains(element.getNode().getElementType())) { + illegal[0] = true; + return; + } + super.visitElement(element); } } - return true; }); + return !illegal[0]; } @Override diff --git a/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/a.py b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/a.py new file mode 100644 index 000000000000..1f613c975cb1 --- /dev/null +++ b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/a.py @@ -0,0 +1,4 @@ +from m import StrictStr + + +s: StrictStr \ No newline at end of file diff --git a/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/m.py b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/m.py new file mode 100644 index 000000000000..66fc3c1d4239 --- /dev/null +++ b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingAnnotatedMultiFile/m.py @@ -0,0 +1,4 @@ +from typing import Annotated + + +StrictStr = Annotated[str, object()] \ No newline at end of file diff --git a/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/a.py b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/a.py new file mode 100644 index 000000000000..c4486c7e53f5 --- /dev/null +++ b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/a.py @@ -0,0 +1,5 @@ +from sample import HttpOk, Http400, Http404 + + +def foo() -> HttpOk[None] | Http400 | Http404: + pass \ No newline at end of file diff --git a/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/sample.py b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/sample.py new file mode 100644 index 000000000000..923fbdc959fc --- /dev/null +++ b/python/testData/inspections/PyTypeHintsInspection/ImplicitTypeAliasUsingLiteralMultiFile/sample.py @@ -0,0 +1,14 @@ +from typing import Literal, TypeVar + +Code = TypeVar("Code", bound=int) +Response = TypeVar("Response", bound=list | str | None) +Error = TypeVar("Error", default=str, bound= str | None) + +Http = tuple[Code, Response] +HttpOk = Http[Literal[200], Response] +Http400 = Http[Literal[400], Error] +Http401 = Http[Literal[401], Error] +Http403 = Http[Literal[403], Error] +Http404 = Http[Literal[404], Error] +Http422 = Http[Literal[422], ErrorResponse[list[ErrorDetails]]] +Http500 = Http[Literal[500], Literal["Internal Server Error"]] \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java index 64bc9ee99c4d..33da5e78771d 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java @@ -3074,6 +3074,16 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase { """); } + // PY-82979 + public void testImplicitTypeAliasUsingLiteralMultiFile() { + doMultiFileTest(); + } + + // PY-81028 + public void testImplicitTypeAliasUsingAnnotatedMultiFile() { + doMultiFileTest(); + } + @NotNull @Override protected Class getInspectionClass() {