From c86296004dc134e1843f18db1f37697a3132892c Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 31 Aug 2025 20:14:29 -0700 Subject: [PATCH 1/2] Fixed bug that resulted in incorrect simplification of `Any | Never`. This addresses #10625. --- packages/pyright-internal/src/analyzer/typeUtils.ts | 6 ++---- packages/pyright-internal/src/tests/samples/never1.py | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index ee53c44119d8..8a29d0805a66 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -862,10 +862,8 @@ export function isUnionableType(subtypes: Type[]): boolean { } // All subtypes need to be instantiable. Some types (like Any - // and None) are both instances and instantiable. It's OK to - // include some of these, but at least one subtype needs to - // be definitively instantiable (not an instance). - return (typeFlags & TypeFlags.Instantiable) !== 0 && (typeFlags & TypeFlags.Instance) === 0; + // and Never) are considered both instances and instantiable. + return (typeFlags & TypeFlags.Instantiable) !== 0; } export function derivesFromAnyOrUnknown(type: Type): boolean { diff --git a/packages/pyright-internal/src/tests/samples/never1.py b/packages/pyright-internal/src/tests/samples/never1.py index 76d7b387afd4..a476d14f73ad 100644 --- a/packages/pyright-internal/src/tests/samples/never1.py +++ b/packages/pyright-internal/src/tests/samples/never1.py @@ -1,7 +1,7 @@ # This sample tests the handling of the "Never" type, # ensuring that it's treated as the same as NoReturn. -from typing import NoReturn, TypeVar, Generic +from typing import Any, NoReturn, TypeVar, Generic from typing_extensions import Never # pyright: ignore[reportMissingModuleSource] T = TypeVar("T") @@ -51,6 +51,10 @@ def func4(): assert_never1() +def func5(x: Any | Never): + reveal_type(x, expected_text="Any") + + reveal_type(assert_never1, expected_text="(val: Never) -> NoReturn") # This should generate an error. From 8e5a943e84cc2e2c468ce8f0c4224f83a745348d Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 1 Sep 2025 10:57:16 -0700 Subject: [PATCH 2/2] Fixed regression caught by mypy-primer. --- .../src/analyzer/operations.ts | 4 +-- .../src/analyzer/typeUtils.ts | 25 ++++++++----------- .../src/tests/samples/never1.py | 3 ++- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/operations.ts b/packages/pyright-internal/src/analyzer/operations.ts index d4ed00b39d5c..83e85af395e0 100644 --- a/packages/pyright-internal/src/analyzer/operations.ts +++ b/packages/pyright-internal/src/analyzer/operations.ts @@ -36,7 +36,7 @@ import { isOptionalType, isTupleClass, isUnboundedTupleClass, - isUnionableType, + isUnionable, lookUpClassMember, makeInferenceContext, mapSubtypes, @@ -365,7 +365,7 @@ export function getTypeOfBinaryOperation( adjustedLeftType = convertToInstantiable(evaluator.getNoneType()); } - if (isUnionableType([adjustedLeftType, adjustedRightType])) { + if (isUnionable(adjustedLeftType, adjustedRightType)) { if (isInstantiableClass(adjustedLeftType)) { adjustedLeftType = specializeWithDefaultTypeArgs(adjustedLeftType); } diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index 3bd389a16150..f84c2dc62fc6 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -846,24 +846,21 @@ export function preserveUnknown(type1: Type, type2: Type): AnyType | UnknownType } } -// Determines whether the specified type is a type that can be -// combined with other types for a union. -export function isUnionableType(subtypes: Type[]): boolean { - // If all of the subtypes are TypeForm types, we know that they - // are unionable. - if (subtypes.every((t) => t.props?.typeForm !== undefined)) { - return true; +// Determines whether the two types will produce a union using the "|" operator. +export function isUnionable(leftType: Type, rightType: Type): boolean { + // If the type of LHS is Any (but is not actually the Any symbol), we + // can't determine whether "|" will produce a union. + if (isAnyOrUnknown(leftType) && !leftType.props?.specialForm) { + return false; } - let typeFlags = TypeFlags.Instance | TypeFlags.Instantiable; - - for (const subtype of subtypes) { - typeFlags &= subtype.flags; + // If the subtypes are TypeForm types, we know that they are unionable. + if (leftType.props?.typeForm && rightType.props?.typeForm) { + return true; } - // All subtypes need to be instantiable. Some types (like Any - // and Never) are considered both instances and instantiable. - return (typeFlags & TypeFlags.Instantiable) !== 0; + // Are both types instantiable? + return (leftType.flags & rightType.flags & TypeFlags.Instantiable) !== 0; } export function derivesFromAnyOrUnknown(type: Type): boolean { diff --git a/packages/pyright-internal/src/tests/samples/never1.py b/packages/pyright-internal/src/tests/samples/never1.py index a476d14f73ad..f7db547709b9 100644 --- a/packages/pyright-internal/src/tests/samples/never1.py +++ b/packages/pyright-internal/src/tests/samples/never1.py @@ -51,8 +51,9 @@ def func4(): assert_never1() -def func5(x: Any | Never): +def func5(x: Any | Never, y: Never | Any): reveal_type(x, expected_text="Any") + reveal_type(y, expected_text="Any") reveal_type(assert_never1, expected_text="(val: Never) -> NoReturn")