1# Copyright 2016-2017 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 15# This class contains the basic functionality needed to run any interpreter 16# or an interpreter-based tool. 17 18from . import mparser, mesonlib, mlog 19from . import environment, dependencies 20 21import abc 22import os, copy, re 23import collections.abc 24from functools import wraps 25import typing as T 26 27class InterpreterObject: 28 def __init__(self): 29 self.methods = {} # type: T.Dict[str, T.Callable] 30 # Current node set during a method call. This can be used as location 31 # when printing a warning message during a method call. 32 self.current_node = None # type: mparser.BaseNode 33 34 def method_call(self, method_name: str, args: T.List[T.Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']], kwargs: T.Dict[str, T.Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']]): 35 if method_name in self.methods: 36 method = self.methods[method_name] 37 if not getattr(method, 'no-args-flattening', False): 38 args = flatten(args) 39 return method(args, kwargs) 40 raise InvalidCode('Unknown method "%s" in object.' % method_name) 41 42TV_InterpreterObject = T.TypeVar('TV_InterpreterObject') 43 44class ObjectHolder(T.Generic[TV_InterpreterObject]): 45 def __init__(self, obj: InterpreterObject, subproject: T.Optional[str] = None): 46 self.held_object = obj # type: InterpreterObject 47 self.subproject = subproject # type: str 48 49 def __repr__(self): 50 return '<Holder: {!r}>'.format(self.held_object) 51 52TYPE_elementary = T.Union[str, int, float, bool] 53TYPE_var = T.Union[TYPE_elementary, list, dict, InterpreterObject, ObjectHolder] 54TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] 55TYPE_nkwargs = T.Dict[T.Union[mparser.BaseNode, str], TYPE_nvar] 56 57# Decorators for method calls. 58 59def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None: 60 if not isinstance(a, list): 61 mlog.debug('Not a list:', str(a)) 62 raise InvalidArguments('Argument not a list.') 63 if not all(isinstance(s, str) for s in a): 64 mlog.debug('Element not a string:', str(a)) 65 raise InvalidArguments(msg) 66 67def _get_callee_args(wrapped_args, want_subproject: bool = False): 68 s = wrapped_args[0] 69 n = len(wrapped_args) 70 # Raise an error if the codepaths are not there 71 subproject = None 72 if want_subproject and n == 2: 73 if hasattr(s, 'subproject'): 74 # Interpreter base types have 2 args: self, node 75 node = wrapped_args[1] 76 # args and kwargs are inside the node 77 args = None 78 kwargs = None 79 subproject = s.subproject 80 elif hasattr(wrapped_args[1], 'subproject'): 81 # Module objects have 2 args: self, interpreter 82 node = wrapped_args[1].current_node 83 # args and kwargs are inside the node 84 args = None 85 kwargs = None 86 subproject = wrapped_args[1].subproject 87 else: 88 raise AssertionError('Unknown args: {!r}'.format(wrapped_args)) 89 elif n == 3: 90 # Methods on objects (*Holder, MesonMain, etc) have 3 args: self, args, kwargs 91 node = s.current_node 92 args = wrapped_args[1] 93 kwargs = wrapped_args[2] 94 if want_subproject: 95 if hasattr(s, 'subproject'): 96 subproject = s.subproject 97 elif hasattr(s, 'interpreter'): 98 subproject = s.interpreter.subproject 99 elif n == 4: 100 # Meson functions have 4 args: self, node, args, kwargs 101 # Module functions have 4 args: self, state, args, kwargs 102 if isinstance(s, InterpreterBase): 103 node = wrapped_args[1] 104 else: 105 node = wrapped_args[1].current_node 106 args = wrapped_args[2] 107 kwargs = wrapped_args[3] 108 if want_subproject: 109 if isinstance(s, InterpreterBase): 110 subproject = s.subproject 111 else: 112 subproject = wrapped_args[1].subproject 113 elif n == 5: 114 # Module snippets have 5 args: self, interpreter, state, args, kwargs 115 node = wrapped_args[2].current_node 116 args = wrapped_args[3] 117 kwargs = wrapped_args[4] 118 if want_subproject: 119 subproject = wrapped_args[2].subproject 120 else: 121 raise AssertionError('Unknown args: {!r}'.format(wrapped_args)) 122 # Sometimes interpreter methods are called internally with None instead of 123 # empty list/dict 124 args = args if args is not None else [] 125 kwargs = kwargs if kwargs is not None else {} 126 return s, node, args, kwargs, subproject 127 128def flatten(args: T.Union[TYPE_nvar, T.List[TYPE_nvar]]) -> T.List[TYPE_nvar]: 129 if isinstance(args, mparser.StringNode): 130 assert isinstance(args.value, str) 131 return [args.value] 132 if not isinstance(args, collections.abc.Sequence): 133 return [args] 134 result = [] # type: T.List[TYPE_nvar] 135 for a in args: 136 if isinstance(a, list): 137 rest = flatten(a) 138 result = result + rest 139 elif isinstance(a, mparser.StringNode): 140 result.append(a.value) 141 else: 142 result.append(a) 143 return result 144 145def noPosargs(f): 146 @wraps(f) 147 def wrapped(*wrapped_args, **wrapped_kwargs): 148 args = _get_callee_args(wrapped_args)[2] 149 if args: 150 raise InvalidArguments('Function does not take positional arguments.') 151 return f(*wrapped_args, **wrapped_kwargs) 152 return wrapped 153 154def builtinMethodNoKwargs(f): 155 @wraps(f) 156 def wrapped(*wrapped_args, **wrapped_kwargs): 157 node = wrapped_args[0].current_node 158 method_name = wrapped_args[2] 159 kwargs = wrapped_args[4] 160 if kwargs: 161 mlog.warning('Method {!r} does not take keyword arguments.'.format(method_name), 162 'This will become a hard error in the future', 163 location=node) 164 return f(*wrapped_args, **wrapped_kwargs) 165 return wrapped 166 167def noKwargs(f): 168 @wraps(f) 169 def wrapped(*wrapped_args, **wrapped_kwargs): 170 kwargs = _get_callee_args(wrapped_args)[3] 171 if kwargs: 172 raise InvalidArguments('Function does not take keyword arguments.') 173 return f(*wrapped_args, **wrapped_kwargs) 174 return wrapped 175 176def stringArgs(f): 177 @wraps(f) 178 def wrapped(*wrapped_args, **wrapped_kwargs): 179 args = _get_callee_args(wrapped_args)[2] 180 assert(isinstance(args, list)) 181 check_stringlist(args) 182 return f(*wrapped_args, **wrapped_kwargs) 183 return wrapped 184 185def noArgsFlattening(f): 186 setattr(f, 'no-args-flattening', True) # noqa: B010 187 return f 188 189def disablerIfNotFound(f): 190 @wraps(f) 191 def wrapped(*wrapped_args, **wrapped_kwargs): 192 kwargs = _get_callee_args(wrapped_args)[3] 193 disabler = kwargs.pop('disabler', False) 194 ret = f(*wrapped_args, **wrapped_kwargs) 195 if disabler and not ret.held_object.found(): 196 return Disabler() 197 return ret 198 return wrapped 199 200class permittedKwargs: 201 202 def __init__(self, permitted: T.Set[str]): 203 self.permitted = permitted # type: T.Set[str] 204 205 def __call__(self, f): 206 @wraps(f) 207 def wrapped(*wrapped_args, **wrapped_kwargs): 208 s, node, args, kwargs, _ = _get_callee_args(wrapped_args) 209 for k in kwargs: 210 if k not in self.permitted: 211 mlog.warning('''Passed invalid keyword argument "{}".'''.format(k), location=node) 212 mlog.warning('This will become a hard error in the future.') 213 return f(*wrapped_args, **wrapped_kwargs) 214 return wrapped 215 216class FeatureCheckBase(metaclass=abc.ABCMeta): 217 "Base class for feature version checks" 218 219 # In python 3.6 we can just forward declare this, but in 3.5 we can't 220 # This will be overwritten by the subclasses by necessity 221 feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] 222 223 def __init__(self, feature_name: str, version: str, extra_message: T.Optional[str] = None): 224 self.feature_name = feature_name # type: str 225 self.feature_version = version # type: str 226 self.extra_message = extra_message or '' # type: str 227 228 @staticmethod 229 def get_target_version(subproject: str) -> str: 230 # Don't do any checks if project() has not been parsed yet 231 if subproject not in mesonlib.project_meson_versions: 232 return '' 233 return mesonlib.project_meson_versions[subproject] 234 235 @staticmethod 236 @abc.abstractmethod 237 def check_version(target_version: str, feature_Version: str) -> bool: 238 pass 239 240 def use(self, subproject: str) -> None: 241 tv = self.get_target_version(subproject) 242 # No target version 243 if tv == '': 244 return 245 # Target version is new enough 246 if self.check_version(tv, self.feature_version): 247 return 248 # Feature is too new for target version, register it 249 if subproject not in self.feature_registry: 250 self.feature_registry[subproject] = {self.feature_version: set()} 251 register = self.feature_registry[subproject] 252 if self.feature_version not in register: 253 register[self.feature_version] = set() 254 if self.feature_name in register[self.feature_version]: 255 # Don't warn about the same feature multiple times 256 # FIXME: This is needed to prevent duplicate warnings, but also 257 # means we won't warn about a feature used in multiple places. 258 return 259 register[self.feature_version].add(self.feature_name) 260 self.log_usage_warning(tv) 261 262 @classmethod 263 def report(cls, subproject: str) -> None: 264 if subproject not in cls.feature_registry: 265 return 266 warning_str = cls.get_warning_str_prefix(cls.get_target_version(subproject)) 267 fv = cls.feature_registry[subproject] 268 for version in sorted(fv.keys()): 269 warning_str += '\n * {}: {}'.format(version, fv[version]) 270 mlog.warning(warning_str) 271 272 def log_usage_warning(self, tv: str) -> None: 273 raise InterpreterException('log_usage_warning not implemented') 274 275 @staticmethod 276 def get_warning_str_prefix(tv: str) -> str: 277 raise InterpreterException('get_warning_str_prefix not implemented') 278 279 def __call__(self, f): 280 @wraps(f) 281 def wrapped(*wrapped_args, **wrapped_kwargs): 282 subproject = _get_callee_args(wrapped_args, want_subproject=True)[4] 283 if subproject is None: 284 raise AssertionError('{!r}'.format(wrapped_args)) 285 self.use(subproject) 286 return f(*wrapped_args, **wrapped_kwargs) 287 return wrapped 288 289 @classmethod 290 def single_use(cls, feature_name: str, version: str, subproject: str, 291 extra_message: T.Optional[str] = None) -> None: 292 """Oneline version that instantiates and calls use().""" 293 cls(feature_name, version, extra_message).use(subproject) 294 295 296class FeatureNew(FeatureCheckBase): 297 """Checks for new features""" 298 299 # Class variable, shared across all instances 300 # 301 # Format: {subproject: {feature_version: set(feature_names)}} 302 feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] 303 304 @staticmethod 305 def check_version(target_version: str, feature_version: str) -> bool: 306 return mesonlib.version_compare_condition_with_min(target_version, feature_version) 307 308 @staticmethod 309 def get_warning_str_prefix(tv: str) -> str: 310 return 'Project specifies a minimum meson_version \'{}\' but uses features which were added in newer versions:'.format(tv) 311 312 def log_usage_warning(self, tv: str) -> None: 313 args = [ 314 'Project targeting', "'{}'".format(tv), 315 'but tried to use feature introduced in', 316 "'{}':".format(self.feature_version), 317 '{}.'.format(self.feature_name), 318 ] 319 if self.extra_message: 320 args.append(self.extra_message) 321 mlog.warning(*args) 322 323class FeatureDeprecated(FeatureCheckBase): 324 """Checks for deprecated features""" 325 326 # Class variable, shared across all instances 327 # 328 # Format: {subproject: {feature_version: set(feature_names)}} 329 feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] 330 331 @staticmethod 332 def check_version(target_version: str, feature_version: str) -> bool: 333 # For deprecation checks we need to return the inverse of FeatureNew checks 334 return not mesonlib.version_compare_condition_with_min(target_version, feature_version) 335 336 @staticmethod 337 def get_warning_str_prefix(tv: str) -> str: 338 return 'Deprecated features used:' 339 340 def log_usage_warning(self, tv: str) -> None: 341 args = [ 342 'Project targeting', "'{}'".format(tv), 343 'but tried to use feature deprecated since', 344 "'{}':".format(self.feature_version), 345 '{}.'.format(self.feature_name), 346 ] 347 if self.extra_message: 348 args.append(self.extra_message) 349 mlog.warning(*args) 350 351 352class FeatureCheckKwargsBase(metaclass=abc.ABCMeta): 353 354 @property 355 @abc.abstractmethod 356 def feature_check_class(self) -> T.Type[FeatureCheckBase]: 357 pass 358 359 def __init__(self, feature_name: str, feature_version: str, 360 kwargs: T.List[str], extra_message: T.Optional[str] = None): 361 self.feature_name = feature_name 362 self.feature_version = feature_version 363 self.kwargs = kwargs 364 self.extra_message = extra_message 365 366 def __call__(self, f): 367 @wraps(f) 368 def wrapped(*wrapped_args, **wrapped_kwargs): 369 kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5] 370 if subproject is None: 371 raise AssertionError('{!r}'.format(wrapped_args)) 372 for arg in self.kwargs: 373 if arg not in kwargs: 374 continue 375 name = arg + ' arg in ' + self.feature_name 376 self.feature_check_class.single_use( 377 name, self.feature_version, subproject, self.extra_message) 378 return f(*wrapped_args, **wrapped_kwargs) 379 return wrapped 380 381class FeatureNewKwargs(FeatureCheckKwargsBase): 382 feature_check_class = FeatureNew 383 384class FeatureDeprecatedKwargs(FeatureCheckKwargsBase): 385 feature_check_class = FeatureDeprecated 386 387 388class InterpreterException(mesonlib.MesonException): 389 pass 390 391class InvalidCode(InterpreterException): 392 pass 393 394class InvalidArguments(InterpreterException): 395 pass 396 397class SubdirDoneRequest(BaseException): 398 pass 399 400class ContinueRequest(BaseException): 401 pass 402 403class BreakRequest(BaseException): 404 pass 405 406class MutableInterpreterObject(InterpreterObject): 407 def __init__(self): 408 super().__init__() 409 410class Disabler(InterpreterObject): 411 def __init__(self): 412 super().__init__() 413 self.methods.update({'found': self.found_method}) 414 415 def found_method(self, args, kwargs): 416 return False 417 418def is_disabler(i) -> bool: 419 return isinstance(i, Disabler) 420 421def is_arg_disabled(arg) -> bool: 422 if is_disabler(arg): 423 return True 424 if isinstance(arg, list): 425 for i in arg: 426 if is_arg_disabled(i): 427 return True 428 return False 429 430def is_disabled(args, kwargs) -> bool: 431 for i in args: 432 if is_arg_disabled(i): 433 return True 434 for i in kwargs.values(): 435 if is_arg_disabled(i): 436 return True 437 return False 438 439class InterpreterBase: 440 elementary_types = (int, float, str, bool, list) 441 442 def __init__(self, source_root: str, subdir: str, subproject: str): 443 self.source_root = source_root 444 self.funcs = {} # type: T.Dict[str, T.Callable[[mparser.BaseNode, T.List[TYPE_nvar], T.Dict[str, TYPE_nvar]], TYPE_var]] 445 self.builtin = {} # type: T.Dict[str, InterpreterObject] 446 self.subdir = subdir 447 self.subproject = subproject 448 self.variables = {} # type: T.Dict[str, TYPE_var] 449 self.argument_depth = 0 450 self.current_lineno = -1 451 # Current node set during a function call. This can be used as location 452 # when printing a warning message during a method call. 453 self.current_node = None # type: mparser.BaseNode 454 455 def load_root_meson_file(self) -> None: 456 mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) 457 if not os.path.isfile(mesonfile): 458 raise InvalidArguments('Missing Meson file in %s' % mesonfile) 459 with open(mesonfile, encoding='utf8') as mf: 460 code = mf.read() 461 if code.isspace(): 462 raise InvalidCode('Builder file is empty.') 463 assert(isinstance(code, str)) 464 try: 465 self.ast = mparser.Parser(code, mesonfile).parse() 466 except mesonlib.MesonException as me: 467 me.file = mesonfile 468 raise me 469 470 def join_path_strings(self, args: T.Sequence[str]) -> str: 471 return os.path.join(*args).replace('\\', '/') 472 473 def parse_project(self) -> None: 474 """ 475 Parses project() and initializes languages, compilers etc. Do this 476 early because we need this before we parse the rest of the AST. 477 """ 478 self.evaluate_codeblock(self.ast, end=1) 479 480 def sanity_check_ast(self) -> None: 481 if not isinstance(self.ast, mparser.CodeBlockNode): 482 raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.') 483 if not self.ast.lines: 484 raise InvalidCode('No statements in code.') 485 first = self.ast.lines[0] 486 if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project': 487 raise InvalidCode('First statement must be a call to project') 488 489 def run(self) -> None: 490 # Evaluate everything after the first line, which is project() because 491 # we already parsed that in self.parse_project() 492 try: 493 self.evaluate_codeblock(self.ast, start=1) 494 except SubdirDoneRequest: 495 pass 496 497 def evaluate_codeblock(self, node: mparser.CodeBlockNode, start: int = 0, end: T.Optional[int] = None) -> None: 498 if node is None: 499 return 500 if not isinstance(node, mparser.CodeBlockNode): 501 e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.') 502 e.lineno = node.lineno 503 e.colno = node.colno 504 raise e 505 statements = node.lines[start:end] 506 i = 0 507 while i < len(statements): 508 cur = statements[i] 509 try: 510 self.current_lineno = cur.lineno 511 self.evaluate_statement(cur) 512 except Exception as e: 513 if getattr(e, 'lineno', None) is None: 514 # We are doing the equivalent to setattr here and mypy does not like it 515 e.lineno = cur.lineno # type: ignore 516 e.colno = cur.colno # type: ignore 517 e.file = os.path.join(self.source_root, self.subdir, environment.build_filename) # type: ignore 518 raise e 519 i += 1 # In THE FUTURE jump over blocks and stuff. 520 521 def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[TYPE_var]: 522 self.current_node = cur 523 if isinstance(cur, mparser.FunctionNode): 524 return self.function_call(cur) 525 elif isinstance(cur, mparser.AssignmentNode): 526 self.assignment(cur) 527 elif isinstance(cur, mparser.MethodNode): 528 return self.method_call(cur) 529 elif isinstance(cur, mparser.StringNode): 530 return cur.value 531 elif isinstance(cur, mparser.BooleanNode): 532 return cur.value 533 elif isinstance(cur, mparser.IfClauseNode): 534 return self.evaluate_if(cur) 535 elif isinstance(cur, mparser.IdNode): 536 return self.get_variable(cur.value) 537 elif isinstance(cur, mparser.ComparisonNode): 538 return self.evaluate_comparison(cur) 539 elif isinstance(cur, mparser.ArrayNode): 540 return self.evaluate_arraystatement(cur) 541 elif isinstance(cur, mparser.DictNode): 542 return self.evaluate_dictstatement(cur) 543 elif isinstance(cur, mparser.NumberNode): 544 return cur.value 545 elif isinstance(cur, mparser.AndNode): 546 return self.evaluate_andstatement(cur) 547 elif isinstance(cur, mparser.OrNode): 548 return self.evaluate_orstatement(cur) 549 elif isinstance(cur, mparser.NotNode): 550 return self.evaluate_notstatement(cur) 551 elif isinstance(cur, mparser.UMinusNode): 552 return self.evaluate_uminusstatement(cur) 553 elif isinstance(cur, mparser.ArithmeticNode): 554 return self.evaluate_arithmeticstatement(cur) 555 elif isinstance(cur, mparser.ForeachClauseNode): 556 self.evaluate_foreach(cur) 557 elif isinstance(cur, mparser.PlusAssignmentNode): 558 self.evaluate_plusassign(cur) 559 elif isinstance(cur, mparser.IndexNode): 560 return self.evaluate_indexing(cur) 561 elif isinstance(cur, mparser.TernaryNode): 562 return self.evaluate_ternary(cur) 563 elif isinstance(cur, mparser.ContinueNode): 564 raise ContinueRequest() 565 elif isinstance(cur, mparser.BreakNode): 566 raise BreakRequest() 567 elif isinstance(cur, self.elementary_types): 568 return cur 569 else: 570 raise InvalidCode("Unknown statement.") 571 return None 572 573 def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> list: 574 (arguments, kwargs) = self.reduce_arguments(cur.args) 575 if len(kwargs) > 0: 576 raise InvalidCode('Keyword arguments are invalid in array construction.') 577 return arguments 578 579 @FeatureNew('dict', '0.47.0') 580 def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Dict[str, T.Any]: 581 (arguments, kwargs) = self.reduce_arguments(cur.args, resolve_key_nodes=False) 582 assert (not arguments) 583 result = {} # type: T.Dict[str, T.Any] 584 self.argument_depth += 1 585 for key, value in kwargs.items(): 586 if not isinstance(key, mparser.StringNode): 587 FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject) 588 assert isinstance(key, mparser.BaseNode) # All keys must be nodes due to resolve_key_nodes=False 589 str_key = self.evaluate_statement(key) 590 if not isinstance(str_key, str): 591 raise InvalidArguments('Key must be a string') 592 if str_key in result: 593 raise InvalidArguments('Duplicate dictionary key: {}'.format(str_key)) 594 result[str_key] = value 595 self.argument_depth -= 1 596 return result 597 598 def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]: 599 v = self.evaluate_statement(cur.value) 600 if isinstance(v, Disabler): 601 return v 602 if not isinstance(v, bool): 603 raise InterpreterException('Argument to "not" is not a boolean.') 604 return not v 605 606 def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: 607 assert(isinstance(node, mparser.IfClauseNode)) 608 for i in node.ifs: 609 result = self.evaluate_statement(i.condition) 610 if isinstance(result, Disabler): 611 return result 612 if not(isinstance(result, bool)): 613 raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result)) 614 if result: 615 self.evaluate_codeblock(i.block) 616 return None 617 if not isinstance(node.elseblock, mparser.EmptyNode): 618 self.evaluate_codeblock(node.elseblock) 619 return None 620 621 def validate_comparison_types(self, val1: T.Any, val2: T.Any) -> bool: 622 if type(val1) != type(val2): 623 return False 624 return True 625 626 def evaluate_in(self, val1: T.Any, val2: T.Any) -> bool: 627 if not isinstance(val1, (str, int, float, ObjectHolder)): 628 raise InvalidArguments('lvalue of "in" operator must be a string, integer, float, or object') 629 if not isinstance(val2, (list, dict)): 630 raise InvalidArguments('rvalue of "in" operator must be an array or a dict') 631 return val1 in val2 632 633 def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[bool, Disabler]: 634 val1 = self.evaluate_statement(node.left) 635 if isinstance(val1, Disabler): 636 return val1 637 val2 = self.evaluate_statement(node.right) 638 if isinstance(val2, Disabler): 639 return val2 640 if node.ctype == 'in': 641 return self.evaluate_in(val1, val2) 642 elif node.ctype == 'notin': 643 return not self.evaluate_in(val1, val2) 644 valid = self.validate_comparison_types(val1, val2) 645 # Ordering comparisons of different types isn't allowed since PR #1810 646 # (0.41.0). Since PR #2884 we also warn about equality comparisons of 647 # different types, which will one day become an error. 648 if not valid and (node.ctype == '==' or node.ctype == '!='): 649 mlog.warning('''Trying to compare values of different types ({}, {}) using {}. 650The result of this is undefined and will become a hard error in a future Meson release.''' 651 .format(type(val1).__name__, type(val2).__name__, node.ctype), location=node) 652 if node.ctype == '==': 653 return val1 == val2 654 elif node.ctype == '!=': 655 return val1 != val2 656 elif not valid: 657 raise InterpreterException( 658 'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__, 659 type(val2).__name__, 660 node.ctype)) 661 elif not isinstance(val1, self.elementary_types): 662 raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.left, 'value', '<ERROR>'))) 663 elif not isinstance(val2, self.elementary_types): 664 raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.right, 'value', '<ERROR>'))) 665 # Use type: ignore because mypy will complain that we are comparing two Unions, 666 # but we actually guarantee earlier that both types are the same 667 elif node.ctype == '<': 668 return val1 < val2 # type: ignore 669 elif node.ctype == '<=': 670 return val1 <= val2 # type: ignore 671 elif node.ctype == '>': 672 return val1 > val2 # type: ignore 673 elif node.ctype == '>=': 674 return val1 >= val2 # type: ignore 675 else: 676 raise InvalidCode('You broke my compare eval.') 677 678 def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[bool, Disabler]: 679 l = self.evaluate_statement(cur.left) 680 if isinstance(l, Disabler): 681 return l 682 if not isinstance(l, bool): 683 raise InterpreterException('First argument to "and" is not a boolean.') 684 if not l: 685 return False 686 r = self.evaluate_statement(cur.right) 687 if isinstance(r, Disabler): 688 return r 689 if not isinstance(r, bool): 690 raise InterpreterException('Second argument to "and" is not a boolean.') 691 return r 692 693 def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[bool, Disabler]: 694 l = self.evaluate_statement(cur.left) 695 if isinstance(l, Disabler): 696 return l 697 if not isinstance(l, bool): 698 raise InterpreterException('First argument to "or" is not a boolean.') 699 if l: 700 return True 701 r = self.evaluate_statement(cur.right) 702 if isinstance(r, Disabler): 703 return r 704 if not isinstance(r, bool): 705 raise InterpreterException('Second argument to "or" is not a boolean.') 706 return r 707 708 def evaluate_uminusstatement(self, cur) -> T.Union[int, Disabler]: 709 v = self.evaluate_statement(cur.value) 710 if isinstance(v, Disabler): 711 return v 712 if not isinstance(v, int): 713 raise InterpreterException('Argument to negation is not an integer.') 714 return -v 715 716 @FeatureNew('/ with string arguments', '0.49.0') 717 def evaluate_path_join(self, l: str, r: str) -> str: 718 if not isinstance(l, str): 719 raise InvalidCode('The division operator can only append to a string.') 720 if not isinstance(r, str): 721 raise InvalidCode('The division operator can only append a string.') 722 return self.join_path_strings((l, r)) 723 724 def evaluate_division(self, l: T.Any, r: T.Any) -> T.Union[int, str]: 725 if isinstance(l, str) or isinstance(r, str): 726 return self.evaluate_path_join(l, r) 727 if isinstance(l, int) and isinstance(r, int): 728 if r == 0: 729 raise InvalidCode('Division by zero.') 730 return l // r 731 raise InvalidCode('Division works only with strings or integers.') 732 733 def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[int, str, dict, list, Disabler]: 734 l = self.evaluate_statement(cur.left) 735 if isinstance(l, Disabler): 736 return l 737 r = self.evaluate_statement(cur.right) 738 if isinstance(r, Disabler): 739 return r 740 741 if cur.operation == 'add': 742 if isinstance(l, dict) and isinstance(r, dict): 743 return {**l, **r} 744 try: 745 # MyPy error due to handling two Unions (we are catching all exceptions anyway) 746 return l + r # type: ignore 747 except Exception as e: 748 raise InvalidCode('Invalid use of addition: ' + str(e)) 749 elif cur.operation == 'sub': 750 if not isinstance(l, int) or not isinstance(r, int): 751 raise InvalidCode('Subtraction works only with integers.') 752 return l - r 753 elif cur.operation == 'mul': 754 if not isinstance(l, int) or not isinstance(r, int): 755 raise InvalidCode('Multiplication works only with integers.') 756 return l * r 757 elif cur.operation == 'div': 758 return self.evaluate_division(l, r) 759 elif cur.operation == 'mod': 760 if not isinstance(l, int) or not isinstance(r, int): 761 raise InvalidCode('Modulo works only with integers.') 762 return l % r 763 else: 764 raise InvalidCode('You broke me.') 765 766 def evaluate_ternary(self, node: mparser.TernaryNode) -> TYPE_var: 767 assert(isinstance(node, mparser.TernaryNode)) 768 result = self.evaluate_statement(node.condition) 769 if isinstance(result, Disabler): 770 return result 771 if not isinstance(result, bool): 772 raise InterpreterException('Ternary condition is not boolean.') 773 if result: 774 return self.evaluate_statement(node.trueblock) 775 else: 776 return self.evaluate_statement(node.falseblock) 777 778 def evaluate_foreach(self, node: mparser.ForeachClauseNode) -> None: 779 assert(isinstance(node, mparser.ForeachClauseNode)) 780 items = self.evaluate_statement(node.items) 781 782 if isinstance(items, list): 783 if len(node.varnames) != 1: 784 raise InvalidArguments('Foreach on array does not unpack') 785 varname = node.varnames[0] 786 for item in items: 787 self.set_variable(varname, item) 788 try: 789 self.evaluate_codeblock(node.block) 790 except ContinueRequest: 791 continue 792 except BreakRequest: 793 break 794 elif isinstance(items, dict): 795 if len(node.varnames) != 2: 796 raise InvalidArguments('Foreach on dict unpacks key and value') 797 for key, value in items.items(): 798 self.set_variable(node.varnames[0], key) 799 self.set_variable(node.varnames[1], value) 800 try: 801 self.evaluate_codeblock(node.block) 802 except ContinueRequest: 803 continue 804 except BreakRequest: 805 break 806 else: 807 raise InvalidArguments('Items of foreach loop must be an array or a dict') 808 809 def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None: 810 assert(isinstance(node, mparser.PlusAssignmentNode)) 811 varname = node.var_name 812 addition = self.evaluate_statement(node.value) 813 814 # Remember that all variables are immutable. We must always create a 815 # full new variable and then assign it. 816 old_variable = self.get_variable(varname) 817 new_value = None # type: T.Union[str, int, float, bool, dict, list] 818 if isinstance(old_variable, str): 819 if not isinstance(addition, str): 820 raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string') 821 new_value = old_variable + addition 822 elif isinstance(old_variable, int): 823 if not isinstance(addition, int): 824 raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int') 825 new_value = old_variable + addition 826 elif isinstance(old_variable, list): 827 if isinstance(addition, list): 828 new_value = old_variable + addition 829 else: 830 new_value = old_variable + [addition] 831 elif isinstance(old_variable, dict): 832 if not isinstance(addition, dict): 833 raise InvalidArguments('The += operator requires a dict on the right hand side if the variable on the left is a dict') 834 new_value = {**old_variable, **addition} 835 # Add other data types here. 836 else: 837 raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints') 838 self.set_variable(varname, new_value) 839 840 def evaluate_indexing(self, node: mparser.IndexNode) -> TYPE_var: 841 assert(isinstance(node, mparser.IndexNode)) 842 iobject = self.evaluate_statement(node.iobject) 843 if isinstance(iobject, Disabler): 844 return iobject 845 if not hasattr(iobject, '__getitem__'): 846 raise InterpreterException( 847 'Tried to index an object that doesn\'t support indexing.') 848 index = self.evaluate_statement(node.index) 849 850 if isinstance(iobject, dict): 851 if not isinstance(index, str): 852 raise InterpreterException('Key is not a string') 853 try: 854 return iobject[index] 855 except KeyError: 856 raise InterpreterException('Key %s is not in dict' % index) 857 else: 858 if not isinstance(index, int): 859 raise InterpreterException('Index value is not an integer.') 860 try: 861 # Ignore the MyPy error, since we don't know all indexable types here 862 # and we handle non indexable types with an exception 863 # TODO maybe find a better solution 864 return iobject[index] # type: ignore 865 except IndexError: 866 # We are already checking for the existance of __getitem__, so this should be save 867 raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore 868 869 def function_call(self, node: mparser.FunctionNode) -> T.Optional[TYPE_var]: 870 func_name = node.func_name 871 (posargs, kwargs) = self.reduce_arguments(node.args) 872 if is_disabled(posargs, kwargs) and func_name not in {'get_variable', 'set_variable', 'is_disabler'}: 873 return Disabler() 874 if func_name in self.funcs: 875 func = self.funcs[func_name] 876 func_args = posargs # type: T.Any 877 if not getattr(func, 'no-args-flattening', False): 878 func_args = flatten(posargs) 879 return func(node, func_args, self.kwargs_string_keys(kwargs)) 880 else: 881 self.unknown_function_called(func_name) 882 return None 883 884 def method_call(self, node: mparser.MethodNode) -> TYPE_var: 885 invokable = node.source_object 886 if isinstance(invokable, mparser.IdNode): 887 object_name = invokable.value 888 obj = self.get_variable(object_name) 889 else: 890 obj = self.evaluate_statement(invokable) 891 method_name = node.name 892 (args, kwargs) = self.reduce_arguments(node.args) 893 if is_disabled(args, kwargs): 894 return Disabler() 895 if isinstance(obj, str): 896 return self.string_method_call(obj, method_name, args, kwargs) 897 if isinstance(obj, bool): 898 return self.bool_method_call(obj, method_name, args, kwargs) 899 if isinstance(obj, int): 900 return self.int_method_call(obj, method_name, args, kwargs) 901 if isinstance(obj, list): 902 return self.array_method_call(obj, method_name, args, kwargs) 903 if isinstance(obj, dict): 904 return self.dict_method_call(obj, method_name, args, kwargs) 905 if isinstance(obj, mesonlib.File): 906 raise InvalidArguments('File object "%s" is not callable.' % obj) 907 if not isinstance(obj, InterpreterObject): 908 raise InvalidArguments('Variable "%s" is not callable.' % object_name) 909 # Special case. This is the only thing you can do with a disabler 910 # object. Every other use immediately returns the disabler object. 911 if isinstance(obj, Disabler): 912 if method_name == 'found': 913 return False 914 else: 915 return Disabler() 916 if method_name == 'extract_objects': 917 if not isinstance(obj, ObjectHolder): 918 raise InvalidArguments('Invalid operation "extract_objects" on variable "{}"'.format(object_name)) 919 self.validate_extraction(obj.held_object) 920 obj.current_node = node 921 return obj.method_call(method_name, args, self.kwargs_string_keys(kwargs)) 922 923 @builtinMethodNoKwargs 924 def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int]: 925 if method_name == 'to_string': 926 if not posargs: 927 if obj: 928 return 'true' 929 else: 930 return 'false' 931 elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str): 932 if obj: 933 return posargs[0] 934 else: 935 return posargs[1] 936 else: 937 raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.') 938 elif method_name == 'to_int': 939 if obj: 940 return 1 941 else: 942 return 0 943 else: 944 raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) 945 946 @builtinMethodNoKwargs 947 def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, bool]: 948 if method_name == 'is_even': 949 if not posargs: 950 return obj % 2 == 0 951 else: 952 raise InterpreterException('int.is_even() must have no arguments.') 953 elif method_name == 'is_odd': 954 if not posargs: 955 return obj % 2 != 0 956 else: 957 raise InterpreterException('int.is_odd() must have no arguments.') 958 elif method_name == 'to_string': 959 if not posargs: 960 return str(obj) 961 else: 962 raise InterpreterException('int.to_string() must have no arguments.') 963 else: 964 raise InterpreterException('Unknown method "%s" for an integer.' % method_name) 965 966 @staticmethod 967 def _get_one_string_posarg(posargs: T.List[TYPE_nvar], method_name: str) -> str: 968 if len(posargs) > 1: 969 m = '{}() must have zero or one arguments' 970 raise InterpreterException(m.format(method_name)) 971 elif len(posargs) == 1: 972 s = posargs[0] 973 if not isinstance(s, str): 974 m = '{}() argument must be a string' 975 raise InterpreterException(m.format(method_name)) 976 return s 977 return None 978 979 @builtinMethodNoKwargs 980 def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int, bool, T.List[str]]: 981 if method_name == 'strip': 982 s1 = self._get_one_string_posarg(posargs, 'strip') 983 if s1 is not None: 984 return obj.strip(s1) 985 return obj.strip() 986 elif method_name == 'format': 987 return self.format_string(obj, posargs) 988 elif method_name == 'to_upper': 989 return obj.upper() 990 elif method_name == 'to_lower': 991 return obj.lower() 992 elif method_name == 'underscorify': 993 return re.sub(r'[^a-zA-Z0-9]', '_', obj) 994 elif method_name == 'split': 995 s2 = self._get_one_string_posarg(posargs, 'split') 996 if s2 is not None: 997 return obj.split(s2) 998 return obj.split() 999 elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith': 1000 s3 = posargs[0] 1001 if not isinstance(s3, str): 1002 raise InterpreterException('Argument must be a string.') 1003 if method_name == 'startswith': 1004 return obj.startswith(s3) 1005 elif method_name == 'contains': 1006 return obj.find(s3) >= 0 1007 return obj.endswith(s3) 1008 elif method_name == 'to_int': 1009 try: 1010 return int(obj) 1011 except Exception: 1012 raise InterpreterException('String {!r} cannot be converted to int'.format(obj)) 1013 elif method_name == 'join': 1014 if len(posargs) != 1: 1015 raise InterpreterException('Join() takes exactly one argument.') 1016 strlist = posargs[0] 1017 check_stringlist(strlist) 1018 assert isinstance(strlist, list) # Required for mypy 1019 return obj.join(strlist) 1020 elif method_name == 'version_compare': 1021 if len(posargs) != 1: 1022 raise InterpreterException('Version_compare() takes exactly one argument.') 1023 cmpr = posargs[0] 1024 if not isinstance(cmpr, str): 1025 raise InterpreterException('Version_compare() argument must be a string.') 1026 return mesonlib.version_compare(obj, cmpr) 1027 raise InterpreterException('Unknown method "%s" for a string.' % method_name) 1028 1029 def format_string(self, templ: str, args: T.List[TYPE_nvar]) -> str: 1030 arg_strings = [] 1031 for arg in args: 1032 if isinstance(arg, mparser.BaseNode): 1033 arg = self.evaluate_statement(arg) 1034 if isinstance(arg, bool): # Python boolean is upper case. 1035 arg = str(arg).lower() 1036 arg_strings.append(str(arg)) 1037 1038 def arg_replace(match): 1039 idx = int(match.group(1)) 1040 if idx >= len(arg_strings): 1041 raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) 1042 return arg_strings[idx] 1043 1044 return re.sub(r'@(\d+)@', arg_replace, templ) 1045 1046 def unknown_function_called(self, func_name: str) -> None: 1047 raise InvalidCode('Unknown function "%s".' % func_name) 1048 1049 @builtinMethodNoKwargs 1050 def array_method_call(self, obj: list, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: 1051 if method_name == 'contains': 1052 def check_contains(el: list) -> bool: 1053 if len(posargs) != 1: 1054 raise InterpreterException('Contains method takes exactly one argument.') 1055 item = posargs[0] 1056 for element in el: 1057 if isinstance(element, list): 1058 found = check_contains(element) 1059 if found: 1060 return True 1061 if element == item: 1062 return True 1063 return False 1064 return check_contains(obj) 1065 elif method_name == 'length': 1066 return len(obj) 1067 elif method_name == 'get': 1068 index = posargs[0] 1069 fallback = None 1070 if len(posargs) == 2: 1071 fallback = posargs[1] 1072 elif len(posargs) > 2: 1073 m = 'Array method \'get()\' only takes two arguments: the ' \ 1074 'index and an optional fallback value if the index is ' \ 1075 'out of range.' 1076 raise InvalidArguments(m) 1077 if not isinstance(index, int): 1078 raise InvalidArguments('Array index must be a number.') 1079 if index < -len(obj) or index >= len(obj): 1080 if fallback is None: 1081 m = 'Array index {!r} is out of bounds for array of size {!r}.' 1082 raise InvalidArguments(m.format(index, len(obj))) 1083 if isinstance(fallback, mparser.BaseNode): 1084 return self.evaluate_statement(fallback) 1085 return fallback 1086 return obj[index] 1087 m = 'Arrays do not have a method called {!r}.' 1088 raise InterpreterException(m.format(method_name)) 1089 1090 @builtinMethodNoKwargs 1091 def dict_method_call(self, obj: dict, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: 1092 if method_name in ('has_key', 'get'): 1093 if method_name == 'has_key': 1094 if len(posargs) != 1: 1095 raise InterpreterException('has_key() takes exactly one argument.') 1096 else: 1097 if len(posargs) not in (1, 2): 1098 raise InterpreterException('get() takes one or two arguments.') 1099 1100 key = posargs[0] 1101 if not isinstance(key, (str)): 1102 raise InvalidArguments('Dictionary key must be a string.') 1103 1104 has_key = key in obj 1105 1106 if method_name == 'has_key': 1107 return has_key 1108 1109 if has_key: 1110 return obj[key] 1111 1112 if len(posargs) == 2: 1113 fallback = posargs[1] 1114 if isinstance(fallback, mparser.BaseNode): 1115 return self.evaluate_statement(fallback) 1116 return fallback 1117 1118 raise InterpreterException('Key {!r} is not in the dictionary.'.format(key)) 1119 1120 if method_name == 'keys': 1121 if len(posargs) != 0: 1122 raise InterpreterException('keys() takes no arguments.') 1123 return list(obj.keys()) 1124 1125 raise InterpreterException('Dictionaries do not have a method called "%s".' % method_name) 1126 1127 def reduce_arguments(self, args: mparser.ArgumentNode, resolve_key_nodes: bool = True) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]: 1128 assert(isinstance(args, mparser.ArgumentNode)) 1129 if args.incorrect_order(): 1130 raise InvalidArguments('All keyword arguments must be after positional arguments.') 1131 self.argument_depth += 1 1132 reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: T.List[TYPE_nvar] 1133 reduced_kw = {} # type: TYPE_nkwargs 1134 for key, val in args.kwargs.items(): 1135 reduced_key = key # type: T.Union[str, mparser.BaseNode] 1136 reduced_val = val # type: TYPE_nvar 1137 if resolve_key_nodes and isinstance(key, mparser.IdNode): 1138 assert isinstance(key.value, str) 1139 reduced_key = key.value 1140 if isinstance(reduced_val, mparser.BaseNode): 1141 reduced_val = self.evaluate_statement(reduced_val) 1142 reduced_kw[reduced_key] = reduced_val 1143 self.argument_depth -= 1 1144 final_kw = self.expand_default_kwargs(reduced_kw) 1145 return reduced_pos, final_kw 1146 1147 def expand_default_kwargs(self, kwargs: TYPE_nkwargs) -> TYPE_nkwargs: 1148 if 'kwargs' not in kwargs: 1149 return kwargs 1150 to_expand = kwargs.pop('kwargs') 1151 if not isinstance(to_expand, dict): 1152 raise InterpreterException('Value of "kwargs" must be dictionary.') 1153 if 'kwargs' in to_expand: 1154 raise InterpreterException('Kwargs argument must not contain a "kwargs" entry. Points for thinking meta, though. :P') 1155 for k, v in to_expand.items(): 1156 if k in kwargs: 1157 raise InterpreterException('Entry "{}" defined both as a keyword argument and in a "kwarg" entry.'.format(k)) 1158 kwargs[k] = v 1159 return kwargs 1160 1161 def kwargs_string_keys(self, kwargs: TYPE_nkwargs) -> T.Dict[str, TYPE_nvar]: 1162 kw = {} # type: T.Dict[str, TYPE_nvar] 1163 for key, val in kwargs.items(): 1164 if not isinstance(key, str): 1165 raise InterpreterException('Key of kwargs is not a string') 1166 kw[key] = val 1167 return kw 1168 1169 def assignment(self, node: mparser.AssignmentNode) -> None: 1170 assert(isinstance(node, mparser.AssignmentNode)) 1171 if self.argument_depth != 0: 1172 raise InvalidArguments('''Tried to assign values inside an argument list. 1173To specify a keyword argument, use : instead of =.''') 1174 var_name = node.var_name 1175 if not isinstance(var_name, str): 1176 raise InvalidArguments('Tried to assign value to a non-variable.') 1177 value = self.evaluate_statement(node.value) 1178 if not self.is_assignable(value): 1179 raise InvalidCode('Tried to assign an invalid value to variable.') 1180 # For mutable objects we need to make a copy on assignment 1181 if isinstance(value, MutableInterpreterObject): 1182 value = copy.deepcopy(value) 1183 self.set_variable(var_name, value) 1184 return None 1185 1186 def set_variable(self, varname: str, variable: TYPE_var) -> None: 1187 if variable is None: 1188 raise InvalidCode('Can not assign None to variable.') 1189 if not isinstance(varname, str): 1190 raise InvalidCode('First argument to set_variable must be a string.') 1191 if not self.is_assignable(variable): 1192 raise InvalidCode('Assigned value not of assignable type.') 1193 if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None: 1194 raise InvalidCode('Invalid variable name: ' + varname) 1195 if varname in self.builtin: 1196 raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) 1197 self.variables[varname] = variable 1198 1199 def get_variable(self, varname) -> TYPE_var: 1200 if varname in self.builtin: 1201 return self.builtin[varname] 1202 if varname in self.variables: 1203 return self.variables[varname] 1204 raise InvalidCode('Unknown variable "%s".' % varname) 1205 1206 def is_assignable(self, value: T.Any) -> bool: 1207 return isinstance(value, (InterpreterObject, dependencies.Dependency, 1208 str, int, list, dict, mesonlib.File)) 1209 1210 def validate_extraction(self, buildtarget: InterpreterObject) -> None: 1211 raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') 1212