Skip to content

[red-knot] Proper handling of empty intersections and Never. #13870

@sharkdp

Description

@sharkdp

I found this while creating the type Literal[1, 2, 3] & ~Literal[1] & ~Literal[2]. I was expecting to get Literal[3] after all simplifications were applied, but I got ~Literal[2] | Literal[3] instead. This is clearly not optimal.

The reason for this behavior is the following (using 1 instead of Literal[1] etc): When adding the first negation, we get the following in the union-of-intersections representation:

  (1 | 2 | 3) & ~1
= (1 & ~1 | 2 & ~1 | 3 & ~1)

In the second and third component, the simplification creates 2 & ~1 = 2 and 3 & ~1 = 3, as expected. In the first component, the current implementation of simplification removes all positive (1) and negative (~1) components from the intersection, leaving an empty intersection:

= (empty-intersection | 2 | 3)

The idea was that the empty-intersection would represent Never (similar to how IntersectionBuilder::build() returns Never for the empty-empty case). But an empty intersection should actually represent the type object. To represent Never, we would need to explicitly add Never to the positive side. Since we don't do that, we end up creating

  (empty-intersection | 2 | 3) & ~2
= (~2 | 2 & ~2 | 3 & ~2)
= ~2 | Never | 3
= ~2 | 3

in the second step.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggreat writeupA wonderful example of a quality contributiontyMulti-file analysis & type inference

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions