aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mensinger <daniel@mensinger-ka.de>2021-06-17 00:07:04 +0200
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-06-18 23:48:33 +0200
commitc2c7f7c9d7b05dddb1cee028b1b685c7f2bd424c (patch)
tree7392e6220ddede2781662bcf0851080b4f0aa1ff
parent6879e84c48632bcd0f6c277e81b4032a00bb1d5c (diff)
downloadmeson-c2c7f7c9d7b05dddb1cee028b1b685c7f2bd424c.zip
meson-c2c7f7c9d7b05dddb1cee028b1b685c7f2bd424c.tar.gz
meson-c2c7f7c9d7b05dddb1cee028b1b685c7f2bd424c.tar.bz2
holders: Ensure that InterpreterBase is the sole instance for (un)holderifying
-rw-r--r--mesonbuild/interpreter/interpreter.py128
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py19
-rw-r--r--mesonbuild/interpreterbase/__init__.py2
-rw-r--r--mesonbuild/interpreterbase/_unholder.py37
-rw-r--r--mesonbuild/interpreterbase/decorators.py12
-rw-r--r--mesonbuild/interpreterbase/helpers.py8
-rw-r--r--mesonbuild/interpreterbase/interpreterbase.py149
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)')