I don’t see how this is supposed to work. If we have
type AnySequence[T] = Sequence[T] | MutableSequence[T]
def foo(seq: AnySequence[int | None]): ...
mylist: list[int]
foo(mylist)
For mylist
to be assignable to the union AnySequence[int | None]
, it has to be assignable to one of the elements of the union, either Sequence[int | None]
or MutableSequence[int | None]
. According to the assignability change you propose (if I understand it correctly), list[int]
would not be assignable to Sequence[int | None]
, because their variance differs. It’s also clearly not assignable to MutableSequence[int | None]
, because MutableSequence
is invariant in its contained type, and int
is not equivalent to int | None
. What am I missing that would allow list[int]
to be passed to foo
in this case?
That’s a matter of perspective. I think that a TypeIs
function that (for example) checks all elements of a sequence and narrows Sequence[object]
to Sequence[int]
is a “correct TypeIs
function.” The safety problem is with the shared mutability of the underlying object, not with the correctness of the TypeIs
function. The same safety problem applies in other narrowing cases (attributes, individual container elements, nonlocal
) where type checkers also choose to narrow even though shared mutability makes it unsafe. It is true that TypeIs
, by allowing users to expand their application of type narrowing, gives users more potential exposure to the unsafety of type-narrowing. I think this should be made clear in the documentation. Jelle’s PR is a good start.
I appreciate this point, because I think it puts the focus on the real root issue, and it’s a position that I sympathize with (and would have held myself a few years ago.) But this is one of those rare cases where we actually have something approaching a controlled experiment and empirical evidence on the effects of a proposed change to the type system. Pyre tried the approach of refusing to type narrow anything that might be mutated between the narrowing check and the use, and instead requiring authors to first pull it out as a local before narrowing it. The audience of Python authors was perhaps as favorable as one could find for this kind of experiment: professional software engineers, all working for the same company, with a team of people working full time to maintain the type checker and educate them about its use. And the result of the experiment was pretty clear: developers did not like this behavior, and they didn’t understand it. There was a never-ending stream of people complaining about Pyre failing to understand their clearly-correct code, and many of them just assumed the type checker was buggy and used cast
or type-ignore comments to work around the lack of narrowing. The result was that the Pyre team changed course and went back to allowing some narrowing that is not necessarily safe with concurrent mutation.
It doesn’t address the “specific issue” of narrowing mutable things. It addresses only part of it, the even more specific issue of narrowing generic types. It doesn’t address the unsafety of narrowing attributes or individual container elements, which major type checkers currently do, even though it’s also unsafe in exactly the same way and for the same reason.
Well, that’s fair. I do think adding strong immutability or ownership to Python would be a very difficult lift. And I do think the status quo, with some unsoundness, is practically better than some attempts to fix it would be.
I think better soundness could be reached here fairly “easily” (and with better consistency than the variance change) by just being way more conservative about where narrowing is allowed. But given Pyre’s experience, I don’t think this would be very popular with users, and I doubt any of the type checkers will be eager to try it. It may be that something closer to Pyre’s current middle ground (where it will narrow e.g. mutable attributes but discard the narrowing as soon as it sees something “likely” to trigger arbitrary code execution elsewhere, like a function call) would be more tolerable: I’d be curious to hear if the Pyre team has thoughts on how well that’s worked out.