Title: Function returning union of type variables has inference/unification behaviour change when assigned to explicitly-typed location · Issue #16659 · python/mypy · GitHub
Open Graph Title: Function returning union of type variables has inference/unification behaviour change when assigned to explicitly-typed location · Issue #16659 · python/mypy
X Title: Function returning union of type variables has inference/unification behaviour change when assigned to explicitly-typed location · Issue #16659 · python/mypy
Description: Bug Report It appears that if a function returns a union of type variables, how mypy chooses to assign/unify those type variables changes if a call happens in a location that has an explicit type (e.g. assigned to a variable with an anno...
Open Graph Description: Bug Report It appears that if a function returns a union of type variables, how mypy chooses to assign/unify those type variables changes if a call happens in a location that has an explicit type (...
X Description: Bug Report It appears that if a function returns a union of type variables, how mypy chooses to assign/unify those type variables changes if a call happens in a location that has an explicit type (...
Opengraph URL: https://github.com/python/mypy/issues/16659
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Function returning union of type variables has inference/unification behaviour change when assigned to explicitly-typed location","articleBody":"**Bug Report**\r\n\r\nIt appears that if a function returns a union of type variables, how mypy chooses to assign/unify those type variables changes if a call happens in a location that has an explicit type (e.g. assigned to a variable with an annotation or in a direct `return f(...)` call within a function that has a return type hint). \r\n\r\nIn particular, it appears _both_ type variables end up being unified to that explicit hint, rather than to individual components of the hint's union (for instance, in the reproducer below, it seems that it chooses `_T1 = _T2 = int | str`, but \"should\" be `_T1 = int` and `_T2 = str`).\r\n\r\nThis is observable when those parameters are used in contravariant position in an argument, such as the parameters of functions, and can result in some seemingly spurious errors.\r\n\r\n(I tried searching for existing discussion/bugs and didn't find any, but I may not have found the exactly correct terms.)\r\n\r\n**To Reproduce**\r\n\r\nReduced example (i.e. not relying on typeshed's hints): https://mypy-play.net/?mypy=latest\u0026python=3.12\u0026gist=697d00f7b3a089d7e56f6d86ec5193e8\r\n```python\r\nfrom typing import TypeVar, Callable\r\n\r\ndef expects_int(x: int) -\u003e None:\r\n pass\r\n\r\ndef expects_str(x: str) -\u003e None:\r\n pass\r\n\r\n\r\n_T1 = TypeVar(\"_T1\")\r\n_T2 = TypeVar(\"_T2\")\r\n\r\ndef f(key: Callable[[_T1], object], default: Callable[[_T2], object]) -\u003e _T1 | _T2:\r\n raise NotImplementedError()\r\n \r\n# error: Argument 1 to \"f\" has incompatible type \"Callable[[int], None]\"; expected \"Callable[[int | str], object]\" [arg-type]\r\n# error: Argument 2 to \"f\" has incompatible type \"Callable[[str], None]\"; expected \"Callable[[int | str], object]\" [arg-type]\r\nbroken: int | str = f(expects_int, expects_str)\r\n\r\n# no error, but `works` has type `int | str` too\r\nworks = f(expects_int, expects_str)\r\n# note: Revealed type is \"Union[builtins.int, builtins.str]\"\r\nreveal_type(works)\r\n```\r\n\r\nThis happens in practice with functions like `min` with the interaction between the `key` functions and `default`: https://mypy-play.net/?mypy=latest\u0026python=3.12\u0026gist=7f892a21b5d3d1db89419620e3cedf47\r\n\r\n```python\r\ndef broken(x: list[int]) -\u003e int | None:\r\n # error: Unsupported operand types for + (\"None\" and \"int\") [operator]\r\n # note: Left operand is of type \"int | None\"\r\n return min(x, key=lambda x: x + 1, default=None)\r\n \r\ndef works(x: list[int]) -\u003e int | None:\r\n result = min(x, key=lambda x: x + 1, default=None)\r\n return result\r\n```\r\n\r\n\r\n**Expected Behavior**\r\n\r\nBoth the `broken` and `works` code should behave the same, and in particular, they should both work.\r\n\r\n**Actual Behavior**\r\n\r\n```\r\nmain.py:18: error: Argument 1 to \"f\" has incompatible type \"Callable[[int], None]\"; expected \"Callable[[int | str], object]\" [arg-type]\r\nmain.py:18: error: Argument 2 to \"f\" has incompatible type \"Callable[[str], None]\"; expected \"Callable[[int | str], object]\" [arg-type]\r\nmain.py:23: note: Revealed type is \"Union[builtins.int, builtins.str]\"\r\n```\r\n\r\n**Your Environment**\r\n\r\n\u003c!-- Include as many relevant details about the environment you experienced the bug in --\u003e\r\n\r\n- Mypy version used: 1.7.1\r\n- Mypy command-line flags: n/a (see mypy-play.net)\r\n- Mypy configuration options from `mypy.ini` (and other config files): n/a\r\n- Python version used: 3.12\r\n","author":{"url":"https://github.com/huonw","@type":"Person","name":"huonw"},"datePublished":"2023-12-13T05:46:14.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/16659/mypy/issues/16659"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:d0a59fb4-e67e-4089-5438-3ad6921ef85b |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 87FC:30460E:3FD9D23:52C1C70:6A5D1C2E |
| html-safe-nonce | 2051771c1c1ecf05d6b3f768331cb269b6908e4bdfad8084fe7ddbe21eb5acb5 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4N0ZDOjMwNDYwRTozRkQ5RDIzOjUyQzFDNzA6NkE1RDFDMkUiLCJ2aXNpdG9yX2lkIjoiNDUwMTczNDQ1ODAwMjMxNjMzNCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | e83f73527a055866f5d8a7f776edca72e10806195fbd1433abc0aaf60493199a |
| hovercard-subject-tag | issue:2039000521 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/mypy/16659/issue_layout |
| twitter:image | https://opengraph.githubassets.com/fb598105a32f3d4d97bd8f8aacd3bd280e14b078bee17178f3575d1261f0a3a7/python/mypy/issues/16659 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/fb598105a32f3d4d97bd8f8aacd3bd280e14b078bee17178f3575d1261f0a3a7/python/mypy/issues/16659 |
| og:image:alt | Bug Report It appears that if a function returns a union of type variables, how mypy chooses to assign/unify those type variables changes if a call happens in a location that has an explicit type (... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | huonw |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| go-import | github.com/python/mypy git https://github.com/python/mypy.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 7053637 |
| octolytics-dimension-repository_nwo | python/mypy |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 7053637 |
| octolytics-dimension-repository_network_root_nwo | python/mypy |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width