aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/interpreterbase.py
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2020-09-09 10:31:52 -0700
committerGitHub <noreply@github.com>2020-09-09 10:31:52 -0700
commit4c2d0eb9bcedefa3ef06a237a0502afbc581268b (patch)
tree1b08ca5fb0c93573409a7a8954e6e1905f8a5b10 /mesonbuild/interpreterbase.py
parent8d54b7bda30062569c981b50a85a175565a7c15a (diff)
parent057c77f7d08b3372e99065fb3f3cd37f16801a82 (diff)
downloadmeson-4c2d0eb9bcedefa3ef06a237a0502afbc581268b.zip
meson-4c2d0eb9bcedefa3ef06a237a0502afbc581268b.tar.gz
meson-4c2d0eb9bcedefa3ef06a237a0502afbc581268b.tar.bz2
Merge pull request #7657 from mensinda/moreTyping
typing: Strict type annotations
Diffstat (limited to 'mesonbuild/interpreterbase.py')
-rw-r--r--mesonbuild/interpreterbase.py160
1 files changed, 84 insertions, 76 deletions
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
index 311314b..1524409 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 '<Holder: {!r}>'.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"
@@ -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,
@@ -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
@@ -439,6 +451,11 @@ def is_disabled(args, kwargs) -> bool:
return True
return False
+def default_resolve_key(key: mparser.BaseNode) -> str:
+ if not isinstance(key, mparser.IdNode):
+ raise InterpreterException('Invalid kwargs format.')
+ return key.value
+
class InterpreterBase:
elementary_types = (int, float, str, bool, list)
@@ -585,23 +602,17 @@ class InterpreterBase:
return arguments
@FeatureNew('dict', '0.47.0')
- def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Dict[str, T.Any]:
- (arguments, kwargs) = self.reduce_arguments(cur.args, resolve_key_nodes=False)
- assert (not arguments)
- result = {} # type: T.Dict[str, T.Any]
- self.argument_depth += 1
- for key, value in kwargs.items():
+ def evaluate_dictstatement(self, cur: mparser.DictNode) -> TYPE_nkwargs:
+ def resolve_key(key: mparser.BaseNode) -> str:
if not isinstance(key, mparser.StringNode):
FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject)
- assert isinstance(key, mparser.BaseNode) # All keys must be nodes due to resolve_key_nodes=False
str_key = self.evaluate_statement(key)
if not isinstance(str_key, str):
raise InvalidArguments('Key must be a string')
- if str_key in result:
- raise InvalidArguments('Duplicate dictionary key: {}'.format(str_key))
- result[str_key] = value
- self.argument_depth -= 1
- return result
+ return str_key
+ arguments, kwargs = self.reduce_arguments(cur.args, key_resolver=resolve_key, duplicate_key_error='Duplicate dictionary key: {}')
+ assert not arguments
+ return kwargs
def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]:
v = self.evaluate_statement(cur.value)
@@ -722,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
@@ -868,7 +879,8 @@ 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]
+ # The cast is required because we don't have recursive types...
+ return T.cast(TYPE_var, iobject[index])
except KeyError:
raise InterpreterException('Key %s is not in dict' % index)
else:
@@ -893,7 +905,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
@@ -935,7 +947,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]:
@@ -1068,7 +1080,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))
@@ -1080,7 +1092,7 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InvalidCode('Unknown function "%s".' % func_name)
@builtinMethodNoKwargs
- def array_method_call(self, obj: list, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var:
+ def array_method_call(self, obj: T.List[TYPE_var], method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var:
if method_name == 'contains':
def check_contains(el: list) -> bool:
if len(posargs) != 1:
@@ -1121,7 +1133,7 @@ The result of this is undefined and will become a hard error in a future Meson 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:
@@ -1157,7 +1169,12 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InterpreterException('Dictionaries do not have a method called "%s".' % method_name)
- def reduce_arguments(self, args: mparser.ArgumentNode, resolve_key_nodes: bool = True) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]:
+ def reduce_arguments(
+ self,
+ args: mparser.ArgumentNode,
+ key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key,
+ duplicate_key_error: T.Optional[str] = None,
+ ) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]:
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
@@ -1165,13 +1182,12 @@ The result of this is undefined and will become a hard error in a future Meson r
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: T.List[TYPE_nvar]
reduced_kw = {} # type: TYPE_nkwargs
for key, val in args.kwargs.items():
- reduced_key = key # type: T.Union[str, mparser.BaseNode]
+ reduced_key = key_resolver(key)
reduced_val = val # type: TYPE_nvar
- if resolve_key_nodes and isinstance(key, mparser.IdNode):
- assert isinstance(key.value, str)
- reduced_key = key.value
if isinstance(reduced_val, mparser.BaseNode):
reduced_val = self.evaluate_statement(reduced_val)
+ if duplicate_key_error and reduced_key in reduced_kw:
+ raise InvalidArguments(duplicate_key_error.format(reduced_key))
reduced_kw[reduced_key] = reduced_val
self.argument_depth -= 1
final_kw = self.expand_default_kwargs(reduced_kw)
@@ -1191,14 +1207,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:
@@ -1229,7 +1237,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: