interpreter: Add IntegerHolder

pull/9174/head
Daniel Mensinger 3 years ago
parent 86f70c873a
commit 8d92e6d865
  1. 3
      mesonbuild/ast/interpreter.py
  2. 26
      mesonbuild/interpreter/__init__.py
  3. 5
      mesonbuild/interpreter/interpreter.py
  4. 8
      mesonbuild/interpreter/primitives/__init__.py
  5. 84
      mesonbuild/interpreter/primitives/integer.py
  6. 9
      mesonbuild/interpreterbase/__init__.py
  7. 6
      mesonbuild/interpreterbase/_unholder.py
  8. 4
      mesonbuild/interpreterbase/baseobjects.py
  9. 2
      mesonbuild/interpreterbase/decorators.py
  10. 100
      mesonbuild/interpreterbase/interpreterbase.py
  11. 1
      run_mypy.py
  12. 3
      test cases/failing/50 executable comparison/test.json

@ -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):

@ -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,
)

@ -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,

@ -0,0 +1,8 @@
# Copyright 2021 The Meson development team
# SPDX-license-identifier: Apache-2.0
__all__ = [
'IntegerHolder',
]
from .integer import IntegerHolder

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -15,6 +15,7 @@ modules = [
'mesonbuild/cmake',
'mesonbuild/compilers',
'mesonbuild/dependencies',
'mesonbuild/interpreter/primitives',
'mesonbuild/interpreterbase',
'mesonbuild/linkers',
'mesonbuild/scripts',

@ -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."
}
]
}

Loading…
Cancel
Save