diff options
author | Dylan Baker <dylan@pnwbakers.com> | 2021-01-13 13:54:15 -0800 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2021-02-06 13:11:25 -0500 |
commit | 0ead2e10db96a75361acdbaa716d507807d205f5 (patch) | |
tree | e32ce7952fbc64e9669e1d4664851a33c6887131 /mesonbuild/interpreterbase.py | |
parent | bb2124084dac10682a26311e969f88aaaca3e910 (diff) | |
download | meson-0ead2e10db96a75361acdbaa716d507807d205f5.zip meson-0ead2e10db96a75361acdbaa716d507807d205f5.tar.gz meson-0ead2e10db96a75361acdbaa716d507807d205f5.tar.bz2 |
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.
Diffstat (limited to 'mesonbuild/interpreterbase.py')
-rw-r--r-- | mesonbuild/interpreterbase.py | 55 |
1 files changed, 55 insertions, 0 deletions
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" |