From bfc7466bb9ad037cbb11ec84a572006ae75d9e30 Mon Sep 17 00:00:00 2001 From: Henry Su Date: Sun, 9 Aug 2026 23:30:44 -0500 Subject: [PATCH 1/3] Fix TypeGuard and TypeIs narrowing for functions with union return types --- .../pyright-internal/src/analyzer/checker.ts | 69 +++++++++++-------- .../src/analyzer/typeGuards.ts | 59 +++++++++++++--- .../src/tests/samples/typeIs5.py | 61 ++++++++++++++++ .../src/tests/typeEvaluator6.test.ts | 5 ++ 4 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/typeIs5.py diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 58db6863cc96..8f7f90fe1c76 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -4669,14 +4669,19 @@ export class Checker extends ParseTreeWalker { return; } - if (!isClassInstance(returnType) || !returnType.priv.typeArgs || returnType.priv.typeArgs.length < 1) { - return; - } - - const isTypeGuard = ClassType.isBuiltIn(returnType, 'TypeGuard'); - const isTypeIs = ClassType.isBuiltIn(returnType, 'TypeIs'); + const guardSubtypes: ClassType[] = []; + doForEachSubtype(returnType, (subtype) => { + if ( + isClassInstance(subtype) && + (ClassType.isBuiltIn(subtype, 'TypeGuard') || ClassType.isBuiltIn(subtype, 'TypeIs')) && + subtype.priv.typeArgs && + subtype.priv.typeArgs.length >= 1 + ) { + guardSubtypes.push(subtype); + } + }); - if (!isTypeGuard && !isTypeIs) { + if (guardSubtypes.length === 0) { return; } @@ -4700,32 +4705,36 @@ export class Checker extends ParseTreeWalker { ); } - if (isTypeIs) { - const scopeIds = getTypeVarScopeIds(functionType); - const narrowedType = returnType.priv.typeArgs[0]; - let typeGuardType = makeTypeVarsBound(narrowedType, scopeIds); - typeGuardType = TypeBase.cloneWithTypeForm(typeGuardType, typeGuardType); + const scopeIds = getTypeVarScopeIds(functionType); - // Determine the type of the first parameter. - const paramIndex = isMethod && !FunctionType.isStaticMethod(functionType) ? 1 : 0; - if (paramIndex >= functionType.shared.parameters.length) { - return; - } + // Determine the type of the first parameter. + const paramIndex = isMethod && !FunctionType.isStaticMethod(functionType) ? 1 : 0; + if (paramIndex >= functionType.shared.parameters.length) { + return; + } - const paramType = makeTypeVarsBound(FunctionType.getParamType(functionType, paramIndex), scopeIds); + const paramType = makeTypeVarsBound(FunctionType.getParamType(functionType, paramIndex), scopeIds); - // Verify that the typeGuardType is a narrower type than the paramType. - if (!this._evaluator.assignType(paramType, typeGuardType)) { - const returnAnnotation = node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; - if (returnAnnotation) { - this._evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeIsReturnType().format({ - type: this._evaluator.printType(paramType), - returnType: this._evaluator.printType(narrowedType), - }), - returnAnnotation - ); + for (const guardSubtype of guardSubtypes) { + if (ClassType.isBuiltIn(guardSubtype, 'TypeIs')) { + const narrowedType = guardSubtype.priv.typeArgs![0]; + let typeGuardType = makeTypeVarsBound(narrowedType, scopeIds); + typeGuardType = TypeBase.cloneWithTypeForm(typeGuardType, typeGuardType); + + // Verify that the typeGuardType is a narrower type than the paramType. + if (!this._evaluator.assignType(paramType, typeGuardType)) { + const returnAnnotation = + node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; + if (returnAnnotation) { + this._evaluator.addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeIsReturnType().format({ + type: this._evaluator.printType(paramType), + returnType: this._evaluator.printType(narrowedType), + }), + returnAnnotation + ); + } } } } diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 7ee3a18d5b37..35d44b5ad90a 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -55,6 +55,7 @@ import { isTypeSame, isTypeVar, isUnpackedTypeVarTuple, + isUnion, maxTypeRecursionCount, OverloadedType, TupleTypeArg, @@ -709,11 +710,23 @@ export function getTypeNarrowingCallback( let isPossiblyTypeGuard = false; const isFunctionReturnTypeGuard = (type: FunctionType) => { - return ( - type.shared.declaredReturnType && - isClassInstance(type.shared.declaredReturnType) && - ClassType.isBuiltIn(type.shared.declaredReturnType, ['TypeGuard', 'TypeIs']) - ); + const returnType = type.shared.declaredReturnType; + if (!returnType) { + return false; + } + if (isClassInstance(returnType)) { + return ClassType.isBuiltIn(returnType, ['TypeGuard', 'TypeIs']); + } + if (isUnion(returnType)) { + let isAllGuards = true; + doForEachSubtype(returnType, (subtype) => { + if (!isClassInstance(subtype) || !ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs'])) { + isAllGuards = false; + } + }); + return isAllGuards; + } + return false; }; const callTypeResult = evaluator.getTypeOfExpression( @@ -738,14 +751,44 @@ export function getTypeNarrowingCallback( const functionReturnTypeResult = evaluator.getTypeOfExpression(testExpression); const functionReturnType = functionReturnTypeResult.type; + let typeGuardType: Type | undefined; + let isStrictTypeGuard = false; + if ( isClassInstance(functionReturnType) && ClassType.isBuiltIn(functionReturnType, ['TypeGuard', 'TypeIs']) && functionReturnType.priv.typeArgs && functionReturnType.priv.typeArgs.length > 0 ) { - const isStrictTypeGuard = ClassType.isBuiltIn(functionReturnType, 'TypeIs'); - const typeGuardType = functionReturnType.priv.typeArgs[0]; + isStrictTypeGuard = ClassType.isBuiltIn(functionReturnType, 'TypeIs'); + typeGuardType = functionReturnType.priv.typeArgs[0]; + } else if (isUnion(functionReturnType)) { + const typeGuardSubtypes: ClassType[] = []; + let isAllGuards = true; + + doForEachSubtype(functionReturnType, (subtype) => { + if ( + isClassInstance(subtype) && + ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs']) && + subtype.priv.typeArgs && + subtype.priv.typeArgs.length > 0 + ) { + typeGuardSubtypes.push(subtype); + } else { + isAllGuards = false; + } + }); + + if (isAllGuards && typeGuardSubtypes.length > 0) { + // A union of type guards cannot be strict in the negative case because at runtime + // only one arm of the overload/union is selected. Treating it as strict would + // unsoundly eliminate types in the negative branch. + isStrictTypeGuard = false; + typeGuardType = combineTypes(typeGuardSubtypes.map((subtype) => subtype.priv.typeArgs![0])); + } + } + + if (typeGuardType) { const isIncomplete = !!callTypeResult.isIncomplete || !!functionReturnTypeResult.isIncomplete; return (type: Type) => { @@ -753,7 +796,7 @@ export function getTypeNarrowingCallback( type: narrowTypeForUserDefinedTypeGuard( evaluator, type, - typeGuardType, + typeGuardType!, isPositiveTest, isStrictTypeGuard, testExpression diff --git a/packages/pyright-internal/src/tests/samples/typeIs5.py b/packages/pyright-internal/src/tests/samples/typeIs5.py new file mode 100644 index 000000000000..312c11449acd --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/typeIs5.py @@ -0,0 +1,61 @@ +# This sample tests user-defined TypeIs and TypeGuard functions whose return type +# is a union of TypeIs or TypeGuard instances. + +from typing import TypeGuard, TypeIs, assert_type, overload + + +def check_single(val: object) -> TypeIs[int] | TypeIs[str]: + return isinstance(val, (int, str)) + + +# This should generate an error because "int" is not a subtype of "str". +def invalid_typeis_union(val: str) -> TypeIs[int] | TypeIs[str]: # pyright: ignore[reportGeneralTypeIssues] + return False + + +@overload +def check_overload(val: object, target: type[int]) -> TypeIs[int]: ... +@overload +def check_overload(val: object, target: type[str]) -> TypeIs[str]: ... + + +def check_overload(val: object, target: type) -> bool: + return isinstance(val, target) + + +def check_mixed(val: object) -> TypeIs[int] | TypeGuard[str]: + return isinstance(val, (int, str)) + + +def check_nonguard(val: object) -> TypeIs[int] | None: + return isinstance(val, int) if val else None + + +def test_single(x: object): + if check_single(x): + assert_type(x, int | str) + + +def test_overload_positive(x: object, target: type[int] | type[str]): + if check_overload(x, target): + assert_type(x, int | str) + + +def test_overload_negative(x: int | str | bytes, target: type[int] | type[str]): + if check_overload(x, target): + assert_type(x, int | str) + else: + # A union of type guards is non-strict in the negative case to prevent + # unsound type elimination when only one overload/arm applies at runtime. + assert_type(x, int | str | bytes) + + +def test_mixed(x: object): + if check_mixed(x): + assert_type(x, int | str) + + +def test_nonguard(x: object): + if check_nonguard(x): + # Non-guard members in the return type union cause the type guard to be rejected. + assert_type(x, object) diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index eb1d6a09ab96..81545ff2429b 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -159,6 +159,11 @@ test('TypeIs4', () => { TestUtils.validateResults(analysisResults, 0); }); +test('TypeIs5', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIs5.py']); + TestUtils.validateResults(analysisResults, 0); +}); + test('Never1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['never1.py']); From 7f45fe2e98376e8dfaaa878b5cc2d2e5d24b28cb Mon Sep 17 00:00:00 2001 From: Henry Su Date: Mon, 10 Aug 2026 11:48:23 -0500 Subject: [PATCH 2/3] Address review feedback: restrict equality class narrowing to instantiable class reference subtypes and remove extraneous TypeGuard union hunk --- .../pyright-internal/src/analyzer/checker.ts | 69 +++++------ .../src/analyzer/typeGuards.ts | 112 +++++++++--------- .../src/tests/samples/typeGuard4.py | 51 ++++++++ .../src/tests/typeEvaluator6.test.ts | 10 +- 4 files changed, 145 insertions(+), 97 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/typeGuard4.py diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 8f7f90fe1c76..58db6863cc96 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -4669,19 +4669,14 @@ export class Checker extends ParseTreeWalker { return; } - const guardSubtypes: ClassType[] = []; - doForEachSubtype(returnType, (subtype) => { - if ( - isClassInstance(subtype) && - (ClassType.isBuiltIn(subtype, 'TypeGuard') || ClassType.isBuiltIn(subtype, 'TypeIs')) && - subtype.priv.typeArgs && - subtype.priv.typeArgs.length >= 1 - ) { - guardSubtypes.push(subtype); - } - }); + if (!isClassInstance(returnType) || !returnType.priv.typeArgs || returnType.priv.typeArgs.length < 1) { + return; + } - if (guardSubtypes.length === 0) { + const isTypeGuard = ClassType.isBuiltIn(returnType, 'TypeGuard'); + const isTypeIs = ClassType.isBuiltIn(returnType, 'TypeIs'); + + if (!isTypeGuard && !isTypeIs) { return; } @@ -4705,36 +4700,32 @@ export class Checker extends ParseTreeWalker { ); } - const scopeIds = getTypeVarScopeIds(functionType); - - // Determine the type of the first parameter. - const paramIndex = isMethod && !FunctionType.isStaticMethod(functionType) ? 1 : 0; - if (paramIndex >= functionType.shared.parameters.length) { - return; - } + if (isTypeIs) { + const scopeIds = getTypeVarScopeIds(functionType); + const narrowedType = returnType.priv.typeArgs[0]; + let typeGuardType = makeTypeVarsBound(narrowedType, scopeIds); + typeGuardType = TypeBase.cloneWithTypeForm(typeGuardType, typeGuardType); - const paramType = makeTypeVarsBound(FunctionType.getParamType(functionType, paramIndex), scopeIds); + // Determine the type of the first parameter. + const paramIndex = isMethod && !FunctionType.isStaticMethod(functionType) ? 1 : 0; + if (paramIndex >= functionType.shared.parameters.length) { + return; + } - for (const guardSubtype of guardSubtypes) { - if (ClassType.isBuiltIn(guardSubtype, 'TypeIs')) { - const narrowedType = guardSubtype.priv.typeArgs![0]; - let typeGuardType = makeTypeVarsBound(narrowedType, scopeIds); - typeGuardType = TypeBase.cloneWithTypeForm(typeGuardType, typeGuardType); + const paramType = makeTypeVarsBound(FunctionType.getParamType(functionType, paramIndex), scopeIds); - // Verify that the typeGuardType is a narrower type than the paramType. - if (!this._evaluator.assignType(paramType, typeGuardType)) { - const returnAnnotation = - node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; - if (returnAnnotation) { - this._evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeIsReturnType().format({ - type: this._evaluator.printType(paramType), - returnType: this._evaluator.printType(narrowedType), - }), - returnAnnotation - ); - } + // Verify that the typeGuardType is a narrower type than the paramType. + if (!this._evaluator.assignType(paramType, typeGuardType)) { + const returnAnnotation = node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; + if (returnAnnotation) { + this._evaluator.addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeIsReturnType().format({ + type: this._evaluator.printType(paramType), + returnType: this._evaluator.printType(narrowedType), + }), + returnAnnotation + ); } } } diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 35d44b5ad90a..364a65b8e1fe 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -55,7 +55,6 @@ import { isTypeSame, isTypeVar, isUnpackedTypeVarTuple, - isUnion, maxTypeRecursionCount, OverloadedType, TupleTypeArg, @@ -374,6 +373,22 @@ export function getTypeNarrowingCallback( }; }; } + + // Look for X == or X != . + if (isInstantiableClass(rightType)) { + return (type: Type) => { + return { + type: narrowTypeForClassComparison( + evaluator, + type, + rightType, + adjIsPositiveTest, + /* isIsOperator */ false + ), + isIncomplete: !!rightTypeResult.isIncomplete, + }; + }; + } } // Look for X[] == or X[] != @@ -710,23 +725,11 @@ export function getTypeNarrowingCallback( let isPossiblyTypeGuard = false; const isFunctionReturnTypeGuard = (type: FunctionType) => { - const returnType = type.shared.declaredReturnType; - if (!returnType) { - return false; - } - if (isClassInstance(returnType)) { - return ClassType.isBuiltIn(returnType, ['TypeGuard', 'TypeIs']); - } - if (isUnion(returnType)) { - let isAllGuards = true; - doForEachSubtype(returnType, (subtype) => { - if (!isClassInstance(subtype) || !ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs'])) { - isAllGuards = false; - } - }); - return isAllGuards; - } - return false; + return ( + type.shared.declaredReturnType && + isClassInstance(type.shared.declaredReturnType) && + ClassType.isBuiltIn(type.shared.declaredReturnType, ['TypeGuard', 'TypeIs']) + ); }; const callTypeResult = evaluator.getTypeOfExpression( @@ -751,44 +754,14 @@ export function getTypeNarrowingCallback( const functionReturnTypeResult = evaluator.getTypeOfExpression(testExpression); const functionReturnType = functionReturnTypeResult.type; - let typeGuardType: Type | undefined; - let isStrictTypeGuard = false; - if ( isClassInstance(functionReturnType) && ClassType.isBuiltIn(functionReturnType, ['TypeGuard', 'TypeIs']) && functionReturnType.priv.typeArgs && functionReturnType.priv.typeArgs.length > 0 ) { - isStrictTypeGuard = ClassType.isBuiltIn(functionReturnType, 'TypeIs'); - typeGuardType = functionReturnType.priv.typeArgs[0]; - } else if (isUnion(functionReturnType)) { - const typeGuardSubtypes: ClassType[] = []; - let isAllGuards = true; - - doForEachSubtype(functionReturnType, (subtype) => { - if ( - isClassInstance(subtype) && - ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs']) && - subtype.priv.typeArgs && - subtype.priv.typeArgs.length > 0 - ) { - typeGuardSubtypes.push(subtype); - } else { - isAllGuards = false; - } - }); - - if (isAllGuards && typeGuardSubtypes.length > 0) { - // A union of type guards cannot be strict in the negative case because at runtime - // only one arm of the overload/union is selected. Treating it as strict would - // unsoundly eliminate types in the negative branch. - isStrictTypeGuard = false; - typeGuardType = combineTypes(typeGuardSubtypes.map((subtype) => subtype.priv.typeArgs![0])); - } - } - - if (typeGuardType) { + const isStrictTypeGuard = ClassType.isBuiltIn(functionReturnType, 'TypeIs'); + const typeGuardType = functionReturnType.priv.typeArgs[0]; const isIncomplete = !!callTypeResult.isIncomplete || !!functionReturnTypeResult.isIncomplete; return (type: Type) => { @@ -796,7 +769,7 @@ export function getTypeNarrowingCallback( type: narrowTypeForUserDefinedTypeGuard( evaluator, type, - typeGuardType!, + typeGuardType, isPositiveTest, isStrictTypeGuard, testExpression @@ -2628,17 +2601,47 @@ function narrowTypeForTypeIs(evaluator: TypeEvaluator, type: Type, classTypes: C return combineTypes(typesToCombine); } +function hasCustomEqualityMetaclass(classType: ClassType): boolean { + const metaclass = classType.shared.effectiveMetaclass; + if (metaclass && isClass(metaclass)) { + if ( + lookUpClassMember( + metaclass, + '__eq__', + MemberAccessFlags.SkipTypeBaseClass | MemberAccessFlags.SkipObjectBaseClass + ) || + lookUpClassMember( + metaclass, + '__ne__', + MemberAccessFlags.SkipTypeBaseClass | MemberAccessFlags.SkipObjectBaseClass + ) + ) { + return true; + } + } + return false; +} + // Attempts to narrow a type based on a comparison with a class using "is" or -// "is not". This pattern is sometimes used for sentinels. +// "is not", or "==" or "!=". function narrowTypeForClassComparison( evaluator: TypeEvaluator, referenceType: Type, classType: ClassType, - isPositiveTest: boolean + isPositiveTest: boolean, + isIsOperator = true ): Type { + if (!isIsOperator && hasCustomEqualityMetaclass(classType)) { + return referenceType; + } + return mapSubtypes(referenceType, (subtype) => { let concreteSubtype = evaluator.makeTopLevelTypeVarsConcrete(subtype); + if (!isIsOperator && isInstantiableClass(concreteSubtype) && hasCustomEqualityMetaclass(concreteSubtype)) { + return subtype; + } + if (isPositiveTest) { if ( isClassInstance(concreteSubtype) && @@ -2657,6 +2660,9 @@ function narrowTypeForClassComparison( if (isClass(concreteSubtype)) { if (TypeBase.isInstance(concreteSubtype)) { + if (!isIsOperator) { + return subtype; + } return ClassType.isBuiltIn(concreteSubtype, 'object') ? classType : undefined; } diff --git a/packages/pyright-internal/src/tests/samples/typeGuard4.py b/packages/pyright-internal/src/tests/samples/typeGuard4.py new file mode 100644 index 000000000000..1686d08fd576 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/typeGuard4.py @@ -0,0 +1,51 @@ +# This sample tests type narrowing when comparing class types +# with equality (== and !=) operators against class objects. + +from typing import TypeVar, assert_type, final + +class Base: pass +class Sub1(Base): pass + +@final +class Sub2(Base): pass + +T = TypeVar("T", bound=Base) + +def test_eq_concrete(cls: type[Base]) -> type[Sub1]: + if cls == Sub1: + assert_type(cls, type[Sub1]) + return cls + raise ValueError() + +def test_neq_concrete(cls: type[Sub1] | type[Sub2]): + if cls != Sub2: + assert_type(cls, type[Sub1]) + +def test_neq_non_final(cls: type[Sub1] | type[Sub2]): + if cls != Sub1: + assert_type(cls, type[Sub1] | type[Sub2]) + +def test_eq_typevar(cls: type[T]) -> type[Sub1]: + if cls == Sub1: + assert_type(cls, type[Sub1]) + return cls + raise ValueError() + +class CustomMeta(type): + def __eq__(cls, other: object) -> bool: + return True + +class Custom1(metaclass=CustomMeta): pass +class Custom2(metaclass=CustomMeta): pass + +def test_eq_custom_meta(cls: type[Custom1] | type[Custom2]): + if cls == Custom1: + assert_type(cls, type[Custom1] | type[Custom2]) + +class EqualityDummy: + def __eq__(self, other: object) -> bool: + return True + +def test_eq_instance_object(x: object): + if x == Sub1: + assert_type(x, object) diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index 81545ff2429b..ca950fa56e44 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -139,6 +139,11 @@ test('TypeGuard3', () => { TestUtils.validateResults(analysisResults, 0); }); +test('TypeGuard4', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeGuard4.py']); + TestUtils.validateResults(analysisResults, 0); +}); + test('TypeIs1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIs1.py']); TestUtils.validateResults(analysisResults, 2); @@ -159,11 +164,6 @@ test('TypeIs4', () => { TestUtils.validateResults(analysisResults, 0); }); -test('TypeIs5', () => { - const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIs5.py']); - TestUtils.validateResults(analysisResults, 0); -}); - test('Never1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['never1.py']); From 8e87e9defe2beb224f936eaff568c3b7646b1001 Mon Sep 17 00:00:00 2001 From: Henry Su Date: Mon, 17 Aug 2026 20:06:23 -0500 Subject: [PATCH 3/3] Remove dormant typeIs5.py sample This sample was not registered with any test and is unrelated to the class-comparison narrowing fix in this PR. --- .../src/tests/samples/typeIs5.py | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 packages/pyright-internal/src/tests/samples/typeIs5.py diff --git a/packages/pyright-internal/src/tests/samples/typeIs5.py b/packages/pyright-internal/src/tests/samples/typeIs5.py deleted file mode 100644 index 312c11449acd..000000000000 --- a/packages/pyright-internal/src/tests/samples/typeIs5.py +++ /dev/null @@ -1,61 +0,0 @@ -# This sample tests user-defined TypeIs and TypeGuard functions whose return type -# is a union of TypeIs or TypeGuard instances. - -from typing import TypeGuard, TypeIs, assert_type, overload - - -def check_single(val: object) -> TypeIs[int] | TypeIs[str]: - return isinstance(val, (int, str)) - - -# This should generate an error because "int" is not a subtype of "str". -def invalid_typeis_union(val: str) -> TypeIs[int] | TypeIs[str]: # pyright: ignore[reportGeneralTypeIssues] - return False - - -@overload -def check_overload(val: object, target: type[int]) -> TypeIs[int]: ... -@overload -def check_overload(val: object, target: type[str]) -> TypeIs[str]: ... - - -def check_overload(val: object, target: type) -> bool: - return isinstance(val, target) - - -def check_mixed(val: object) -> TypeIs[int] | TypeGuard[str]: - return isinstance(val, (int, str)) - - -def check_nonguard(val: object) -> TypeIs[int] | None: - return isinstance(val, int) if val else None - - -def test_single(x: object): - if check_single(x): - assert_type(x, int | str) - - -def test_overload_positive(x: object, target: type[int] | type[str]): - if check_overload(x, target): - assert_type(x, int | str) - - -def test_overload_negative(x: int | str | bytes, target: type[int] | type[str]): - if check_overload(x, target): - assert_type(x, int | str) - else: - # A union of type guards is non-strict in the negative case to prevent - # unsound type elimination when only one overload/arm applies at runtime. - assert_type(x, int | str | bytes) - - -def test_mixed(x: object): - if check_mixed(x): - assert_type(x, int | str) - - -def test_nonguard(x: object): - if check_nonguard(x): - # Non-guard members in the return type union cause the type guard to be rejected. - assert_type(x, object)