Skip to content

Typing FAQ: New entry explaining invariance#3073

Merged
sharkdp merged 8 commits intomainfrom
david/faq-invariance
Mar 18, 2026
Merged

Typing FAQ: New entry explaining invariance#3073
sharkdp merged 8 commits intomainfrom
david/faq-invariance

Conversation

@sharkdp
Copy link
Copy Markdown
Contributor

@sharkdp sharkdp commented Mar 18, 2026

Summary

Now that we do not union in Unknown into 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

image

@sharkdp sharkdp added the documentation Improvements or additions to documentation label Mar 18, 2026
Comment thread docs/reference/typing-faq.md Outdated
Comment thread docs/reference/typing-faq.md Outdated
Copy link
Copy Markdown
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Comment thread docs/reference/typing-faq.md Outdated
Comment thread docs/reference/typing-faq.md Outdated
Comment thread docs/reference/typing-faq.md Outdated
Comment thread docs/reference/typing-faq.md Outdated
Comment thread docs/reference/typing-faq.md Outdated
Copy link
Copy Markdown
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Comment thread docs/reference/typing-faq.md Outdated

```py
def modify(entries: list[Entry]):
entries.append(File("README.txt"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 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.

I'll think about this

Comment on lines +103 to +109
```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
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
```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
```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about adding a playground link somewhere...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want it more concise, you could go for more "stub-like" formatting:

Suggested change
```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
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also address my comment about File coming from nowhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now (ab)used inline annotations to hide the setup code away in a tooltip

image

Copy link
Copy Markdown
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great, thank you

@sharkdp sharkdp merged commit 95150e7 into main Mar 18, 2026
13 checks passed
@sharkdp sharkdp deleted the david/faq-invariance branch March 18, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants