From ad6569e3e976cee0320939572d17eef228a6e7a0 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Wed, 30 Jul 2025 18:52:20 +0100 Subject: [PATCH 1/2] Modify MustUse to iterate the stack frame and collate options from all frames. Options in closer frames take precedence --- amaranth/_unused.py | 6 ++++-- amaranth/_utils.py | 27 +++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/amaranth/_unused.py b/amaranth/_unused.py index 4f2507bc1..4d28bf8d3 100644 --- a/amaranth/_unused.py +++ b/amaranth/_unused.py @@ -18,7 +18,9 @@ class MustUse: def __new__(cls, *args, src_loc_at=0, **kwargs): frame = sys._getframe(1 + src_loc_at) self = super().__new__(cls) - self._MustUse__used = False + + self._MustUse__used = False + self._MustUse__frame = frame self._MustUse__context = dict( filename=frame.f_code.co_filename, lineno=frame.f_lineno, @@ -31,7 +33,7 @@ def __del__(self): if getattr(self._MustUse__warning, "_MustUse__silence", False): return if hasattr(self, "_MustUse__used") and not self._MustUse__used: - if get_linter_option(self._MustUse__context["filename"], + if get_linter_option(self._MustUse__frame, self._MustUse__warning.__qualname__, bool, True): warnings.warn_explicit( f"{self!r} created but never used", self._MustUse__warning, diff --git a/amaranth/_utils.py b/amaranth/_utils.py index b6591fdbb..da90c9dcf 100644 --- a/amaranth/_utils.py +++ b/amaranth/_utils.py @@ -75,6 +75,7 @@ def decorator_like(*args, **kwargs): return decorator_like + def get_linter_options(filename): first_line = linecache.getline(filename, 1) if first_line: @@ -84,8 +85,30 @@ def get_linter_options(filename): return dict() -def get_linter_option(filename, name, type, default): - options = get_linter_options(filename) +def get_linter_option(frame, name, type, default): + """ + Get given linter option for a given stack frame. This iterates down the frames, collating options of the form: + + .. code:: + # amaranth: {name}=value + + The earliest option value in the stack takes precedence + + Returns + ------- + :class:`bool` or :class:`int` + Option value + """ + + options = {} + while frame: + f_opts = get_linter_options(frame.f_code.co_filename) + options = f_opts | options + if frame.f_back is None: + break + else: + frame = frame.f_back + if name not in options: return default From 5d8232351e01ab5bdab6a8542b62daf781a7d579 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 5 Aug 2025 00:14:10 +0100 Subject: [PATCH 2/2] MustUse: Don't hold onto frame pointers --- amaranth/_unused.py | 6 +++--- amaranth/_utils.py | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/amaranth/_unused.py b/amaranth/_unused.py index 4d28bf8d3..dbb9b0c33 100644 --- a/amaranth/_unused.py +++ b/amaranth/_unused.py @@ -1,7 +1,7 @@ import sys import warnings -from ._utils import get_linter_option +from ._utils import get_linter_option, collate_linter_options __all__ = ["UnusedMustUse", "MustUse"] @@ -20,7 +20,7 @@ def __new__(cls, *args, src_loc_at=0, **kwargs): self = super().__new__(cls) self._MustUse__used = False - self._MustUse__frame = frame + self._MustUse__options = collate_linter_options(frame) self._MustUse__context = dict( filename=frame.f_code.co_filename, lineno=frame.f_lineno, @@ -33,7 +33,7 @@ def __del__(self): if getattr(self._MustUse__warning, "_MustUse__silence", False): return if hasattr(self, "_MustUse__used") and not self._MustUse__used: - if get_linter_option(self._MustUse__frame, + if get_linter_option(self._MustUse__options, self._MustUse__warning.__qualname__, bool, True): warnings.warn_explicit( f"{self!r} created but never used", self._MustUse__warning, diff --git a/amaranth/_utils.py b/amaranth/_utils.py index da90c9dcf..f728e5153 100644 --- a/amaranth/_utils.py +++ b/amaranth/_utils.py @@ -85,9 +85,9 @@ def get_linter_options(filename): return dict() -def get_linter_option(frame, name, type, default): +def collate_linter_options(frame): """ - Get given linter option for a given stack frame. This iterates down the frames, collating options of the form: + Get all linter options for a given stack frame. This iterates down the frames, collating options of the form: .. code:: # amaranth: {name}=value @@ -108,7 +108,10 @@ def get_linter_option(frame, name, type, default): break else: frame = frame.f_back + return options + +def get_linter_option(options, name, type, default): if name not in options: return default