diff --git a/.travis.yml b/.travis.yml index 29f1d441..2113aec8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,21 @@ language: python cache: pip -python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 - - nightly - - pypy - - pypy-5.3 - - pypy3 +matrix: + include: + - python: 2.7 + - python: 3.4 + - python: 3.5 + - python: 3.6 + - python: pypy + - python: pypy-5.3 + - python: pypy3 + - python: 3.7 + dist: xenial + - python: nightly + dist: xenial install: - pip install --upgrade . - pip list script: - python setup.py test -q - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then pip install flake8==2.1.0 pep8==1.5.6 && flake8 --version && flake8 pyflakes setup.py; fi -sudo: false diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 5c128209..e05bdf4d 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -476,6 +476,7 @@ class GeneratorScope(Scope): class ModuleScope(Scope): """Scope for a module.""" _futures_allowed = True + _annotations_future_enabled = False class DoctestScope(ModuleScope): @@ -628,6 +629,19 @@ def futuresAllowed(self, value): if isinstance(self.scope, ModuleScope): self.scope._futures_allowed = False + @property + def annotationsFutureEnabled(self): + scope = self.scopeStack[0] + if not isinstance(scope, ModuleScope): + return False + return scope._annotations_future_enabled + + @annotationsFutureEnabled.setter + def annotationsFutureEnabled(self, value): + assert value is True + assert isinstance(self.scope, ModuleScope) + self.scope._annotations_future_enabled = True + @property def scope(self): return self.scopeStack[-1] @@ -1068,6 +1082,8 @@ def handleForwardAnnotation(): self.handleNode(parsed_annotation, node) self.deferFunction(handleForwardAnnotation) + elif self.annotationsFutureEnabled: + self.deferFunction(lambda: self.handleNode(annotation, node)) else: self.handleNode(annotation, node) @@ -1448,6 +1464,8 @@ def IMPORTFROM(self, node): if alias.name not in __future__.all_feature_names: self.report(messages.FutureFeatureNotDefined, node, alias.name) + if alias.name == 'annotations': + self.annotationsFutureEnabled = True elif alias.name == '*': # Only Python 2, local import * is a SyntaxWarning if not PY2 and not isinstance(self.scope, ModuleScope): diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index b8301ea2..d0fff0af 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -2055,6 +2055,24 @@ class A: pass a: 'a: "A"' ''', m.ForwardAnnotationSyntaxError) + @skipIf(version_info < (3, 7), 'new in Python 3.7') + def test_postponed_annotations(self): + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: B + class B: pass + ''') + + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: Undefined + class B: pass + ''', m.UndefinedName) + def test_raise_notimplemented(self): self.flakes(''' raise NotImplementedError("This is fine") diff --git a/tox.ini b/tox.ini index fabad9db..f3db2bb6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] skip_missing_interpreters = True envlist = - py27,py34,py35,py36,pypy,pypy3 + py27,py34,py35,py36,py37,pypy,pypy3 [testenv] deps =