diff --git a/src/exceptiongroup/_formatting.py b/src/exceptiongroup/_formatting.py index e7e4fd9..4ff269b 100644 --- a/src/exceptiongroup/_formatting.py +++ b/src/exceptiongroup/_formatting.py @@ -433,6 +433,7 @@ def print_exc( _MAX_STRING_SIZE = 40 _MOVE_COST = 2 _CASE_COST = 1 +_SENTINEL = object() def _substitution_cost(ch_a, ch_b): @@ -448,6 +449,9 @@ def _compute_suggestion_error(exc_value, tb): if wrong_name is None or not isinstance(wrong_name, str): return None if isinstance(exc_value, AttributeError): + obj = getattr(exc_value, "obj", _SENTINEL) + if obj is _SENTINEL: + return None obj = exc_value.obj try: d = dir(obj) diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 6b202b5..4f8b0ad 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -504,3 +504,27 @@ def test_nameerror_suggestions_in_group( print_exception(eg) output = capsys.readouterr().err assert "Did you mean" in output and "'append'?" in output + + +def test_bug_suggestions_attributeerror_no_obj( + patched: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + if not patched: + # Block monkey patching, then force the module to be re-imported + del sys.modules["traceback"] + del sys.modules["exceptiongroup"] + del sys.modules["exceptiongroup._formatting"] + monkeypatch.setattr(sys, "excepthook", lambda *args: sys.__excepthook__(*args)) + + from exceptiongroup import print_exception + + class NamedAttributeError(AttributeError): + def __init__(self, name: str) -> None: + self.name: str = name + + try: + raise NamedAttributeError(name="mykey") + except AttributeError as e: + print_exception(e) # does not crash + output = capsys.readouterr().err + assert "NamedAttributeError" in output