Skip to content

Order of declarations matters, but it shouldn't #14509

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

Closed
finite-state-machine opened this issue Jan 23, 2023 · 4 comments
Closed

Order of declarations matters, but it shouldn't #14509

finite-state-machine opened this issue Jan 23, 2023 · 4 comments
Labels
bug mypy got something wrong

Comments

@finite-state-machine
Copy link

finite-state-machine commented Jan 23, 2023

Bug Report

The order of declarations shouldn't matter to mypy, but in the example that follow, they do.

To Reproduce

(Exactly as written below: mypy-play.net)
(With slightly better comments, but different line numbers: mypy-play.net)

from __future__ import annotations

import functools
from typing import (
        Any,
        Callable,
        cast,
        Counter,
        Optional,
        TypeVar,
        )


Tc = TypeVar('Tc', bound=Callable[..., Any])


def func_wraps(wraps: Tc) -> Callable[[Callable[..., Any]], Tc]:
    '''declare that one function wraps another

    Args:
        wraps: the function being wrapped

    Returns:
        a function 'decorator()', where...
        Args:
            the function which wraps 'wraps'
        Returns:
            the input argument, unchanged

    The type annotations of the return value of the 'decorator()' will
    be those of 'wraps'.
    '''
    def inner(wrapper: Callable[..., Any], /) -> Tc:
        '''decorator

        Args:
            wrapper: the function which wraps 'wraps'
        Returns:
            the same function, unchanged
        '''
        wrapper_ = cast(Tc, wrapper)
        return functools.wraps(wraps)(wrapper_)
    return inner


reveal_type(ParentClass)
reveal_type(ChildClass)
reveal_type(ParentClass.__init__)
reveal_type(ChildClass.__init__)


def function1() -> ChildClass:
    return ChildClass()


class ParentClass:
    def __init__(self, *, param: Optional[int] = None) -> None:
        _ = param

class ChildClass(ParentClass):
    some_counter: Counter[int]

    @func_wraps(ParentClass.__init__)
    def __init__(self, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        self.some_counter = Counter()


def function2() -> ChildClass:
    return ChildClass()


reveal_type(ParentClass)
reveal_type(ChildClass)
reveal_type(ParentClass.__init__)
reveal_type(ChildClass.__init__)

Expected Behavior

The reveal_type lines and function1 declaration, found before the class definitions, should result in the same output as those found after the class definitions.

mypy's output should be (lines which differ are marked with a "*"):

  bug.py:47: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ParentClass"
* bug.py:48: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ChildClass"
  bug.py:49: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
* bug.py:50: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
  [edit: removed line that shouldn't have been here]
  bug.py:74: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ParentClass"
  bug.py:75: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ChildClass"
  bug.py:76: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
  bug.py:77: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
* Success: no issues found in 1 source file

Actual Behavior

The first group ofreveal_type lines and the function1 declaration misbehave, while everything after the class definitions behaves as expected.

The actual output is:

  bug.py:47: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ParentClass"
* bug.py:48: note: Revealed type is "Any"
  bug.py:49: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
* bug.py:50: note: Revealed type is "Any"
* bug.py:54: error: Returning Any from function declared to return "ChildClass"  [no-any-return]
  bug.py:74: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ParentClass"
  bug.py:75: note: Revealed type is "def (*, param: Union[builtins.int, None] =) -> bug.ChildClass"
  bug.py:76: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
  bug.py:77: note: Revealed type is "def (self: bug.ParentClass, *, param: Union[builtins.int, None] =)"
* Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 0.991, master (2023-01-23)
  • Mypy command-line flags: --follow-imports=silent --show-error-codes --strict --warn-unreachable
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.8, 3.11
@finite-state-machine finite-state-machine added the bug mypy got something wrong label Jan 23, 2023
@finite-state-machine
Copy link
Author

With apologies, I haven't done a thorough search for existing bugs, because I really don't know what keywords to search for.

@erictraut
Copy link

Why do you think the reveal_type before the declaration should reveal the same information as the reveal_type after the declaration? The argument to reveal_type is not a type annotation, so its evaluation is not affected by from __future__ import annotations. If you were to import reveal_type from typing_extensions and run your code, a runtime exception would be reported on the line of the first reveal_type call because ParentClass is undefined at that point in the program.

@finite-state-machine
Copy link
Author

@erictraut Yes, on second thoughts, you're right about reveal_type. I don't think this explains the error on line 54 (in function1: return ChildClass() -> "Returning Any from function declared to return 'ChildClass'"). (I accidentally left this error in the 'expected output'; sorry!)

If we are agreed that the error on the return statement is inappropriate, I can either edit this bug to remove the reveal_type distractions, or file a new bug, according to your preference.

@finite-state-machine
Copy link
Author

In the absence of feedback, I've filed a new, more concise issue (#14519).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants