TypeIs is designed to use isinstance (inheritance) semantics for type narrowing. Callable is a structural type, and the spec is not clear what the behavior should be in this case.
The less rule of thumb bit right before the part of the specification you quoted is:
Formally, type NP should be narrowed to A∧R, the intersection of A and R , and type NN should be narrowed to A∧¬R, the intersection of A and the complement of R . In practice, the theoretic types for strict type guards cannot be expressed precisely in the Python type system. Type checkers should fall back on practical approximations of these types.
I think including TypeIs prior to Intersections was a mistake and said as much at the time, but you may need to wait for Intersection work to be finished to be able to expect consistent behavior across typecheckers here given that the formal definition relies on them, but then kicks that down the curb and says “use an approximation”
Using the current draft of intersections, this would work as you seem to expect.
TypeIs may not be what you want here. Using TypeGuard should work for the positive branch today.
There’s a problem with using intersections to narrow here because there is a type that is “more compatible” than your intent. Intersecting a gradual callable with a structural type that accepts no parameters in that you haven’t ruled out that it can also take parameters.
A function like:
def x(foo: int = 0, /) -> None: ...
Is compatible with the structural type Callable[[], None] and Callable[..., Any]
The tough questions around Any have answers already, so most of what is left is writing up examples, specifically showing what this solves, how it interacts with other “special” parts of the type system such as with dataclass_transform, and some language changes around TypeIs and Never.
As TypeIs is basically a limited Intersection already and specified as such, any improvement in the specification of the expectations of intersections, including adding them directly, will improve usability of TypeIs.