aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/interpreterbase
diff options
context:
space:
mode:
authorDaniel Mensinger <daniel@mensinger-ka.de>2021-08-29 13:08:20 +0200
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-08-31 23:01:21 +0200
commit8d92e6d865e2a03875c29d2e2aa131ce774a5d5d (patch)
tree7d614e505c7a35ae08ea97eadfe77400f1fa3cca /mesonbuild/interpreterbase
parent86f70c873a46c63e7a6e12de562ad8c907eabbc5 (diff)
downloadmeson-8d92e6d865e2a03875c29d2e2aa131ce774a5d5d.zip
meson-8d92e6d865e2a03875c29d2e2aa131ce774a5d5d.tar.gz
meson-8d92e6d865e2a03875c29d2e2aa131ce774a5d5d.tar.bz2
interpreter: Add IntegerHolder
Diffstat (limited to 'mesonbuild/interpreterbase')
-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
5 files changed, 58 insertions, 63 deletions
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