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

Fine-grained: Don't infer partial types from multiple targets #4553

Merged
merged 13 commits into from
Feb 20, 2018

Conversation

JukkaL
Copy link
Collaborator

@JukkaL JukkaL commented Feb 8, 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 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 Feb 8, 2018

This is ready for review but don't merge yet since I want to fix various issues in internal Dropbox codebases first.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good, I have few suggestions here.

def is_defined_in_base_class(self, var: Var) -> bool:
if var.info is not None:
for base in var.info.mro[1:]:
if base.get(var.name()) is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we also check that it is not None in base class?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure what you mean by this. Can you give an example where this would make a difference?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean add ... and not isinstance(base.get(var.name()), NoneTyp)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base.get() returns Optional[SymbolTableNode] so that exact check doesn't make sense. If you meant guarding agains a definition like x = None # type: None in a base class, I don't agree -- it should be treated as a valid definition. If you still don't agree, can you give me a test case where this would make a difference?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry, you are right, this is perfectly valid.

mypy/checker.py Outdated
@@ -107,6 +107,10 @@
('is_upper_bound', bool), # False => precise type
])

# Keeps track of partial types in a single scope
PartialTypeScope = NamedTuple('PartialTypeMap', [('map', Dict[Var, Context]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't NAME in NAME = NamedTuple('NAME', ...) always be the same in l.h.s. and r.h.s.?

mypy/checker.py Outdated
@@ -107,6 +107,10 @@
('is_upper_bound', bool), # False => precise type
])

# Keeps track of partial types in a single scope
PartialTypeScope = NamedTuple('PartialTypeMap', [('map', Dict[Var, Context]),
('is_function', bool)])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a short comment about why we need to keep track about is_function will help here. (My understanding this is because fine-grained targets are always top level functions.)

# flags: --local-partial-types --strict-optional
from typing import Any

A: Any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add the same test where A is not Any, but a normal class. (And None in two subclasses like here.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test case.

y = ''
[out]
==
a.py:1: error: Need type annotation for 'y'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for completes I would add a tests where the error appears in the first run, but not in the second one (like if a user listened and actually added an annotation).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test case.

@JukkaL
Copy link
Collaborator Author

JukkaL commented Feb 20, 2018

The travis failure seems like a known flake.

@JukkaL JukkaL merged commit 0c12b21 into master Feb 20, 2018
@gvanrossum gvanrossum deleted the fine-grained-partial-scope branch February 21, 2018 16:16
carljm added a commit to carljm/mypy that referenced this pull request Feb 28, 2018
* master: (27 commits)
  Don't call --strict-optional and --incremental experimental (python#4642)
  Sync typeshed (python#4641)
  Fix callable types with inconsistent argument counts (python#4611)
  Fix example (add 'class A:')
  Make psutil an optional dependency (python#4634)
  mypy and mypy_extensions aren't posix only (python#3765)
  Documentation for attr support (python#4632)
  Use read_with_python_encoding in stubgen to handle file encoding (python#3790)
  Sync typeshed (python#4631)
  Add remaining core team emails to CREDITS (python#4629)
  Fix issues with attr code. (python#4628)
  Better support for converter in attrs plugin. (python#4607)
  Clean up credits (python#4626)
  Support type aliases in fine-grained incremental mode (python#4525)
  Fine-grained: Fix crash caused by unreachable class (python#4613)
  Treat divmod like a binary operator (python#4585)
  Sync typeshed (python#4605)
  Fine-grained: Don't infer partial types from multiple targets (python#4553)
  Fine-grained: Compare symbol table snapshots when following dependencies (python#4598)
  Fix type of forward reference to a decorated class method (python#4486)
  ...
yedpodtrzitko pushed a commit to kiwicom/mypy that referenced this pull request 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
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants