From acba8a231a8ebb5568d777aa32db85990caf714b Mon Sep 17 00:00:00 2001 From: "Aleksandr.Govenko" Date: Mon, 8 Sep 2025 18:16:05 +0200 Subject: [PATCH] PY-83348 Narrow `X | None` object to `X` after `object or x` GitOrigin-RevId: d835132ade43a3f12b78f4029f584cad97180dc2 --- .../controlflow/PyTypeAssertionEvaluator.java | 2 ++ .../python/psi/impl/PyBinaryExpressionImpl.java | 11 +++++++++-- python/testSrc/com/jetbrains/python/Py3TypeTest.java | 12 ++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index 1589d7d26e82..2422c37e8aa3 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -392,6 +392,8 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { return null; }; } + + private static boolean isIfReferenceStatement(@NotNull PyExpression node) { return PsiTreeUtil.skipParentsOfType(node, PyParenthesizedExpression.class) instanceof PyIfPart; diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java index 09d53f29f2f5..d3f5f960338f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java @@ -2,9 +2,11 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.controlflow.PyTypeAssertionEvaluator; import com.jetbrains.python.psi.PyBinaryExpression; import com.jetbrains.python.psi.PyElementVisitor; import com.jetbrains.python.psi.PyExpression; @@ -53,14 +55,19 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp @Override public @Nullable PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) { - if (isOperator("and") || isOperator("or")) { + if (isOperator(PyNames.AND) || isOperator(PyNames.OR)) { final PyExpression left = getLeftExpression(); - final PyType leftType = left != null ? context.getType(left) : null; + PyType leftType = left != null ? context.getType(left) : null; final PyExpression right = getRightExpression(); final PyType rightType = right != null ? context.getType(right) : null; if (leftType == null && rightType == null) { return null; } + if (isOperator(PyNames.OR)) { + // TODO: also exclude Literal[False, 0, ""] + leftType = Ref.deref(PyTypeAssertionEvaluator.createAssertionType( + leftType, PyBuiltinCache.getInstance(this).getNoneType(), false, true, context)); + } return PyUnionType.union(leftType, rightType); } final String referencedName = getReferencedName(); diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 45b6fcd7aa83..99bf757e9ae3 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -150,6 +150,18 @@ public class Py3TypeTest extends PyTestCase { d = {} expr = d.pop("abc", None)"""); } + + // PY-83348 + public void testOrExpressionType() { + doTest("int | str", """ + def foo(x: int | None): + expr = x or "foo" + """); + doTest("str", """ + def foo(x: None): + expr = x or "foo" + """); + } public void testYieldInsideLambda() { // Checks that foo is not a generator