Skip to content
Sarj Code Standards

no-partial-type-is

TypeIs must not reject a value belonging to its declared target type.

Why

TypeIs narrows both branches. A positive-int or non-empty-container filter can return false for a valid member of its target type, making callers incorrectly exclude that type in the false branch.

Fix

Use an ordinary bool return for a value filter. Ensure every accepted value satisfies the declared target. Use TypeGuard for an intentional partial predicate only when its true branch is sound; TypeIs must recognize exactly the target values. Check false-branch callers when changing the narrowing contract.

Examples

Before — flagged Every declared literal belongs to the TypeIs target
app/guards.py
from typing import Literal, TypeIs
def is_selected(value: str) -> TypeIs[Literal["red", "blue"]]:
return value == "red"
After — preferred Equality recognizes every member of a singleton target
app/guards.py
from typing import Literal, TypeIs
def is_selected(value: str) -> TypeIs[Literal["red"]]:
return value == "red"
Before — flagged Zero is still an integer in the false branch
app/guards.py
from typing import TypeIs
def is_positive(value: object) -> TypeIs[int]:
return isinstance(value, int) and value > 0
After — preferred TypeGuard permits an intentional partial predicate
app/guards.py
from typing import TypeGuard
def is_positive(value: object) -> TypeGuard[int]:
return isinstance(value, int) and value > 0
Before — flagged Truthiness can accept integers and reject the empty string
app/guards.py
from typing import TypeIs
def is_text(value: object) -> TypeIs[str]:
return bool(value)
After — preferred A value filter can return an ordinary bool
app/guards.py
def is_truthy(value: object) -> bool:
return bool(value)