Skip to content

Type error randomly raised when using a decorator and a type: ignore comment #18125

Closed

Description

@Viicos

To Reproduce

  1. Create a new environment with pydantic==2.9.2 installed.
  2. Create a file t.py:
from typing import Any

from typing_extensions import Self

from pydantic import BaseModel, model_validator
from pydantic.functional_validators import ModelWrapValidatorHandler


class WrapModelValidator(BaseModel):
    @model_validator(mode='wrap')  # type: ignore[arg-type]  # pyright: ignore[reportArgumentType]
    def no_classmethod(cls, value: Any, handler: ModelWrapValidatorHandler[Self]) -> Self: ...
  1. Run mypy --cache-dir=/dev/null --python-version 3.10 --disable-error-code empty-body --warn-unused-ignores t.py

Mypy will randomly raise:

t.py:10: error: Unused "type: ignore" comment  [unused-ignore]

And sometimes suceed.

Sorry I couldn't provide more details, the issue is really weird and it seems like mypy doesn't produce fully reproducible results in this case.

Your Environment

  • Mypy version used: 1.13.0
  • Mypy command-line flags: --cache-dir=/dev/null --python-version 3.10 --disable-error-code empty-body --warn-unused-ignores
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.12

Activity

  1. brianschubert commented on Nov 9, 2024

    @brianschubert
    Collaborator

    Thanks! I can reproduce this locally. This is certainly a weird one!

    So far I've narrowed this down to some non-deterministic behavior of is_subtype (specifically during this invocation).

    What's quite interesting is that the nondeterministic behavior isn't just between mypy runs, but also between invocations of is_subtype with the same arguments during the same run (here's a video demo in my debugger).

  2. sterliakov commented on Apr 22, 2025

    @sterliakov
    Collaborator

    Okay, this is really indeterminate. mypy.solve.solve_one is order-sensitive and iterates over sets. The following patch (not a fix, but only a proof) makes mypy output on this snippet completely reproducible.

    diff --git a/mypy/solve.py b/mypy/solve.py
    index 57988790a..a6d9cfcd7 100644
    --- a/mypy/solve.py
    +++ b/mypy/solve.py
    @@ -214,7 +214,7 @@ def solve_iteratively(
                 break
             # Solve each solvable type variable separately.
             s_batch.remove(solvable_tv)
    -        result = solve_one(lowers[solvable_tv], uppers[solvable_tv])
    +        result = solve_one(sorted(lowers[solvable_tv], key=str), sorted(uppers[solvable_tv], key=str))
             solutions[solvable_tv] = result
             if result is None:
                 # TODO: support backtracking lower/upper bound choices and order within SCCs.

    The problematic case is solving the following set of constraints:

    [1677 :> type[Self`0], 1677 :> Self`0, 1677 <: Self`0]
    

    for Self`1667 where Self`0 has default=Any, upper_bound=p.WrapModelValidator. If the iteration starts with Self`0, we solve 1677 to Any, otherwise we conclude that deps can't be satisfied (None).

    This problem stems from non-commutative join and meet implementation.

  3. hauntsaninja commented on Apr 22, 2025

    @hauntsaninja
    Collaborator

    Ah I ran into that in #16979 (comment) as well

  4. added a commit that references this issue on May 26, 2025
    2debb86
  5. hauntsaninja commented on May 26, 2025

    @hauntsaninja
    Collaborator

    Spent some time today looking at a few of these nondeterminism issues.

    #19149 is a fix for this (it makes mypy consistently emit the error)

  6. added a commit that references this issue on May 27, 2025
    27d118b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions