Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Move intersection type tests to Markdown #15396

Merged
merged 6 commits into from
Jan 10, 2025

Conversation

sharkdp
Copy link
Contributor

@sharkdp sharkdp commented Jan 10, 2025

Summary

Rendered version of the new test suite

Moves most of our existing intersection-types tests to a dedicated Markdown test suite, extends the test coverage, unifies the notation for these tests, groups tests into a proper structure, and adds some explanations for various simplification strategies.

This changeset also:

  • Adds a new simplification where ~Never is removed from intersections.
  • Adds a new simplification where adding ~object simplifies the whole intersection to Never
  • Avoids unnecessary assignment-checks between inferred and declared type. This was added to this changeset to avoid many false positive errors in this test suite.

Resolves the task described in this old comment here.

Test Plan

Running the new Markdown tests

@sharkdp sharkdp added the red-knot Multi-file analysis & type inference label Jan 10, 2025

This comment was marked as off-topic.

Moves most of our existing intersection-types tests to a dedicated
Markdown test suite, extends the test coverage, unifies the notation
for these tests, groups tests into a proper structure, and adds some
explanations for various simplification strategies.

This changeset also:
  - Adds a new simplification where `~Never` is removed from
    intersections.
  - Adds a new simplification where adding `~object` simplifies the
    whole intersection to `Never`
  - Avoids unnecessary assignment-checks between inferred and declared
    type. This was added to this changeset to avoid many false positive
    errors in this test suite.
@sharkdp sharkdp force-pushed the david/intersection-type-tests branch from 7e8a1eb to 94447af Compare January 10, 2025 11:19
Copy link
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.

The tests look fantastic! Haven't reviewed the code changes yet

Comment on lines 320 to 321
`Never` represents the empty set of values, while `object` represents the set of all values, so
`~Never` is equivalent to `object`, and `~object` is equivalent to `Never`:
Copy link
Member

Choose a reason for hiding this comment

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

Just to make sure I understand properly: this follows from the intuition that the type ~int is really a shorthand way of saying object & ~int, and therefore ~Never is a shorthand for object & ~Never, which is the same as <the set of all possible objects> - <the empty set>, which is the same as <the set of all possible objects>, which is the object type. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, exactly. In this document, I tried to move away a bit from how we represent these types internally. I think it's not important that we represent ~T as an intersection with a single negative contribution (where the empty "positive side" represents object). What's more important is that the negation-operation represents the set-theoretic complement w.r.t. the set of all possible values (object).

Set theory calls this set of all possible elements the "universe". In our case, object is the universe. And Never is the empty set ∅. The tests here are a manifestation of the universal complement laws

U = ∅
= U

I wonder if it makes sense to refer to these more set-theoretic terms in the document. And if it makes sense to test a few more of the laws in a systematic way (I think we probably test most of them already, but it's distributed across the whole document).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That actually revealed one more thing that we could simplify. I chose not to implement this right now, as it seems rather costly: if we see T | ~T, we could simplify that to object. I added a test that describes that we don't perform this optimization at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know if you think that we should add this. I think it would be straightforward to add a (simple) version of this.

Copy link
Member

@AlexWaygood AlexWaygood Jan 10, 2025

Choose a reason for hiding this comment

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

Let me know if you think that we should add this.

The simplification of T | ~T to object? Yes, I think we probably should simplify that. But it can probably be done as a followup, right? This PR looks mergeable right now to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Working on this

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

Nice. The code changes feel like they're kinda papering over the underlying issue a little bit (we really should recognise int & Any as being assignable to int & Any!!), but I think skipping the assignability check for function-parameter definitions is probably a worthwhile optimisation anyway. So this LGTM

crates/red_knot_python_semantic/src/types/infer.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types/infer.rs Outdated Show resolved Hide resolved
Copy link
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.

Excellent!

@sharkdp sharkdp merged commit f2c3ddc into main Jan 10, 2025
21 checks passed
@sharkdp sharkdp deleted the david/intersection-type-tests branch January 10, 2025 13:04

### Single-element unions

If we have a union of a single element, we can simplify to that element. Similarly, we show an
Copy link
Contributor

Choose a reason for hiding this comment

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

This should say "intersection of a single element"?


#### Negative type and negative subtype

For negative contributions, this property is reversed. Here we can get remove superfluous
Copy link
Contributor

Choose a reason for hiding this comment

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

"Here we can get remove" -> "Here we can remove"

#### Positive and negative contributions

If we intersect a type `X` with the negation of a disjoint type `Y`, we can remove the negative
contribution `~Y`, as it necessarily overlaps with the positive contribution `X`:
Copy link
Contributor

Choose a reason for hiding this comment

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

"overlaps" seems not strong enough here, since it only means "not disjoint from" -- what is needed for this simplification to be valid is recognizing that ~Y is necessarily a super-set of X, if X and Y are disjoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants