Skip to content

Comments

[ty] fix assigning a typevar to a union with itself#17901

Closed
carljm wants to merge 1 commit intomainfrom
cjm/tvassign
Closed

[ty] fix assigning a typevar to a union with itself#17901
carljm wants to merge 1 commit intomainfrom
cjm/tvassign

Conversation

@carljm
Copy link
Contributor

@carljm carljm commented May 6, 2025

Summary

Noticed in #17800 that a typevar was not considered assignable to a union containing itself.

A bound typevar T: int must be assignable to e.g. int | None but must also be assignable to e.g. T | None. There's no way to order the bound/constrained-typevar and union arms of is_subtype_of and is_assignable_to that would achieve both of these; it requires trying the recursive union treatment for one possibility (treat the typevar as itself), and then if that fails, also trying it for the other possibility (treat the typevar as its bound/constraints).

It doesn't work to just put the union case first and let the typevar bound/constraint handling happen one level deeper, because then we fail to consider e.g. T: (X, Y) a subtype of X | Y -- we would separately try X | Y <: X and X | Y <: Y, and both fail. (There's already a test for this, fortunately, or I probably would have pushed the simpler fix that breaks it.)

Test Plan

Added mdtest.

@carljm carljm added the ty Multi-file analysis & type inference label May 6, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 6, 2025

mypy_primer results

Changes were detected when running on open source projects
kopf (https://github.com/nolar/kopf)
- error[lint:invalid-assignment] kopf/_cogs/aiokits/aioenums.py:54:9: Object of type `FlagReasonT | Unknown` is not assignable to `FlagReasonT | None`
- Found 227 diagnostics
+ Found 226 diagnostics

mkosi (https://github.com/systemd/mkosi)
- error[lint:invalid-return-type] mkosi/config.py:1092:16: Return type does not match returned value: Expected `SE | None`, found `SE`
- Found 327 diagnostics
+ Found 326 diagnostics

operator (https://github.com/canonical/operator)
- error[lint:invalid-argument-type] ops/pebble.py:1833:67: Argument to this function is incorrect: Expected `AnyStr | None`, found `AnyStr`
- Found 208 diagnostics
+ Found 207 diagnostics

setuptools (https://github.com/pypa/setuptools)
- error[lint:invalid-return-type] setuptools/_distutils/spawn.py:38:16: Return type does not match returned value: Expected `_MappingT | dict[str, str | int] | None`, found `_MappingT | None`
- Found 1212 diagnostics
+ Found 1211 diagnostics

arviz (https://github.com/arviz-devs/arviz)
- error[lint:invalid-return-type] arviz/data/inference_data.py:972:20: Return type does not match returned value: Expected `InferenceDataT | None`, found `InferenceDataT`
- error[lint:invalid-return-type] arviz/data/inference_data.py:1052:20: Return type does not match returned value: Expected `InferenceDataT | None`, found `InferenceDataT`
- Found 2605 diagnostics
+ Found 2603 diagnostics

streamlit (https://github.com/streamlit/streamlit)
- error[lint:invalid-return-type] lib/streamlit/elements/lib/options_selector_utils.py:121:16: Return type does not match returned value: Expected `E1 | E2`, found `E1`
- error[lint:invalid-return-type] lib/streamlit/elements/lib/options_selector_utils.py:131:16: Return type does not match returned value: Expected `E1 | E2`, found `E1`
- error[lint:invalid-return-type] lib/streamlit/elements/lib/options_selector_utils.py:150:16: Return type does not match returned value: Expected `E1 | E2`, found `E1`
- Found 3291 diagnostics
+ Found 3288 diagnostics

@carljm
Copy link
Contributor Author

carljm commented May 6, 2025

Not a huge number of false positives from this, but the ecosystem results all look like fixing false positives.

@LaBatata101
Copy link
Contributor

What does this X | Y <: X notation means?

@carljm
Copy link
Contributor Author

carljm commented May 7, 2025

What does this X | Y <: X notation means?

<: means "is a subtype of"

@AlexWaygood
Copy link
Member

It would be awesome if we could add a property test for this, but that obviously doesn't need to be done in this PR

// implicit upper bound of `object` (which is handled above).
//
// This must be handled here, only if the above failed, because a typevar `T: int` needs to
// both a subtype of `int` and a subtype of e.g. `T | None`, so we need to also try
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// both a subtype of `int` and a subtype of e.g. `T | None`, so we need to also try
// be both a subtype of `int` and a subtype of e.g. `T | None`, so we need to also try

@AlexWaygood
Copy link
Member

I proposed a more targeted fix which I like somewhat more in #17910 -- WDYT?

@carljm
Copy link
Contributor Author

carljm commented May 7, 2025

Closing in favor of #17910

@carljm carljm closed this May 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants