Skip to content
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

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

Closed
JukkaL opened this issue Jan 19, 2018 · 8 comments
Closed

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

JukkaL opened this issue Jan 19, 2018 · 8 comments

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 19, 2018

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?

@ilevkivskyi
Copy link
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.

@ilevkivskyi
Copy link
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

@gvanrossum
Copy link
Member

gvanrossum commented Jan 19, 2018 via email

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jan 25, 2018

Ok, let's go with option 1.

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jan 25, 2018

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.

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jan 26, 2018

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]")

@gvanrossum
Copy link
Member

gvanrossum commented Jan 26, 2018

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.

JukkaL added a commit that referenced this issue Feb 20, 2018
Previously if partial types were inferred from assignments in multiple
scopes, like in the example below, fine-grained incremental mode could
display bogus messages, since only one of the scopes could be
reprocessed in one incremental update:

```
x = None

def f() -> None:
    global x
    x = ''
```

This PR fixes the issue by always inferring partial types within a single
fine-grained incremental target. This is always enabled in fine-grained
incremental mode and can also be turned on in other modes through
a new (but hidden) `--local-partial-types` option.

This was complicated by various edge cases that are covered by the 
new test cases and/or documented in comments.

If the new option is not enabled, the old behavior should (mostly) be
preserved, module some fixes that may affect also the default mode.

Work towards #4492.
@JukkaL
Copy link
Collaborator Author

JukkaL commented Mar 8, 2018

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

@JukkaL JukkaL closed this as completed Mar 8, 2018
yedpodtrzitko pushed a commit to kiwicom/mypy that referenced this issue Mar 15, 2018
…#4553)

Previously if partial types were inferred from assignments in multiple
scopes, like in the example below, fine-grained incremental mode could
display bogus messages, since only one of the scopes could be
reprocessed in one incremental update:

```
x = None

def f() -> None:
    global x
    x = ''
```

This PR fixes the issue by always inferring partial types within a single
fine-grained incremental target. This is always enabled in fine-grained
incremental mode and can also be turned on in other modes through
a new (but hidden) `--local-partial-types` option.

This was complicated by various edge cases that are covered by the 
new test cases and/or documented in comments.

If the new option is not enabled, the old behavior should (mostly) be
preserved, module some fixes that may affect also the default mode.

Work towards python#4492.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants