Typing FAQ: New entry explaining invariance#3073
Conversation
|
|
||
| ```py | ||
| def modify(entries: list[Entry]): | ||
| entries.append(File("README.txt")) |
There was a problem hiding this comment.
I think it's sufficiently self-evident that File is a distinct subclass of Entry, but that's entirely implicit and caught me out the first time I read through this -- this is the first time we see File mentioned.
It would be nice if our example only required two types, not three, but I'm not sure if there's a good realistic example with that shape. X vs X | None is one realistic case, but then it requires also understanding unions and subtyping.
There was a problem hiding this comment.
It would be nice if our example only required two types, not three
That's exactly what I said to Alex. The drawback of the class hierarchy example is that it requires three types if you really want to demonstrate a bug.
We did consider using int | None, but also thought that explaining subtyping of unions would make it even more complicated.
I think it's sufficiently self-evident that
Fileis a distinct subclass ofEntry, but that's entirely implicit and caught me out the first time I read through this -- this is the first time we seeFilementioned.
I'll think about this
| ```py | ||
| def modify(entries: list[Entry]): | ||
| entries.append(File("README.txt")) | ||
|
|
||
| directories: list[Directory] = [Directory("Downloads"), Directory("Documents")] | ||
| modify(directories) # ty emits an error on this call | ||
| ``` |
There was a problem hiding this comment.
I know that it makes the example frustratingly verbose, but I do think being able to copy and paste examples directly into a Python REPL and/or type checker playgrounds is quite valuable. So I would be inclined to defined these classes in the Python snippet:
| ```py | |
| def modify(entries: list[Entry]): | |
| entries.append(File("README.txt")) | |
| directories: list[Directory] = [Directory("Downloads"), Directory("Documents")] | |
| modify(directories) # ty emits an error on this call | |
| ``` | |
| ```py | |
| from dataclasses import dataclass | |
| @dataclass | |
| class Entry: | |
| path: str | |
| class Directory(Entry): | |
| pass | |
| class File(Entry): | |
| pass | |
| def modify(entries: list[Entry]): | |
| entries.append(File("README.txt")) | |
| directories: list[Directory] = [Directory("Downloads"), Directory("Documents")] | |
| modify(directories) # ty emits an error on this call | |
| ``` |
There was a problem hiding this comment.
I thought about adding a playground link somewhere...
There was a problem hiding this comment.
if you want it more concise, you could go for more "stub-like" formatting:
| ```py | |
| def modify(entries: list[Entry]): | |
| entries.append(File("README.txt")) | |
| directories: list[Directory] = [Directory("Downloads"), Directory("Documents")] | |
| modify(directories) # ty emits an error on this call | |
| ``` | |
| ```py | |
| from dataclasses import dataclass | |
| @dataclass | |
| class Entry: | |
| path: str | |
| class Directory(Entry): ... | |
| class File(Entry): ... | |
| def modify(entries: list[Entry]): | |
| entries.append(File("README.txt")) | |
| directories: list[Directory] = [Directory("Downloads"), Directory("Documents")] | |
| modify(directories) # ty emits an error on this call | |
| ``` |
There was a problem hiding this comment.
This would also address my comment about File coming from nowhere.

Summary
Now that we do not union in
Unknowninto element types of invariant collections anymore, this question comes up more frequently. I'm taking a first stab at trying to explain this in a concise way. I am also planning to add a diagnostic hint that would be emitted in those situations, which could refer to this FAQ entry.Rendered