interpreter: Introduce operators support for InterpreterObjects

pull/9174/head
Daniel Mensinger 3 years ago
parent a6c9a151d3
commit 86f70c873a
  1. 4
      mesonbuild/interpreterbase/__init__.py
  2. 83
      mesonbuild/interpreterbase/baseobjects.py
  3. 44
      mesonbuild/interpreterbase/decorators.py
  4. 42
      mesonbuild/interpreterbase/interpreterbase.py
  5. 34
      mesonbuild/interpreterbase/operator.py

@ -20,6 +20,8 @@ __all__ = [
'MesonVersionString', 'MesonVersionString',
'MutableInterpreterObject', 'MutableInterpreterObject',
'MesonOperator',
'Disabler', 'Disabler',
'is_disabled', 'is_disabled',
@ -43,6 +45,8 @@ __all__ = [
'permissive_unholder_return', 'permissive_unholder_return',
'disablerIfNotFound', 'disablerIfNotFound',
'permittedKwargs', 'permittedKwargs',
'typed_operator',
'unary_operator',
'typed_pos_args', 'typed_pos_args',
'ContainerTypeInfo', 'ContainerTypeInfo',
'KwargInfo', 'KwargInfo',

@ -13,9 +13,11 @@
# limitations under the License. # limitations under the License.
from .. import mparser from .. import mparser
from .exceptions import InvalidCode from .exceptions import InvalidCode, InvalidArguments
from .helpers import flatten, resolve_second_level_holders from .helpers import flatten, resolve_second_level_holders
from ..mesonlib import HoldableObject from .operator import MesonOperator
from ..mesonlib import HoldableObject, MesonBugException
import textwrap
import typing as T import typing as T
@ -36,17 +38,41 @@ TYPE_kwargs = T.Dict[str, TYPE_var]
TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_nkwargs = T.Dict[str, TYPE_nvar]
TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str]
if T.TYPE_CHECKING:
from typing_extensions import Protocol
__T = T.TypeVar('__T', bound=TYPE_var, contravariant=True)
class OperatorCall(Protocol[__T]):
def __call__(self, other: __T) -> TYPE_var: ...
class InterpreterObject: class InterpreterObject:
def __init__(self, *, subproject: T.Optional[str] = None) -> None: def __init__(self, *, subproject: T.Optional[str] = None) -> None:
self.methods: T.Dict[ self.methods: T.Dict[
str, str,
T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var] T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var]
] = {} ] = {}
self.operators: T.Dict[MesonOperator, 'OperatorCall'] = {}
self.trivial_operators: T.Dict[
MesonOperator,
T.Tuple[
T.Type[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]],
'OperatorCall'
]
] = {}
# Current node set during a method call. This can be used as location # Current node set during a method call. This can be used as location
# when printing a warning message during a method call. # when printing a warning message during a method call.
self.current_node: mparser.BaseNode = None self.current_node: mparser.BaseNode = None
self.subproject: str = subproject or '' self.subproject: str = subproject or ''
# Some default operators supported by all objects
self.operators.update({
MesonOperator.EQUALS: self.op_equals,
MesonOperator.NOT_EQUALS: self.op_not_equals,
})
# The type of the object that can be printed to the user
def display_name(self) -> str:
return type(self).__name__
def method_call( def method_call(
self, self,
method_name: str, method_name: str,
@ -62,13 +88,47 @@ class InterpreterObject:
return method(args, kwargs) return method(args, kwargs)
raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.') raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.')
def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var:
if operator in self.trivial_operators:
op = self.trivial_operators[operator]
if op[0] is None and other is not None:
raise MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
if op[0] is not None and not isinstance(other, op[0]):
raise InvalidArguments(f'The `{operator.value}` of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})')
return op[1](other)
if operator in self.operators:
return self.operators[operator](other)
raise InvalidCode(f'Object {self} of type {self.display_name()} does not support the `{operator.value}` operator.')
# Default comparison operator support
def _throw_comp_exception(self, other: TYPE_var, opt_type: str) -> T.NoReturn:
raise InvalidArguments(textwrap.dedent(
f'''
Trying to compare values of different types ({self.display_name()}, {type(other).__name__}) using {opt_type}.
This was deprecated and undefined behavior previously and is as of 0.60.0 a hard error.
'''
))
def op_equals(self, other: TYPE_var) -> bool:
if type(self) != type(other):
self._throw_comp_exception(other, '==')
return self == other
def op_not_equals(self, other: TYPE_var) -> bool:
if type(self) != type(other):
self._throw_comp_exception(other, '!=')
return self != other
class MesonInterpreterObject(InterpreterObject): class MesonInterpreterObject(InterpreterObject):
''' All non-elementary objects and non-object-holders should be derived from this ''' ''' All non-elementary objects and non-object-holders should be derived from this '''
class MutableInterpreterObject: class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable ''' ''' Dummy class to mark the object type as mutable '''
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=HoldableObject) HoldableTypes = (HoldableObject, int)
TYPE_HoldableTypes = T.Union[HoldableObject, int]
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None: def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None:
@ -77,11 +137,26 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
# HoldableObject, not the specialized type, so only do this assert in # HoldableObject, not the specialized type, so only do this assert in
# non-type checking situations # non-type checking situations
if not T.TYPE_CHECKING: if not T.TYPE_CHECKING:
assert isinstance(obj, HoldableObject), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not an `HoldableObject`' assert isinstance(obj, HoldableTypes), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not in `{HoldableTypes}`'
self.held_object = obj self.held_object = obj
self.interpreter = interpreter self.interpreter = interpreter
self.env = self.interpreter.environment self.env = self.interpreter.environment
# Hide the object holder abstrction from the user
def display_name(self) -> str:
return type(self.held_object).__name__
# Override default comparison operators for the held object
def op_equals(self, other: TYPE_var) -> bool:
if type(self.held_object) != type(other):
self._throw_comp_exception(other, '==')
return self.held_object == other
def op_not_equals(self, other: TYPE_var) -> bool:
if type(self.held_object) != type(other):
self._throw_comp_exception(other, '!=')
return self.held_object != other
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>' return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>'

@ -17,6 +17,7 @@ from .baseobjects import TV_func, TYPE_var, TYPE_kwargs
from .disabler import Disabler from .disabler import Disabler
from .exceptions import InterpreterException, InvalidArguments from .exceptions import InterpreterException, InvalidArguments
from .helpers import check_stringlist from .helpers import check_stringlist
from .operator import MesonOperator
from ._unholder import _unholder from ._unholder import _unholder
from functools import wraps from functools import wraps
@ -110,6 +111,49 @@ class permittedKwargs:
return f(*wrapped_args, **wrapped_kwargs) return f(*wrapped_args, **wrapped_kwargs)
return T.cast(TV_func, wrapped) return T.cast(TV_func, wrapped)
if T.TYPE_CHECKING:
from .baseobjects import InterpreterObject
from typing_extensions import Protocol
_TV_IntegerObject = T.TypeVar('_TV_IntegerObject', bound=InterpreterObject, contravariant=True)
_TV_ARG1 = T.TypeVar('_TV_ARG1', bound=TYPE_var, contravariant=True)
class FN_Operator(Protocol[_TV_IntegerObject, _TV_ARG1]):
def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> TYPE_var: ...
_TV_FN_Operator = T.TypeVar('_TV_FN_Operator', bound=FN_Operator)
def typed_operator(operator: MesonOperator,
types: T.Union[T.Type, T.Tuple[T.Type, ...]]) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
"""Decorator that does type checking for operator calls.
The principle here is similar to typed_pos_args, however much simpler
since only one other object ever is passed
"""
def inner(f: '_TV_FN_Operator') -> '_TV_FN_Operator':
@wraps(f)
def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
if not isinstance(other, types):
raise InvalidArguments(f'The `{operator.value}` of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})')
return f(self, other)
return T.cast('_TV_FN_Operator', wrapper)
return inner
def unary_operator(operator: MesonOperator) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
"""Decorator that does type checking for 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
this check always indicates a bug in the Meson interpreter.
"""
def inner(f: '_TV_FN_Operator') -> '_TV_FN_Operator':
@wraps(f)
def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
if other is not None:
raise mesonlib.MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
return f(self, other)
return T.cast('_TV_FN_Operator', wrapper)
return inner
def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]], def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None, varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,

@ -43,6 +43,7 @@ from .exceptions import (
from .decorators import FeatureNew, noKwargs from .decorators import FeatureNew, noKwargs
from .disabler import Disabler, is_disabled from .disabler import Disabler, is_disabled
from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
from .operator import MesonOperator
from ._unholder import _unholder from ._unholder import _unholder
import os, copy, re, pathlib import os, copy, re, pathlib
@ -290,13 +291,37 @@ class InterpreterBase:
raise InvalidArguments('rvalue of "in" operator must be an array or a dict') raise InvalidArguments('rvalue of "in" operator must be an array or a dict')
return val1 in val2 return val1 in val2
def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[bool, Disabler]: def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[TYPE_var, InterpreterObject]:
val1 = self.evaluate_statement(node.left) val1 = self.evaluate_statement(node.left)
if isinstance(val1, Disabler): if isinstance(val1, Disabler):
return val1 return val1
val2 = self.evaluate_statement(node.right) val2 = self.evaluate_statement(node.right)
if isinstance(val2, Disabler): if isinstance(val2, Disabler):
return val2 return val2
# New code based on InterpreterObjects
operator = {
'in': MesonOperator.IN,
'notin': MesonOperator.NOT_IN,
'==': MesonOperator.EQUALS,
'!=': MesonOperator.NOT_EQUALS,
'>': MesonOperator.GREATER,
'<': MesonOperator.LESS,
'>=': MesonOperator.GREATER_EQUALS,
'<=': MesonOperator.LESS_EQUALS,
}[node.ctype]
# Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`)
if operator in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val2, InterpreterObject):
return self._holderify(val2.operator_call(operator, _unholder(val1)))
# Normal evaluation, with the same semantics
elif operator not in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val1, InterpreterObject):
return self._holderify(val1.operator_call(operator, _unholder(val2)))
# OLD CODE, based on the builtin types -- remove once we have switched
# over to all ObjectHolders.
# Do not compare the ObjectHolders but the actual held objects # Do not compare the ObjectHolders but the actual held objects
val1 = _unholder(val1) val1 = _unholder(val1)
val2 = _unholder(val2) val2 = _unholder(val2)
@ -404,6 +429,21 @@ class InterpreterBase:
if isinstance(r, Disabler): if isinstance(r, Disabler):
return r return r
# New code based on InterpreterObjects
if isinstance(l, InterpreterObject):
mapping: T.Dict[str, MesonOperator] = {
'add': MesonOperator.PLUS,
'sub': MesonOperator.MINUS,
'mul': MesonOperator.TIMES,
'div': MesonOperator.DIV,
'mod': MesonOperator.MOD,
}
res = l.operator_call(mapping[cur.operation], _unholder(r))
return self._holderify(res)
# OLD CODE, based on the builtin types -- remove once we have switched
# over to all ObjectHolders.
if cur.operation == 'add': if cur.operation == 'add':
if isinstance(l, dict) and isinstance(r, dict): if isinstance(l, dict) and isinstance(r, dict):
return {**l, **r} return {**l, **r}

@ -0,0 +1,34 @@
# SPDX-license-identifier: Apache-2.0
from enum import Enum
class MesonOperator(Enum):
# Arithmetic
PLUS = '+'
MINUS = '-'
TIMES = '*'
DIV = '/'
MOD = '%'
UMINUS = 'uminus'
# Logic
NOT = 'not'
AND = 'and'
OR = 'or'
# Should return the boolsche interpretation of the value (`'' == false` for instance)
BOOL = 'bool()'
# Comparision
EQUALS = '=='
NOT_EQUALS = '!='
GREATER = '>'
LESS = '<'
GREATER_EQUALS = '>='
LESS_EQUALS = '<='
# Container
IN = 'in'
NOT_IN = 'not in'
INDEX = '[]'
Loading…
Cancel
Save