diff options
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 128 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 19 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/_unholder.py | 37 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/decorators.py | 12 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/helpers.py | 8 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/interpreterbase.py | 149 |
7 files changed, 251 insertions, 104 deletions
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ea36288..13be8e4 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -21,33 +21,35 @@ from .. import optinterpreter from .. import compilers from ..wrap import wrap, WrapMode from .. import mesonlib -from ..mesonlib import FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep, unholder +from ..mesonlib import HoldableObject, FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep from ..programs import ExternalProgram, NonExistingExternalProgram from ..dependencies import Dependency from ..depfile import DepFile from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args -from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening +from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening, unholder_return from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from ..interpreterbase import InterpreterObject, Disabler, disablerIfNotFound from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs from ..interpreterbase import ObjectHolder, RangeHolder +from ..interpreterbase import TYPE_nkwargs, TYPE_nvar, TYPE_var from ..modules import ModuleObject, MutableModuleObject from ..cmake import CMakeInterpreter from ..backend.backends import Backend, ExecutableSerialisation +from . import interpreterobjects as OBJ +from . import compiler as compilerOBJ from .mesonmain import MesonMain -from .compiler import CompilerHolder -from .interpreterobjects import (SubprojectHolder, MachineHolder, EnvironmentVariablesHolder, - FeatureOptionHolder, ExternalProgramHolder, CustomTargetHolder, - RunTargetHolder, IncludeDirsHolder, ConfigurationDataHolder, - DependencyHolder, ModuleObjectHolder, GeneratedListHolder, - TargetHolder, CustomTargetIndexHolder, GeneratedObjectsHolder, - StaticLibraryHolder, ExecutableHolder, SharedLibraryHolder, - SharedModuleHolder, HeadersHolder, BothLibrariesHolder, - BuildTargetHolder, DataHolder, JarHolder, Test, RunProcess, - ManHolder, GeneratorHolder, InstallDirHolder, extract_required_kwarg, - extract_search_dirs, MutableModuleObjectHolder, FileHolder) from .dependencyfallbacks import DependencyFallbacksHolder +from .interpreterobjects import ( + SubprojectHolder, + EnvironmentVariablesObject, + ConfigurationDataObject, + Test, + RunProcess, + extract_required_kwarg, + extract_search_dirs, + NullSubprojectInterpreter, +) from pathlib import Path import os @@ -386,45 +388,70 @@ class Interpreter(InterpreterBase, HoldableObject): if 'MESON_UNIT_TEST' in os.environ: self.funcs.update({'exception': self.func_exception}) - def holderify(self, item): - if isinstance(item, list): - return [self.holderify(x) for x in item] - if isinstance(item, dict): - return {k: self.holderify(v) for k, v in item.items()} - - if isinstance(item, build.CustomTarget): - return CustomTargetHolder(item, self) - elif isinstance(item, (int, str, bool, InterpreterObject)) or item is None: - return item - elif isinstance(item, build.Executable): - return ExecutableHolder(item, self) - elif isinstance(item, build.GeneratedList): - return GeneratedListHolder(item) - elif isinstance(item, build.RunTarget): - raise RuntimeError('This is not a pipe.') - elif isinstance(item, ExecutableSerialisation): - raise RuntimeError('Do not do this.') - elif isinstance(item, build.Data): - return DataHolder(item) - elif isinstance(item, dependencies.Dependency): - return DependencyHolder(item, self.subproject) - elif isinstance(item, ExternalProgram): - return ExternalProgramHolder(item, self.subproject) - elif isinstance(item, MutableModuleObject): - return MutableModuleObjectHolder(item, self) - elif isinstance(item, ModuleObject): - return ModuleObjectHolder(item, self) - elif isinstance(item, mesonlib.File): - return FileHolder(item) - else: - raise InterpreterException('Module returned a value of unknown type.') - - def process_new_values(self, invalues): + def build_holder_map(self) -> None: + ''' + Build a mapping of `HoldableObject` types to their corresponding + `ObjectHolder`s. This mapping is used in `InterpreterBase` to automatically + holderify all returned values from methods and functions. + ''' + self.holder_map.update({ + mesonlib.File: OBJ.FileHolder, + build.SharedLibrary: OBJ.SharedLibraryHolder, + build.StaticLibrary: OBJ.StaticLibraryHolder, + build.BothLibraries: OBJ.BothLibrariesHolder, + build.SharedModule: OBJ.SharedModuleHolder, + build.Executable: OBJ.ExecutableHolder, + build.Jar: OBJ.JarHolder, + build.CustomTarget: OBJ.CustomTargetHolder, + build.CustomTargetIndex: OBJ.CustomTargetIndexHolder, + build.Generator: OBJ.GeneratorHolder, + build.GeneratedList: OBJ.GeneratedListHolder, + build.ExtractedObjects: OBJ.GeneratedObjectsHolder, + build.RunTarget: OBJ.RunTargetHolder, + build.AliasTarget: OBJ.AliasTargetHolder, + build.Headers: OBJ.HeadersHolder, + build.Man: OBJ.ManHolder, + build.Data: OBJ.DataHolder, + build.InstallDir: OBJ.InstallDirHolder, + build.IncludeDirs: OBJ.IncludeDirsHolder, + compilers.RunResult: compilerOBJ.TryRunResultHolder, + dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder, + coredata.UserFeatureOption: OBJ.FeatureOptionHolder, + }) + + ''' + Build a mapping of `HoldableObject` base classes to their + corresponding `ObjectHolder`s. The difference to `self.holder_map` + is that the keys here define an upper bound instead of requireing an + exact match. + + The mappings defined here are only used when there was no direct hit + found in `self.holder_map`. + ''' + self.bound_holder_map.update({ + dependencies.Dependency: OBJ.DependencyHolder, + ExternalProgram: OBJ.ExternalProgramHolder, + compilers.Compiler: compilerOBJ.CompilerHolder, + ModuleObject: OBJ.ModuleObjectHolder, + MutableModuleObject: OBJ.MutableModuleObjectHolder, + }) + + def append_holder_map(self, held_type: T.Type[mesonlib.HoldableObject], holder_type: T.Type[ObjectHolder]) -> None: + ''' + Adds one additional mapping to the `holder_map`. + + The intended use for this function is in the `initialize` method of + modules to register custom object holders. + ''' + self.holder_map.update({ + held_type: holder_type + }) + + def process_new_values(self, invalues: T.List[TYPE_var]) -> None: invalues = listify(invalues) for v in invalues: - if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)): - v = v.held_object - + if isinstance(v, ObjectHolder): + raise InterpreterException('Modules must not return ObjectHolders') if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)): self.add_target(v.name, v) elif isinstance(v, list): @@ -2647,6 +2674,7 @@ This will become a hard error in the future.''', location=self.current_node) @noKwargs @noArgsFlattening + @unholder_return def func_get_variable(self, node, args, kwargs): if len(args) < 1 or len(args) > 2: raise InvalidCode('Get_variable takes one or two arguments.') diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index ac37f3a..8b62496 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -13,15 +13,13 @@ from .. import mlog from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule from ..backend.backends import TestProtocol -from ..interpreterbase import (ContainerTypeInfo, KwargInfo, - MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, - FeatureNewKwargs, FeatureNew, FeatureDeprecated, - typed_kwargs, typed_pos_args, stringArgs, - permittedKwargs, noArgsFlattening, noPosargs, - TYPE_var, TYPE_nkwargs, flatten, - InterpreterException, InvalidArguments, - InvalidCode) -from ..interpreterbase.decorators import FeatureCheckBase +from ..interpreterbase import ( + ContainerTypeInfo, KwargInfo, + InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, + FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated, + typed_pos_args, typed_kwargs, KwargInfo, stringArgs, permittedKwargs, + noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs, + flatten, InterpreterException, InvalidArguments, InvalidCode) from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..programs import ExternalProgram from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe @@ -751,7 +749,8 @@ class SubprojectHolder(ObjectHolder[T.Optional['Interpreter']]): @permittedKwargs({}) @noArgsFlattening - def get_variable_method(self, args, kwargs): + @unholder_return + def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if len(args) < 1 or len(args) > 2: raise InterpreterException('Get_variable takes one or two arguments.') if not self.found(): diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index 1d5c75f..a4163c3 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -39,6 +39,7 @@ __all__ = [ 'noKwargs', 'stringArgs', 'noArgsFlattening', + 'unholder_return', 'disablerIfNotFound', 'permittedKwargs', 'typed_pos_args', @@ -87,6 +88,7 @@ from .decorators import ( noKwargs, stringArgs, noArgsFlattening, + unholder_return, disablerIfNotFound, permittedKwargs, typed_pos_args, diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py new file mode 100644 index 0000000..b5663a5 --- /dev/null +++ b/mesonbuild/interpreterbase/_unholder.py @@ -0,0 +1,37 @@ +# Copyright 2013-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, TYPE_var +from .exceptions import InvalidArguments +from ..mesonlib import HoldableObject, MesonBugException + +import typing as T + +def _unholder(obj: T.Union[TYPE_var, InterpreterObject]) -> TYPE_var: + if isinstance(obj, (int, bool, str)): + return obj + elif isinstance(obj, list): + return [_unholder(x) for x in obj] + elif isinstance(obj, dict): + return {k: _unholder(v) for k, v in obj.items()} + elif isinstance(obj, ObjectHolder): + assert isinstance(obj.held_object, HoldableObject) + return obj.held_object + elif isinstance(obj, MesonInterpreterObject): + return obj + elif isinstance(obj, HoldableObject): + raise MesonBugException(f'Argument {obj} of type {type(obj).__name__} is not held by an ObjectHolder.') + elif isinstance(obj, InterpreterObject): + raise InvalidArguments(f'Argument {obj} of type {type(obj).__name__} cannot be passed to a method or function') + raise MesonBugException(f'Unknown object {obj} of type {type(obj).__name__} in the parameters.') diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 5fe8653..a011b66 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -13,10 +13,11 @@ # limitations under the License. from .. import mesonlib, mlog -from .baseobjects import TV_func, TYPE_nvar +from .baseobjects import TV_func, TYPE_var from .disabler import Disabler from .exceptions import InterpreterException, InvalidArguments from .helpers import check_stringlist, get_callee_args +from ._unholder import _unholder from functools import wraps import abc @@ -67,13 +68,20 @@ def noArgsFlattening(f: TV_func) -> TV_func: setattr(f, 'no-args-flattening', True) # noqa: B010 return f +def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]: + @wraps(f) + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: + res = f(*wrapped_args, **wrapped_kwargs) + return _unholder(res) + return T.cast(T.Callable[..., TYPE_var], wrapped) + def disablerIfNotFound(f: TV_func) -> TV_func: @wraps(f) 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(): + if disabler and not ret.found(): return Disabler() return ret return T.cast(TV_func, wrapped) diff --git a/mesonbuild/interpreterbase/helpers.py b/mesonbuild/interpreterbase/helpers.py index 1070a9e..2602e80 100644 --- a/mesonbuild/interpreterbase/helpers.py +++ b/mesonbuild/interpreterbase/helpers.py @@ -19,15 +19,15 @@ import collections.abc import typing as T if T.TYPE_CHECKING: - from .baseobjects import TYPE_nvar, TV_fw_args, TV_fw_kwargs + from .baseobjects import TYPE_var, TYPE_kwargs -def flatten(args: T.Union['TYPE_nvar', T.List['TYPE_nvar']]) -> T.List['TYPE_nvar']: +def flatten(args: T.Union['TYPE_var', T.List['TYPE_var']]) -> T.List['TYPE_var']: if isinstance(args, mparser.StringNode): assert isinstance(args.value, str) return [args.value] if not isinstance(args, collections.abc.Sequence): return [args] - result: T.List['TYPE_nvar'] = [] + result: T.List['TYPE_var'] = [] for a in args: if isinstance(a, list): rest = flatten(a) @@ -51,7 +51,7 @@ def default_resolve_key(key: mparser.BaseNode) -> str: raise InterpreterException('Invalid kwargs format.') return key.value -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]]: +def get_callee_args(wrapped_args: T.Sequence[T.Any], want_subproject: bool = False) -> T.Tuple[T.Any, mparser.BaseNode, T.List['TYPE_var'], 'TYPE_kwargs', T.Optional[str]]: s = wrapped_args[0] n = len(wrapped_args) # Raise an error if the codepaths are not there diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index be90049..0acb699 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -20,13 +20,15 @@ from .. import environment, dependencies from .baseobjects import ( InterpreterObject, + MesonInterpreterObject, MutableInterpreterObject, + InterpreterObjectTypeVar, ObjectHolder, RangeHolder, + TYPE_elementary, TYPE_var, - TYPE_nvar, - TYPE_nkwargs, + TYPE_kwargs, ) from .exceptions import ( @@ -41,10 +43,24 @@ from .exceptions import ( from .decorators import FeatureNew, builtinMethodNoKwargs from .disabler import Disabler, is_disabled from .helpers import check_stringlist, default_resolve_key, flatten +from ._unholder import _unholder import os, copy, re import typing as T +if T.TYPE_CHECKING: + from ..interpreter import Interpreter + +HolderMapType = T.Dict[ + T.Type[mesonlib.HoldableObject], + # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar] + T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]] +] + +FunctionType = T.Dict[ + str, + T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var] +] class MesonVersionString(str): pass @@ -54,12 +70,16 @@ class InterpreterBase: def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root - self.funcs = {} # type: T.Dict[str, T.Callable[[mparser.BaseNode, T.List[TYPE_nvar], T.Dict[str, TYPE_nvar]], TYPE_var]] - self.builtin = {} # type: T.Dict[str, InterpreterObject] + self.funcs: FunctionType = {} + self.builtin: T.Dict[str, InterpreterObject] = {} + # Holder maps store a mapping from an HoldableObject to a class ObjectHolder + self.holder_map: HolderMapType = {} + self.bound_holder_map: HolderMapType = {} self.subdir = subdir self.root_subdir = subdir self.subproject = subproject - self.variables = {} # type: T.Dict[str, TYPE_var] + # TODO: This should actually be more strict: T.Union[TYPE_elementary, InterpreterObject] + self.variables: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} self.argument_depth = 0 self.current_lineno = -1 # Current node set during a function call. This can be used as location @@ -137,7 +157,7 @@ class InterpreterBase: raise e i += 1 # In THE FUTURE jump over blocks and stuff. - def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[TYPE_var]: + def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: self.current_node = cur if isinstance(cur, mparser.FunctionNode): return self.function_call(cur) @@ -191,14 +211,14 @@ class InterpreterBase: raise InvalidCode("Unknown statement.") return None - def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> list: + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> T.List[T.Union[TYPE_var, InterpreterObject]]: (arguments, kwargs) = self.reduce_arguments(cur.args) if len(kwargs) > 0: raise InvalidCode('Keyword arguments are invalid in array construction.') return arguments @FeatureNew('dict', '0.47.0') - def evaluate_dictstatement(self, cur: mparser.DictNode) -> TYPE_nkwargs: + def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Union[TYPE_var, InterpreterObject]: 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) @@ -387,7 +407,7 @@ The result of this is undefined and will become a hard error in a future Meson r else: raise InvalidCode('You broke me.') - def evaluate_ternary(self, node: mparser.TernaryNode) -> TYPE_var: + def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Union[TYPE_var, InterpreterObject]: assert(isinstance(node, mparser.TernaryNode)) result = self.evaluate_statement(node.condition) if isinstance(result, Disabler): @@ -479,7 +499,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints') self.set_variable(varname, new_value) - def evaluate_indexing(self, node: mparser.IndexNode) -> TYPE_var: + def evaluate_indexing(self, node: mparser.IndexNode) -> T.Union[TYPE_elementary, InterpreterObject]: assert(isinstance(node, mparser.IndexNode)) iobject = self.evaluate_statement(node.iobject) if isinstance(iobject, Disabler): @@ -494,7 +514,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Key is not a string') try: # The cast is required because we don't have recursive types... - return T.cast(TYPE_var, iobject[index]) + return T.cast(T.Union[TYPE_elementary, InterpreterObject], iobject[index]) except KeyError: raise InterpreterException('Key %s is not in dict' % index) else: @@ -504,35 +524,45 @@ The result of this is undefined and will become a hard error in a future Meson r # Ignore the MyPy error, since we don't know all indexable types here # and we handle non indexable types with an exception # TODO maybe find a better solution - return iobject[index] # type: ignore + res = iobject[index] # type: ignore + # Only holderify if we are dealing with `InterpreterObject`, since raw + # lists already store ObjectHolders + if isinstance(iobject, InterpreterObject): + return self._holderify(res) + else: + return res except IndexError: # We are already checking for the existence of __getitem__, so this should be save raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore - def function_call(self, node: mparser.FunctionNode) -> T.Optional[TYPE_var]: + def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_elementary, InterpreterObject]]: func_name = node.func_name - (posargs, kwargs) = self.reduce_arguments(node.args) + (h_posargs, h_kwargs) = self.reduce_arguments(node.args) + (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) if is_disabled(posargs, kwargs) and func_name not in {'get_variable', 'set_variable', 'is_disabler'}: return Disabler() if func_name in self.funcs: func = self.funcs[func_name] - func_args = posargs # type: T.Any + func_args = posargs if not getattr(func, 'no-args-flattening', False): func_args = flatten(posargs) - return func(node, func_args, kwargs) + res = func(node, func_args, kwargs) + return self._holderify(res) else: self.unknown_function_called(func_name) return None - def method_call(self, node: mparser.MethodNode) -> TYPE_var: + def method_call(self, node: mparser.MethodNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: invokable = node.source_object + obj: T.Union[TYPE_var, InterpreterObject] if isinstance(invokable, mparser.IdNode): object_name = invokable.value obj = self.get_variable(object_name) else: obj = self.evaluate_statement(invokable) method_name = node.name - (args, kwargs) = self.reduce_arguments(node.args) + (h_args, h_kwargs) = self.reduce_arguments(node.args) + (args, kwargs) = self._unholder_args(h_args, h_kwargs) if is_disabled(args, kwargs): return Disabler() if isinstance(obj, str): @@ -554,15 +584,48 @@ The result of this is undefined and will become a hard error in a future Meson r return False else: return Disabler() + # TODO: InterpreterBase **really** shouldn't be in charge of checking this if method_name == 'extract_objects': if not isinstance(obj, ObjectHolder): - raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}"') + raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}') self.validate_extraction(obj.held_object) obj.current_node = node - return obj.method_call(method_name, args, kwargs) + return self._holderify(obj.method_call(method_name, args, kwargs)) + + def _holderify(self, res: T.Optional[TYPE_var]) -> T.Union[TYPE_elementary, InterpreterObject]: + if res is None: + return None + if isinstance(res, (int, bool, str)): + return res + elif isinstance(res, list): + return [self._holderify(x) for x in res] + elif isinstance(res, dict): + return {k: self._holderify(v) for k, v in res.items()} + elif isinstance(res, mesonlib.HoldableObject): + # Always check for an exact match first. + cls = self.holder_map.get(type(res), None) + if cls is not None: + # Casts to Interpreter are required here since an assertion would + # not work for the `ast` module. + return cls(res, T.cast('Interpreter', self)) + # Try the boundary types next. + for typ, cls in self.bound_holder_map.items(): + if isinstance(res, typ): + return cls(res, T.cast('Interpreter', self)) + raise mesonlib.MesonBugException(f'Object {res} of type {type(res).__name__} is neither in self.holder_map nor self.bound_holder_map.') + elif isinstance(res, ObjectHolder): + raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.') + elif isinstance(res, MesonInterpreterObject): + return res + raise mesonlib.MesonBugException(f'Unknown returned object {res} of type {type(res).__name__} in the parameters.') + + def _unholder_args(self, + args: T.List[T.Union[TYPE_var, InterpreterObject]], + kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: + return [_unholder(x) for x in args], {k: _unholder(v) for k, v in kwargs.items()} @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]: + def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int]: if method_name == 'to_string': if not posargs: if obj: @@ -585,7 +648,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) @builtinMethodNoKwargs - def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, bool]: + def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, bool]: if method_name == 'is_even': if not posargs: return obj % 2 == 0 @@ -605,7 +668,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Unknown method "%s" for an integer.' % method_name) @staticmethod - def _get_one_string_posarg(posargs: T.List[TYPE_nvar], method_name: str) -> str: + def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str: if len(posargs) > 1: m = '{}() must have zero or one arguments' raise InterpreterException(m.format(method_name)) @@ -618,7 +681,7 @@ The result of this is undefined and will become a hard error in a future Meson r return None @builtinMethodNoKwargs - def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int, bool, T.List[str]]: + def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool, T.List[str]]: if method_name == 'strip': s1 = self._get_one_string_posarg(posargs, 'strip') if s1 is not None: @@ -690,7 +753,7 @@ The result of this is undefined and will become a hard error in a future Meson r return obj.replace(posargs[0], posargs[1]) raise InterpreterException('Unknown method "%s" for a string.' % method_name) - def format_string(self, templ: str, args: T.List[TYPE_nvar]) -> str: + def format_string(self, templ: str, args: T.List[TYPE_var]) -> str: arg_strings = [] for arg in args: if isinstance(arg, mparser.BaseNode): @@ -711,7 +774,11 @@ 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: T.List[TYPE_var], method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: + def array_method_call(self, + obj: T.List[T.Union[TYPE_elementary, InterpreterObject]], + method_name: str, + posargs: T.List[TYPE_var], + kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if method_name == 'contains': def check_contains(el: list) -> bool: if len(posargs) != 1: @@ -752,7 +819,11 @@ 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: T.Dict[str, TYPE_var], 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, T.Union[TYPE_elementary, InterpreterObject]], + method_name: str, + posargs: T.List[TYPE_var], + kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if method_name in ('has_key', 'get'): if method_name == 'has_key': if len(posargs) != 1: @@ -793,18 +864,20 @@ The result of this is undefined and will become a hard error in a future Meson r 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]: + ) -> T.Tuple[ + T.List[T.Union[TYPE_var, InterpreterObject]], + T.Dict[str, T.Union[TYPE_var, InterpreterObject]] + ]: assert(isinstance(args, mparser.ArgumentNode)) if args.incorrect_order(): raise InvalidArguments('All keyword arguments must be after positional arguments.') self.argument_depth += 1 - reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: T.List[TYPE_nvar] - reduced_kw = {} # type: TYPE_nkwargs + reduced_pos: T.List[T.Union[TYPE_var, InterpreterObject]] = [self.evaluate_statement(arg) for arg in args.arguments] + reduced_kw: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} for key, val in args.kwargs.items(): reduced_key = key_resolver(key) - reduced_val = val # type: TYPE_nvar - if isinstance(reduced_val, mparser.BaseNode): - reduced_val = self.evaluate_statement(reduced_val) + assert isinstance(val, mparser.BaseNode) + reduced_val = self.evaluate_statement(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 @@ -812,7 +885,7 @@ The result of this is undefined and will become a hard error in a future Meson r final_kw = self.expand_default_kwargs(reduced_kw) return reduced_pos, final_kw - def expand_default_kwargs(self, kwargs: TYPE_nkwargs) -> TYPE_nkwargs: + def expand_default_kwargs(self, kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Dict[str, T.Union[TYPE_var, InterpreterObject]]: if 'kwargs' not in kwargs: return kwargs to_expand = kwargs.pop('kwargs') @@ -843,7 +916,7 @@ To specify a keyword argument, use : instead of =.''') self.set_variable(var_name, value) return None - def set_variable(self, varname: str, variable: TYPE_var) -> None: + def set_variable(self, varname: str, variable: T.Union[TYPE_var, InterpreterObject]) -> None: if variable is None: raise InvalidCode('Can not assign None to variable.') if not isinstance(varname, str): @@ -856,7 +929,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: str) -> TYPE_var: + def get_variable(self, varname: str) -> T.Union[TYPE_var, InterpreterObject]: if varname in self.builtin: return self.builtin[varname] if varname in self.variables: @@ -865,7 +938,7 @@ To specify a keyword argument, use : instead of =.''') def is_assignable(self, value: T.Any) -> bool: return isinstance(value, (InterpreterObject, dependencies.Dependency, - str, int, list, dict)) + str, int, list, dict, mesonlib.File)) - def validate_extraction(self, buildtarget: InterpreterObject) -> None: + def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None: raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') |