From 0ead2e10db96a75361acdbaa716d507807d205f5 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 13 Jan 2021 13:54:15 -0800 Subject: interpreterbase: Add a helper method for typing positional arguments We don't do a very good job of type checking in the interpreter, sometimes we leave it to the mid layers of backends to do that (layering violations) and sometimes we just don't check them at all. When we do check them it's a ton of boilerplate and complicates the code. This should help quite a bit. --- mesonbuild/interpreterbase.py | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'mesonbuild/interpreterbase.py') diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index f17dfba..4fd1ae9 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -228,6 +228,61 @@ class permittedKwargs: return f(*wrapped_args, **wrapped_kwargs) return T.cast(TV_func, wrapped) + +def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]]) -> T.Callable[..., T.Any]: + """Decorator that types type checking of positional arguments. + + allows replacing this: + ```python + def func(self, node, args, kwargs): + if len(args) != 2: + raise Exception('... takes exactly 2 arguments) + foo: str = args[0] + if not isinstance(foo, str): + raise ... + bar: int = args[1] + if not isinstance(bar, int): + raise ... + + # actual useful stuff + ``` + with: + ```python + @typed_pos_args('func_name', str, int) + def func(self, node, args: T.Tuple[str, int], kwargs): + foo, bar = args + + # actual useful stuff + ``` + """ + def inner(f: TV_func) -> TV_func: + + @wraps(f) + def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: + args = _get_callee_args(wrapped_args)[2] + assert isinstance(args, list), args + if len(args) != len(types): + raise InvalidArguments(f'{name} takes exactly {len(types)} arguments, but got {len(args)}.') + for i, (arg, type_) in enumerate(zip(args, types), start=1): + if not isinstance(arg, type_): + if isinstance(type_, tuple): + shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in type_)) + else: + shouldbe = f'"{type_.__name__}"' + raise InvalidArguments(f'{name} argument {i} was of type "{type(arg).__name__}" but should have been {shouldbe}') + + # Ensure that we're actually passing a tuple. + # Depending on what kind of function we're calling the length of + # wrapped_args can vary. + nargs = list(wrapped_args) + i = nargs.index(args) + nargs[i] = tuple(args) + return f(*nargs, **wrapped_kwargs) + + return T.cast(TV_func, wrapper) + return inner + + class FeatureCheckBase(metaclass=abc.ABCMeta): "Base class for feature version checks" -- cgit v1.1