diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 53dc5ac28..4ae242641 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -24,14 +24,31 @@ import collections.abc from functools import wraps import typing as T +TV_fw_var = T.Union[str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder'] +TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]] +TV_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]] + +TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) + +TYPE_elementary = T.Union[str, int, float, bool] +TYPE_var = T.Union[TYPE_elementary, T.List[T.Any], T.Dict[str, T.Any], 'InterpreterObject', 'ObjectHolder'] +TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] +TYPE_nkwargs = T.Dict[str, TYPE_nvar] +TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] + class InterpreterObject: - def __init__(self): - self.methods = {} # type: T.Dict[str, T.Callable] + def __init__(self) -> None: + self.methods = {} # type: T.Dict[str, T.Callable[[T.List[TYPE_nvar], TYPE_nkwargs], TYPE_var]] # Current node set during a method call. This can be used as location # when printing a warning message during a method call. self.current_node = None # type: mparser.BaseNode - def method_call(self, method_name: str, args: T.List[T.Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']], kwargs: T.Dict[str, T.Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']]): + def method_call( + self, + method_name: str, + args: TV_fw_args, + kwargs: TV_fw_kwargs + ) -> TYPE_var: if method_name in self.methods: method = self.methods[method_name] if not getattr(method, 'no-args-flattening', False): @@ -42,18 +59,13 @@ class InterpreterObject: TV_InterpreterObject = T.TypeVar('TV_InterpreterObject') class ObjectHolder(T.Generic[TV_InterpreterObject]): - def __init__(self, obj: InterpreterObject, subproject: T.Optional[str] = None): + def __init__(self, obj: InterpreterObject, subproject: T.Optional[str] = None) -> None: self.held_object = obj # type: InterpreterObject self.subproject = subproject # type: str - def __repr__(self): + def __repr__(self) -> str: return ''.format(self.held_object) -TYPE_elementary = T.Union[str, int, float, bool] -TYPE_var = T.Union[TYPE_elementary, list, dict, InterpreterObject, ObjectHolder] -TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] -TYPE_nkwargs = T.Dict[T.Union[mparser.BaseNode, str], TYPE_nvar] - class MesonVersionString(str): pass @@ -67,11 +79,11 @@ def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None: mlog.debug('Element not a string:', str(a)) raise InvalidArguments(msg) -def _get_callee_args(wrapped_args, want_subproject: bool = False): +def _get_callee_args(wrapped_args: T.Sequence[T.Any], want_subproject: bool = False) -> T.Tuple[T.Any, mparser.BaseNode, TV_fw_args, TV_fw_kwargs, T.Optional[str]]: s = wrapped_args[0] n = len(wrapped_args) # Raise an error if the codepaths are not there - subproject = None + subproject = None # type: T.Optional[str] if want_subproject and n == 2: if hasattr(s, 'subproject'): # Interpreter base types have 2 args: self, node @@ -145,18 +157,18 @@ def flatten(args: T.Union[TYPE_nvar, T.List[TYPE_nvar]]) -> T.List[TYPE_nvar]: result.append(a) return result -def noPosargs(f): +def noPosargs(f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: args = _get_callee_args(wrapped_args)[2] if args: raise InvalidArguments('Function does not take positional arguments.') return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) -def builtinMethodNoKwargs(f): +def builtinMethodNoKwargs(f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: node = wrapped_args[0].current_node method_name = wrapped_args[2] kwargs = wrapped_args[4] @@ -165,56 +177,56 @@ def builtinMethodNoKwargs(f): 'This will become a hard error in the future', location=node) return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) -def noKwargs(f): +def noKwargs(f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: kwargs = _get_callee_args(wrapped_args)[3] if kwargs: raise InvalidArguments('Function does not take keyword arguments.') return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) -def stringArgs(f): +def stringArgs(f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: args = _get_callee_args(wrapped_args)[2] assert(isinstance(args, list)) check_stringlist(args) return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) -def noArgsFlattening(f): +def noArgsFlattening(f: TV_func) -> TV_func: setattr(f, 'no-args-flattening', True) # noqa: B010 return f -def disablerIfNotFound(f): +def disablerIfNotFound(f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: kwargs = _get_callee_args(wrapped_args)[3] disabler = kwargs.pop('disabler', False) ret = f(*wrapped_args, **wrapped_kwargs) if disabler and not ret.held_object.found(): return Disabler() return ret - return wrapped + return T.cast(TV_func, wrapped) class permittedKwargs: def __init__(self, permitted: T.Set[str]): self.permitted = permitted # type: T.Set[str] - def __call__(self, f): + def __call__(self, f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: s, node, args, kwargs, _ = _get_callee_args(wrapped_args) for k in kwargs: if k not in self.permitted: mlog.warning('''Passed invalid keyword argument "{}".'''.format(k), location=node) mlog.warning('This will become a hard error in the future.') return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) class FeatureCheckBase(metaclass=abc.ABCMeta): "Base class for feature version checks" @@ -233,7 +245,7 @@ class FeatureCheckBase(metaclass=abc.ABCMeta): # Don't do any checks if project() has not been parsed yet if subproject not in mesonlib.project_meson_versions: return '' - return mesonlib.project_meson_versions[subproject] + return T.cast(str, mesonlib.project_meson_versions[subproject]) # TODO: remove type cast when fully typing mesonlib @staticmethod @abc.abstractmethod @@ -279,15 +291,15 @@ class FeatureCheckBase(metaclass=abc.ABCMeta): def get_warning_str_prefix(tv: str) -> str: raise InterpreterException('get_warning_str_prefix not implemented') - def __call__(self, f): + def __call__(self, f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: subproject = _get_callee_args(wrapped_args, want_subproject=True)[4] if subproject is None: raise AssertionError('{!r}'.format(wrapped_args)) self.use(subproject) return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) @classmethod def single_use(cls, feature_name: str, version: str, subproject: str, @@ -306,7 +318,7 @@ class FeatureNew(FeatureCheckBase): @staticmethod def check_version(target_version: str, feature_version: str) -> bool: - return mesonlib.version_compare_condition_with_min(target_version, feature_version) + return T.cast(bool, mesonlib.version_compare_condition_with_min(target_version, feature_version)) # TODO: remove once mesonlib is annotated @staticmethod def get_warning_str_prefix(tv: str) -> str: @@ -366,9 +378,9 @@ class FeatureCheckKwargsBase(metaclass=abc.ABCMeta): self.kwargs = kwargs self.extra_message = extra_message - def __call__(self, f): + def __call__(self, f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5] if subproject is None: raise AssertionError('{!r}'.format(wrapped_args)) @@ -379,7 +391,7 @@ class FeatureCheckKwargsBase(metaclass=abc.ABCMeta): self.feature_check_class.single_use( name, self.feature_version, subproject, self.extra_message) return f(*wrapped_args, **wrapped_kwargs) - return wrapped + return T.cast(TV_func, wrapped) class FeatureNewKwargs(FeatureCheckKwargsBase): feature_check_class = FeatureNew @@ -407,21 +419,21 @@ class BreakRequest(BaseException): pass class MutableInterpreterObject(InterpreterObject): - def __init__(self): + def __init__(self) -> None: super().__init__() class Disabler(InterpreterObject): - def __init__(self): + def __init__(self) -> None: super().__init__() self.methods.update({'found': self.found_method}) - def found_method(self, args, kwargs): + def found_method(self, args: T.Sequence[T.Any], kwargs: T.Dict[str, T.Any]) -> bool: return False -def is_disabler(i) -> bool: +def is_disabler(i: T.Any) -> bool: return isinstance(i, Disabler) -def is_arg_disabled(arg) -> bool: +def is_arg_disabled(arg: T.Any) -> bool: if is_disabler(arg): return True if isinstance(arg, list): @@ -430,7 +442,7 @@ def is_arg_disabled(arg) -> bool: return True return False -def is_disabled(args, kwargs) -> bool: +def is_disabled(args: T.Sequence[T.Any], kwargs: T.Dict[str, T.Any]) -> bool: for i in args: if is_arg_disabled(i): return True @@ -540,9 +552,9 @@ class InterpreterBase: elif isinstance(cur, mparser.MethodNode): return self.method_call(cur) elif isinstance(cur, mparser.StringNode): - return cur.value + return T.cast(str, cur.value) elif isinstance(cur, mparser.BooleanNode): - return cur.value + return T.cast(bool, cur.value) elif isinstance(cur, mparser.IfClauseNode): return self.evaluate_if(cur) elif isinstance(cur, mparser.IdNode): @@ -554,7 +566,7 @@ class InterpreterBase: elif isinstance(cur, mparser.DictNode): return self.evaluate_dictstatement(cur) elif isinstance(cur, mparser.NumberNode): - return cur.value + return T.cast(int, cur.value) elif isinstance(cur, mparser.AndNode): return self.evaluate_andstatement(cur) elif isinstance(cur, mparser.OrNode): @@ -721,7 +733,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Second argument to "or" is not a boolean.') return r - def evaluate_uminusstatement(self, cur) -> T.Union[int, Disabler]: + def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[int, Disabler]: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return v @@ -867,7 +879,7 @@ The result of this is undefined and will become a hard error in a future Meson r if not isinstance(index, str): raise InterpreterException('Key is not a string') try: - return iobject[index] + return T.cast(TYPE_var, iobject[index]) except KeyError: raise InterpreterException('Key %s is not in dict' % index) else: @@ -892,7 +904,7 @@ The result of this is undefined and will become a hard error in a future Meson r func_args = posargs # type: T.Any if not getattr(func, 'no-args-flattening', False): func_args = flatten(posargs) - return func(node, func_args, self.kwargs_string_keys(kwargs)) + return func(node, func_args, kwargs) else: self.unknown_function_called(func_name) return None @@ -934,7 +946,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InvalidArguments('Invalid operation "extract_objects" on variable "{}"'.format(object_name)) self.validate_extraction(obj.held_object) obj.current_node = node - return obj.method_call(method_name, args, self.kwargs_string_keys(kwargs)) + return obj.method_call(method_name, args, kwargs) @builtinMethodNoKwargs def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int]: @@ -1067,7 +1079,7 @@ The result of this is undefined and will become a hard error in a future Meson r arg = str(arg).lower() arg_strings.append(str(arg)) - def arg_replace(match): + def arg_replace(match: T.Match[str]) -> str: idx = int(match.group(1)) if idx >= len(arg_strings): raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) @@ -1115,12 +1127,12 @@ The result of this is undefined and will become a hard error in a future Meson r if isinstance(fallback, mparser.BaseNode): return self.evaluate_statement(fallback) return fallback - return obj[index] + return T.cast(TYPE_var, obj[index]) m = 'Arrays do not have a method called {!r}.' raise InterpreterException(m.format(method_name)) @builtinMethodNoKwargs - def dict_method_call(self, obj: dict, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: + def dict_method_call(self, obj: T.Dict[str, TYPE_var], method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: if method_name in ('has_key', 'get'): if method_name == 'has_key': if len(posargs) != 1: @@ -1194,14 +1206,6 @@ The result of this is undefined and will become a hard error in a future Meson r kwargs[k] = v return kwargs - def kwargs_string_keys(self, kwargs: TYPE_nkwargs) -> T.Dict[str, TYPE_nvar]: - kw = {} # type: T.Dict[str, TYPE_nvar] - for key, val in kwargs.items(): - if not isinstance(key, str): - raise InterpreterException('Key of kwargs is not a string') - kw[key] = val - return kw - def assignment(self, node: mparser.AssignmentNode) -> None: assert(isinstance(node, mparser.AssignmentNode)) if self.argument_depth != 0: @@ -1232,7 +1236,7 @@ To specify a keyword argument, use : instead of =.''') raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) self.variables[varname] = variable - def get_variable(self, varname) -> TYPE_var: + def get_variable(self, varname: str) -> TYPE_var: if varname in self.builtin: return self.builtin[varname] if varname in self.variables: