Skip to content

[ty] Fix false-positive unsupported-operator for "symmetric" TypeVars#22756

Merged
charliermarsh merged 1 commit intomainfrom
charlie/con
Jan 23, 2026
Merged

[ty] Fix false-positive unsupported-operator for "symmetric" TypeVars#22756
charliermarsh merged 1 commit intomainfrom
charlie/con

Conversation

@charliermarsh
Copy link
Member

@charliermarsh charliermarsh commented Jan 20, 2026

Summary

When a TypeVar has value constraints (e.g., TypeVar("Scalar", int, float)), we were reporting unsupported-operator because the constrained TypeVars were being treated like unions -- we checked every possible combination, rather than understanding that both occurrences of the same TypeVar must have the same type.

@charliermarsh charliermarsh added bug Something isn't working ty Multi-file analysis & type inference labels Jan 20, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 20, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 77.33% to 77.41%. The percentage of expected errors that received a diagnostic held steady at 69.23%.

Summary

Metric Old New Diff Outcome
True Positives 747 747 +0
False Positives 219 218 -1 ⏬ (✅)
False Negatives 332 332 +0
Total Diagnostics 966 965 -1
Precision 77.33% 77.41% +0.08% ⏫ (✅)
Recall 69.23% 69.23% +0.00%

False positives removed

Details
Location Name Message
generics_basic.py:34:12 unsupported-operator Operator + is not supported between two objects of type AnyStr@concat

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 20, 2026

mypy_primer results

Changes were detected when running on open source projects
pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/capture.py:948:13: error[unsupported-operator] Operator `+=` is not supported between two objects of type `AnyStr@CaptureFixture`
- src/_pytest/capture.py:949:13: error[unsupported-operator] Operator `+=` is not supported between two objects of type `AnyStr@CaptureFixture`
- src/_pytest/capture.py:964:13: error[unsupported-operator] Operator `+=` is not supported between two objects of type `AnyStr@CaptureFixture`
- src/_pytest/capture.py:965:13: error[unsupported-operator] Operator `+=` is not supported between two objects of type `AnyStr@CaptureFixture`
- src/_pytest/capture.py:968:30: error[invalid-argument-type] Argument is incorrect: Argument type `AnyStr@CaptureFixture | Unknown` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
+ src/_pytest/capture.py:968:30: error[invalid-argument-type] Argument is incorrect: Argument type `AnyStr@CaptureFixture` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
- src/_pytest/capture.py:968:44: error[invalid-argument-type] Argument is incorrect: Argument type `AnyStr@CaptureFixture | Unknown` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
+ src/_pytest/capture.py:968:44: error[invalid-argument-type] Argument is incorrect: Argument type `AnyStr@CaptureFixture` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
- Found 399 diagnostics
+ Found 395 diagnostics

