from __future__ import annotations
now implies 3.7+ (#2690)
This commit is contained in:
parent
1c6b3a3a6f
commit
ab86513710
@ -15,6 +15,7 @@
|
||||
- Fix determination of f-string expression spans (#2654)
|
||||
- Fix bad formatting of error messages about EOF in multi-line statements (#2343)
|
||||
- Functions and classes in blocks now have more consistent surrounding spacing (#2472)
|
||||
- `from __future__ import annotations` statement now implies Python 3.7+ (#2690)
|
||||
|
||||
#### Jupyter Notebook support
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
||||
from black.lines import Line, EmptyLineTracker
|
||||
from black.linegen import transform_line, LineGenerator, LN
|
||||
from black.comments import normalize_fmt_off
|
||||
from black.mode import Mode, TargetVersion
|
||||
from black.mode import FUTURE_FLAG_TO_FEATURE, Mode, TargetVersion
|
||||
from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
|
||||
from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
|
||||
from black.concurrency import cancel, shutdown, maybe_install_uvloop
|
||||
@ -1080,7 +1080,7 @@ def f(
|
||||
if mode.target_versions:
|
||||
versions = mode.target_versions
|
||||
else:
|
||||
versions = detect_target_versions(src_node)
|
||||
versions = detect_target_versions(src_node, future_imports=future_imports)
|
||||
|
||||
# TODO: fully drop support and this code hopefully in January 2022 :D
|
||||
if TargetVersion.PY27 in mode.target_versions or versions == {TargetVersion.PY27}:
|
||||
@ -1132,7 +1132,9 @@ def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
|
||||
return tiow.read(), encoding, newline
|
||||
|
||||
|
||||
def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
|
||||
def get_features_used( # noqa: C901
|
||||
node: Node, *, future_imports: Optional[Set[str]] = None
|
||||
) -> Set[Feature]:
|
||||
"""Return a set of (relatively) new Python features used in this file.
|
||||
|
||||
Currently looking for:
|
||||
@ -1142,9 +1144,17 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
|
||||
- positional only arguments in function signatures and lambdas;
|
||||
- assignment expression;
|
||||
- relaxed decorator syntax;
|
||||
- usage of __future__ flags (annotations);
|
||||
- print / exec statements;
|
||||
"""
|
||||
features: Set[Feature] = set()
|
||||
if future_imports:
|
||||
features |= {
|
||||
FUTURE_FLAG_TO_FEATURE[future_import]
|
||||
for future_import in future_imports
|
||||
if future_import in FUTURE_FLAG_TO_FEATURE
|
||||
}
|
||||
|
||||
for n in node.pre_order():
|
||||
if n.type == token.STRING:
|
||||
value_head = n.value[:2] # type: ignore
|
||||
@ -1229,9 +1239,11 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
|
||||
return features
|
||||
|
||||
|
||||
def detect_target_versions(node: Node) -> Set[TargetVersion]:
|
||||
def detect_target_versions(
|
||||
node: Node, *, future_imports: Optional[Set[str]] = None
|
||||
) -> Set[TargetVersion]:
|
||||
"""Detect the version to target based on the nodes used."""
|
||||
features = get_features_used(node)
|
||||
features = get_features_used(node, future_imports=future_imports)
|
||||
return {
|
||||
version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
|
||||
}
|
||||
|
@ -4,11 +4,18 @@
|
||||
chosen by the user.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from operator import attrgetter
|
||||
from typing import Dict, Set
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
from typing_extensions import Final
|
||||
else:
|
||||
from typing import Final
|
||||
|
||||
from black.const import DEFAULT_LINE_LENGTH
|
||||
|
||||
|
||||
@ -44,6 +51,9 @@ class Feature(Enum):
|
||||
PATTERN_MATCHING = 11
|
||||
FORCE_OPTIONAL_PARENTHESES = 50
|
||||
|
||||
# __future__ flags
|
||||
FUTURE_ANNOTATIONS = 51
|
||||
|
||||
# temporary for Python 2 deprecation
|
||||
PRINT_STMT = 200
|
||||
EXEC_STMT = 201
|
||||
@ -55,6 +65,11 @@ class Feature(Enum):
|
||||
BACKQUOTE_REPR = 207
|
||||
|
||||
|
||||
FUTURE_FLAG_TO_FEATURE: Final = {
|
||||
"annotations": Feature.FUTURE_ANNOTATIONS,
|
||||
}
|
||||
|
||||
|
||||
VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
|
||||
TargetVersion.PY27: {
|
||||
Feature.ASYNC_IDENTIFIERS,
|
||||
@ -89,6 +104,7 @@ class Feature(Enum):
|
||||
Feature.TRAILING_COMMA_IN_CALL,
|
||||
Feature.TRAILING_COMMA_IN_DEF,
|
||||
Feature.ASYNC_KEYWORDS,
|
||||
Feature.FUTURE_ANNOTATIONS,
|
||||
},
|
||||
TargetVersion.PY38: {
|
||||
Feature.UNICODE_LITERALS,
|
||||
@ -97,6 +113,7 @@ class Feature(Enum):
|
||||
Feature.TRAILING_COMMA_IN_CALL,
|
||||
Feature.TRAILING_COMMA_IN_DEF,
|
||||
Feature.ASYNC_KEYWORDS,
|
||||
Feature.FUTURE_ANNOTATIONS,
|
||||
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||
Feature.POS_ONLY_ARGUMENTS,
|
||||
},
|
||||
@ -107,6 +124,7 @@ class Feature(Enum):
|
||||
Feature.TRAILING_COMMA_IN_CALL,
|
||||
Feature.TRAILING_COMMA_IN_DEF,
|
||||
Feature.ASYNC_KEYWORDS,
|
||||
Feature.FUTURE_ANNOTATIONS,
|
||||
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||
Feature.RELAXED_DECORATORS,
|
||||
Feature.POS_ONLY_ARGUMENTS,
|
||||
@ -118,6 +136,7 @@ class Feature(Enum):
|
||||
Feature.TRAILING_COMMA_IN_CALL,
|
||||
Feature.TRAILING_COMMA_IN_DEF,
|
||||
Feature.ASYNC_KEYWORDS,
|
||||
Feature.FUTURE_ANNOTATIONS,
|
||||
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||
Feature.RELAXED_DECORATORS,
|
||||
Feature.POS_ONLY_ARGUMENTS,
|
||||
|
@ -811,6 +811,24 @@ def test_get_features_used(self) -> None:
|
||||
node = black.lib2to3_parse("def fn(a, /, b): ...")
|
||||
self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
|
||||
|
||||
def test_get_features_used_for_future_flags(self) -> None:
|
||||
for src, features in [
|
||||
("from __future__ import annotations", {Feature.FUTURE_ANNOTATIONS}),
|
||||
(
|
||||
"from __future__ import (other, annotations)",
|
||||
{Feature.FUTURE_ANNOTATIONS},
|
||||
),
|
||||
("a = 1 + 2\nfrom something import annotations", set()),
|
||||
("from __future__ import x, y", set()),
|
||||
]:
|
||||
with self.subTest(src=src, features=features):
|
||||
node = black.lib2to3_parse(src)
|
||||
future_imports = black.get_future_imports(node)
|
||||
self.assertEqual(
|
||||
black.get_features_used(node, future_imports=future_imports),
|
||||
features,
|
||||
)
|
||||
|
||||
def test_get_future_imports(self) -> None:
|
||||
node = black.lib2to3_parse("\n")
|
||||
self.assertEqual(set(), black.get_future_imports(node))
|
||||
|
Loading…
Reference in New Issue
Block a user