diff options
| author | Claudiu Popa <pcmanticore@gmail.com> | 2019-06-02 10:04:33 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-02 10:04:33 +0200 |
| commit | f818d1a2b95cf6d0f66800ccc2c119dd44700fa1 (patch) | |
| tree | cd6867331706d1c24ac80d35fdfb17d9df21e887 /astroid | |
| parent | 40a7ee9b3882f25d8f0098dbb430ee8f97ea36b0 (diff) | |
| parent | 0441055d1b917781ba838b4796fe82d07ec478e3 (diff) | |
| download | astroid-git-f818d1a2b95cf6d0f66800ccc2c119dd44700fa1.tar.gz | |
Merge branch 'master' into bug_pylint_2721
Diffstat (limited to 'astroid')
| -rw-r--r-- | astroid/__pkginfo__.py | 2 | ||||
| -rw-r--r-- | astroid/as_string.py | 6 | ||||
| -rw-r--r-- | astroid/inference.py | 43 | ||||
| -rw-r--r-- | astroid/node_classes.py | 36 | ||||
| -rw-r--r-- | astroid/nodes.py | 2 | ||||
| -rw-r--r-- | astroid/protocols.py | 17 | ||||
| -rw-r--r-- | astroid/raw_building.py | 14 | ||||
| -rw-r--r-- | astroid/rebuilder.py | 7 | ||||
| -rw-r--r-- | astroid/tests/unittest_inference.py | 29 | ||||
| -rw-r--r-- | astroid/tests/unittest_nodes.py | 25 | ||||
| -rw-r--r-- | astroid/tests/unittest_protocols.py | 54 |
11 files changed, 225 insertions, 10 deletions
diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py index 55e89629..d3cb4705 100644 --- a/astroid/__pkginfo__.py +++ b/astroid/__pkginfo__.py @@ -26,7 +26,7 @@ install_requires = [ "six", "wrapt", 'typing;python_version<"3.5"', - 'typed-ast>=1.3.0;implementation_name== "cpython"', + 'typed-ast>=1.3.0;implementation_name== "cpython" and python_version<"3.8"', ] # pylint: disable=redefined-builtin; why license is a builtin anyway? diff --git a/astroid/as_string.py b/astroid/as_string.py index 7042272f..9d92b3cf 100644 --- a/astroid/as_string.py +++ b/astroid/as_string.py @@ -611,6 +611,12 @@ class AsStringVisitor3(AsStringVisitor): super(AsStringVisitor3, self).visit_comprehension(node), ) + def visit_namedexpr(self, node): + """Return an assignment expression node as string""" + target = node.target.accept(self) + value = node.value.accept(self) + return "%s := %s" % (target, value) + def _import_string(names): """return a list of (name, asname) formatted as a string""" diff --git a/astroid/inference.py b/astroid/inference.py index 16346a77..1ea45248 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -71,6 +71,11 @@ def _infer_sequence_helper(node, context=None): if not hasattr(starred, "elts"): raise exceptions.InferenceError(node=node, context=context) values.extend(_infer_sequence_helper(starred)) + elif isinstance(elt, nodes.NamedExpr): + value = helpers.safe_infer(elt.value, context) + if not value: + raise exceptions.InferenceError(node=node, context=context) + values.append(value) else: values.append(elt) return values @@ -78,9 +83,10 @@ def _infer_sequence_helper(node, context=None): @decorators.raise_if_nothing_inferred def infer_sequence(self, context=None): - if not any(isinstance(e, nodes.Starred) for e in self.elts): - yield self - else: + has_starred_named_expr = any( + isinstance(e, (nodes.Starred, nodes.NamedExpr)) for e in self.elts + ) + if has_starred_named_expr: values = _infer_sequence_helper(self, context) new_seq = type(self)( lineno=self.lineno, col_offset=self.col_offset, parent=self.parent @@ -88,6 +94,8 @@ def infer_sequence(self, context=None): new_seq.postinit(values) yield new_seq + else: + yield self nodes.List._infer = infer_sequence @@ -897,3 +905,32 @@ def _populate_context_lookup(call, context): for keyword in keywords: context_lookup[keyword.value] = context return context_lookup + + +@decorators.raise_if_nothing_inferred +def infer_ifexp(self, context=None): + """Support IfExp inference + + If we can't infer the truthiness of the condition, we default + to inferring both branches. Otherwise, we infer either branch + depending on the condition. + """ + both_branches = False + try: + test = next(self.test.infer(context=context)) + except exceptions.InferenceError: + both_branches = True + else: + if test is not util.Uninferable: + if test.bool_value(): + yield from self.body.infer(context=context) + else: + yield from self.orelse.infer(context=context) + else: + both_branches = True + if both_branches: + yield from self.body.infer(context=context) + yield from self.orelse.infer(context=context) + + +nodes.IfExp._infer = infer_ifexp diff --git a/astroid/node_classes.py b/astroid/node_classes.py index 2e39b060..44fe3df4 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -31,8 +31,8 @@ import abc import builtins as builtins_mod import itertools import pprint -from functools import lru_cache -from functools import singledispatch as _singledispatch +import sys +from functools import lru_cache, singledispatch as _singledispatch from astroid import as_string from astroid import bases @@ -46,6 +46,7 @@ from astroid import util BUILTINS = builtins_mod.__name__ MANAGER = manager.AstroidManager() +PY38 = sys.version_info[:2] >= (3, 8) def _is_const(value): @@ -1200,6 +1201,10 @@ class LookupMixIn: _stmt_parents = [stmt.parent] continue + if isinstance(assign_type, NamedExpr): + _stmts = [node] + continue + # XXX comment various branches below!!! try: pindex = _stmt_parents.index(stmt.parent) @@ -4622,6 +4627,31 @@ class JoinedStr(NodeNG): yield from self.values +class NamedExpr(mixins.AssignTypeMixin, NodeNG): + """Represents the assignment from the assignment expression + + >>> module = astroid.parse('if a := 1: pass') + >>> module.body[0].test + <NamedExpr l.1 at 0x7f23b2e4ed30> + """ + + _astroid_fields = ("target", "value") + target = None + """The assignment target + + :type: Name + """ + value = None + """The value that gets assigned in the expression + + :type: NodeNG + """ + + def postinit(self, target, value): + self.target = target + self.value = value + + class Unknown(mixins.AssignTypeMixin, NodeNG): """This node represents a node in a constructed AST where introspection is not possible. At the moment, it's only used in @@ -4649,6 +4679,8 @@ CONST_CLS = { type(None): Const, type(NotImplemented): Const, } +if PY38: + CONST_CLS[type(...)] = Const def _update_const_classes(): diff --git a/astroid/nodes.py b/astroid/nodes.py index 20672593..bf6911ac 100644 --- a/astroid/nodes.py +++ b/astroid/nodes.py @@ -59,6 +59,7 @@ from astroid.node_classes import ( Keyword, List, Name, + NamedExpr, Nonlocal, Pass, Print, @@ -149,6 +150,7 @@ ALL_NODE_CLASSES = ( List, ListComp, Name, + NamedExpr, Nonlocal, Module, Pass, diff --git a/astroid/protocols.py b/astroid/protocols.py index bf497ff1..b598ec3d 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -572,6 +572,23 @@ def with_assigned_stmts(self, node=None, context=None, assign_path=None): nodes.With.assigned_stmts = with_assigned_stmts +@decorators.raise_if_nothing_inferred +def named_expr_assigned_stmts(self, node, context=None, assign_path=None): + """Infer names and other nodes from an assignment expression""" + if self.target == node: + yield from self.value.infer(context=context) + else: + raise exceptions.InferenceError( + "Cannot infer NamedExpr node {node!r}", + node=self, + assign_path=assign_path, + context=context, + ) + + +nodes.NamedExpr.assigned_stmts = named_expr_assigned_stmts + + @decorators.yes_if_nothing_inferred def starred_assigned_stmts(self, node=None, context=None, assign_path=None): """ diff --git a/astroid/raw_building.py b/astroid/raw_building.py index 60000fdb..d9a1dc47 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -34,6 +34,9 @@ MANAGER = manager.AstroidManager() _CONSTANTS = tuple(node_classes.CONST_CLS) _BUILTINS = vars(builtins) +TYPE_NONE = type(None) +TYPE_NOTIMPLEMENTED = type(NotImplemented) +TYPE_ELLIPSIS = type(...) def _io_discrepancy(member): @@ -390,12 +393,15 @@ def _astroid_bootstrapping(): # pylint: disable=redefined-outer-name for cls, node_cls in node_classes.CONST_CLS.items(): - if cls is type(None): + if cls is TYPE_NONE: proxy = build_class("NoneType") proxy.parent = astroid_builtin - elif cls is type(NotImplemented): + elif cls is TYPE_NOTIMPLEMENTED: proxy = build_class("NotImplementedType") proxy.parent = astroid_builtin + elif cls is TYPE_ELLIPSIS: + proxy = build_class("Ellipsis") + proxy.parent = astroid_builtin else: proxy = astroid_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): @@ -425,8 +431,8 @@ def _astroid_bootstrapping(): types.GetSetDescriptorType, types.GeneratorType, types.MemberDescriptorType, - type(None), - type(NotImplemented), + TYPE_NONE, + TYPE_NOTIMPLEMENTED, types.FunctionType, types.MethodType, types.BuiltinFunctionType, diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index c18893d8..a60785c4 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1063,6 +1063,13 @@ class TreeRebuilder3(TreeRebuilder): ) return newnode + def visit_namedexpr(self, node, parent): + newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent) + newnode.postinit( + self.visit(node.target, newnode), self.visit(node.value, newnode) + ) + return newnode + if sys.version_info >= (3, 0): TreeRebuilder = TreeRebuilder3 diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index eff9b148..1f9c7e44 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -5260,5 +5260,34 @@ def test_subclass_of_exception(): assert isinstance(args, nodes.Tuple) +def test_ifexp_inference(): + code = """ + def truth_branch(): + return 1 if True else 2 + + def false_branch(): + return 1 if False else 2 + + def both_branches(): + return 1 if unknown() else 2 + + truth_branch() #@ + false_branch() #@ + both_branches() #@ + """ + ast_nodes = extract_node(code) + first = next(ast_nodes[0].infer()) + assert isinstance(first, nodes.Const) + assert first.value == 1 + + second = next(ast_nodes[1].infer()) + assert isinstance(second, nodes.Const) + assert second.value == 2 + + third = list(ast_nodes[2].infer()) + assert isinstance(third, list) + assert [third[0].value, third[1].value] == [1, 2] + + if __name__ == "__main__": unittest.main() diff --git a/astroid/tests/unittest_nodes.py b/astroid/tests/unittest_nodes.py index 800eef3a..96a7f830 100644 --- a/astroid/tests/unittest_nodes.py +++ b/astroid/tests/unittest_nodes.py @@ -1134,5 +1134,30 @@ def test_f_string_correct_line_numbering(): assert node.last_child().last_child().lineno == 5 +@pytest.mark.skipif( + sys.version_info[:2] < (3, 8), reason="needs assignment expressions" +) +def test_assignment_expression(): + code = """ + if __(a := 1): + pass + if __(b := test): + pass + """ + first, second = astroid.extract_node(code) + + assert isinstance(first.target, nodes.AssignName) + assert first.target.name == "a" + assert isinstance(first.value, nodes.Const) + assert first.value.value == 1 + assert first.as_string() == "a := 1" + + assert isinstance(second.target, nodes.AssignName) + assert second.target.name == "b" + assert isinstance(second.value, nodes.Name) + assert second.value.name == "test" + assert second.as_string() == "b := test" + + if __name__ == "__main__": unittest.main() diff --git a/astroid/tests/unittest_protocols.py b/astroid/tests/unittest_protocols.py index 79648dcf..e05c43cc 100644 --- a/astroid/tests/unittest_protocols.py +++ b/astroid/tests/unittest_protocols.py @@ -213,5 +213,59 @@ class ProtocolTests(unittest.TestCase): parsed.accept(Visitor()) +def test_named_expr_inference(): + code = """ + if (a := 2) == 2: + a #@ + + + # Test a function call + def test(): + return 24 + + if (a := test()): + a #@ + + # Normal assignments in sequences + { (a:= 4) } #@ + [ (a:= 5) ] #@ + + # Something more complicated + def test(value=(p := 24)): return p + [ y:= test()] #@ + + # Priority assignment + (x := 1, 2) + x #@ + """ + ast_nodes = extract_node(code) + node = next(ast_nodes[0].infer()) + assert isinstance(node, nodes.Const) + assert node.value == 2 + + node = next(ast_nodes[1].infer()) + assert isinstance(node, nodes.Const) + assert node.value == 24 + + node = next(ast_nodes[2].infer()) + assert isinstance(node, nodes.Set) + assert isinstance(node.elts[0], nodes.Const) + assert node.elts[0].value == 4 + + node = next(ast_nodes[3].infer()) + assert isinstance(node, nodes.List) + assert isinstance(node.elts[0], nodes.Const) + assert node.elts[0].value == 5 + + node = next(ast_nodes[4].infer()) + assert isinstance(node, nodes.List) + assert isinstance(node.elts[0], nodes.Const) + assert node.elts[0].value == 24 + + node = next(ast_nodes[4].infer()) + assert isinstance(node, nodes.Const) + assert node.value == 1 + + if __name__ == "__main__": unittest.main() |
