summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2019-06-02 10:04:33 +0200
committerGitHub <noreply@github.com>2019-06-02 10:04:33 +0200
commitf818d1a2b95cf6d0f66800ccc2c119dd44700fa1 (patch)
treecd6867331706d1c24ac80d35fdfb17d9df21e887
parent40a7ee9b3882f25d8f0098dbb430ee8f97ea36b0 (diff)
parent0441055d1b917781ba838b4796fe82d07ec478e3 (diff)
downloadastroid-git-f818d1a2b95cf6d0f66800ccc2c119dd44700fa1.tar.gz
Merge branch 'master' into bug_pylint_2721
-rw-r--r--ChangeLog8
-rw-r--r--astroid/__pkginfo__.py2
-rw-r--r--astroid/as_string.py6
-rw-r--r--astroid/inference.py43
-rw-r--r--astroid/node_classes.py36
-rw-r--r--astroid/nodes.py2
-rw-r--r--astroid/protocols.py17
-rw-r--r--astroid/raw_building.py14
-rw-r--r--astroid/rebuilder.py7
-rw-r--r--astroid/tests/unittest_inference.py29
-rw-r--r--astroid/tests/unittest_nodes.py25
-rw-r--r--astroid/tests/unittest_protocols.py54
12 files changed, 232 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 7f58ae3f..64a300d0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -8,7 +8,7 @@ Release Date: TBA
* Numpy brain support is improved.
- Numpy's fondamental type ``numpy.ndarray`` has its own brain : ``brain_numpy_ndarray`` and
+ Numpy's fundamental type ``numpy.ndarray`` has its own brain : ``brain_numpy_ndarray`` and
each numpy module that necessitates brain action has now its own numpy brain :
- ``numpy.core.numeric``
@@ -25,6 +25,12 @@ Release Date: TBA
Close PyCQA/pylint#2326
Close PyCQA/pylint#2021
+* Add support for Python 3.8's `NamedExpr` nodes, which is part of assignment expressions.
+
+ Close #674
+
+* Added support for inferring `IfExp` nodes.
+
* Instances of exceptions are inferred as such when inferring in non-exception context
This allows special inference support for exception attributes such as `.args`.
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()