From 6caed6ce0504e284b2cfefdb50ffbfd4ff4de53a Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 26 Oct 2019 05:34:00 +0000 Subject: [PATCH] hdl.ir: allow disabling UnusedElaboratable warning in file scope. This warning is usually quite handy, but is problematic in tests: although it can be suppressed by using Fragment.get on elaboratable, that is not always possible, in particular when writing tests for exceptions raised by __init__, e.g.: def test_wrong_csr_bus(self): with self.assertRaisesRegex(ValueError, r"blah blah"): WishboneCSRBridge(csr_bus=object()) In theory, it should be possible to suppress warnings per-module and even per-line using code such as: import re, warnings from nmigen.hdl.ir import UnusedElaboratable warnings.filterwarnings("ignore", category=UnusedElaboratable, module=re.escape(__name__)) Unfortunately, not only is this code quite convoluted, but it also does not actually work; we are using warnings.warn_explicit() because we collect source locations on our own, but it requires the caller to extract the __warningregistry__ dictionary from module globals, or warning suppression would not work. Not only is this not feasible in most diagnostic sites in nMigen, but also I never got it to work anyway, even when passing all of module, registry, and module_globals to warn_explicit(). Instead, use a magic comment at the start of a file to do this job, which might not be elegant but is simple and practical. For now, only UnusedElaboratable can be suppressed with it, but in future, other linter parameters may become tweakable this way. --- nmigen/_utils.py | 34 +++++++++++++++++++++++++++++++++- nmigen/hdl/ir.py | 15 ++++++++++----- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/nmigen/_utils.py b/nmigen/_utils.py index 85c8cdb..6168ea1 100644 --- a/nmigen/_utils.py +++ b/nmigen/_utils.py @@ -1,6 +1,8 @@ import contextlib import functools import warnings +import linecache +import re from collections import OrderedDict from collections.abc import Iterable from contextlib import contextmanager @@ -8,7 +10,8 @@ from contextlib import contextmanager from .utils import * -__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated"] +__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated", + "get_linter_options", "get_linter_option"] def flatten(i): @@ -82,3 +85,32 @@ def extend(cls): name = f.__name__ setattr(cls, name, f) return decorator + + +def get_linter_options(filename): + first_line = linecache.getline(filename, 1) + if first_line: + match = re.match(r"^#\s*nmigen:\s*((?:\w+=\w+\s*)(?:,\s*\w+=\w+\s*)*)\n$", first_line) + if match: + return dict(map(lambda s: s.strip().split("=", 2), match.group(1).split(","))) + return dict() + + +def get_linter_option(filename, name, type, default): + options = get_linter_options(filename) + if name not in options: + return default + + option = options[name] + if type is bool: + if option in ("1", "yes", "enable"): + return True + if option in ("0", "no", "disable"): + return False + return default + if type is int: + try: + return int(option, 0) + except ValueError: + return default + assert False diff --git a/nmigen/hdl/ir.py b/nmigen/hdl/ir.py index 44c3ecd..c213943 100644 --- a/nmigen/hdl/ir.py +++ b/nmigen/hdl/ir.py @@ -21,19 +21,24 @@ class Elaboratable(metaclass=ABCMeta): _Elaboratable__silence = False def __new__(cls, *args, src_loc_at=0, **kwargs): + frame = sys._getframe(1 + src_loc_at) self = super().__new__(cls) - self._Elaboratable__src_loc = traceback.extract_stack(limit=2 + src_loc_at)[0] self._Elaboratable__used = False + self._Elaboratable__context = dict( + filename=frame.f_code.co_filename, + lineno=frame.f_lineno, + source=self) return self def __del__(self): if self._Elaboratable__silence: return if hasattr(self, "_Elaboratable__used") and not self._Elaboratable__used: - warnings.warn_explicit("{!r} created but never used".format(self), UnusedElaboratable, - filename=self._Elaboratable__src_loc.filename, - lineno=self._Elaboratable__src_loc.lineno, - source=self) + if get_linter_option(self._Elaboratable__context["filename"], + "UnusedElaboratable", bool, True): + warnings.warn_explicit( + "{!r} created but never used".format(self), UnusedElaboratable, + **self._Elaboratable__context) _old_excepthook = sys.excepthook -- 2.30.2