From ffb73c65cba805eaee6b879d423ded136928ffc2 Mon Sep 17 00:00:00 2001 From: Zen Lee Date: Fri, 19 Sep 2025 19:59:57 +0800 Subject: [PATCH 1/4] Implement boolean constraint --- astroid/constraint.py | 48 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/astroid/constraint.py b/astroid/constraint.py index 458b55bac..75a5e6aca 100644 --- a/astroid/constraint.py +++ b/astroid/constraint.py @@ -84,6 +84,47 @@ def satisfied_by(self, inferred: InferenceResult) -> bool: return self.negate ^ _matches(inferred, self.CONST_NONE) +class BooleanConstraint(Constraint): + """Represents an "x" or "not x" constraint.""" + + @classmethod + def match( + cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False + ) -> Self | None: + """Return a new constraint for node if expr matches one of these patterns: + + - direct match (expr == node): use given negate value + - negated match (expr == `not node`): flip negate value + + Return None if no pattern matches. + """ + if _matches(expr, node): + return cls(node=node, negate=negate) + + if ( + isinstance(expr, nodes.UnaryOp) + and expr.op == "not" + and _matches(expr.operand, node) + ): + return cls(node=node, negate=not negate) + + return None + + def satisfied_by(self, inferred: InferenceResult) -> bool: + """Return True for uninferable results, or depending on negate flag: + + - negate=False: satisfied if boolean value is True + - negate=True: satisfied if boolean value is False + """ + inferred_booleaness = inferred.bool_value() + if isinstance(inferred, util.UninferableBase) or isinstance( + inferred_booleaness, util.UninferableBase + ): + return True + + return self.negate ^ inferred_booleaness + + def get_constraints( expr: _NameNodes, frame: nodes.LocalsDictNodeNG ) -> dict[nodes.If, set[Constraint]]: @@ -114,7 +155,12 @@ def get_constraints( return constraints_mapping -ALL_CONSTRAINT_CLASSES = frozenset((NoneConstraint,)) +ALL_CONSTRAINT_CLASSES = frozenset( + ( + NoneConstraint, + BooleanConstraint, + ) +) """All supported constraint types.""" From 6ef4431969d22836c0142c102279d3100075eef0 Mon Sep 17 00:00:00 2001 From: Zen Lee Date: Fri, 19 Sep 2025 20:00:05 +0800 Subject: [PATCH 2/4] Update tests --- tests/test_constraint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_constraint.py b/tests/test_constraint.py index 63f62754b..84ef498d0 100644 --- a/tests/test_constraint.py +++ b/tests/test_constraint.py @@ -17,6 +17,8 @@ def common_params(node: str) -> pytest.MarkDecorator: ( (f"{node} is None", None, 3), (f"{node} is not None", 3, None), + (f"{node}", 3, None), + (f"not {node}", None, 3), ), ) From 9f14252a01440c1d872454b69d060c62cf7d036a Mon Sep 17 00:00:00 2001 From: Zen Lee Date: Mon, 22 Sep 2025 21:14:18 +0800 Subject: [PATCH 3/4] Update broken test --- tests/test_inference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index b8714224e..235563d0a 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -5491,7 +5491,7 @@ def add(x, y): else: kwargs = {} - if nums: + if nums is not None: add(*nums) print(**kwargs) """ From 04eb2cb70e668ee7473e8805847abdfbd3ba4cc1 Mon Sep 17 00:00:00 2001 From: Zen Lee Date: Mon, 22 Sep 2025 21:14:34 +0800 Subject: [PATCH 4/4] Add changelog entry --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index f207eb727..118601d76 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,10 @@ What's New in astroid 4.0.0? ============================ Release date: TBA +* Add support for boolean truthiness constraints (`x`, `not x`) in inference. + + Closes pylint-dev/pylint#9515 + * Fix false positive `invalid-name` on `attrs` classes with `ClassVar` annotated variables. Closes pylint-dev/pylint#10525