summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2019-11-26 09:17:10 +0100
committerClaudiu Popa <pcmanticore@gmail.com>2019-11-26 09:17:10 +0100
commit2cb6818ad5db25fd0f32a193ab90425e7dfcbee0 (patch)
tree3d8666f65a776dbdd81c61b20b1c6404d09d0582
parent5cdefb0d278dfe80a662156a942472794df5ca67 (diff)
downloadastroid-git-2cb6818ad5db25fd0f32a193ab90425e7dfcbee0.tar.gz
Retry parsing a module that has invalid type comments
It is possible for a module to use comments that might be interpreted as type comments by the `ast` library. We do not want to completely crash on those invalid type comments. Close #708
-rw-r--r--ChangeLog8
-rw-r--r--astroid/_ast.py15
-rw-r--r--astroid/builder.py18
-rw-r--r--tests/unittest_builder.py25
4 files changed, 60 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index 99457ef8..a38dab51 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,14 @@ Release Date: TBA
* Allow inferring positional only arguments.
+* Retry parsing a module that has invalid type comments
+
+ It is possible for a module to use comments that might be interpreted
+ as type comments by the `ast` library. We do not want to completely crash on those
+ invalid type comments.
+
+ Close #708
+
* Scope the inference to the current bound node when inferring instances of classes
When inferring instances of classes from arguments, such as ``self``
diff --git a/astroid/_ast.py b/astroid/_ast.py
index 2e44c1f1..66c5cf25 100644
--- a/astroid/_ast.py
+++ b/astroid/_ast.py
@@ -21,7 +21,10 @@ if PY38:
FunctionType = namedtuple("FunctionType", ["argtypes", "returns"])
-def _get_parser_module(parse_python_two: bool = False):
+def _get_parser_module(parse_python_two=False, type_comments_support=True):
+ if not type_comments_support:
+ return ast
+
if parse_python_two:
parser_module = _ast_py2
else:
@@ -29,12 +32,14 @@ def _get_parser_module(parse_python_two: bool = False):
return parser_module or ast
-def _parse(string: str, parse_python_two: bool = False):
- parse_module = _get_parser_module(parse_python_two=parse_python_two)
+def _parse(string: str, parse_python_two=False, type_comments=True):
+ parse_module = _get_parser_module(
+ parse_python_two=parse_python_two, type_comments_support=type_comments
+ )
parse_func = parse_module.parse
- if _ast_py3:
+ if parse_module is _ast_py3:
if PY38:
- parse_func = partial(parse_func, type_comments=True)
+ parse_func = partial(parse_func, type_comments=type_comments)
if not parse_python_two:
parse_func = partial(parse_func, feature_version=sys.version_info.minor)
return parse_func(string)
diff --git a/astroid/builder.py b/astroid/builder.py
index 34bde0a1..7f30aaeb 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -165,7 +165,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
def _data_build(self, data, modname, path):
"""Build tree node from data and add some informations"""
try:
- node = _parse(data + "\n")
+ node = _parse_string(data)
except (TypeError, ValueError, SyntaxError) as exc:
raise exceptions.AstroidSyntaxError(
"Parsing Python code failed:\n{error}",
@@ -436,3 +436,19 @@ def extract_node(code, module_name=""):
if len(extracted) == 1:
return extracted[0]
return extracted
+
+
+MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation"
+
+
+def _parse_string(data, type_comments=True):
+ try:
+ node = _parse(data + "\n", type_comments=type_comments)
+ except SyntaxError as exc:
+ # If the type annotations are misplaced for some reason, we do not want
+ # to fail the entire parsing of the file, so we need to retry the parsing without
+ # type comment support.
+ if exc.args[0] != MISPLACED_TYPE_ANNOTATION_ERROR or not type_comments:
+ raise
+ node = _parse(data + "\n", type_comments=False)
+ return node
diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py
index 0f1a470f..75557761 100644
--- a/tests/unittest_builder.py
+++ b/tests/unittest_builder.py
@@ -692,5 +692,30 @@ def test_module_build_dunder_file():
assert module.path[0] == collections.__file__
+def test_parse_module_with_invalid_type_comments_does_not_crash():
+ node = builder.parse(
+ """
+ # op {
+ # name: "AssignAddVariableOp"
+ # input_arg {
+ # name: "resource"
+ # type: DT_RESOURCE
+ # }
+ # input_arg {
+ # name: "value"
+ # type_attr: "dtype"
+ # }
+ # attr {
+ # name: "dtype"
+ # type: "type"
+ # }
+ # is_stateful: true
+ # }
+ a, b = 2
+ """
+ )
+ assert isinstance(node, nodes.Module)
+
+
if __name__ == "__main__":
unittest.main()