mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
* Keep old exception messages (avoid breaking-changes for users relying on exception messages) * Move ``get_expected_str`` out of _exceptions.py, where it does not belong, to its own file in _parser/_parsing_check.py
346 lines
13 KiB
Python
346 lines
13 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
# pyre-unsafe
|
|
|
|
from typing import Any, List, Optional, Sequence, Union
|
|
|
|
from libcst import CSTLogicError
|
|
from libcst._exceptions import PartialParserSyntaxError
|
|
from libcst._maybe_sentinel import MaybeSentinel
|
|
from libcst._nodes.expression import (
|
|
Annotation,
|
|
Name,
|
|
Param,
|
|
Parameters,
|
|
ParamSlash,
|
|
ParamStar,
|
|
)
|
|
from libcst._nodes.op import AssignEqual, Comma
|
|
from libcst._parser.custom_itertools import grouper
|
|
from libcst._parser.production_decorator import with_production
|
|
from libcst._parser.types.config import ParserConfig
|
|
from libcst._parser.types.partials import ParamStarPartial
|
|
from libcst._parser.whitespace_parser import parse_parenthesizable_whitespace
|
|
|
|
|
|
@with_production( # noqa: C901: too complex
|
|
"typedargslist",
|
|
"""(
|
|
(tfpdef_assign (',' tfpdef_assign)* ',' tfpdef_posind [',' [ tfpdef_assign (
|
|
',' tfpdef_assign)* [',' [
|
|
tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
|
|
| tfpdef_starstar [',']]]
|
|
| tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
|
|
| tfpdef_starstar [',']]] )
|
|
| (tfpdef_assign (',' tfpdef_assign)* [',' [
|
|
tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
|
|
| tfpdef_starstar [',']]]
|
|
| tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]]
|
|
| tfpdef_starstar [','])
|
|
)""",
|
|
version=">=3.8",
|
|
)
|
|
@with_production( # noqa: C901: too complex
|
|
"typedargslist",
|
|
(
|
|
"(tfpdef_assign (',' tfpdef_assign)* "
|
|
+ "[',' [tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]] | tfpdef_starstar [',']]]"
|
|
+ "| tfpdef_star (',' tfpdef_assign)* [',' [tfpdef_starstar [',']]] | tfpdef_starstar [','])"
|
|
),
|
|
version=">=3.6,<=3.7",
|
|
)
|
|
@with_production( # noqa: C901: too complex
|
|
"typedargslist",
|
|
(
|
|
"(tfpdef_assign (',' tfpdef_assign)* "
|
|
+ "[',' [tfpdef_star (',' tfpdef_assign)* [',' tfpdef_starstar] | tfpdef_starstar]]"
|
|
+ "| tfpdef_star (',' tfpdef_assign)* [',' tfpdef_starstar] | tfpdef_starstar)"
|
|
),
|
|
version="<=3.5",
|
|
)
|
|
@with_production(
|
|
"varargslist",
|
|
"""vfpdef_assign (',' vfpdef_assign)* ',' vfpdef_posind [',' [ (vfpdef_assign (',' vfpdef_assign)* [',' [
|
|
vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
|
|
| vfpdef_starstar [',']]]
|
|
| vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
|
|
| vfpdef_starstar [',']) ]] | (vfpdef_assign (',' vfpdef_assign)* [',' [
|
|
vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
|
|
| vfpdef_starstar [',']]]
|
|
| vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]]
|
|
| vfpdef_starstar [',']
|
|
)""",
|
|
version=">=3.8",
|
|
)
|
|
@with_production(
|
|
"varargslist",
|
|
(
|
|
"(vfpdef_assign (',' vfpdef_assign)* "
|
|
+ "[',' [vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]] | vfpdef_starstar [',']]]"
|
|
+ "| vfpdef_star (',' vfpdef_assign)* [',' [vfpdef_starstar [',']]] | vfpdef_starstar [','])"
|
|
),
|
|
version=">=3.6,<=3.7",
|
|
)
|
|
@with_production(
|
|
"varargslist",
|
|
(
|
|
"(vfpdef_assign (',' vfpdef_assign)* "
|
|
+ "[',' [vfpdef_star (',' vfpdef_assign)* [',' vfpdef_starstar] | vfpdef_starstar]]"
|
|
+ "| vfpdef_star (',' vfpdef_assign)* [',' vfpdef_starstar] | vfpdef_starstar)"
|
|
),
|
|
version="<=3.5",
|
|
)
|
|
def convert_argslist( # noqa: C901
|
|
config: ParserConfig, children: Sequence[Any]
|
|
) -> Any:
|
|
posonly_params: List[Param] = []
|
|
posonly_ind: Union[ParamSlash, MaybeSentinel] = MaybeSentinel.DEFAULT
|
|
params: List[Param] = []
|
|
seen_default: bool = False
|
|
star_arg: Union[Param, ParamStar, MaybeSentinel] = MaybeSentinel.DEFAULT
|
|
kwonly_params: List[Param] = []
|
|
star_kwarg: Optional[Param] = None
|
|
|
|
def add_param(
|
|
current_param: Optional[List[Param]], param: Union[Param, ParamStar]
|
|
) -> Optional[List[Param]]:
|
|
nonlocal star_arg
|
|
nonlocal star_kwarg
|
|
nonlocal seen_default
|
|
nonlocal posonly_params
|
|
nonlocal posonly_ind
|
|
nonlocal params
|
|
|
|
if isinstance(param, ParamStar):
|
|
# Only can add this if we don't already have a "*" or a "*param".
|
|
if current_param is params:
|
|
star_arg = param
|
|
current_param = kwonly_params
|
|
else:
|
|
# Example code:
|
|
# def fn(*abc, *): ...
|
|
# This should be unreachable, the grammar already disallows it.
|
|
raise ValueError(
|
|
"Cannot have multiple star ('*') markers in a single argument "
|
|
+ "list."
|
|
)
|
|
elif isinstance(param, ParamSlash):
|
|
# Only can add this if we don't already have a "/" or a "*" or a "*param".
|
|
if current_param is params and len(posonly_params) == 0:
|
|
posonly_ind = param
|
|
posonly_params = params
|
|
params = []
|
|
current_param = params
|
|
else:
|
|
# Example code:
|
|
# def fn(foo, /, *, /, bar): ...
|
|
# This should be unreachable, the grammar already disallows it.
|
|
raise ValueError(
|
|
"Cannot have multiple slash ('/') markers in a single argument "
|
|
+ "list."
|
|
)
|
|
elif isinstance(param.star, str) and param.star == "" and param.default is None:
|
|
# Can only add this if we're in the params or kwonly_params section
|
|
if current_param is params and not seen_default:
|
|
params.append(param)
|
|
elif current_param is kwonly_params:
|
|
kwonly_params.append(param)
|
|
else:
|
|
# Example code:
|
|
# def fn(first=None, second): ...
|
|
# This code is reachable, so we should use a PartialParserSyntaxError.
|
|
raise PartialParserSyntaxError(
|
|
"Cannot have a non-default argument following a default argument."
|
|
)
|
|
elif (
|
|
isinstance(param.star, str)
|
|
and param.star == ""
|
|
and param.default is not None
|
|
):
|
|
# Can only add this if we're not yet at star args.
|
|
if current_param is params:
|
|
seen_default = True
|
|
params.append(param)
|
|
elif current_param is kwonly_params:
|
|
kwonly_params.append(param)
|
|
else:
|
|
# Example code:
|
|
# def fn(**kwargs, trailing=None)
|
|
# This should be unreachable, the grammar already disallows it.
|
|
raise ValueError("Cannot have any arguments after a kwargs expansion.")
|
|
elif (
|
|
isinstance(param.star, str) and param.star == "*" and param.default is None
|
|
):
|
|
# Can only add this if we're in params, since we only allow one of
|
|
# "*" or "*param".
|
|
if current_param is params:
|
|
star_arg = param
|
|
current_param = kwonly_params
|
|
else:
|
|
# Example code:
|
|
# def fn(*first, *second): ...
|
|
# This should be unreachable, the grammar already disallows it.
|
|
raise ValueError(
|
|
"Expected a keyword argument but found a starred positional "
|
|
+ "argument expansion."
|
|
)
|
|
elif (
|
|
isinstance(param.star, str) and param.star == "**" and param.default is None
|
|
):
|
|
# Can add this in all cases where we don't have a star_kwarg
|
|
# yet.
|
|
if current_param is not None:
|
|
star_kwarg = param
|
|
current_param = None
|
|
else:
|
|
# Example code:
|
|
# def fn(**first, **second)
|
|
# This should be unreachable, the grammar already disallows it.
|
|
raise ValueError(
|
|
"Multiple starred keyword argument expansions are not allowed in a "
|
|
+ "single argument list"
|
|
)
|
|
else:
|
|
# The state machine should never end up here.
|
|
raise CSTLogicError("Logic error!")
|
|
|
|
return current_param
|
|
|
|
# The parameter list we are adding to
|
|
current: Optional[List[Param]] = params
|
|
|
|
# We should have every other item in the group as a param or a comma by now,
|
|
# so split them up, add commas and then put them in the appropriate group.
|
|
for parameter, comma in grouper(children, 2):
|
|
if comma is None:
|
|
if isinstance(parameter, ParamStarPartial):
|
|
# Example:
|
|
# def fn(abc, *): ...
|
|
#
|
|
# There's also the case where we have bare * with a trailing comma.
|
|
# That's handled later.
|
|
#
|
|
# It's not valid to construct a ParamStar object without a comma, so we
|
|
# need to catch the non-comma case separately.
|
|
raise PartialParserSyntaxError(
|
|
"Named (keyword) arguments must follow a bare *."
|
|
)
|
|
else:
|
|
current = add_param(current, parameter)
|
|
else:
|
|
comma = Comma(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, comma.whitespace_before
|
|
),
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, comma.whitespace_after
|
|
),
|
|
)
|
|
if isinstance(parameter, ParamStarPartial):
|
|
current = add_param(current, ParamStar(comma=comma))
|
|
else:
|
|
current = add_param(current, parameter.with_changes(comma=comma))
|
|
|
|
if isinstance(star_arg, ParamStar) and len(kwonly_params) == 0:
|
|
# Example:
|
|
# def fn(abc, *,): ...
|
|
#
|
|
# This will raise a validation error, but we want to make sure to raise a syntax
|
|
# error instead.
|
|
#
|
|
# The case where there's no trailing comma is already handled by this point, so
|
|
# this conditional is only for the case where we have a trailing comma.
|
|
raise PartialParserSyntaxError(
|
|
"Named (keyword) arguments must follow a bare *."
|
|
)
|
|
|
|
return Parameters(
|
|
posonly_params=tuple(posonly_params),
|
|
posonly_ind=posonly_ind,
|
|
params=tuple(params),
|
|
star_arg=star_arg,
|
|
kwonly_params=tuple(kwonly_params),
|
|
star_kwarg=star_kwarg,
|
|
)
|
|
|
|
|
|
@with_production("tfpdef_star", "'*' [tfpdef]")
|
|
@with_production("vfpdef_star", "'*' [vfpdef]")
|
|
def convert_fpdef_star(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(star,) = children
|
|
return ParamStarPartial()
|
|
else:
|
|
star, param = children
|
|
return param.with_changes(
|
|
star=star.string,
|
|
whitespace_after_star=parse_parenthesizable_whitespace(
|
|
config, star.whitespace_after
|
|
),
|
|
)
|
|
|
|
|
|
@with_production("tfpdef_starstar", "'**' tfpdef")
|
|
@with_production("vfpdef_starstar", "'**' vfpdef")
|
|
def convert_fpdef_starstar(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
starstar, param = children
|
|
return param.with_changes(
|
|
star=starstar.string,
|
|
whitespace_after_star=parse_parenthesizable_whitespace(
|
|
config, starstar.whitespace_after
|
|
),
|
|
)
|
|
|
|
|
|
@with_production("tfpdef_assign", "tfpdef ['=' test]")
|
|
@with_production("vfpdef_assign", "vfpdef ['=' test]")
|
|
def convert_fpdef_assign(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(child,) = children
|
|
return child
|
|
|
|
param, equal, default = children
|
|
return param.with_changes(
|
|
equal=AssignEqual(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, equal.whitespace_before
|
|
),
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, equal.whitespace_after
|
|
),
|
|
),
|
|
default=default.value,
|
|
)
|
|
|
|
|
|
@with_production("tfpdef", "NAME [':' test]")
|
|
@with_production("vfpdef", "NAME")
|
|
def convert_fpdef(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
# This is just a parameter
|
|
(child,) = children
|
|
namenode = Name(child.string)
|
|
annotation = None
|
|
else:
|
|
# This is a parameter with a type hint
|
|
name, colon, typehint = children
|
|
namenode = Name(name.string)
|
|
annotation = Annotation(
|
|
whitespace_before_indicator=parse_parenthesizable_whitespace(
|
|
config, colon.whitespace_before
|
|
),
|
|
whitespace_after_indicator=parse_parenthesizable_whitespace(
|
|
config, colon.whitespace_after
|
|
),
|
|
annotation=typehint.value,
|
|
)
|
|
|
|
return Param(star="", name=namenode, annotation=annotation, default=None)
|
|
|
|
|
|
@with_production("tfpdef_posind", "'/'")
|
|
@with_production("vfpdef_posind", "'/'")
|
|
def convert_fpdef_slash(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
return ParamSlash()
|