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

Enable pattern matching by default #2758

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -26,6 +26,8 @@
be formatted. (#2526)
- Speed-up the new backtracking parser about 4X in general (enabled when
`--target-version` is set to 3.10 and higher). (#2728)
- Enable Python 3.10+ by default, without any extra need to specify
`--target-version=py310`. (#2758)

### Packaging

Expand Down
27 changes: 16 additions & 11 deletions src/black/parsing.py
Expand Up @@ -58,12 +58,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 +74,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 +89,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 +103,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}"
ichard26 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -190,6 +190,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:
isidentical marked this conversation as resolved.
Show resolved Hide resolved
source, expected = read_data("pattern_matching_invalid")
mode = black.Mode(target_versions={black.TargetVersion.PY310})
Expand All @@ -199,15 +205,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