aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/ast/interpreter.py3
-rw-r--r--mesonbuild/interpreter/__init__.py26
-rw-r--r--mesonbuild/interpreter/interpreter.py5
-rw-r--r--mesonbuild/interpreter/primitives/__init__.py8
-rw-r--r--mesonbuild/interpreter/primitives/integer.py84
-rw-r--r--mesonbuild/interpreterbase/__init__.py9
-rw-r--r--mesonbuild/interpreterbase/_unholder.py6
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py4
-rw-r--r--mesonbuild/interpreterbase/decorators.py2
-rw-r--r--mesonbuild/interpreterbase/interpreterbase.py100
-rwxr-xr-xrun_mypy.py1
-rw-r--r--test cases/failing/50 executable comparison/test.json3
12 files changed, 186 insertions, 65 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index 8c2be37..7e4bc62 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -368,7 +368,8 @@ class AstInterpreter(InterpreterBase):
elif isinstance(src, bool):
result = self.bool_method_call(src, node.name, margs, mkwargs)
elif isinstance(src, int):
- result = self.int_method_call(src, node.name, margs, mkwargs)
+ from ..interpreter import Interpreter, IntegerHolder
+ result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, list):
result = self.array_method_call(src, node.name, margs, mkwargs)
elif isinstance(src, dict):
diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py
index 62b09bf..0a7717f 100644
--- a/mesonbuild/interpreter/__init__.py
+++ b/mesonbuild/interpreter/__init__.py
@@ -16,6 +16,28 @@
"""Meson interpreter."""
+__all__ = [
+ 'Interpreter',
+ 'permitted_dependency_kwargs',
+
+ 'CompilerHolder',
+
+ 'ExecutableHolder',
+ 'BuildTargetHolder',
+ 'CustomTargetHolder',
+ 'CustomTargetIndexHolder',
+ 'MachineHolder',
+ 'Test',
+ 'ConfigurationDataObject',
+ 'SubprojectHolder',
+ 'DependencyHolder',
+ 'GeneratedListHolder',
+ 'ExternalProgramHolder',
+ 'extract_required_kwarg',
+
+ 'IntegerHolder',
+]
+
from .interpreter import Interpreter, permitted_dependency_kwargs
from .compiler import CompilerHolder
from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder,
@@ -23,3 +45,7 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg
ConfigurationDataObject, SubprojectHolder, DependencyHolder,
GeneratedListHolder, ExternalProgramHolder,
extract_required_kwarg)
+
+from .primitives import (
+ IntegerHolder,
+)
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 1a34885..db4f9e9 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -58,6 +58,7 @@ from .type_checking import (
NoneType,
in_set_validator,
)
+from . import primitives as P_OBJ
from pathlib import Path
import os
@@ -376,6 +377,10 @@ class Interpreter(InterpreterBase, HoldableObject):
holderify all returned values from methods and functions.
'''
self.holder_map.update({
+ # Primitives
+ int: P_OBJ.IntegerHolder,
+
+ # Meson types
mesonlib.File: OBJ.FileHolder,
build.SharedLibrary: OBJ.SharedLibraryHolder,
build.StaticLibrary: OBJ.StaticLibraryHolder,
diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py
new file mode 100644
index 0000000..56af72a
--- /dev/null
+++ b/mesonbuild/interpreter/primitives/__init__.py
@@ -0,0 +1,8 @@
+# Copyright 2021 The Meson development team
+# SPDX-license-identifier: Apache-2.0
+
+__all__ = [
+ 'IntegerHolder',
+]
+
+from .integer import IntegerHolder
diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py
new file mode 100644
index 0000000..296a3ca
--- /dev/null
+++ b/mesonbuild/interpreter/primitives/integer.py
@@ -0,0 +1,84 @@
+# Copyright 2021 The Meson development team
+# SPDX-license-identifier: Apache-2.0
+
+from ...interpreterbase import (
+ ObjectHolder,
+ MesonOperator,
+ typed_operator,
+ noKwargs,
+ noPosargs,
+
+ TYPE_var,
+ TYPE_kwargs,
+
+ InvalidArguments
+)
+
+import typing as T
+
+if T.TYPE_CHECKING:
+ # Object holders need the actual interpreter
+ from ...interpreter import Interpreter
+
+class IntegerHolder(ObjectHolder[int]):
+ def __init__(self, obj: int, interpreter: 'Interpreter') -> None:
+ super().__init__(obj, interpreter)
+ self.methods.update({
+ 'is_even': self.is_even_method,
+ 'is_odd': self.is_odd_method,
+ 'to_string': self.to_string_method,
+ })
+
+
+ self.trivial_operators.update({
+ # Arithmetic
+ MesonOperator.UMINUS: (None, lambda x: -self.held_object),
+ MesonOperator.PLUS: (int, lambda x: self.held_object + x),
+ MesonOperator.MINUS: (int, lambda x: self.held_object - x),
+ MesonOperator.TIMES: (int, lambda x: self.held_object * x),
+
+ # Comparison
+ MesonOperator.EQUALS: (int, lambda x: self.held_object == x),
+ MesonOperator.NOT_EQUALS: (int, lambda x: self.held_object != x),
+ MesonOperator.GREATER: (int, lambda x: self.held_object > x),
+ MesonOperator.LESS: (int, lambda x: self.held_object < x),
+ MesonOperator.GREATER_EQUALS: (int, lambda x: self.held_object >= x),
+ MesonOperator.LESS_EQUALS: (int, lambda x: self.held_object <= x),
+ })
+
+ # Use actual methods for functions that require additional checks
+ self.operators.update({
+ MesonOperator.DIV: self.op_div,
+ MesonOperator.MOD: self.op_mod,
+ })
+
+ def display_name(self) -> str:
+ return 'int'
+
+ @noKwargs
+ @noPosargs
+ def is_even_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.held_object % 2 == 0
+
+ @noKwargs
+ @noPosargs
+ def is_odd_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.held_object % 2 != 0
+
+ @noKwargs
+ @noPosargs
+ def to_string_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return str(self.held_object)
+
+ @typed_operator(MesonOperator.DIV, int)
+ def op_div(self, other: int) -> int:
+ if other == 0:
+ raise InvalidArguments('Tried to divide by 0')
+ return self.held_object // other
+
+ @typed_operator(MesonOperator.MOD, int)
+ def op_mod(self, other: int) -> int:
+ if other == 0:
+ raise InvalidArguments('Tried to divide by 0')
+ return self.held_object % other
+
diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py
index c3605b2..e630bbf 100644
--- a/mesonbuild/interpreterbase/__init__.py
+++ b/mesonbuild/interpreterbase/__init__.py
@@ -69,6 +69,9 @@ __all__ = [
'TYPE_kwargs',
'TYPE_nkwargs',
'TYPE_key_resolver',
+ 'TYPE_HoldableTypes',
+
+ 'HoldableTypes',
]
from .baseobjects import (
@@ -88,6 +91,9 @@ from .baseobjects import (
TYPE_kwargs,
TYPE_nkwargs,
TYPE_key_resolver,
+ TYPE_HoldableTypes,
+
+ HoldableTypes,
)
from .decorators import (
@@ -102,6 +108,8 @@ from .decorators import (
typed_pos_args,
ContainerTypeInfo,
KwargInfo,
+ typed_operator,
+ unary_operator,
typed_kwargs,
FeatureCheckBase,
FeatureNew,
@@ -122,3 +130,4 @@ from .exceptions import (
from .disabler import Disabler, is_disabled
from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
from .interpreterbase import MesonVersionString, InterpreterBase
+from .operator import MesonOperator
diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py
index 10c7cfc..55f3423 100644
--- a/mesonbuild/interpreterbase/_unholder.py
+++ b/mesonbuild/interpreterbase/_unholder.py
@@ -12,21 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, TYPE_var
+from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, TYPE_var, HoldableTypes
from .exceptions import InvalidArguments
from ..mesonlib import HoldableObject, MesonBugException
import typing as T
def _unholder(obj: T.Union[TYPE_var, InterpreterObject], *, permissive: bool = False) -> TYPE_var:
- if isinstance(obj, (int, bool, str)):
+ if isinstance(obj, (bool, str)):
return obj
elif isinstance(obj, list):
return [_unholder(x, permissive=permissive) for x in obj]
elif isinstance(obj, dict):
return {k: _unholder(v, permissive=permissive) for k, v in obj.items()}
elif isinstance(obj, ObjectHolder):
- assert isinstance(obj.held_object, HoldableObject)
+ assert isinstance(obj.held_object, HoldableTypes)
return obj.held_object
elif isinstance(obj, MesonInterpreterObject):
return obj
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py
index b28654a..4b538fb 100644
--- a/mesonbuild/interpreterbase/baseobjects.py
+++ b/mesonbuild/interpreterbase/baseobjects.py
@@ -111,6 +111,9 @@ class InterpreterObject:
))
def op_equals(self, other: TYPE_var) -> bool:
+ # We use `type(...) == type(...)` here to enforce an *exact* match for comparison. We
+ # don't want comparisons to be possible where `isinstance(derived_obj, type(base_obj))`
+ # would pass because this comparison must never be true: `derived_obj == base_obj`
if type(self) != type(other):
self._throw_comp_exception(other, '==')
return self == other
@@ -148,6 +151,7 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
# Override default comparison operators for the held object
def op_equals(self, other: TYPE_var) -> bool:
+ # See the comment from InterpreterObject why we are using `type()` here.
if type(self.held_object) != type(other):
self._throw_comp_exception(other, '==')
return self.held_object == other
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index 717853f..156b4c9 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -139,7 +139,7 @@ def typed_operator(operator: MesonOperator,
return inner
def unary_operator(operator: MesonOperator) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
- """Decorator that does type checking for operator calls.
+ """Decorator that does type checking for unary operator calls.
This decorator is for unary operators that do not take any other objects.
It should be impossible for a user to accidentally break this. Triggering
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
index b6e7d0d..d990e61 100644
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -15,7 +15,7 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
-from .. import mparser, mesonlib, mlog
+from .. import mparser, mesonlib
from .. import environment
from .baseobjects import (
@@ -29,6 +29,8 @@ from .baseobjects import (
TYPE_elementary,
TYPE_var,
TYPE_kwargs,
+
+ HoldableTypes,
)
from .exceptions import (
@@ -54,7 +56,10 @@ if T.TYPE_CHECKING:
from ..interpreter import Interpreter
HolderMapType = T.Dict[
- T.Type[mesonlib.HoldableObject],
+ T.Union[
+ T.Type[mesonlib.HoldableObject],
+ T.Type[int],
+ ],
# For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar]
T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]]
]
@@ -68,7 +73,7 @@ class MesonVersionString(str):
pass
class InterpreterBase:
- elementary_types = (int, str, bool, list)
+ elementary_types = (str, bool, list)
def __init__(self, source_root: str, subdir: str, subproject: str):
self.source_root = source_root
@@ -197,7 +202,7 @@ class InterpreterBase:
elif isinstance(cur, mparser.DictNode):
return self.evaluate_dictstatement(cur)
elif isinstance(cur, mparser.NumberNode):
- return cur.value
+ return self._holderify(cur.value)
elif isinstance(cur, mparser.AndNode):
return self.evaluate_andstatement(cur)
elif isinstance(cur, mparser.OrNode):
@@ -396,13 +401,14 @@ class InterpreterBase:
raise InterpreterException('Second argument to "or" is not a boolean.')
return r
- def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[int, Disabler]:
+ def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[TYPE_var, InterpreterObject]:
v = self.evaluate_statement(cur.value)
if isinstance(v, Disabler):
return v
- if not isinstance(v, int):
- raise InterpreterException('Argument to negation is not an integer.')
- return -v
+ # TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects
+ if not isinstance(v, InterpreterObject):
+ raise InterpreterException(f'Argument to negation ({v}) is not an InterpreterObject but {type(v).__name__}.')
+ return self._holderify(v.operator_call(MesonOperator.UMINUS, None))
@FeatureNew('/ with string arguments', '0.49.0')
def evaluate_path_join(self, l: str, r: str) -> str:
@@ -412,16 +418,7 @@ class InterpreterBase:
raise InvalidCode('The division operator can only append a string.')
return self.join_path_strings((l, r))
- def evaluate_division(self, l: T.Any, r: T.Any) -> T.Union[int, str]:
- if isinstance(l, str) or isinstance(r, str):
- return self.evaluate_path_join(l, r)
- if isinstance(l, int) and isinstance(r, int):
- if r == 0:
- raise InvalidCode('Division by zero.')
- return l // r
- raise InvalidCode('Division works only with strings or integers.')
-
- def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[int, str, dict, list, Disabler]:
+ def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[TYPE_var, InterpreterObject]:
l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler):
return l
@@ -455,17 +452,19 @@ class InterpreterBase:
elif cur.operation == 'sub':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Subtraction works only with integers.')
- return l - r
+ raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!')
elif cur.operation == 'mul':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Multiplication works only with integers.')
- return l * r
+ raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!')
elif cur.operation == 'div':
- return self.evaluate_division(l, r)
+ if isinstance(l, str) and isinstance(r, str):
+ return self.evaluate_path_join(l, r)
+ raise InvalidCode('Division works only with strings or integers.')
elif cur.operation == 'mod':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Modulo works only with integers.')
- return l % r
+ raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!')
else:
raise InvalidCode('You broke me.')
@@ -488,7 +487,7 @@ class InterpreterBase:
def replace(match: T.Match[str]) -> str:
var = str(match.group(1))
try:
- val = self.variables[var]
+ val = _unholder(self.variables[var])
if not isinstance(val, (str, int, float, bool)):
raise InvalidCode(f'Identifier "{var}" does not name a formattable variable ' +
'(has to be an integer, a string, a floating point number or a boolean).')
@@ -508,7 +507,7 @@ class InterpreterBase:
raise InvalidArguments('Foreach on array does not unpack')
varname = node.varnames[0]
for item in items:
- self.set_variable(varname, item)
+ self.set_variable(varname, self._holderify(item, permissive=True))
try:
self.evaluate_codeblock(node.block)
except ContinueRequest:
@@ -538,15 +537,12 @@ class InterpreterBase:
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
old_variable = self.get_variable(varname)
- new_value = None # type: T.Union[str, int, float, bool, dict, list]
+ # TYPING TODO: This should only be InterpreterObject in the future
+ new_value: T.Union[None, TYPE_var, InterpreterObject] = None
if isinstance(old_variable, str):
if not isinstance(addition, str):
raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string')
new_value = old_variable + addition
- elif isinstance(old_variable, int):
- if not isinstance(addition, int):
- raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int')
- new_value = old_variable + addition
elif isinstance(old_variable, list):
if isinstance(addition, list):
new_value = old_variable + addition
@@ -556,6 +552,9 @@ class InterpreterBase:
if not isinstance(addition, dict):
raise InvalidArguments('The += operator requires a dict on the right hand side if the variable on the left is a dict')
new_value = {**old_variable, **addition}
+ elif isinstance(old_variable, InterpreterObject):
+ # TODO: don't make _unholder permissive
+ new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition, permissive=True)))
# Add other data types here.
else:
raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints')
@@ -569,7 +568,7 @@ class InterpreterBase:
if not hasattr(iobject, '__getitem__'):
raise InterpreterException(
'Tried to index an object that doesn\'t support indexing.')
- index = self.evaluate_statement(node.index)
+ index = _unholder(self.evaluate_statement(node.index))
if isinstance(iobject, dict):
if not isinstance(index, str):
@@ -630,11 +629,11 @@ class InterpreterBase:
if is_disabled(args, kwargs):
return Disabler()
if isinstance(obj, str):
- return self.string_method_call(obj, method_name, args, kwargs)
+ return self._holderify(self.string_method_call(obj, method_name, args, kwargs))
if isinstance(obj, bool):
- return self.bool_method_call(obj, method_name, args, kwargs)
+ return self._holderify(self.bool_method_call(obj, method_name, args, kwargs))
if isinstance(obj, int):
- return self.int_method_call(obj, method_name, args, kwargs)
+ raise mesonlib.MesonBugException('Integers are now wrapped in object holders!')
if isinstance(obj, list):
return self.array_method_call(obj, method_name, args, kwargs)
if isinstance(obj, dict):
@@ -656,16 +655,17 @@ class InterpreterBase:
obj.current_node = node
return self._holderify(obj.method_call(method_name, args, kwargs))
- def _holderify(self, res: T.Union[TYPE_var, InterpreterObject, None]) -> T.Union[TYPE_elementary, InterpreterObject]:
+ def _holderify(self, res: T.Union[TYPE_var, InterpreterObject, None], *, permissive: bool = False) -> T.Union[TYPE_elementary, InterpreterObject]:
+ # TODO: remove `permissive` once all primitives are ObjectHolders
if res is None:
return None
- if isinstance(res, (int, bool, str)):
+ if isinstance(res, (bool, str)):
return res
elif isinstance(res, list):
- return [self._holderify(x) for x in res]
+ return [self._holderify(x, permissive=permissive) for x in res]
elif isinstance(res, dict):
- return {k: self._holderify(v) for k, v in res.items()}
- elif isinstance(res, mesonlib.HoldableObject):
+ return {k: self._holderify(v, permissive=permissive) for k, v in res.items()}
+ elif isinstance(res, HoldableTypes):
# Always check for an exact match first.
cls = self.holder_map.get(type(res), None)
if cls is not None:
@@ -678,6 +678,8 @@ class InterpreterBase:
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):
+ if permissive:
+ return res
raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.')
elif isinstance(res, MesonInterpreterObject):
return res
@@ -711,26 +713,6 @@ class InterpreterBase:
else:
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
- @noKwargs
- 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
- else:
- raise InterpreterException('int.is_even() must have no arguments.')
- elif method_name == 'is_odd':
- if not posargs:
- return obj % 2 != 0
- else:
- raise InterpreterException('int.is_odd() must have no arguments.')
- elif method_name == 'to_string':
- if not posargs:
- return str(obj)
- else:
- raise InterpreterException('int.to_string() must have no arguments.')
- else:
- raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
-
@staticmethod
def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str:
if len(posargs) > 1:
@@ -856,7 +838,7 @@ class InterpreterBase:
return False
return check_contains([_unholder(x) for x in obj])
elif method_name == 'length':
- return len(obj)
+ return self._holderify(len(obj))
elif method_name == 'get':
index = posargs[0]
fallback = None
diff --git a/run_mypy.py b/run_mypy.py
index fc69063..f6add90 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -15,6 +15,7 @@ modules = [
'mesonbuild/cmake',
'mesonbuild/compilers',
'mesonbuild/dependencies',
+ 'mesonbuild/interpreter/primitives',
'mesonbuild/interpreterbase',
'mesonbuild/linkers',
'mesonbuild/scripts',
diff --git a/test cases/failing/50 executable comparison/test.json b/test cases/failing/50 executable comparison/test.json
index 585b382..7f6bd3b 100644
--- a/test cases/failing/50 executable comparison/test.json
+++ b/test cases/failing/50 executable comparison/test.json
@@ -1,7 +1,8 @@
{
"stdout": [
{
- "line": "test cases/failing/50 executable comparison/meson.build:6:0: ERROR: exe1 can only be compared for equality."
+ "match": "re",
+ "line": "test cases/failing/50 executable comparison/meson.build:6:0: ERROR: Object <ExecutableHolder prog1@exe: prog1(.exe)?> of type Executable does not support the `<` operator."
}
]
}