Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/constraint.py: 51%

67 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:53 +0000

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5"""Classes representing different types of constraints on inference values.""" 

6from __future__ import annotations 

7 

8import sys 

9from abc import ABC, abstractmethod 

10from collections.abc import Iterator 

11from typing import Union 

12 

13from astroid import bases, nodes, util 

14from astroid.typing import InferenceResult 

15 

16if sys.version_info >= (3, 11): 

17 from typing import Self 

18else: 

19 from typing_extensions import Self 

20 

21_NameNodes = Union[nodes.AssignAttr, nodes.Attribute, nodes.AssignName, nodes.Name] 

22 

23 

24class Constraint(ABC): 

25 """Represents a single constraint on a variable.""" 

26 

27 def __init__(self, node: nodes.NodeNG, negate: bool) -> None: 

28 self.node = node 

29 """The node that this constraint applies to.""" 

30 self.negate = negate 

31 """True if this constraint is negated. E.g., "is not" instead of "is".""" 

32 

33 @classmethod 

34 @abstractmethod 

35 def match( 

36 cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False 

37 ) -> Self | None: 

38 """Return a new constraint for node matched from expr, if expr matches 

39 the constraint pattern. 

40 

41 If negate is True, negate the constraint. 

42 """ 

43 

44 @abstractmethod 

45 def satisfied_by(self, inferred: InferenceResult) -> bool: 

46 """Return True if this constraint is satisfied by the given inferred value.""" 

47 

48 

49class NoneConstraint(Constraint): 

50 """Represents an "is None" or "is not None" constraint.""" 

51 

52 CONST_NONE: nodes.Const = nodes.Const(None) 

53 

54 @classmethod 

55 def match( 

56 cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False 

57 ) -> Self | None: 

58 """Return a new constraint for node matched from expr, if expr matches 

59 the constraint pattern. 

60 

61 Negate the constraint based on the value of negate. 

62 """ 

63 if isinstance(expr, nodes.Compare) and len(expr.ops) == 1: 

64 left = expr.left 

65 op, right = expr.ops[0] 

66 if op in {"is", "is not"} and ( 

67 _matches(left, node) and _matches(right, cls.CONST_NONE) 

68 ): 

69 negate = (op == "is" and negate) or (op == "is not" and not negate) 

70 return cls(node=node, negate=negate) 

71 

72 return None 

73 

74 def satisfied_by(self, inferred: InferenceResult) -> bool: 

75 """Return True if this constraint is satisfied by the given inferred value.""" 

76 # Assume true if uninferable 

77 if isinstance(inferred, util.UninferableBase): 

78 return True 

79 

80 # Return the XOR of self.negate and matches(inferred, self.CONST_NONE) 

81 return self.negate ^ _matches(inferred, self.CONST_NONE) 

82 

83 

84def get_constraints( 

85 expr: _NameNodes, frame: nodes.LocalsDictNodeNG 

86) -> dict[nodes.If, set[Constraint]]: 

87 """Returns the constraints for the given expression. 

88 

89 The returned dictionary maps the node where the constraint was generated to the 

90 corresponding constraint(s). 

91 

92 Constraints are computed statically by analysing the code surrounding expr. 

93 Currently this only supports constraints generated from if conditions. 

94 """ 

95 current_node: nodes.NodeNG | None = expr 

96 constraints_mapping: dict[nodes.If, set[Constraint]] = {} 

97 while current_node is not None and current_node is not frame: 

98 parent = current_node.parent 

99 if isinstance(parent, nodes.If): 

100 branch, _ = parent.locate_child(current_node) 

101 constraints: set[Constraint] | None = None 

102 if branch == "body": 

103 constraints = set(_match_constraint(expr, parent.test)) 

104 elif branch == "orelse": 

105 constraints = set(_match_constraint(expr, parent.test, invert=True)) 

106 

107 if constraints: 

108 constraints_mapping[parent] = constraints 

109 current_node = parent 

110 

111 return constraints_mapping 

112 

113 

114ALL_CONSTRAINT_CLASSES = frozenset((NoneConstraint,)) 

115"""All supported constraint types.""" 

116 

117 

118def _matches(node1: nodes.NodeNG | bases.Proxy, node2: nodes.NodeNG) -> bool: 

119 """Returns True if the two nodes match.""" 

120 if isinstance(node1, nodes.Name) and isinstance(node2, nodes.Name): 

121 return node1.name == node2.name 

122 if isinstance(node1, nodes.Attribute) and isinstance(node2, nodes.Attribute): 

123 return node1.attrname == node2.attrname and _matches(node1.expr, node2.expr) 

124 if isinstance(node1, nodes.Const) and isinstance(node2, nodes.Const): 

125 return node1.value == node2.value 

126 

127 return False 

128 

129 

130def _match_constraint( 

131 node: _NameNodes, expr: nodes.NodeNG, invert: bool = False 

132) -> Iterator[Constraint]: 

133 """Yields all constraint patterns for node that match.""" 

134 for constraint_cls in ALL_CONSTRAINT_CLASSES: 

135 constraint = constraint_cls.match(node, expr, invert) 

136 if constraint: 

137 yield constraint