From f352441ab7482b5af3fdcba88a7a8c804f8934b8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:05:02 +0200 Subject: [PATCH 01/12] interpreterbase, modules: remove dead field Signed-off-by: Paolo Bonzini --- mesonbuild/interpreterbase/interpreterbase.py | 1 - mesonbuild/modules/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 3899858f5d58..58568c6ed088 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -88,7 +88,6 @@ def __init__(self, source_root: str, subdir: str, subproject: SubProject, subpro self.coredata = env.get_coredata() self.variables: T.Dict[str, InterpreterObject] = {} self.argument_depth = 0 - self.current_lineno = -1 # Current node set during a function call. This can be used as location # when printing a warning message during a method call. self.current_node = mparser.BaseNode(-1, -1, 'sentinel') diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 26b7dcd21767..0c8cc39dde2c 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -43,7 +43,6 @@ def __init__(self, interpreter: 'Interpreter') -> None: self.subproject = interpreter.subproject self.subdir = interpreter.subdir self.root_subdir = interpreter.root_subdir - self.current_lineno = interpreter.current_node.lineno self.environment = interpreter.environment self.project_name = interpreter.active_projectname self.project_version = interpreter.project_version From a425553068b1134cecdcfe09f0f42f22b67e540f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:13:05 +0200 Subject: [PATCH 02/12] mparser: remove unnecessary asserts These are now guaranteed by typing annotations. Signed-off-by: Paolo Bonzini --- mesonbuild/interpreterbase/interpreterbase.py | 1 - mesonbuild/mparser.py | 6 ------ 2 files changed, 7 deletions(-) diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 58568c6ed088..271b9acb7525 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -596,7 +596,6 @@ def reduce_arguments( reduced_kw: T.Dict[str, InterpreterObject] = {} for key, val in args.kwargs.items(): reduced_key = key_resolver(key) - assert isinstance(val, mparser.BaseNode) reduced_val = self.evaluate_statement(val) if reduced_val is None: raise InvalidArguments(f'Value of key {reduced_key} is void.') diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index c8b9d9a2ccbf..9940441d8353 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -795,7 +795,6 @@ def e1(self) -> BaseNode: value = self.e1() if not isinstance(left, IdNode): raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno) - assert isinstance(left.value, str) return self.create_node(PlusAssignmentNode, left, operator, value) elif self.accept('assign'): operator = self.create_node(SymbolNode, self.previous) @@ -803,7 +802,6 @@ def e1(self) -> BaseNode: if not isinstance(left, IdNode): raise ParseException('Assignment target must be an id.', self.getline(), left.lineno, left.colno) - assert isinstance(left.value, str) return self.create_node(AssignmentNode, left, operator, value) elif self.accept('questionmark'): if self.in_ternary: @@ -904,7 +902,6 @@ def e8(self) -> BaseNode: if not isinstance(left, IdNode): raise ParseException('Function call must be applied to plain id', self.getline(), left.lineno, left.colno) - assert isinstance(left.value, str) left = self.create_node(FunctionNode, left, lpar, args, rpar) go_again = True while go_again: @@ -1005,7 +1002,6 @@ def method_call(self, source_object: BaseNode) -> MethodNode: self.getline(), source_object.lineno, source_object.colno) raise ParseException('Method name must be plain id', self.getline(), self.current.lineno, self.current.colno) - assert isinstance(methodname.value, str) self.expect('lparen') lpar = self.create_node(SymbolNode, self.previous) args = self.args() @@ -1026,14 +1022,12 @@ def index_call(self, source_object: BaseNode) -> IndexNode: def foreachblock(self) -> ForeachClauseNode: foreach_ = self.create_node(SymbolNode, self.previous) self.expect('id') - assert isinstance(self.previous.value, str) varnames = [self.create_node(IdNode, self.previous)] commas = [] if self.accept('comma'): commas.append(self.create_node(SymbolNode, self.previous)) self.expect('id') - assert isinstance(self.previous.value, str) varnames.append(self.create_node(IdNode, self.previous)) self.expect('colon') From 9b32ca0885f2f44b807e25d6f9d73e214407c8f3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:09:16 +0200 Subject: [PATCH 03/12] ast/interpreter: remove dead conditional While the typing annotations in ast/interpreter.py are a mess, the calls to reduce_arguments() are all from the tame evaluate_* functions. There is no way that the args argument is a list instead of an ArgumentNode. Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 035ee2af2f1b..bd85b687d5ce 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -41,7 +41,6 @@ ) from ..mparser import ( - ArgumentNode, ArithmeticNode, ArrayNode, AssignmentNode, @@ -355,15 +354,12 @@ def reduce_arguments( self.evaluate_statement(arg) for value in args.kwargs.values(): self.evaluate_statement(value) - if isinstance(args, ArgumentNode): - kwargs = {} - for key, val in args.kwargs.items(): - kwargs[key_resolver(key)] = val - if args.incorrect_order(): - raise InvalidArguments('All keyword arguments must be after positional arguments.') - return self.flatten_args(args.arguments), kwargs - else: - return self.flatten_args(args), {} + kwargs = {} + for key, val in args.kwargs.items(): + kwargs[key_resolver(key)] = val + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + return self.flatten_args(args.arguments), kwargs def evaluate_comparison(self, node: ComparisonNode) -> None: self.evaluate_statement(node.left) From 6824ebf074dbf96446b5e4c21c10fc74f007f8f0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:06:49 +0200 Subject: [PATCH 04/12] interpreter: use TYPE_var and TYPE_kwargs, not nvar/nkwargs Follow what is specified by TYPE_method_func. Signed-off-by: Paolo Bonzini --- mesonbuild/interpreter/interpreterobjects.py | 10 +++++----- mesonbuild/interpreterbase/__init__.py | 2 -- mesonbuild/interpreterbase/baseobjects.py | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 3ef37e78424d..329f01e314e6 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -35,7 +35,7 @@ from ..cmake.interpreter import CMakeInterpreter from ..dependencies.base import IncludeType from ..envconfig import MachineInfo - from ..interpreterbase import FeatureCheckBase, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs + from ..interpreterbase import FeatureCheckBase, TYPE_var, TYPE_kwargs from ..mesonlib import SubProject from .interpreter import Interpreter @@ -563,7 +563,7 @@ def configtool_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> str: @noPosargs @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) @InterpreterObject.method('partial_dependency') - def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: + def partial_dependency_method(self, args: T.List[TYPE_var], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) return pdep @@ -740,7 +740,7 @@ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: @noPosargs @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) @InterpreterObject.method('partial_dependency') - def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: + def partial_dependency_method(self, args: T.List[TYPE_var], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) return pdep @@ -1034,7 +1034,7 @@ def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: @noKwargs @typed_pos_args('extract_objects', varargs=(mesonlib.File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) @InterpreterObject.method('extract_objects') - def extract_objects_method(self, args: T.Tuple[T.List[str | build.TargetSources]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects: + def extract_objects_method(self, args: T.Tuple[T.List[str | build.TargetSources]], kwargs: TYPE_kwargs) -> build.ExtractedObjects: if self.subproject != self.held_object.subproject: raise InterpreterException('Tried to extract objects from a different subproject.') tobj = self._target_object @@ -1067,7 +1067,7 @@ def extract_objects_method(self, args: T.Tuple[T.List[str | build.TargetSources] ) ) @InterpreterObject.method('extract_all_objects') - def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: + def extract_all_objects_method(self, args: T.List[TYPE_var], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: return self._target_object.extract_all_objects(kwargs['recursive']) @noPosargs diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index dddb2027f3da..722a880ba82f 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -53,7 +53,6 @@ 'TYPE_var', 'TYPE_nvar', 'TYPE_kwargs', - 'TYPE_nkwargs', 'TYPE_key_resolver', 'TYPE_HoldableTypes', @@ -76,7 +75,6 @@ TYPE_var, TYPE_nvar, TYPE_kwargs, - TYPE_nkwargs, TYPE_key_resolver, TYPE_HoldableTypes, diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 39f3cebdcec6..cc4e49049618 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -26,7 +26,6 @@ TYPE_var: TypeAlias = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject', T.Sequence['TYPE_var'], T.Dict[str, 'TYPE_var']] TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_kwargs = T.Dict[str, TYPE_var] -TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] TYPE_op_arg = T.TypeVar('TYPE_op_arg', bound='TYPE_var', contravariant=True) TYPE_op_func = T.Callable[[TYPE_op_arg, TYPE_op_arg], TYPE_var] From 6023e5cec25e207bd03e98386ec52d818c171fd6 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:49:02 +0200 Subject: [PATCH 05/12] ast/interpreter: simplify flatten_args Remove unused argument as well as the non-list case. Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index bd85b687d5ce..a70503b9d271 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -725,13 +725,7 @@ def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[st rtvals: T.List[T.Any] = flatten([self.node_to_runtime_value(sn) for sn in nodes]) return [src_to_abs(x) for x in rtvals] - def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False) -> T.List[TYPE_var]: - # Make sure we are always dealing with lists - if isinstance(args_raw, list): - args = args_raw - else: - args = [args_raw] - + def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: # BaseNode resolves to Any. :/ flattened_args: T.List[T.Union[TYPE_var, T.Any]] = [] @@ -743,7 +737,7 @@ def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], incl if not isinstance(resolved, list): resolved = [resolved] flattened_args += resolved - elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)) or include_unknown_args: + elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)): flattened_args += [i] else: raise NotImplementedError From ff6eb46c45aa136916e91c7e323b6cfb24730ec0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 16:16:15 +0200 Subject: [PATCH 06/12] ast/interpreter: call out cases in which Any is needed by flatten_args The return type of flatten_args can actually be described more precisely than T.Any, but in some cases it is used to operate on variables described as T.List[TYPE_var] specifically and erroneously (because they can be Unknownvalue, etc.). Call those out specifically. Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 10 +++++++++- mesonbuild/ast/introspection.py | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index a70503b9d271..a5c5e9ee0e04 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -270,7 +270,7 @@ def load_root_meson_file(self) -> None: self.ast.accept(i) def func_subdir(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: - args = self.flatten_args(args) + args = self.flatten_args_hack(args) if len(args) != 1 or not isinstance(args[0], str): sys.stderr.write(f'Unable to evaluate subdir({args}) in AstInterpreter --> Skipping\n') return @@ -743,6 +743,14 @@ def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: raise NotImplementedError return flattened_args + def flatten_args_hack(self, args: T.List[TYPE_var]) -> T.List[TYPE_var]: + # Unlike method calls, functions are invoked even if one or more values + # are unknown. The types in this declaration are completely wrong; + # the right solution would involve making TYPE_var an argument to + # InterpreterBase, so that FunctionType is also changed to not + # use TYPE_var. + return self.flatten_args(args) + def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None: return Disabler(subproject=self.subproject) diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 1cc80ad843a2..02d8102f981c 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -121,7 +121,7 @@ def _str_list(node: T.Any) -> T.Optional[T.List[str]]: proj_vers = proj_vers.value if not isinstance(proj_vers, str): proj_vers = 'undefined' - proj_langs = self.flatten_args(args[1:]) + proj_langs = self.flatten_args_hack(args[1:]) # Match the value returned by ``meson.project_license()`` when # no ``license`` argument is specified in the ``project()`` call. proj_license = _str_list(kwargs.get('license', None)) or ['unknown'] @@ -174,7 +174,7 @@ def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.D def _add_languages(self, raw_langs: T.List[TYPE_var], required: T.Union[bool, UnknownValue], for_machine: MachineChoice) -> None: langs: T.List[Language] = [] - for l in self.flatten_args(raw_langs): + for l in self.flatten_args_hack(raw_langs): # we need to call .lower() here because `project('foo', 'CpP')` is valid. if isinstance(l, str): langs.append(T.cast('Language', l.lower())) @@ -194,7 +194,7 @@ def _add_languages(self, raw_langs: T.List[TYPE_var], required: T.Union[bool, Un def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[IntrospectionDependency]: assert isinstance(node, FunctionNode) - args = self.flatten_args(args) + args = self.flatten_args_hack(args) kwargs = self.flatten_kwargs(kwargs) if not args: return None @@ -223,7 +223,7 @@ def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict def build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs_raw: T.Dict[str, TYPE_var], targetclass: T.Type[BuildTarget]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: assert isinstance(node, FunctionNode) - args = self.flatten_args(args) + args = self.flatten_args_hack(args) if not args or not isinstance(args[0], str): return UnknownValue() name = args[0] From 1276e739abce6aae61639cd5d1f621c52e5ac509 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 19 Jun 2026 13:19:16 +0200 Subject: [PATCH 07/12] ast/interpreter: pass more Introspection* values through flatten_args --- mesonbuild/ast/interpreter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index a5c5e9ee0e04..85f46b13f6e3 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -737,10 +737,11 @@ def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: if not isinstance(resolved, list): resolved = [resolved] flattened_args += resolved - elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)): + elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile, + IntrospectionBuildTarget, IntrospectionDependency)): flattened_args += [i] else: - raise NotImplementedError + raise NotImplementedError(f'flatten_args is missing a case for {type(i)}') return flattened_args def flatten_args_hack(self, args: T.List[TYPE_var]) -> T.List[TYPE_var]: From 28d75eca5258a008d759f4c6e8f85ebabfafdd24 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 16:58:49 +0200 Subject: [PATCH 08/12] ast/interpreter, rewriter: make flatten_args() recursive This also enables using it instead of flatten(). Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 8 ++++---- mesonbuild/rewriter.py | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 85f46b13f6e3..60ec6927ce72 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -30,8 +30,6 @@ InterpreterObject, ) -from ..interpreterbase.helpers import flatten - from ..interpreter import ( StringHolder, BooleanHolder, @@ -722,7 +720,7 @@ def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[st else: raise TypeError - rtvals: T.List[T.Any] = flatten([self.node_to_runtime_value(sn) for sn in nodes]) + rtvals: T.List[T.Any] = self.flatten_args(nodes) return [src_to_abs(x) for x in rtvals] def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: @@ -736,10 +734,12 @@ def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: if resolved is not None: if not isinstance(resolved, list): resolved = [resolved] - flattened_args += resolved + flattened_args += self.flatten_args(resolved) elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile, IntrospectionBuildTarget, IntrospectionDependency)): flattened_args += [i] + elif isinstance(i, list): + flattened_args += self.flatten_args(i) else: raise NotImplementedError(f'flatten_args is missing a case for {type(i)}') return flattened_args diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index f4e0cd0ad1cd..b4cbd925edf5 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -12,7 +12,6 @@ from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter from .ast.interpreter import IntrospectionBuildTarget, IntrospectionDependency, _symbol from .interpreterbase import UnknownValue, TV_func -from .interpreterbase.helpers import flatten from mesonbuild.mesonlib import MesonException, pathname_sort_key, relpath, setup_vsenv from . import mlog, environment from functools import wraps @@ -761,7 +760,7 @@ def path_contains_unknowns(candidate: BaseNode) -> bool: tgt_function.args.kwargs[extra_files_idnode] = new_extra_files_node newfiles_relto = self.get_relto(target.node, chosen) - old_src_list: T.List[T.Any] = flatten([self.interpreter.node_to_runtime_value(sn) for sn in old]) + old_src_list: T.List[T.Any] = self.interpreter.flatten_args(list(old)) if op == 'src_add': name = 'Source' From 3646308bd93e7ba4e3b1ac954bd2ed89410d096f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 15:56:35 +0200 Subject: [PATCH 09/12] ast/interpreter: do short circuit evaluation The return value of "and" and "or" might be known even if one of the operands is unknown. Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 60ec6927ce72..05bc802cc930 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -625,15 +625,19 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) elif isinstance(node, mparser.OrNode): left = self.node_to_runtime_value(node.left) right = self.node_to_runtime_value(node.right) + if left is True or right is True: + return True if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): return UnknownValue() - return left or right + return False elif isinstance(node, mparser.AndNode): left = self.node_to_runtime_value(node.left) right = self.node_to_runtime_value(node.right) + if left is False or right is False: + return False if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): return UnknownValue() - return left and right + return True elif isinstance(node, mparser.UMinusNode): val = self.node_to_runtime_value(node.value) if isinstance(val, UnknownValue): From 8a26a45ef119e64c864afbcbb66446e496a5bb65 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 16:13:56 +0200 Subject: [PATCH 10/12] ast/interpreter: make node_to_runtime_value() more precise for dicts Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 05bc802cc930..3f257633b71e 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -550,7 +550,17 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) elif isinstance(node, ArrayNode): return [self.node_to_runtime_value(x) for x in node.args.arguments] elif isinstance(node, mparser.DictNode): - return {self.node_to_runtime_value(k): self.node_to_runtime_value(v) for k, v in node.args.kwargs.items()} + result: T.Dict[str | UnknownValue, T.Any] = {} + for raw_k, raw_v in node.args.kwargs.items(): + k = self.node_to_runtime_value(raw_k) + if not isinstance(k, (str, UnknownValue)): + k = UnknownValue() + if isinstance(k, UnknownValue): + # there could be more than one unknown key, so add an unknown mapping + result[k] = UnknownValue() + else: + result[k] = self.node_to_runtime_value(raw_v) + return result elif isinstance(node, IdNode): assert len(self.dataflow_dag.tgt_to_srcs[node]) == 1 val = next(iter(self.dataflow_dag.tgt_to_srcs[node])) From 440b22a1a4f571b766c67619d9aa4a3f530a9d1f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 17:07:38 +0200 Subject: [PATCH 11/12] rewriter: give precise type to potential_deps Signed-off-by: Paolo Bonzini --- mesonbuild/rewriter.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index b4cbd925edf5..ab31c00171fd 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -449,7 +449,7 @@ def find_target(self, target: str) -> T.Optional[IntrospectionBuildTarget]: return None def find_dependency(self, dependency: str) -> T.Optional[IntrospectionDependency]: - potential_deps = [] + potential_deps: T.List[IntrospectionDependency] = [] for i in self.interpreter.dependencies: if i.name == dependency: potential_deps.append(i) @@ -457,8 +457,10 @@ def find_dependency(self, dependency: str) -> T.Optional[IntrospectionDependency checking_varnames = len(potential_deps) == 0 if checking_varnames: - potential_deps1 = self.all_assignments(dependency) - potential_deps = [self.interpreter.node_to_runtime_value(el) for el in potential_deps1 if isinstance(el, FunctionNode) and el.func_name.value == 'dependency'] + potential_deps1 = [self.interpreter.node_to_runtime_value(el) + for el in self.all_assignments(dependency) + if isinstance(el, FunctionNode) and el.func_name.value == 'dependency'] + potential_deps = T.cast('T.List[IntrospectionDependency]', potential_deps1) if not potential_deps: return None From 85c73555f6b2f5fda6f7735759ba960d5eec41cb Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 18 Jun 2026 17:06:52 +0200 Subject: [PATCH 12/12] ast/interpreter: make types closer to the truth reduce_arguments and flatten_args operate on values that are TYPE_var | mparser.BaseNode | some other types, but this is hidden behind Any. Introduce TYPE_ivar and TYPE_ikwargs to make the representation a bit closer to the truth. Of course, because this replaces Any, a lot of casts and "type: ignore" comments are needed. Signed-off-by: Paolo Bonzini --- mesonbuild/ast/interpreter.py | 79 +++++++++++++---------- mesonbuild/interpreterbase/__init__.py | 2 - mesonbuild/interpreterbase/baseobjects.py | 1 - 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 3f257633b71e..0d7e4288490d 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -57,7 +57,7 @@ if T.TYPE_CHECKING: from .visitor import AstVisitor from ..interpreter import Interpreter - from ..interpreterbase import TYPE_var, TYPE_nvar + from ..interpreterbase import TYPE_var, TYPE_kwargs from ..mparser import ( AndNode, ComparisonNode, @@ -68,7 +68,13 @@ TestCaseClauseNode, UMinusNode, ) - from ..mesonlib import SubProject + from ..mesonlib import HoldableObject, SubProject + + TYPE_ivar = T.Union[str, int, bool, 'HoldableObject', 'MesonInterpreterObject', + 'UnknownValue', 'IntrospectionBuildTarget', 'IntrospectionFile', + 'IntrospectionDependency', list['TYPE_ivar'], dict[str | UnknownValue, 'TYPE_ivar']] + TYPE_nvar = T.Union[TYPE_ivar, mparser.BaseNode] + TYPE_nkwargs = T.Dict[str, TYPE_nvar] _T = T.TypeVar('_T') _V = T.TypeVar('_V') @@ -282,24 +288,28 @@ def func_subdir(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str buildfilename = os.path.join(subdir, environment.build_filename) sys.stderr.write(f'Unable to find build file {buildfilename} --> Skipping\n') - def inner_method_call(self, obj: BaseNode, method_name: str, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any: - for arg in itertools.chain(args, kwargs.values()): + def inner_method_call(self, iobj: TYPE_ivar, method_name: str, iargs: T.List[TYPE_ivar], nkwargs: TYPE_nkwargs) -> TYPE_ivar: + for arg in itertools.chain(iargs, nkwargs.values()): if isinstance(arg, UnknownValue): return UnknownValue() - if isinstance(obj, str): - result = StringHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) - elif isinstance(obj, bool): - result = BooleanHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) - elif isinstance(obj, int): - result = IntegerHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) - elif isinstance(obj, list): + args = T.cast('list[TYPE_var]', iargs) + kwargs = T.cast('TYPE_kwargs', nkwargs) + if isinstance(iobj, str): + result = StringHolder(iobj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(iobj, bool): + result = BooleanHolder(iobj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(iobj, int): + result = IntegerHolder(iobj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(iobj, list): + obj = T.cast('list[TYPE_var]', iobj) # assuming ArrayHolder is fine with receiving TYPE_ivars... result = ArrayHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) - elif isinstance(obj, dict): + elif isinstance(iobj, dict): + obj = T.cast('dict[str, TYPE_var]', iobj) # assuming DictHolder is fine with receiving TYPE_ivars... result = DictHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) else: return UnknownValue() - return result + return T.cast('TYPE_ivar', result) def method_call(self, node: mparser.MethodNode) -> None: invocable = node.source_object @@ -342,17 +352,19 @@ def evaluate_indexing(self, node: IndexNode) -> None: self.evaluate_statement(node.iobject) self.evaluate_statement(node.index) - def reduce_arguments( + # this returns a different type than the supertype; TYPE_nvar and TYPE_nkwargs + # are limited to the AstInterpreter + def reduce_arguments( # type: ignore[override] 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[T.Any], T.Any]: + ) -> T.Tuple[T.List[TYPE_ivar], TYPE_nkwargs]: for arg in args.arguments: self.evaluate_statement(arg) for value in args.kwargs.values(): self.evaluate_statement(value) - kwargs = {} + kwargs: TYPE_nkwargs = {} for key, val in args.kwargs.items(): kwargs[key_resolver(key)] = val if args.incorrect_order(): @@ -537,7 +549,7 @@ def get_cur_value(self, var_name: str) -> T.Union[BaseNode, UnknownValue]: # node = [123, somedep.found()] # ``` # `node_to_runtime_value` will return `[123, UnknownValue()]`. - def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) -> T.Any: + def node_to_runtime_value(self, node: T.Union[UnknownValue, TYPE_ivar, mparser.BaseNode]) -> TYPE_ivar: if isinstance(node, (mparser.StringNode, mparser.BooleanNode, mparser.NumberNode)): return node.value elif isinstance(node, mparser.StringNode): @@ -550,7 +562,7 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) elif isinstance(node, ArrayNode): return [self.node_to_runtime_value(x) for x in node.args.arguments] elif isinstance(node, mparser.DictNode): - result: T.Dict[str | UnknownValue, T.Any] = {} + result: T.Dict[str | UnknownValue, TYPE_ivar] = {} for raw_k, raw_v in node.args.kwargs.items(): k = self.node_to_runtime_value(raw_k) if not isinstance(k, (str, UnknownValue)): @@ -572,8 +584,8 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) else: return self.node_to_runtime_value(funcval) elif isinstance(node, ArithmeticNode): - left = self.node_to_runtime_value(node.left) - right = self.node_to_runtime_value(node.right) + left: TYPE_ivar = self.node_to_runtime_value(node.left) + right: TYPE_ivar = self.node_to_runtime_value(node.right) if isinstance(left, list) and isinstance(right, UnknownValue): return left + [right] if isinstance(right, list) and isinstance(left, UnknownValue): @@ -590,11 +602,11 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) if not isinstance(right, list): right = [right] return left + right - return left + right + return left + right # type: ignore[operator] elif node.operation == '-': - return left - right + return left - right # type: ignore[operator] elif node.operation == '*': - return left * right + return left * right # type: ignore[operator] elif node.operation == '/': if isinstance(left, int) and isinstance(right, int): return left // right @@ -610,7 +622,7 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) index = self.node_to_runtime_value(node.index) if isinstance(iobject, UnknownValue) or isinstance(index, UnknownValue): return UnknownValue() - return iobject[index] + return iobject[index] # type: ignore[index] elif isinstance(node, mparser.ComparisonNode): left = self.node_to_runtime_value(node.left) right = self.node_to_runtime_value(node.right) @@ -621,9 +633,9 @@ def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) elif node.ctype == '!=': return left != right elif node.ctype == 'in': - return left in right + return left in right # type: ignore[operator] elif node.ctype == 'not in': - return left not in right + return left not in right # type: ignore[operator] elif isinstance(node, mparser.TernaryNode): cond = self.node_to_runtime_value(node.condition) if isinstance(cond, UnknownValue): @@ -724,7 +736,7 @@ def func_unset_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T. self.cur_assignments[var_name].append((self.nesting.copy(), node)) def nodes_to_pretty_filelist(self, root_path: Path, subdir: str, nodes: T.List[BaseNode]) -> T.List[T.Union[str, UnknownValue]]: - def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[str, UnknownValue]: + def src_to_abs(src: TYPE_ivar) -> T.Union[str, UnknownValue]: if isinstance(src, str): return os.path.normpath(os.path.join(root_path, subdir, src)) elif isinstance(src, IntrospectionFile): @@ -737,9 +749,8 @@ def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[st rtvals: T.List[T.Any] = self.flatten_args(nodes) return [src_to_abs(x) for x in rtvals] - def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: - # BaseNode resolves to Any. :/ - flattened_args: T.List[T.Union[TYPE_var, T.Any]] = [] + def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_ivar]: + flattened_args: T.List[TYPE_ivar] = [] # Resolve the contents of args for i in args: @@ -760,11 +771,13 @@ def flatten_args(self, args: T.Sequence[TYPE_nvar]) -> T.List[TYPE_var]: def flatten_args_hack(self, args: T.List[TYPE_var]) -> T.List[TYPE_var]: # Unlike method calls, functions are invoked even if one or more values - # are unknown. The types in this declaration are completely wrong; - # the right solution would involve making TYPE_var an argument to + # are unknown. The return type is actually T.List[TYPE_ivar], and + # while callers declare args as T.List[TYPE_var], they receive a + # list of TYPE_ivar too. + # The right solution would involve making TYPE_var an argument to # InterpreterBase, so that FunctionType is also changed to not # use TYPE_var. - return self.flatten_args(args) + return T.cast('T.List[TYPE_var]', self.flatten_args(T.cast('T.List[TYPE_ivar]', args))) def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None: return Disabler(subproject=self.subproject) diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index 722a880ba82f..6873cab8f02b 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -51,7 +51,6 @@ 'TV_func', 'TYPE_elementary', 'TYPE_var', - 'TYPE_nvar', 'TYPE_kwargs', 'TYPE_key_resolver', 'TYPE_HoldableTypes', @@ -73,7 +72,6 @@ TV_func, TYPE_elementary, TYPE_var, - TYPE_nvar, TYPE_kwargs, TYPE_key_resolver, TYPE_HoldableTypes, diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index cc4e49049618..8dcf081feb41 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -24,7 +24,6 @@ TYPE_elementary: TypeAlias = T.Union[str, int, bool, T.Sequence['TYPE_elementary'], T.Dict[str, 'TYPE_elementary']] TYPE_var: TypeAlias = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject', T.Sequence['TYPE_var'], T.Dict[str, 'TYPE_var']] -TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_kwargs = T.Dict[str, TYPE_var] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] TYPE_op_arg = T.TypeVar('TYPE_op_arg', bound='TYPE_var', contravariant=True)