Skip to content

Allow disabling implicit Any for type aliases #2696

Closed

Description

@mohabusama

The issue is somehow discussed here #2690

Here is a sample code that mypy allows and eventually fails in runtime

from typing import *
T = TypeVar('T', List[int], Dict)
Processor = Callable[[T], None]
def output(inp: T, processor: Processor) -> None:
    processor(inp)
def process_dict(inp: Dict) -> None:
    print(inp.keys())
output([1, 2, 3], process_dict)

As described in #606 discussions and https://www.python.org/dev/peps/pep-0484/#type-aliases this is not a problem with mypy, but an overlooked mistake in non-parameterizing generic type aliasProcessor in output.

reveal_type for output:
Revealed type is 'def [T in (builtins.list[builtins.int], builtins.dict)] (inp: T-1, processor: def (Any))'`

Processor is implicitly treated as Processor[Any] in this case.

Changing output to def output(inp: T, processor: Processor[T]) -> None: resolves the issue and lets mypy catch the bug.

The problem is that implicit Any for type aliases can easily go unnoticed.

Suggestions:

  1. As suggested by @roganov & @ilevkivskyi : Add --disable-implicit-any flag to mypy, which will disable this behavior and forces the user to be explicit. Drawback, users who do not know about the flag might learn about the bug the hard way (in production 😉 )

  2. Disable implicit Any by default for type aliases, and force users to be explicit (either by parameterizing [Any] or [T]). Drawback, might introduce a lot of mypy errors for existing, "error free" code base (confusing/annoying to users). Also, not entirely sure about how this impacts other scenarios.

  3. Produce warning when used type alias has no explicit parameterization. This will make mypy exit without failure, but just point the user to potential bugs. Drawback, warnings might be annoying for users who are ok with their codebase (i.e. might require --suppress-implicit-any-warning)

  4. Any other suggestion ... 😄

Thanks,

Activity

  1. JukkaL commented on Jan 16, 2017

    @JukkaL
    Collaborator

    One option would be to use deprecate --disallow-untyped-defs and use --disallow-implicit-any as a superset of that option + checking that there no implicit Any types for other reasons, such the example above.

  2. ilevkivskyi commented on Jan 16, 2017

    @ilevkivskyi
    Member

    +1 to Jukka's idea. We have at least three places where an absence of type is equivalent to Any:

    • Untyped definitions: def f(x, ...): ... is equivalent to def f(x: Any, ...): ...
    • Normal generics x: Iterable is equivalent to x: Iterable[Any]
    • Generic aliases

    Maybe there others that I don't remember. It would be difficult to remember what is the option to disable each usage.

  3. gvanrossum commented on Jan 16, 2017

    @gvanrossum
    Member

    Hm, I expect that the "x: Iterable" case is very common. Would be worth doing a little experiment with that: find the code that implements this, make it raise an error instead, and see what it catches in some real-world codebases, starting with mypy. FWIW the pattern '\[AnyType\(\)\]\s*\*' seems to find many of these (I count 11).

  4. pkch commented on Feb 24, 2017

    @pkch
    Contributor

    I'm very much in favor of --disallow-implicit-any.

    However, this isn't currently allowed (not sure why):

    from typing import *
    T = TypeVar('T')
    x: List[T] # Invalid type
    ListAlias = List[T]
    y: ListAlias[T] # Invalid type
    def f() -> None:
        x: ListAlias[T] # Invalid type
    

    So when we need to define variables that belong to a generic container type, but without the item type being part of the type of the function call arguments, we have to rely on implicit Any like this:

    from typing import *
    T = TypeVar('T')
    x: List
    ListAlias = List[T]
    y: ListAlias
    def f() -> None:
        x: ListAlias
    

    Therefore I think either the proposed --disallow-implicit-any flag should not create warnings in the second code snippet; or the first code snippet should be made legal.

  5. ilevkivskyi commented on Feb 24, 2017

    @ilevkivskyi
    Member

    or the first code snippet should be made legal.

    But what you expect to be the meaning of this? Type variables are only used for generic functions, and generic types and aliases. What would be the meaning of an unbound type variable in an annotation?

  6. pkch commented on Feb 24, 2017

    @pkch
    Contributor

    I agree, x: SomeGenericContainerAlias[T] outside a function signature leaves mypy no chance to solve for T; however, it can still be used to define a generic container if someone prefers to completely eliminate reliance on implicit Any. I didn't mean to propose that, just listing it as an option.

    My main point was that currently we often rely on implicit Any with a normal generic or a generic alias to specify a container type (as in my second code snippet). So --disallow-implicit-any shouldn't complain in those cases.

  7. gvanrossum commented on Feb 25, 2017

    @gvanrossum
    Member

    It feels like a special case is needed for aliases.

    When C is a generic class (defined with a class statement that uses a type variable in a base class), I think we should still allow using x: C as a shorthand for x: C[Any].

    But when A is a generic alias (defined with an assignment statement using a type variable on the right-hand side) the experience from #2690 and the discussion here suggest that it may be better to outright reject using x: A, instead requiring x: A[Any] if that's what is meant, or (in the parameter list of a generic function or in the body of a generic class) x: A[T].

  8. ddfisher commented on Feb 27, 2017

    @ddfisher
    Collaborator

    I'm not sure I understand why we'd want to make that distinction. It seems like a good idea to keep the behavior of aliases and classes as similar as possible.

  9. gvanrossum commented on Feb 28, 2017

    @gvanrossum
    Member
  10. ilevkivskyi commented on Nov 2, 2017

    @ilevkivskyi
    Member

    I think this is now fixed since we have --disallow-any=generics in 0.540 that also catches implicit Any in generic aliases. (IIRC it was replaced by a dedicated flag in a recent PR.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions