From 03acd8e10674b8e41b302612ba7f1b9444ea56ea Mon Sep 17 00:00:00 2001 From: Yulei Sui <7608399+yuleisui@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:51:09 +1000 Subject: [PATCH 1/7] Fix IntervalValue equality/bitshift stubs, expose CopyKind, preserve AbstractState subclass identity Fixes several mismatches between the Python bindings/stubs and the actual C++ implementation/behavior: - IntervalValue.__eq__/__ne__ were wired to equals() (bool), while the stub documented (and C++'s operator==/!= actually implement) an abstract-domain IntervalValue result ([1,1]/[0,0]/[0,1]). Rewire the bindings to call operator==/operator!=, and add __bool__ (true only for a definite [1,1]) so common `if a == b:` patterns keep behaving correctly. Also fix equals()'s stub, which was mistakenly typed as returning IntervalValue instead of bool. - Add a CopyKind IntEnum (pysvf/enums.py) mirroring CopyStmt::CopyKind, following the same convention already used for Predicate/OpCode, so zext/trunc/etc. copy kinds don't need to be guessed as magic numbers. - AbstractState.clone()/widening()/narrowing() always constructed a plain AbstractState in C++, silently discarding any Python subclass (e.g. a `class AEState(AbstractState): ...` pattern). Reconstruct an instance of the caller's actual runtime type instead. - isCmpBranchFeasible/isSwitchBranchFeasible are pysvf-only static helpers (the real AbstractInterpretation methods are private in SVF); document this in their stubs, mark them @staticmethod, and fix their argument lists to match the real 4-arg (svfir, stmt, succ, abstract_state) binding. - Fix IntervalValue.__lshift__/__rshift__ stubs: the binding accepts another IntervalValue as the RHS (py::self << py::self), not int. Validated by rebuilding the extension and running it against the Assignment-3 course material (Software-Security-Analysis[-Sol]): the full 120-case regression corpus passes identically before/after these changes (120/120, 69/69 assertions verified, 0 regressions), and the new equality/CopyKind/branch-feasibility behavior was independently exercised against real compiled IR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pybind/AE.cpp | 45 ++++++++++++++++++++++++++++++++++++++------- pysvf/__init__.py | 2 +- pysvf/enums.py | 19 ++++++++++++++++++- pysvf/pysvf.pyi | 27 ++++++++++++++++++++++----- 4 files changed, 79 insertions(+), 14 deletions(-) diff --git a/pybind/AE.cpp b/pybind/AE.cpp index d79d181..34ebc02 100644 --- a/pybind/AE.cpp +++ b/pybind/AE.cpp @@ -115,11 +115,23 @@ void bind_abstract_state(py::module& m) { }), py::arg("lb"), py::arg("ub")) // Equality + // C++'s operator== / operator!= return an IntervalValue (abstract "boolean" domain: + // [1,1] definitely true, [0,0] definitely false, [0,1]/top/bottom ambiguous). Mirror + // that here so Python's `==`/`!=` behave the same as C++'s `==`/`!=`. `equals()` + // remains the separate, real bool-returning concrete-equality check. .def("__eq__", [](const IntervalValue &self, const IntervalValue &other) { - return self.equals(other); + return self.operator==(other); }) .def("__ne__", [](const IntervalValue &self, const IntervalValue &other) { - return !self.equals(other); + return self.operator!=(other); + }) + // Truthiness: only a definite [1,1] result is truthy, so common patterns like + // `if a == b:` continue to behave sensibly even though `==` now returns an + // IntervalValue rather than a bool. Ambiguous/top/bottom/other-numeral results + // are falsy; callers needing three-way logic should inspect the IntervalValue + // directly (e.g. via is_numeral()/getIntNumeral() or eq_interval()). + .def("__bool__", [](const IntervalValue &self) { + return self.is_numeral() && self.getIntNumeral() == 1; }) .def("clone", [](const IntervalValue &self) { @@ -350,8 +362,23 @@ void bind_abstract_state(py::module& m) { // Abstract operations .def("joinWith", &AbstractState::joinWith, py::arg("other")) .def("meetWith", &AbstractState::meetWith, py::arg("other")) - .def("widening", &AbstractState::widening, py::arg("other")) - .def("narrowing", &AbstractState::narrowing, py::arg("other")) + // `widening`/`narrowing` construct a brand-new AbstractState in C++, so a naive + // binding would silently downgrade any Python subclass (e.g. `class AEState + // (AbstractState): ...`) back to a plain AbstractState. Reconstruct an instance of + // the caller's actual runtime type (assumes a no-arg constructor, true for + // subclasses that don't override __init__) and copy the computed state into it. + .def("widening", [](py::object self, const AbstractState& other) -> py::object { + AbstractState result = py::cast(self).widening(other); + py::object new_obj = self.attr("__class__")(); + py::cast(new_obj) = result; + return new_obj; + }, py::arg("other")) + .def("narrowing", [](py::object self, const AbstractState& other) -> py::object { + AbstractState result = py::cast(self).narrowing(other); + py::object new_obj = self.attr("__class__")(); + py::cast(new_obj) = result; + return new_obj; + }, py::arg("other")) .def("getIDFromAddr", &AbstractState::getIDFromAddr, py::arg("addr")) // Static utilities for address handling @@ -392,9 +419,13 @@ void bind_abstract_state(py::module& m) { .def("getVarToVal", &AbstractState::getVarToVal, py::return_value_policy::reference) .def("getLocToVal", &AbstractState::getLocToVal, py::return_value_policy::reference) .def("printAbstractState", &AbstractState::printAbstractState) - .def("clone", [](const AbstractState &self) { - return std::make_unique(self); // clone - }, py::return_value_policy::move) + .def("clone", [](py::object self) -> py::object { + // See widening/narrowing above: preserve the caller's actual Python + // (sub)class rather than always returning a plain AbstractState. + py::object new_obj = self.attr("__class__")(); + py::cast(new_obj) = py::cast(self); + return new_obj; + }) .def("bottom", &AbstractState::bottom) .def("top", &AbstractState::top) .def("inVarToValTable", &AbstractState::inVarToValTable, py::arg("var_id")) diff --git a/pysvf/__init__.py b/pysvf/__init__.py index 246367b..e20a55f 100644 --- a/pysvf/__init__.py +++ b/pysvf/__init__.py @@ -88,7 +88,7 @@ def main(): args = sys.argv[2:] run_svf_tool(tool_name, args) -from .enums import Predicate, OpCode +from .enums import Predicate, OpCode, CopyKind # Import all the module classes and functions from .pysvf import ( releasePAG, diff --git a/pysvf/enums.py b/pysvf/enums.py index 8c144c7..e8706d3 100644 --- a/pysvf/enums.py +++ b/pysvf/enums.py @@ -67,4 +67,21 @@ class PTAType(IntEnum): """Pointer Analysis Types""" Andersen = 0 # Andersen's analysis - Steensgaard = 1 # Steensgaard's analysis \ No newline at end of file + Steensgaard = 1 # Steensgaard's analysis + + +class CopyKind(IntEnum): + """Copy kinds for CopyStmt (mirrors SVF::CopyStmt::CopyKind)""" + + COPYVAL = 0 # Value copies (default one) + ZEXT = 1 # Zero extend integers + SEXT = 2 # Sign extend integers + BITCAST = 3 # Type cast + TRUNC = 4 # Truncate integers + FPTRUNC = 5 # Truncate floating point + FPTOUI = 6 # floating point -> UInt + FPTOSI = 7 # floating point -> SInt + UITOFP = 8 # UInt -> floating point + SITOFP = 9 # SInt -> floating point + INTTOPTR = 10 # Integer -> Pointer + PTRTOINT = 11 # Pointer -> Integer diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index 8da22a4..d429991 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -1868,9 +1868,12 @@ class IntervalValue: def __and__(self, other: "IntervalValue") -> "IntervalValue": ... def __or__(self, other: "IntervalValue") -> "IntervalValue": ... def __xor__(self, other: "IntervalValue") -> "IntervalValue": ... - def __lshift__(self, bits: int) -> "IntervalValue": ... - def __rshift__(self, bits: int) -> "IntervalValue": ... - def equals(self, other: "IntervalValue") -> "IntervalValue": ... + def __lshift__(self, other: "IntervalValue") -> "IntervalValue": ... + def __rshift__(self, other: "IntervalValue") -> "IntervalValue": ... + def __bool__(self) -> bool: ... + """True only when this interval is the definite/concrete truth value [1, 1]; + False for [0, 0], ambiguous ranges (e.g. [0, 1]), top, and bottom.""" + def equals(self, other: "IntervalValue") -> bool: ... def lb(self) -> BoundedInt: ... def ub(self) -> BoundedInt: ... def clone(self) -> 'IntervalValue': ... @@ -1970,7 +1973,11 @@ class AbstractState: def joinWith(self, other: 'AbstractState') -> None: ... def meetWith(self, other: 'AbstractState') -> None: ... def widening(self, other: 'AbstractState') -> 'AbstractState': ... + """Returns an instance of type(self) (subclasses are preserved), not necessarily + a plain AbstractState.""" def narrowing(self, other: 'AbstractState') -> 'AbstractState': ... + """Returns an instance of type(self) (subclasses are preserved), not necessarily + a plain AbstractState.""" def bottom(self) -> None: ... def getIDFromAddr(self, addr: int) -> int: ... def top(self) -> None: ... @@ -1978,8 +1985,16 @@ class AbstractState: def isVirtualMemAddress(val: int) -> bool: ... @staticmethod def getVirtualMemAddress(idx: int) -> int: ... - def isCmpBranchFeasible(self, cmp: 'CmpStmt', succ: int, abstract_state: AbstractState) -> bool: ... - def isSwitchBranchFeasible(self, switch_var: SVFVar, succ: int, abstract_state: AbstractState) -> bool: ... + @staticmethod + def isCmpBranchFeasible(pag: 'SVFIR', cmpStmt: 'CmpStmt', succ: int, as_: 'AbstractState') -> bool: ... + """Not a real SVF C++ AbstractState/AbstractInterpretation API: this is a pysvf-only + static helper that reimplements AbstractInterpretation::isCmpBranchFeasible's logic + in the Python bindings, since that method is private in the C++ library.""" + @staticmethod + def isSwitchBranchFeasible(svfir: 'SVFIR', var: SVFVar, succ: int, as_: 'AbstractState') -> bool: ... + """Not a real SVF C++ AbstractState/AbstractInterpretation API: this is a pysvf-only + static helper that reimplements AbstractInterpretation::isSwitchBranchFeasible's logic + in the Python bindings, since that method is private in the C++ library.""" def inVarToValTable(self, var_id: int) -> bool: ... def inVarToAddrsTable(self, var_id: int) -> bool: ... def inAddrToAddrsTable(self, id: int) -> bool: ... @@ -1995,6 +2010,8 @@ class AbstractState: def clear(self) -> None: ... def clone(self) -> 'AbstractState': ... + """Returns an instance of type(self) (subclasses are preserved), not necessarily + a plain AbstractState.""" def getLocToVal(self) -> dict: ... def getVarToVal(self) -> dict: ... def printAbstractState(self) -> None: ... From 286afbf36162db3f58ea5c0a47c1f35e7b50d5fa Mon Sep 17 00:00:00 2001 From: hanyuone Date: Wed, 12 Aug 2026 18:47:05 +1000 Subject: [PATCH 2/7] fix: remove incorrect copilot changes for equality, widening/narrowing/clone --- pybind/AE.cpp | 48 +++++++----------------------------------------- pysvf/pysvf.pyi | 4 ---- 2 files changed, 7 insertions(+), 45 deletions(-) diff --git a/pybind/AE.cpp b/pybind/AE.cpp index 34ebc02..dfd7bc3 100644 --- a/pybind/AE.cpp +++ b/pybind/AE.cpp @@ -113,27 +113,12 @@ void bind_abstract_state(py::module& m) { return new IntervalValue(to_bounded_int(lb), to_bounded_int(ub)); }), py::arg("lb"), py::arg("ub")) - - // Equality - // C++'s operator== / operator!= return an IntervalValue (abstract "boolean" domain: - // [1,1] definitely true, [0,0] definitely false, [0,1]/top/bottom ambiguous). Mirror - // that here so Python's `==`/`!=` behave the same as C++'s `==`/`!=`. `equals()` - // remains the separate, real bool-returning concrete-equality check. .def("__eq__", [](const IntervalValue &self, const IntervalValue &other) { - return self.operator==(other); + return self.equals(other); }) .def("__ne__", [](const IntervalValue &self, const IntervalValue &other) { - return self.operator!=(other); - }) - // Truthiness: only a definite [1,1] result is truthy, so common patterns like - // `if a == b:` continue to behave sensibly even though `==` now returns an - // IntervalValue rather than a bool. Ambiguous/top/bottom/other-numeral results - // are falsy; callers needing three-way logic should inspect the IntervalValue - // directly (e.g. via is_numeral()/getIntNumeral() or eq_interval()). - .def("__bool__", [](const IntervalValue &self) { - return self.is_numeral() && self.getIntNumeral() == 1; + return !self.equals(other); }) - .def("clone", [](const IntervalValue &self) { return std::make_unique(self); }, py::return_value_policy::move) @@ -362,23 +347,8 @@ void bind_abstract_state(py::module& m) { // Abstract operations .def("joinWith", &AbstractState::joinWith, py::arg("other")) .def("meetWith", &AbstractState::meetWith, py::arg("other")) - // `widening`/`narrowing` construct a brand-new AbstractState in C++, so a naive - // binding would silently downgrade any Python subclass (e.g. `class AEState - // (AbstractState): ...`) back to a plain AbstractState. Reconstruct an instance of - // the caller's actual runtime type (assumes a no-arg constructor, true for - // subclasses that don't override __init__) and copy the computed state into it. - .def("widening", [](py::object self, const AbstractState& other) -> py::object { - AbstractState result = py::cast(self).widening(other); - py::object new_obj = self.attr("__class__")(); - py::cast(new_obj) = result; - return new_obj; - }, py::arg("other")) - .def("narrowing", [](py::object self, const AbstractState& other) -> py::object { - AbstractState result = py::cast(self).narrowing(other); - py::object new_obj = self.attr("__class__")(); - py::cast(new_obj) = result; - return new_obj; - }, py::arg("other")) + .def("widening", &AbstractState::widening, py::arg("other")) + .def("narrowing", &AbstractState::narrowing, py::arg("other")) .def("getIDFromAddr", &AbstractState::getIDFromAddr, py::arg("addr")) // Static utilities for address handling @@ -419,13 +389,9 @@ void bind_abstract_state(py::module& m) { .def("getVarToVal", &AbstractState::getVarToVal, py::return_value_policy::reference) .def("getLocToVal", &AbstractState::getLocToVal, py::return_value_policy::reference) .def("printAbstractState", &AbstractState::printAbstractState) - .def("clone", [](py::object self) -> py::object { - // See widening/narrowing above: preserve the caller's actual Python - // (sub)class rather than always returning a plain AbstractState. - py::object new_obj = self.attr("__class__")(); - py::cast(new_obj) = py::cast(self); - return new_obj; - }) + .def("clone", [](const AbstractState &self) { + return std::make_unique(self); // clone + }, py::return_value_policy::move) .def("bottom", &AbstractState::bottom) .def("top", &AbstractState::top) .def("inVarToValTable", &AbstractState::inVarToValTable, py::arg("var_id")) diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index d429991..f4c965a 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -1973,11 +1973,7 @@ class AbstractState: def joinWith(self, other: 'AbstractState') -> None: ... def meetWith(self, other: 'AbstractState') -> None: ... def widening(self, other: 'AbstractState') -> 'AbstractState': ... - """Returns an instance of type(self) (subclasses are preserved), not necessarily - a plain AbstractState.""" def narrowing(self, other: 'AbstractState') -> 'AbstractState': ... - """Returns an instance of type(self) (subclasses are preserved), not necessarily - a plain AbstractState.""" def bottom(self) -> None: ... def getIDFromAddr(self, addr: int) -> int: ... def top(self) -> None: ... From 9eab11b79d2eb49c8ab5c32f9ad5a98276075279 Mon Sep 17 00:00:00 2001 From: hanyuone Date: Wed, 12 Aug 2026 18:48:28 +1000 Subject: [PATCH 3/7] --amend --- pysvf/pysvf.pyi | 2 -- 1 file changed, 2 deletions(-) diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index f4c965a..9f4f939 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -2006,8 +2006,6 @@ class AbstractState: def clear(self) -> None: ... def clone(self) -> 'AbstractState': ... - """Returns an instance of type(self) (subclasses are preserved), not necessarily - a plain AbstractState.""" def getLocToVal(self) -> dict: ... def getVarToVal(self) -> dict: ... def printAbstractState(self) -> None: ... From eb0806effff275b5a232e9d3d50cfd63177f43f4 Mon Sep 17 00:00:00 2001 From: hanyuone Date: Wed, 12 Aug 2026 18:49:36 +1000 Subject: [PATCH 4/7] fix: remove incorrect copilot changes for isCmpBranchFeasible, isSwitchBranchFeasible --- pysvf/pysvf.pyi | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index 9f4f939..ed1d036 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -1981,16 +1981,6 @@ class AbstractState: def isVirtualMemAddress(val: int) -> bool: ... @staticmethod def getVirtualMemAddress(idx: int) -> int: ... - @staticmethod - def isCmpBranchFeasible(pag: 'SVFIR', cmpStmt: 'CmpStmt', succ: int, as_: 'AbstractState') -> bool: ... - """Not a real SVF C++ AbstractState/AbstractInterpretation API: this is a pysvf-only - static helper that reimplements AbstractInterpretation::isCmpBranchFeasible's logic - in the Python bindings, since that method is private in the C++ library.""" - @staticmethod - def isSwitchBranchFeasible(svfir: 'SVFIR', var: SVFVar, succ: int, as_: 'AbstractState') -> bool: ... - """Not a real SVF C++ AbstractState/AbstractInterpretation API: this is a pysvf-only - static helper that reimplements AbstractInterpretation::isSwitchBranchFeasible's logic - in the Python bindings, since that method is private in the C++ library.""" def inVarToValTable(self, var_id: int) -> bool: ... def inVarToAddrsTable(self, var_id: int) -> bool: ... def inAddrToAddrsTable(self, id: int) -> bool: ... From 8e3b8c532f08db7f2c7c9f420ee849a2c6453bad Mon Sep 17 00:00:00 2001 From: hanyuone Date: Thu, 13 Aug 2026 13:40:26 +1000 Subject: [PATCH 5/7] fix: revert changes to AE.cpp --- pybind/AE.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pybind/AE.cpp b/pybind/AE.cpp index dfd7bc3..3c899e3 100644 --- a/pybind/AE.cpp +++ b/pybind/AE.cpp @@ -113,12 +113,15 @@ void bind_abstract_state(py::module& m) { return new IntervalValue(to_bounded_int(lb), to_bounded_int(ub)); }), py::arg("lb"), py::arg("ub")) + + // Equality .def("__eq__", [](const IntervalValue &self, const IntervalValue &other) { return self.equals(other); }) .def("__ne__", [](const IntervalValue &self, const IntervalValue &other) { return !self.equals(other); }) + .def("clone", [](const IntervalValue &self) { return std::make_unique(self); }, py::return_value_policy::move) @@ -160,11 +163,11 @@ void bind_abstract_state(py::module& m) { .def("is_int", &IntervalValue::is_int) .def("equals", &IntervalValue::equals, py::arg("other")) .def("eq_interval", [](const IntervalValue &self, const IntervalValue &other) { - return self.operator==(other); - }, py::arg("other")) + return self.operator==(other); + }, py::arg("other")) .def("ne_interval", [](const IntervalValue &self, const IntervalValue &other) { - return self.operator!=(other); - }, py::arg("other")) + return self.operator!=(other); + }, py::arg("other")) .def("getNumeral", &IntervalValue::getNumeral) .def("getIntNumeral", &IntervalValue::getIntNumeral) .def("getRealNumeral", &IntervalValue::getRealNumeral) From 28bd5bddb7edadb802f10d026b42076782af7f55 Mon Sep 17 00:00:00 2001 From: hanyuone Date: Thu, 13 Aug 2026 13:41:09 +1000 Subject: [PATCH 6/7] fix: revert equality/inequality overloading, add explanatory comments --- pysvf/pysvf.pyi | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index ed1d036..d30dff5 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -1846,16 +1846,18 @@ class IntervalValue: def __init__(self, lb: BoundedInt, ub: BoundedInt) -> None: ... @overload def __init__(self, val: int) -> None: ... - # `__eq__` and `__ne__`'s type annotations are forced as `bool` for all objects, - # use `type: ignore` so that equality operators in C++ and Python are the same - @overload - def __eq__(self, other: 'IntervalValue') -> 'IntervalValue': ... # type: ignore - @overload - def __eq__(self, other: object) -> bool: ... - @overload - def __ne__(self, other: 'IntervalValue') -> 'IntervalValue': ... # type: ignore - @overload - def __ne__(self, other: object) -> bool: ... + def __eq__(self, other: object) -> bool: + """ + Alias for the C++ `IntervalValue::equals`. Python requires the equality operator + to return booleans. + """ + ... + def __ne__(self, other: object) -> bool: + """ + Alias for the C++ `!IntervalValue::equals`. Python requires the inequality operator + to return booleans. + """ + ... def __add__(self, other: 'IntervalValue') -> 'IntervalValue': ... def __sub__(self, other: 'IntervalValue') -> 'IntervalValue': ... def __mul__(self, other: 'IntervalValue') -> 'IntervalValue': ... @@ -1870,10 +1872,7 @@ class IntervalValue: def __xor__(self, other: "IntervalValue") -> "IntervalValue": ... def __lshift__(self, other: "IntervalValue") -> "IntervalValue": ... def __rshift__(self, other: "IntervalValue") -> "IntervalValue": ... - def __bool__(self) -> bool: ... - """True only when this interval is the definite/concrete truth value [1, 1]; - False for [0, 0], ambiguous ranges (e.g. [0, 1]), top, and bottom.""" - def equals(self, other: "IntervalValue") -> bool: ... + def equals(self, other: "IntervalValue") -> "IntervalValue": ... def lb(self) -> BoundedInt: ... def ub(self) -> BoundedInt: ... def clone(self) -> 'IntervalValue': ... @@ -1897,8 +1896,12 @@ class IntervalValue: def set_to_bottom(self) -> None: ... def set_to_top(self) -> None: ... def toString(self) -> str: ... - def eq_interval(self, other: 'IntervalValue') -> 'IntervalValue': ... - def ne_interval(self, other: 'IntervalValue') -> 'IntervalValue': ... + def eq_interval(self, other: 'IntervalValue') -> 'IntervalValue': + """Alias for the C++ `IntervalValue::operator==`.""" + ... + def ne_interval(self, other: 'IntervalValue') -> 'IntervalValue': + """Alias for the C++ `IntervalValue::operator!=`.""" + ... @staticmethod def top() -> 'IntervalValue': ... @staticmethod From b0c8ee1da3d785b52ac0d4227a1b42cae83b320d Mon Sep 17 00:00:00 2001 From: hanyuone Date: Thu, 13 Aug 2026 13:45:29 +1000 Subject: [PATCH 7/7] fix: align IntervalValue::equals with binding --- pysvf/pysvf.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index d30dff5..10be21f 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -1872,7 +1872,7 @@ class IntervalValue: def __xor__(self, other: "IntervalValue") -> "IntervalValue": ... def __lshift__(self, other: "IntervalValue") -> "IntervalValue": ... def __rshift__(self, other: "IntervalValue") -> "IntervalValue": ... - def equals(self, other: "IntervalValue") -> "IntervalValue": ... + def equals(self, other: "IntervalValue") -> bool: ... def lb(self) -> BoundedInt: ... def ub(self) -> BoundedInt: ... def clone(self) -> 'IntervalValue': ...