koda-validate (https://github.com/keithasaurus/koda-validate)
- koda_validate/generic.py:107:16: error[unsupported-operator] Operator `%` is not supported between two objects of type `Num@MultipleOf`
- Found 405 diagnostics
+ Found 404 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:99:28: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:87:21: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 3 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | dict[str, Any]` on object of type `dict[str, Any]`
+ src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | int | dict[str, Any] | ... omitted 4 union elements` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any]]`
+ src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | int | dict[str, Any] | ... omitted 4 union elements]`
- src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown]`
+ src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown | int | float | ... omitted 4 union elements]`
- src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown]`
+ src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown | int | float | ... omitted 4 union elements]`
- src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | dict[str, Any]` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | int | dict[str, Any] | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:234:20: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `int | Unknown | float | ... omitted 4 union elements`
- Found 5346 diagnostics
+ Found 5352 diagnostics

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 46 diagnostics
+ Found 47 diagnostics

sympy (https://github.com/sympy/sympy)
- sympy/polys/domains/gaussiandomains.py:97:25: error[unsupported-operator] Operator `+` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:97:37: error[unsupported-operator] Operator `+` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:106:25: error[unsupported-operator] Operator `-` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:106:37: error[unsupported-operator] Operator `-` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:113:25: error[unsupported-operator] Operator `-` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:113:37: error[unsupported-operator] Operator `-` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:120:25: error[unsupported-operator] Operator `*` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:120:36: error[unsupported-operator] Operator `*` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:120:46: error[unsupported-operator] Operator `*` is not supported between two objects of type `Tdom@GaussianElement`
- sympy/polys/domains/gaussiandomains.py:120:57: error[unsupported-operator] Operator `*` is not supported between two objects of type `Tdom@GaussianElement`
- Found 15653 diagnostics
+ Found 15643 diagnostics

rotki (https://github.com/rotki/rotki)
- rotkehlchen/chain/decoding/tools.py:96:44: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- rotkehlchen/chain/decoding/tools.py:99:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `Sequence[A@BaseDecoderTools]`, found `Unknown | tuple[BTCAddress, ...] | tuple[ChecksumAddress, ...] | tuple[SubstrateAddress, ...] | tuple[SolanaAddress, ...]`
- rotkehlchen/chain/decoding/tools.py:100:62: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
+ rotkehlchen/chain/decoding/tools.py:97:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `BTCAddress | ChecksumAddress | SubstrateAddress | SolanaAddress`, found `A@BaseDecoderTools`
+ rotkehlchen/chain/decoding/tools.py:98:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `BTCAddress | ChecksumAddress | SubstrateAddress | SolanaAddress | None`, found `A@BaseDecoderTools | None`
- Found 2052 diagnostics
+ Found 2051 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/util/variance.py:41:43: error[unsupported-operator] Operator `-` is not supported between two objects of type `_R@ignore_variance`
- Found 14467 diagnostics
+ Found 14466 diagnostics

No memory usage changes detected ✅

@charliermarsh charliermarsh marked this pull request as ready for review January 20, 2026 03:58
@charliermarsh charliermarsh marked this pull request as draft January 20, 2026 15:24
@charliermarsh charliermarsh marked this pull request as ready for review January 20, 2026 15:37
@charliermarsh charliermarsh marked this pull request as draft January 20, 2026 16:59
@charliermarsh charliermarsh marked this pull request as ready for review January 20, 2026 19:16
@charliermarsh charliermarsh marked this pull request as draft January 20, 2026 19:16
@charliermarsh
Copy link
Member Author

(I think this isn't quite right, revisiting.)

@charliermarsh charliermarsh marked this pull request as ready for review January 21, 2026 02:16
@charliermarsh charliermarsh changed the title [ty] Fix false-positive unsupported-operator for constrained TypeVars [ty] Fix false-positive unsupported-operator for symmetric TypeVars Jan 21, 2026
@charliermarsh charliermarsh force-pushed the charlie/con branch 2 times, most recently from 6089cf8 to ebd3b15 Compare January 21, 2026 02:33
@charliermarsh charliermarsh changed the title [ty] Fix false-positive unsupported-operator for symmetric TypeVars [ty] Fix false-positive unsupported-operator for "symmetric" TypeVars Jan 21, 2026
@charliermarsh charliermarsh force-pushed the charlie/con branch 2 times, most recently from 4f3384d to f011749 Compare January 21, 2026 03:17
@charliermarsh
Copy link
Member Author

I think this fix makes sense, but I'm not certain if there's a more "general" fix or whether this should be happening at a deeper level.

Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

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

I think this fix makes sense, but I'm not certain if there's a more "general" fix or whether this should be happening at a deeper level.

I think you're right that this is a more pointed fix than what we (at least eventually) want.

For one, this only handles binary operators, not arbitrary method calls:

class A:
    def method2(self, x: A) -> A: ...
    def method3(self, x: A, y: A) -> A: ...

class B:
    def method2(self, x: B) -> B: ...
    def method3(self, x: B, y: B) -> B: ...

def f[T: (A, B)](a: T, b: T, c: T):
    # both of these should be okay
    a.method2(b)
    a.method3(b, c)

And another case we have to consider is an overloaded function:

class A: ...
class B: ...

@overload
def func(x: A, y: A) -> A: ...
@overload
def func(x: B, y: B) -> B: ...

def f[T: (A, B)](a: T, b: T):
    # should be okay
    func(a, b)

But, I think to solve all of these examples in general, we need the new solver. That would let us create a constraint set that constrains both arguments (and the receiver that we did member lookup on!) simultaneously.

So given that, I'm okay with it landing this version as a stop-gap, since it definitely resolves some real false positives.

@charliermarsh charliermarsh enabled auto-merge (squash) January 23, 2026 20:14
Union the return types

Add more tests

Also support unary

Limit to symmetric typevar

Simplfiy tests
@charliermarsh charliermarsh merged commit 6385dec into main Jan 23, 2026
48 checks passed
@charliermarsh charliermarsh deleted the charlie/con branch January 23, 2026 20:23
carljm added a commit that referenced this pull request Jan 30, 2026
* main: (62 commits)
  [`refurb`] Do not add `abc.ABC` if already present (`FURB180`) (#22234)
  [ty] Add a new `assert-type-unspellable-subtype` diagnostic (#22815)
  [ty] Avoid duplicate syntax errors for `await` outside functions (#22826)
  [ty] Fix unary operator false-positive for constrained TypeVars (#22783)
  [ty] Fix binary operator false-positive for constrained TypeVars (#22782)
  [ty] Fix false-positive `unsupported-operator` for "symmetric" TypeVars (#22756)
  [`pydocstyle`] Clarify which quote styles are allowed (`D300`) (#22825)
  [ty] Use distributed versions of AND and OR on constraint sets (#22614)
  [ty] Add support for dict literals and dict() calls as default values for parameters with TypedDict types (#22161)
  Document `-` stdin convention in CLI help text (#22817)
  [ty] Make `infer_subscript_expression_types` a method on `Type` (#22731)
  [ty] Simplify `OverloadLiteral::spans` and `OverloadLiteral::parameter_span` (#22823)
  [ty] Require both `*args` and `**kwargs` when calling a `ParamSpec` callable (#22820)
  [ty] Handle tagged errors in conformance (#22746)
  Add `--color` cli option to force colored output (#22806)
  Identify notebooks by LSP didOpen instead of `.ipynb` file extension (#22810)
  [ty] Fix docstring rendering for literal blocks after doctests (#22676)
  [ty] Update salsa to fix out-of-order query validation (#22498)
  [ty] Inline cycle initial and recovery functions (#22814)
  [ty] Pass the generic context through the decorator (#22544)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants