Skip to content

Commit

Permalink
Enable pattern matching by default (#2758)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com>
  • Loading branch information
isidentical and ichard26 committed Jan 22, 2022
1 parent b3b341b commit 022f896
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -36,6 +36,8 @@
Jupyter Notebooks (#2744)
- Deprecate `--experimental-string-processing` and move the functionality under
`--preview` (#2789)
- Enable Python 3.10+ by default, without any extra need to specify
`--target-version=py310`. (#2758)

### Packaging

Expand Down
28 changes: 16 additions & 12 deletions src/black/parsing.py
Expand Up @@ -42,7 +42,6 @@
ast3 = ast


PY310_HINT: Final = "Consider using --target-version py310 to parse Python 3.10 code."
PY2_HINT: Final = "Python 2 support was removed in version 22.0."


Expand All @@ -58,12 +57,11 @@ def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:
pygram.python_grammar_no_print_statement_no_exec_statement_async_keywords,
# Python 3.0-3.6
pygram.python_grammar_no_print_statement_no_exec_statement,
# Python 3.10+
pygram.python_grammar_soft_keywords,
]

grammars = []
if supports_feature(target_versions, Feature.PATTERN_MATCHING):
# Python 3.10+
grammars.append(pygram.python_grammar_soft_keywords)
# If we have to parse both, try to parse async as a keyword first
if not supports_feature(
target_versions, Feature.ASYNC_IDENTIFIERS
Expand All @@ -75,6 +73,10 @@ def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:
if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS):
# Python 3.0-3.6
grammars.append(pygram.python_grammar_no_print_statement_no_exec_statement)
if supports_feature(target_versions, Feature.PATTERN_MATCHING):
# Python 3.10+
grammars.append(pygram.python_grammar_soft_keywords)

# At least one of the above branches must have been taken, because every Python
# version has exactly one of the two 'ASYNC_*' flags
return grammars
Expand All @@ -86,6 +88,7 @@ def lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) -
src_txt += "\n"

grammars = get_grammars(set(target_versions))
errors = {}
for grammar in grammars:
drv = driver.Driver(grammar)
try:
Expand All @@ -99,20 +102,21 @@ def lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) -
faulty_line = lines[lineno - 1]
except IndexError:
faulty_line = "<line number missing in source>"
exc = InvalidInput(f"Cannot parse: {lineno}:{column}: {faulty_line}")
errors[grammar.version] = InvalidInput(
f"Cannot parse: {lineno}:{column}: {faulty_line}"
)

except TokenError as te:
# In edge cases these are raised; and typically don't have a "faulty_line".
lineno, column = te.args[1]
exc = InvalidInput(f"Cannot parse: {lineno}:{column}: {te.args[0]}")
errors[grammar.version] = InvalidInput(
f"Cannot parse: {lineno}:{column}: {te.args[0]}"
)

else:
if pygram.python_grammar_soft_keywords not in grammars and matches_grammar(
src_txt, pygram.python_grammar_soft_keywords
):
original_msg = exc.args[0]
msg = f"{original_msg}\n{PY310_HINT}"
raise InvalidInput(msg) from None
# Choose the latest version when raising the actual parsing error.
assert len(errors) >= 1
exc = errors[max(errors)]

if matches_grammar(src_txt, pygram.python_grammar) or matches_grammar(
src_txt, pygram.python_grammar_no_print_statement
Expand Down
2 changes: 2 additions & 0 deletions src/blib2to3/pgen2/grammar.py
Expand Up @@ -92,6 +92,7 @@ def __init__(self) -> None:
self.soft_keywords: Dict[str, int] = {}
self.tokens: Dict[int, int] = {}
self.symbol2label: Dict[str, int] = {}
self.version: Tuple[int, int] = (0, 0)
self.start = 256
# Python 3.7+ parses async as a keyword, not an identifier
self.async_keywords = False
Expand Down Expand Up @@ -145,6 +146,7 @@ def copy(self: _P) -> _P:
new.labels = self.labels[:]
new.states = self.states[:]
new.start = self.start
new.version = self.version
new.async_keywords = self.async_keywords
return new

Expand Down
5 changes: 5 additions & 0 deletions src/blib2to3/pygram.py
Expand Up @@ -178,6 +178,8 @@ def initialize(cache_dir: Union[str, "os.PathLike[str]", None] = None) -> None:

# Python 2
python_grammar = driver.load_packaged_grammar("blib2to3", _GRAMMAR_FILE, cache_dir)
python_grammar.version = (2, 0)

soft_keywords = python_grammar.soft_keywords.copy()
python_grammar.soft_keywords.clear()

Expand All @@ -191,6 +193,7 @@ def initialize(cache_dir: Union[str, "os.PathLike[str]", None] = None) -> None:
python_grammar_no_print_statement_no_exec_statement = python_grammar.copy()
del python_grammar_no_print_statement_no_exec_statement.keywords["print"]
del python_grammar_no_print_statement_no_exec_statement.keywords["exec"]
python_grammar_no_print_statement_no_exec_statement.version = (3, 0)

# Python 3.7+
python_grammar_no_print_statement_no_exec_statement_async_keywords = (
Expand All @@ -199,12 +202,14 @@ def initialize(cache_dir: Union[str, "os.PathLike[str]", None] = None) -> None:
python_grammar_no_print_statement_no_exec_statement_async_keywords.async_keywords = (
True
)
python_grammar_no_print_statement_no_exec_statement_async_keywords.version = (3, 7)

# Python 3.10+
python_grammar_soft_keywords = (
python_grammar_no_print_statement_no_exec_statement_async_keywords.copy()
)
python_grammar_soft_keywords.soft_keywords = soft_keywords
python_grammar_soft_keywords.version = (3, 10)

pattern_grammar = driver.load_packaged_grammar(
"blib2to3", _PATTERN_GRAMMAR_FILE, cache_dir
Expand Down
15 changes: 6 additions & 9 deletions tests/test_format.py
Expand Up @@ -191,6 +191,12 @@ def test_python_310(filename: str) -> None:
assert_format(source, expected, mode, minimum_version=(3, 10))


def test_python_310_without_target_version() -> None:
source, expected = read_data("pattern_matching_simple")
mode = black.Mode()
assert_format(source, expected, mode, minimum_version=(3, 10))


def test_patma_invalid() -> None:
source, expected = read_data("pattern_matching_invalid")
mode = black.Mode(target_versions={black.TargetVersion.PY310})
Expand All @@ -200,15 +206,6 @@ def test_patma_invalid() -> None:
exc_info.match("Cannot parse: 10:11")


def test_patma_hint() -> None:
source, expected = read_data("pattern_matching_simple")
mode = black.Mode(target_versions={black.TargetVersion.PY39})
with pytest.raises(black.parsing.InvalidInput) as exc_info:
assert_format(source, expected, mode, minimum_version=(3, 10))

exc_info.match(black.parsing.PY310_HINT)


def test_python_2_hint() -> None:
with pytest.raises(black.parsing.InvalidInput) as exc_info:
assert_format("print 'daylily'", "print 'daylily'")
Expand Down

0 comments on commit 022f896

Please sign in to comment.