1# Copyright 2013-2021 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from .. import mparser
16from .exceptions import InvalidCode, InvalidArguments
17from .helpers import flatten, resolve_second_level_holders
18from .operator import MesonOperator
19from ..mesonlib import HoldableObject, MesonBugException
20import textwrap
21
22import typing as T
23from abc import ABCMeta
24
25if T.TYPE_CHECKING:
26    # Object holders need the actual interpreter
27    from ..interpreter import Interpreter
28
29TV_fw_var = T.Union[str, int, bool, list, dict, 'InterpreterObject']
30TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]]
31TV_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]]
32
33TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any])
34
35TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any]]
36TYPE_var = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject']
37TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode]
38TYPE_kwargs = T.Dict[str, TYPE_var]
39TYPE_nkwargs = T.Dict[str, TYPE_nvar]
40TYPE_key_resolver = T.Callable[[mparser.BaseNode], str]
41
42if T.TYPE_CHECKING:
43    from typing_extensions import Protocol
44    __T = T.TypeVar('__T', bound=TYPE_var, contravariant=True)
45
46    class OperatorCall(Protocol[__T]):
47        def __call__(self, other: __T) -> TYPE_var: ...
48
49class InterpreterObject:
50    def __init__(self, *, subproject: T.Optional[str] = None) -> None:
51        self.methods: T.Dict[
52            str,
53            T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var]
54        ] = {}
55        self.operators: T.Dict[MesonOperator, 'OperatorCall'] = {}
56        self.trivial_operators: T.Dict[
57            MesonOperator,
58            T.Tuple[
59                T.Union[T.Type, T.Tuple[T.Type, ...]],
60                'OperatorCall'
61            ]
62        ] = {}
63        # Current node set during a method call. This can be used as location
64        # when printing a warning message during a method call.
65        self.current_node:  mparser.BaseNode = None
66        self.subproject: str = subproject or ''
67
68        # Some default operators supported by all objects
69        self.operators.update({
70            MesonOperator.EQUALS: self.op_equals,
71            MesonOperator.NOT_EQUALS: self.op_not_equals,
72        })
73
74    # The type of the object that can be printed to the user
75    def display_name(self) -> str:
76        return type(self).__name__
77
78    def method_call(
79                self,
80                method_name: str,
81                args: T.List[TYPE_var],
82                kwargs: TYPE_kwargs
83            ) -> TYPE_var:
84        if method_name in self.methods:
85            method = self.methods[method_name]
86            if not getattr(method, 'no-args-flattening', False):
87                args = flatten(args)
88            if not getattr(method, 'no-second-level-holder-flattening', False):
89                args, kwargs = resolve_second_level_holders(args, kwargs)
90            return method(args, kwargs)
91        raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.')
92
93    def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var:
94        if operator in self.trivial_operators:
95            op = self.trivial_operators[operator]
96            if op[0] is None and other is not None:
97                raise MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
98            if op[0] is not None and not isinstance(other, op[0]):
99                raise InvalidArguments(f'The `{operator.value}` operator of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})')
100            return op[1](other)
101        if operator in self.operators:
102            return self.operators[operator](other)
103        raise InvalidCode(f'Object {self} of type {self.display_name()} does not support the `{operator.value}` operator.')
104
105    # Default comparison operator support
106    def _throw_comp_exception(self, other: TYPE_var, opt_type: str) -> T.NoReturn:
107        raise InvalidArguments(textwrap.dedent(
108            f'''
109                Trying to compare values of different types ({self.display_name()}, {type(other).__name__}) using {opt_type}.
110                This was deprecated and undefined behavior previously and is as of 0.60.0 a hard error.
111            '''
112        ))
113
114    def op_equals(self, other: TYPE_var) -> bool:
115        # We use `type(...) == type(...)` here to enforce an *exact* match for comparison. We
116        # don't want comparisons to be possible where `isinstance(derived_obj, type(base_obj))`
117        # would pass because this comparison must never be true: `derived_obj == base_obj`
118        if type(self) != type(other):
119            self._throw_comp_exception(other, '==')
120        return self == other
121
122    def op_not_equals(self, other: TYPE_var) -> bool:
123        if type(self) != type(other):
124            self._throw_comp_exception(other, '!=')
125        return self != other
126
127class MesonInterpreterObject(InterpreterObject):
128    ''' All non-elementary objects and non-object-holders should be derived from this '''
129
130class MutableInterpreterObject:
131    ''' Dummy class to mark the object type as mutable '''
132
133HoldableTypes = (HoldableObject, int, bool, str, list, dict)
134TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject]
135InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
136
137class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
138    def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None:
139        super().__init__(subproject=interpreter.subproject)
140        # This causes some type checkers to assume that obj is a base
141        # HoldableObject, not the specialized type, so only do this assert in
142        # non-type checking situations
143        if not T.TYPE_CHECKING:
144            assert isinstance(obj, HoldableTypes), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not in `{HoldableTypes}`'
145        self.held_object = obj
146        self.interpreter = interpreter
147        self.env = self.interpreter.environment
148
149    # Hide the object holder abstraction from the user
150    def display_name(self) -> str:
151        return type(self.held_object).__name__
152
153    # Override default comparison operators for the held object
154    def op_equals(self, other: TYPE_var) -> bool:
155        # See the comment from InterpreterObject why we are using `type()` here.
156        if type(self.held_object) != type(other):
157            self._throw_comp_exception(other, '==')
158        return self.held_object == other
159
160    def op_not_equals(self, other: TYPE_var) -> bool:
161        if type(self.held_object) != type(other):
162            self._throw_comp_exception(other, '!=')
163        return self.held_object != other
164
165    def __repr__(self) -> str:
166        return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>'
167
168class IterableObject(metaclass=ABCMeta):
169    '''Base class for all objects that can be iterated over in a foreach loop'''
170
171    def iter_tuple_size(self) -> T.Optional[int]:
172        '''Return the size of the tuple for each iteration. Returns None if only a single value is returned.'''
173        raise MesonBugException(f'iter_tuple_size not implemented for {self.__class__.__name__}')
174
175    def iter_self(self) -> T.Iterator[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]]:
176        raise MesonBugException(f'iter not implemented for {self.__class__.__name__}')
177
178    def size(self) -> int:
179        raise MesonBugException(f'size not implemented for {self.__class__.__name__}')
180