From 31a5d06da1202a854afb9e61a1e6ad10f3ae000b Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 1 Aug 2023 18:16:45 +0100 Subject: [PATCH] Argument clinic: remove the `LandMine` class --- Lib/test/test_clinic.py | 3 +-- Tools/clinic/clinic.py | 41 ++++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 5e74b4fb77022d..6100444e6da797 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -331,8 +331,7 @@ def converter_init(self): [clinic start generated code]*/ """) msg = ( - "Stepped on a land mine, trying to access attribute 'noaccess':\n" - "Don't access members of self.function inside converter_init!" + "accessing self.function inside converter_init is disallowed!" ) self.assertIn(msg, out) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 3501c16a567ccc..ba0132fc35903d 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -38,6 +38,7 @@ ) from types import FunctionType, NoneType from typing import ( + TYPE_CHECKING, Any, Final, Literal, @@ -2638,18 +2639,6 @@ def get_displayname(self, i: int) -> str: return f'"argument {i}"' -@dc.dataclass -class LandMine: - # try to access any - __message__: str - - def __getattribute__(self, name: str) -> Any: - if name in ('__repr__', '__message__'): - return super().__getattribute__(name) - # raise RuntimeError(repr(name)) - fail("Stepped on a land mine, trying to access attribute " + repr(name) + ":\n" + self.__message__) - - CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"]) def add_c_converter( @@ -2844,16 +2833,28 @@ def __init__(self, if annotation is not unspecified: fail("The 'annotation' parameter is not currently permitted.") - # this is deliberate, to prevent you from caching information - # about the function in the init. - # (that breaks if we get cloned.) - # so after this change we will noisily fail. - self.function: Function | LandMine = LandMine( - "Don't access members of self.function inside converter_init!" - ) + # Make sure not to set self.function until after converter_init() has been called. + # This prevents you from caching information + # about the function in converter_init(). + # (That breaks if we get cloned.) self.converter_init(**kwargs) self.function = function + # Add a custom __getattr__ method to improve the error message + # if somebody tries to access self.function in converter_init(). + # + # mypy will assume arbitrary access is okay for a class with a __getattr__ method, + # and that's not what we want, + # so put it inside an `if not TYPE_CHECKING` block + if not TYPE_CHECKING: + def __getattr__(self, attr): + if attr == "function": + fail( + f"{self.__class__.__name__!r} object has no attribute 'function'.\n" + f"Note: accessing self.function inside converter_init is disallowed!" + ) + return super().__getattr__(attr) + def converter_init(self) -> None: pass @@ -4005,7 +4006,6 @@ def converter_init(self, *, type: str | None = None) -> None: def pre_render(self) -> None: f = self.function - assert isinstance(f, Function) default_type, default_name = correct_name_for_self(f) self.signature_name = default_name self.type = self.specified_type or self.type or default_type @@ -4056,7 +4056,6 @@ def pre_render(self) -> None: @property def parser_type(self) -> str: assert self.type is not None - assert isinstance(self.function, Function) return required_type_for_self_for_parser(self.function) or self.type def render(self, parameter: Parameter, data: CRenderData) -> None: