Skip to content

What to do about inferring types from multiple scopes? #4492

Closed

Description

@JukkaL

Typically mypy infers the type of the variable from the first assignment. In some cases mypy infers the type from two assignments. The first assignment creates internally a partial type, which is missing some information. The second assignment completes the type. Example:

x = []  # x gets a partial List type
x.append(1)  # x gets complete type List[int]

In some cases the assignments can be in two different scopes. Example:

x = []

def f() -> None:
    x.append(1)

# type of x is List[int]

This feature has apparently been supported a while, but it has some issues:

There are a few alternatives which might be reasonable, instead of the current behavior:

  1. Require an annotation for the variable in cases such as the above, where the assignment that completes a partial type is in a different scope. This would break compatibility with previous mypy releases. Based on a quick experiment, the S internal codebase at Dropbox would require about 40 additional type annotations if we make this change.
  2. Ignore assignments in nested scopes and assume Any for any missing partial type information. For example, if the initial assignment is x = [] and the only additional assignments are in nested scopes, infer List[Any] as the type of x. This way the type of x is defined entirely by the contents of a single scope, which solves the above problems. This also introduces an additional problem -- implicit Any types. We might want to have a strictness flag that falls back to requiring a type annotation (as in option 1 above).

Currently my biggest concern is fine-grained incremental mode. There it may be possible to support the current behavior by introducing a new concept: "bound targets". I'll add a writeup about this to #4464.

@gvanrossum @msullivan @ilevkivskyi Thoughts?

Activity

  1. ilevkivskyi commented on Jan 19, 2018

    @ilevkivskyi
    Member

    I am in favor of option 1. I think I mentioned this some time ago, and I didn't change my mind. My argument is simple: I don't like implicit Any types. Currently PEP 484 defines small set of situations where Any is assumed (like non-annotated functions and missing generic arguments) and we have flags to prohibit those. Adding one more source of implicit Anys seems not worth it (especially that users might not be aware of it and will therefore get false negatives).

    Also the number 40 seems relatively small.

  2. ilevkivskyi commented on Jan 19, 2018

    @ilevkivskyi
    Member

    An additional argument for option 1 is that the function where the second assignment happens may be non-annotated. In this case the inferred type will be e.g. Optional[Any]. This can lead to some hard to spot false negatives. This has been also discussed in #4455

  3. gvanrossum commented on Jan 19, 2018

    @gvanrossum
    Member
  4. JukkaL commented on Jan 25, 2018

    @JukkaL
    CollaboratorAuthor

    Ok, let's go with option 1.

  5. self-assigned this
    on Jan 25, 2018
  6. JukkaL commented on Jan 25, 2018

    @JukkaL
    CollaboratorAuthor

    Turns out that my figure of about 40 annotations was too optimistic -- I wasn't counting all kinds of partial types. A better figure is about 80 annotations for the S codebase. I think that the adaptation work is still reasonable and option 1 is still the best one.

  7. JukkaL commented on Jan 26, 2018

    @JukkaL
    CollaboratorAuthor

    I found some further cases. It looks like the C codebase needs around 40 annotations and S around 140. The most common case in S is an attribute initialized to None in the class body.

    I'm about half way through the migration of Dropbox codebases. It has been enough work that it might make sense to make it easier. First, we could have a per-file option that ignores missing annotations (and will just infer Any components for missing collection item types, and so on). Second, we could try to infer a type and suggest it in some of the most common cases. Example:

    foo.py:234: error: Need type annotation for 'things' (suggestion: "List[str]")
    
  8. gvanrossum commented on Jan 26, 2018

    @gvanrossum
    Member

    Off-line we decided on a compromise solution. IIRC it's something like add a global flag to enable or disable the current behavior, and force disabling the behavior in fine-grained mode; and when the current behavior is disabled, show an error with a suggestion for the type. E.g.

    class C:
        x = None
        def __init__(self) -> None:
            self.x = 0
    

    This would either be allowed (default in non-fine-grained mode) or produce an error on x = None with the suggested type int. The suggested types would be formatted the same as types in messages from reveal_type(), and the phrasing would make it clear that the suggestion is just that, not a guarantee.

  9. JukkaL commented on Mar 8, 2018

    @JukkaL
    CollaboratorAuthor

    Closing in favor of #4703 which is about suggesting type annotations. Requiring an explicit annotation was implemented in #4553.

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions