Merge pull request #6316 from mensinda/typesAst

types: Annotations for ast, mparser.py, interpreterbase.py
pull/6727/head
Dylan Baker 5 years ago committed by GitHub
commit 6e865fc08d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/lint_mypy.yml
  2. 104
      mesonbuild/ast/interpreter.py
  3. 104
      mesonbuild/ast/introspection.py
  4. 29
      mesonbuild/ast/postprocess.py
  5. 76
      mesonbuild/ast/printer.py
  6. 57
      mesonbuild/ast/visitor.py
  7. 6
      mesonbuild/cmake/interpreter.py
  8. 28
      mesonbuild/interpreter.py
  9. 409
      mesonbuild/interpreterbase.py
  10. 4
      mesonbuild/mesonlib.py
  11. 5
      mesonbuild/mintro.py
  12. 489
      mesonbuild/mparser.py
  13. 4
      mesonbuild/optinterpreter.py
  14. 20
      mesonbuild/rewriter.py
  15. 2
      run_unittests.py

@ -30,4 +30,4 @@ jobs:
with:
python-version: '3.x'
- run: python -m pip install mypy
- run: mypy --follow-imports=skip mesonbuild/mtest.py mesonbuild/minit.py mesonbuild/mintro.py mesonbuild/msetup.py mesonbuild/wrap tools/ mesonbuild/modules/fs.py mesonbuild/dependencies/boost.py mesonbuild/dependencies/mpi.py mesonbuild/dependencies/hdf5.py mesonbuild/compilers/mixins/intel.py mesonbuild/mlog.py
- run: mypy --follow-imports=skip mesonbuild/interpreterbase.py mesonbuild/mtest.py mesonbuild/minit.py mesonbuild/mintro.py mesonbuild/mparser.py mesonbuild/msetup.py mesonbuild/ast mesonbuild/wrap tools/ mesonbuild/modules/fs.py mesonbuild/dependencies/boost.py mesonbuild/dependencies/mpi.py mesonbuild/dependencies/hdf5.py mesonbuild/compilers/mixins/intel.py mesonbuild/mlog.py

@ -19,19 +19,27 @@ from .visitor import AstVisitor
from .. import interpreterbase, mparser, mesonlib
from .. import environment
from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest
from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar, TYPE_nkwargs
from ..mparser import (
AndNode,
ArgumentNode,
ArithmeticNode,
ArrayNode,
AssignmentNode,
BaseNode,
ComparisonNode,
ElementaryNode,
EmptyNode,
ForeachClauseNode,
IdNode,
IfClauseNode,
IndexNode,
MethodNode,
OrNode,
PlusAssignmentNode,
StringNode,
TernaryNode,
UMinusNode,
)
import os, sys
@ -59,13 +67,13 @@ ADD_SOURCE = 0
REMOVE_SOURCE = 1
class AstInterpreter(interpreterbase.InterpreterBase):
def __init__(self, source_root: str, subdir: str, visitors: T.Optional[T.List[AstVisitor]] = None):
super().__init__(source_root, subdir)
def __init__(self, source_root: str, subdir: str, subproject: str, visitors: T.Optional[T.List[AstVisitor]] = None):
super().__init__(source_root, subdir, subproject)
self.visitors = visitors if visitors is not None else []
self.visited_subdirs = {}
self.assignments = {}
self.assign_vals = {}
self.reverse_assignment = {}
self.visited_subdirs = {} # type: T.Dict[str, bool]
self.assignments = {} # type: T.Dict[str, BaseNode]
self.assign_vals = {} # type: T.Dict[str, T.Any]
self.reverse_assignment = {} # type: T.Dict[str, BaseNode]
self.funcs.update({'project': self.func_do_nothing,
'test': self.func_do_nothing,
'benchmark': self.func_do_nothing,
@ -122,15 +130,15 @@ class AstInterpreter(interpreterbase.InterpreterBase):
'summary': self.func_do_nothing,
})
def func_do_nothing(self, node, args, kwargs):
def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> bool:
return True
def load_root_meson_file(self):
def load_root_meson_file(self) -> None:
super().load_root_meson_file()
for i in self.visitors:
self.ast.accept(i)
def func_subdir(self, node, args, kwargs):
def func_subdir(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None:
args = self.flatten_args(args)
if len(args) != 1 or not isinstance(args[0], str):
sys.stderr.write('Unable to evaluate subdir({}) in AstInterpreter --> Skipping\n'.format(args))
@ -165,64 +173,69 @@ class AstInterpreter(interpreterbase.InterpreterBase):
self.evaluate_codeblock(codeblock)
self.subdir = prev_subdir
def method_call(self, node):
def method_call(self, node: BaseNode) -> bool:
return True
def evaluate_arithmeticstatement(self, cur):
def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
return 0
def evaluate_uminusstatement(self, cur):
def evaluate_uminusstatement(self, cur: UMinusNode) -> int:
self.evaluate_statement(cur.value)
return 0
def evaluate_ternary(self, node):
def evaluate_ternary(self, node: TernaryNode) -> None:
assert(isinstance(node, TernaryNode))
self.evaluate_statement(node.condition)
self.evaluate_statement(node.trueblock)
self.evaluate_statement(node.falseblock)
def evaluate_plusassign(self, node):
def evaluate_plusassign(self, node: PlusAssignmentNode) -> None:
assert(isinstance(node, PlusAssignmentNode))
if node.var_name not in self.assignments:
self.assignments[node.var_name] = []
self.assign_vals[node.var_name] = []
self.assignments[node.var_name] += [node.value] # Save a reference to the value node
if hasattr(node.value, 'ast_id'):
# Cheat by doing a reassignment
self.assignments[node.var_name] = node.value # Save a reference to the value node
if node.value.ast_id:
self.reverse_assignment[node.value.ast_id] = node
self.assign_vals[node.var_name] += [self.evaluate_statement(node.value)]
self.assign_vals[node.var_name] = self.evaluate_statement(node.value)
def evaluate_indexing(self, node):
def evaluate_indexing(self, node: IndexNode) -> int:
return 0
def unknown_function_called(self, func_name):
def unknown_function_called(self, func_name: str) -> None:
pass
def reduce_arguments(self, args):
def reduce_arguments(self, args: ArgumentNode, resolve_key_nodes: bool = True) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]:
if isinstance(args, ArgumentNode):
kwargs = {} # type: T.Dict[T.Union[str, BaseNode], TYPE_nvar]
for key, val in args.kwargs.items():
if isinstance(key, (StringNode, IdNode)):
assert isinstance(key.value, str)
kwargs[key.value] = val
else:
kwargs[key] = val
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
return self.flatten_args(args.arguments), args.kwargs
return self.flatten_args(args.arguments), kwargs
else:
return self.flatten_args(args), {}
def evaluate_comparison(self, node):
def evaluate_comparison(self, node: ComparisonNode) -> bool:
self.evaluate_statement(node.left)
self.evaluate_statement(node.right)
return False
def evaluate_andstatement(self, cur):
def evaluate_andstatement(self, cur: AndNode) -> bool:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
return False
def evaluate_orstatement(self, cur):
def evaluate_orstatement(self, cur: OrNode) -> bool:
self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right)
return False
def evaluate_foreach(self, node):
def evaluate_foreach(self, node: ForeachClauseNode) -> None:
try:
self.evaluate_codeblock(node.block)
except ContinueRequest:
@ -230,30 +243,31 @@ class AstInterpreter(interpreterbase.InterpreterBase):
except BreakRequest:
pass
def evaluate_if(self, node):
def evaluate_if(self, node: IfClauseNode) -> None:
for i in node.ifs:
self.evaluate_codeblock(i.block)
if not isinstance(node.elseblock, EmptyNode):
self.evaluate_codeblock(node.elseblock)
def get_variable(self, varname):
def get_variable(self, varname: str) -> int:
return 0
def assignment(self, node):
def assignment(self, node: AssignmentNode) -> None:
assert(isinstance(node, AssignmentNode))
self.assignments[node.var_name] = [node.value] # Save a reference to the value node
if hasattr(node.value, 'ast_id'):
self.assignments[node.var_name] = node.value # Save a reference to the value node
if node.value.ast_id:
self.reverse_assignment[node.value.ast_id] = node
self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case
self.assign_vals[node.var_name] = self.evaluate_statement(node.value) # Evaluate the value just in case
def resolve_node(self, node: BaseNode, include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.Optional[T.Any]:
def quick_resolve(n: BaseNode, loop_detect: T.Optional[T.List[str]] = None) -> T.Any:
if loop_detect is None:
loop_detect = []
if isinstance(n, IdNode):
assert isinstance(n.value, str)
if n.value in loop_detect or n.value not in self.assignments:
return []
return quick_resolve(self.assignments[n.value][0], loop_detect = loop_detect + [n.value])
return quick_resolve(self.assignments[n.value], loop_detect = loop_detect + [n.value])
elif isinstance(n, ElementaryNode):
return n.value
else:
@ -266,7 +280,7 @@ class AstInterpreter(interpreterbase.InterpreterBase):
if not isinstance(node, BaseNode):
return None
assert(hasattr(node, 'ast_id'))
assert node.ast_id
if node.ast_id in id_loop_detect:
return None # Loop detected
id_loop_detect += [node.ast_id]
@ -296,7 +310,7 @@ class AstInterpreter(interpreterbase.InterpreterBase):
elif isinstance(node, MethodNode):
src = quick_resolve(node.source_object)
margs = self.flatten_args(node.args, include_unknown_args, id_loop_detect)
margs = self.flatten_args(node.args.arguments, include_unknown_args, id_loop_detect)
try:
if isinstance(src, str):
result = self.string_method_call(src, node.name, margs)
@ -315,7 +329,7 @@ class AstInterpreter(interpreterbase.InterpreterBase):
if isinstance(result, BaseNode):
result = self.resolve_node(result, include_unknown_args, id_loop_detect)
elif isinstance(result, list):
new_res = []
new_res = [] # type: T.List[TYPE_nvar]
for i in result:
if isinstance(i, BaseNode):
resolved = self.resolve_node(i, include_unknown_args, id_loop_detect)
@ -327,12 +341,14 @@ class AstInterpreter(interpreterbase.InterpreterBase):
return result
def flatten_args(self, args: T.Any, include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[T.Any]:
def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[TYPE_nvar]:
# Make sure we are always dealing with lists
if not isinstance(args, list):
args = [args]
if isinstance(args_raw, list):
args = args_raw
else:
args = [args_raw]
flattend_args = []
flattend_args = [] # type: T.List[TYPE_nvar]
# Resolve the contents of args
for i in args:
@ -346,7 +362,7 @@ class AstInterpreter(interpreterbase.InterpreterBase):
flattend_args += [i]
return flattend_args
def flatten_kwargs(self, kwargs: object, include_unknown_args: bool = False):
def flatten_kwargs(self, kwargs: T.Dict[str, TYPE_nvar], include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]:
flattend_kwargs = {}
for key, val in kwargs.items():
if isinstance(val, BaseNode):

@ -15,30 +15,40 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool
from . import AstInterpreter
from .interpreter import AstInterpreter
from .visitor import AstVisitor
from .. import compilers, environment, mesonlib, optinterpreter
from .. import coredata as cdata
from ..mesonlib import MachineChoice
from ..interpreterbase import InvalidArguments
from ..interpreterbase import InvalidArguments, TYPE_nvar
from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary
from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
import typing as T
import os
build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries']
class IntrospectionHelper:
# mimic an argparse namespace
def __init__(self, cross_file):
self.cross_file = cross_file
self.native_file = None
self.cmd_line_options = {}
def __init__(self, cross_file: str):
self.cross_file = cross_file # type: str
self.native_file = None # type: str
self.cmd_line_options = {} # type: T.Dict[str, str]
class IntrospectionInterpreter(AstInterpreter):
# Interpreter to detect the options without a build directory
# Most of the code is stolen from interpreter.Interpreter
def __init__(self, source_root, subdir, backend, visitors=None, cross_file=None, subproject='', subproject_dir='subprojects', env=None):
def __init__(self,
source_root: str,
subdir: str,
backend: str,
visitors: T.Optional[T.List[AstVisitor]] = None,
cross_file: T.Optional[str] = None,
subproject: str = '',
subproject_dir: str = 'subprojects',
env: T.Optional[environment.Environment] = None):
visitors = visitors if visitors is not None else []
super().__init__(source_root, subdir, visitors=visitors)
super().__init__(source_root, subdir, subproject, visitors=visitors)
options = IntrospectionHelper(cross_file)
self.cross_file = cross_file
@ -46,16 +56,15 @@ class IntrospectionInterpreter(AstInterpreter):
self.environment = environment.Environment(source_root, None, options)
else:
self.environment = env
self.subproject = subproject
self.subproject_dir = subproject_dir
self.coredata = self.environment.get_coredata()
self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
self.backend = backend
self.default_options = {'backend': self.backend}
self.project_data = {}
self.targets = []
self.dependencies = []
self.project_node = None
self.project_data = {} # type: T.Dict[str, T.Any]
self.targets = [] # type: T.List[T.Dict[str, T.Any]]
self.dependencies = [] # type: T.List[T.Dict[str, T.Any]]
self.project_node = None # type: BaseNode
self.funcs.update({
'add_languages': self.func_add_languages,
@ -70,7 +79,7 @@ class IntrospectionInterpreter(AstInterpreter):
'both_libraries': self.func_both_lib,
})
def func_project(self, node, args, kwargs):
def func_project(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None:
if self.project_node:
raise InvalidArguments('Second call to project()')
self.project_node = node
@ -99,7 +108,8 @@ class IntrospectionInterpreter(AstInterpreter):
if not self.is_subproject() and 'subproject_dir' in kwargs:
spdirname = kwargs['subproject_dir']
if isinstance(spdirname, ElementaryNode):
if isinstance(spdirname, StringNode):
assert isinstance(spdirname.value, str)
self.subproject_dir = spdirname.value
if not self.is_subproject():
self.project_data['subprojects'] = []
@ -115,7 +125,7 @@ class IntrospectionInterpreter(AstInterpreter):
self.coredata.set_options(options)
self.func_add_languages(None, proj_langs, None)
def do_subproject(self, dirname):
def do_subproject(self, dirname: str) -> None:
subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
subpr = os.path.join(subproject_dir_abs, dirname)
try:
@ -126,15 +136,20 @@ class IntrospectionInterpreter(AstInterpreter):
except (mesonlib.MesonException, RuntimeError):
return
def func_add_languages(self, node, args, kwargs):
def func_add_languages(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None:
args = self.flatten_args(args)
for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]:
for lang in sorted(args, key=compilers.sort_clink):
if isinstance(lang, StringNode):
assert isinstance(lang.value, str)
lang = lang.value
if not isinstance(lang, str):
continue
lang = lang.lower()
if lang not in self.coredata.compilers[for_machine]:
self.environment.detect_compiler_for(lang, for_machine)
def func_dependency(self, node, args, kwargs):
def func_dependency(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None:
args = self.flatten_args(args)
kwargs = self.flatten_kwargs(kwargs)
if not args:
@ -145,7 +160,6 @@ class IntrospectionInterpreter(AstInterpreter):
version = kwargs.get('version', [])
if not isinstance(version, list):
version = [version]
condition_level = node.condition_level if hasattr(node, 'condition_level') else 0
if isinstance(required, ElementaryNode):
required = required.value
if not isinstance(required, bool):
@ -155,24 +169,24 @@ class IntrospectionInterpreter(AstInterpreter):
'required': required,
'version': version,
'has_fallback': has_fallback,
'conditional': condition_level > 0,
'node': node,
'conditional': node.condition_level > 0,
'node': node
}]
def build_target(self, node, args, kwargs, targetclass):
def build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs_raw: T.Dict[str, TYPE_nvar], targetclass) -> T.Optional[T.Dict[str, T.Any]]:
args = self.flatten_args(args)
if not args or not isinstance(args[0], str):
return
return None
name = args[0]
srcqueue = [node]
# Process the sources BEFORE flattening the kwargs, to preserve the original nodes
if 'sources' in kwargs:
srcqueue += mesonlib.listify(kwargs['sources'])
if 'sources' in kwargs_raw:
srcqueue += mesonlib.listify(kwargs_raw['sources'])
kwargs = self.flatten_kwargs(kwargs, True)
kwargs = self.flatten_kwargs(kwargs_raw, True)
source_nodes = []
source_nodes = [] # type: T.List[BaseNode]
while srcqueue:
curr = srcqueue.pop(0)
arg_node = None
@ -183,9 +197,10 @@ class IntrospectionInterpreter(AstInterpreter):
arg_node = curr.args
elif isinstance(curr, IdNode):
# Try to resolve the ID and append the node to the queue
assert isinstance(curr.value, str)
var_name = curr.value
if var_name in self.assignments and self.assignments[var_name]:
tmp_node = self.assignments[var_name][0]
if var_name in self.assignments:
tmp_node = self.assignments[var_name]
if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)):
srcqueue += [tmp_node]
elif isinstance(curr, ArithmeticNode):
@ -206,8 +221,9 @@ class IntrospectionInterpreter(AstInterpreter):
kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in kwargs_reduced.items()}
kwargs_reduced = {k: v for k, v in kwargs_reduced.items() if not isinstance(v, BaseNode)}
for_machine = MachineChoice.HOST
objects = []
empty_sources = [] # Passing the unresolved sources list causes errors
objects = [] # type: T.List[T.Any]
empty_sources = [] # type: T.List[T.Any]
# Passing the unresolved sources list causes errors
target = targetclass(name, self.subdir, self.subproject, for_machine, empty_sources, objects, self.environment, kwargs_reduced)
new_target = {
@ -227,7 +243,7 @@ class IntrospectionInterpreter(AstInterpreter):
self.targets += [new_target]
return new_target
def build_library(self, node, args, kwargs):
def build_library(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
default_library = self.coredata.get_builtin_option('default_library')
if default_library == 'shared':
return self.build_target(node, args, kwargs, SharedLibrary)
@ -235,31 +251,32 @@ class IntrospectionInterpreter(AstInterpreter):
return self.build_target(node, args, kwargs, StaticLibrary)
elif default_library == 'both':
return self.build_target(node, args, kwargs, SharedLibrary)
return None
def func_executable(self, node, args, kwargs):
def func_executable(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, Executable)
def func_static_lib(self, node, args, kwargs):
def func_static_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, StaticLibrary)
def func_shared_lib(self, node, args, kwargs):
def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, SharedLibrary)
def func_both_lib(self, node, args, kwargs):
def func_both_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, SharedLibrary)
def func_shared_module(self, node, args, kwargs):
def func_shared_module(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, SharedModule)
def func_library(self, node, args, kwargs):
def func_library(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_library(node, args, kwargs)
def func_jar(self, node, args, kwargs):
def func_jar(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
return self.build_target(node, args, kwargs, Jar)
def func_build_target(self, node, args, kwargs):
def func_build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]:
if 'target_type' not in kwargs:
return
return None
target_type = kwargs.pop('target_type')
if isinstance(target_type, ElementaryNode):
target_type = target_type.value
@ -275,11 +292,12 @@ class IntrospectionInterpreter(AstInterpreter):
return self.build_library(node, args, kwargs)
elif target_type == 'jar':
return self.build_target(node, args, kwargs, Jar)
return None
def is_subproject(self):
def is_subproject(self) -> bool:
return self.subproject != ''
def analyze(self):
def analyze(self) -> None:
self.load_root_meson_file()
self.sanity_check_ast()
self.parse_project()

@ -17,48 +17,49 @@
from . import AstVisitor
from .. import mparser
import typing as T
class AstIndentationGenerator(AstVisitor):
def __init__(self):
self.level = 0
def visit_default_func(self, node: mparser.BaseNode):
def visit_default_func(self, node: mparser.BaseNode) -> None:
# Store the current level in the node
node.level = self.level
def visit_ArrayNode(self, node: mparser.ArrayNode):
def visit_ArrayNode(self, node: mparser.ArrayNode) -> None:
self.visit_default_func(node)
self.level += 1
node.args.accept(self)
self.level -= 1
def visit_DictNode(self, node: mparser.DictNode):
def visit_DictNode(self, node: mparser.DictNode) -> None:
self.visit_default_func(node)
self.level += 1
node.args.accept(self)
self.level -= 1
def visit_MethodNode(self, node: mparser.MethodNode):
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
self.visit_default_func(node)
node.source_object.accept(self)
self.level += 1
node.args.accept(self)
self.level -= 1
def visit_FunctionNode(self, node: mparser.FunctionNode):
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
self.visit_default_func(node)
self.level += 1
node.args.accept(self)
self.level -= 1
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode):
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
self.visit_default_func(node)
self.level += 1
node.items.accept(self)
node.block.accept(self)
self.level -= 1
def visit_IfClauseNode(self, node: mparser.IfClauseNode):
def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
self.visit_default_func(node)
for i in node.ifs:
i.accept(self)
@ -67,7 +68,7 @@ class AstIndentationGenerator(AstVisitor):
node.elseblock.accept(self)
self.level -= 1
def visit_IfNode(self, node: mparser.IfNode):
def visit_IfNode(self, node: mparser.IfNode) -> None:
self.visit_default_func(node)
self.level += 1
node.condition.accept(self)
@ -76,9 +77,9 @@ class AstIndentationGenerator(AstVisitor):
class AstIDGenerator(AstVisitor):
def __init__(self):
self.counter = {}
self.counter = {} # type: T.Dict[str, int]
def visit_default_func(self, node: mparser.BaseNode):
def visit_default_func(self, node: mparser.BaseNode) -> None:
name = type(node).__name__
if name not in self.counter:
self.counter[name] = 0
@ -89,17 +90,17 @@ class AstConditionLevel(AstVisitor):
def __init__(self):
self.condition_level = 0
def visit_default_func(self, node: mparser.BaseNode):
def visit_default_func(self, node: mparser.BaseNode) -> None:
node.condition_level = self.condition_level
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode):
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
self.visit_default_func(node)
self.condition_level += 1
node.items.accept(self)
node.block.accept(self)
self.condition_level -= 1
def visit_IfClauseNode(self, node: mparser.IfClauseNode):
def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
self.visit_default_func(node)
for i in node.ifs:
i.accept(self)
@ -108,7 +109,7 @@ class AstConditionLevel(AstVisitor):
node.elseblock.accept(self)
self.condition_level -= 1
def visit_IfNode(self, node: mparser.IfNode):
def visit_IfNode(self, node: mparser.IfNode) -> None:
self.visit_default_func(node)
self.condition_level += 1
node.condition.accept(self)

@ -36,114 +36,111 @@ class AstPrinter(AstVisitor):
self.is_newline = True
self.last_level = 0
def post_process(self):
def post_process(self) -> None:
self.result = re.sub(r'\s+\n', '\n', self.result)
def append(self, data: str, node: mparser.BaseNode):
level = 0
if node and hasattr(node, 'level'):
level = node.level
else:
level = self.last_level
self.last_level = level
def append(self, data: str, node: mparser.BaseNode) -> None:
self.last_level = node.level
if self.is_newline:
self.result += ' ' * (level * self.indent)
self.result += ' ' * (node.level * self.indent)
self.result += data
self.is_newline = False
def append_padded(self, data: str, node: mparser.BaseNode):
def append_padded(self, data: str, node: mparser.BaseNode) -> None:
if self.result[-1] not in [' ', '\n']:
data = ' ' + data
self.append(data + ' ', node)
def newline(self):
def newline(self) -> None:
self.result += '\n'
self.is_newline = True
def visit_BooleanNode(self, node: mparser.BooleanNode):
def visit_BooleanNode(self, node: mparser.BooleanNode) -> None:
self.append('true' if node.value else 'false', node)
def visit_IdNode(self, node: mparser.IdNode):
def visit_IdNode(self, node: mparser.IdNode) -> None:
assert isinstance(node.value, str)
self.append(node.value, node)
def visit_NumberNode(self, node: mparser.NumberNode):
def visit_NumberNode(self, node: mparser.NumberNode) -> None:
self.append(str(node.value), node)
def visit_StringNode(self, node: mparser.StringNode):
def visit_StringNode(self, node: mparser.StringNode) -> None:
assert isinstance(node.value, str)
self.append("'" + node.value + "'", node)
def visit_ContinueNode(self, node: mparser.ContinueNode):
def visit_ContinueNode(self, node: mparser.ContinueNode) -> None:
self.append('continue', node)
def visit_BreakNode(self, node: mparser.BreakNode):
def visit_BreakNode(self, node: mparser.BreakNode) -> None:
self.append('break', node)
def visit_ArrayNode(self, node: mparser.ArrayNode):
def visit_ArrayNode(self, node: mparser.ArrayNode) -> None:
self.append('[', node)
node.args.accept(self)
self.append(']', node)
def visit_DictNode(self, node: mparser.DictNode):
def visit_DictNode(self, node: mparser.DictNode) -> None:
self.append('{', node)
node.args.accept(self)
self.append('}', node)
def visit_OrNode(self, node: mparser.OrNode):
def visit_OrNode(self, node: mparser.OrNode) -> None:
node.left.accept(self)
self.append_padded('or', node)
node.right.accept(self)
def visit_AndNode(self, node: mparser.AndNode):
def visit_AndNode(self, node: mparser.AndNode) -> None:
node.left.accept(self)
self.append_padded('and', node)
node.right.accept(self)
def visit_ComparisonNode(self, node: mparser.ComparisonNode):
def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None:
node.left.accept(self)
self.append_padded(node.ctype, node)
node.right.accept(self)
def visit_ArithmeticNode(self, node: mparser.ArithmeticNode):
def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None:
node.left.accept(self)
self.append_padded(arithmic_map[node.operation], node)
node.right.accept(self)
def visit_NotNode(self, node: mparser.NotNode):
def visit_NotNode(self, node: mparser.NotNode) -> None:
self.append_padded('not', node)
node.value.accept(self)
def visit_CodeBlockNode(self, node: mparser.CodeBlockNode):
def visit_CodeBlockNode(self, node: mparser.CodeBlockNode) -> None:
for i in node.lines:
i.accept(self)
self.newline()
def visit_IndexNode(self, node: mparser.IndexNode):
def visit_IndexNode(self, node: mparser.IndexNode) -> None:
node.iobject.accept(self)
self.append('[', node)
node.index.accept(self)
self.append(']', node)
def visit_MethodNode(self, node: mparser.MethodNode):
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
node.source_object.accept(self)
self.append('.' + node.name + '(', node)
node.args.accept(self)
self.append(')', node)
def visit_FunctionNode(self, node: mparser.FunctionNode):
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
self.append(node.func_name + '(', node)
node.args.accept(self)
self.append(')', node)
def visit_AssignmentNode(self, node: mparser.AssignmentNode):
def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
self.append(node.var_name + ' = ', node)
node.value.accept(self)
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode):
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
self.append(node.var_name + ' += ', node)
node.value.accept(self)
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode):
varnames = [x.value for x in node.varnames]
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
varnames = [x for x in node.varnames]
self.append_padded('foreach', node)
self.append_padded(', '.join(varnames), node)
self.append_padded(':', node)
@ -152,7 +149,7 @@ class AstPrinter(AstVisitor):
node.block.accept(self)
self.append('endforeach', node)
def visit_IfClauseNode(self, node: mparser.IfClauseNode):
def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
prefix = ''
for i in node.ifs:
self.append_padded(prefix + 'if', node)
@ -163,23 +160,23 @@ class AstPrinter(AstVisitor):
node.elseblock.accept(self)
self.append('endif', node)
def visit_UMinusNode(self, node: mparser.UMinusNode):
def visit_UMinusNode(self, node: mparser.UMinusNode) -> None:
self.append_padded('-', node)
node.value.accept(self)
def visit_IfNode(self, node: mparser.IfNode):
def visit_IfNode(self, node: mparser.IfNode) -> None:
node.condition.accept(self)
self.newline()
node.block.accept(self)
def visit_TernaryNode(self, node: mparser.TernaryNode):
def visit_TernaryNode(self, node: mparser.TernaryNode) -> None:
node.condition.accept(self)
self.append_padded('?', node)
node.trueblock.accept(self)
self.append_padded(':', node)
node.falseblock.accept(self)
def visit_ArgumentNode(self, node: mparser.ArgumentNode):
def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None:
break_args = (len(node.arguments) + len(node.kwargs)) > self.arg_newline_cutoff
for i in node.arguments + list(node.kwargs.values()):
if not isinstance(i, (mparser.ElementaryNode, mparser.IndexNode)):
@ -192,10 +189,7 @@ class AstPrinter(AstVisitor):
if break_args:
self.newline()
for key, val in node.kwargs.items():
if isinstance(key, str):
self.append(key, node)
else:
key.accept(self)
key.accept(self)
self.append_padded(':', node)
val.accept(self)
self.append(', ', node)

@ -21,119 +21,120 @@ class AstVisitor:
def __init__(self):
pass
def visit_default_func(self, node: mparser.BaseNode):
def visit_default_func(self, node: mparser.BaseNode) -> None:
pass
def visit_BooleanNode(self, node: mparser.BooleanNode):
def visit_BooleanNode(self, node: mparser.BooleanNode) -> None:
self.visit_default_func(node)
def visit_IdNode(self, node: mparser.IdNode):
def visit_IdNode(self, node: mparser.IdNode) -> None:
self.visit_default_func(node)
def visit_NumberNode(self, node: mparser.NumberNode):
def visit_NumberNode(self, node: mparser.NumberNode) -> None:
self.visit_default_func(node)
def visit_StringNode(self, node: mparser.StringNode):
def visit_StringNode(self, node: mparser.StringNode) -> None:
self.visit_default_func(node)
def visit_ContinueNode(self, node: mparser.ContinueNode):
def visit_ContinueNode(self, node: mparser.ContinueNode) -> None:
self.visit_default_func(node)
def visit_BreakNode(self, node: mparser.BreakNode):
def visit_BreakNode(self, node: mparser.BreakNode) -> None:
self.visit_default_func(node)
def visit_ArrayNode(self, node: mparser.ArrayNode):
def visit_ArrayNode(self, node: mparser.ArrayNode) -> None:
self.visit_default_func(node)
node.args.accept(self)
def visit_DictNode(self, node: mparser.DictNode):
def visit_DictNode(self, node: mparser.DictNode) -> None:
self.visit_default_func(node)
node.args.accept(self)
def visit_EmptyNode(self, node: mparser.EmptyNode):
def visit_EmptyNode(self, node: mparser.EmptyNode) -> None:
self.visit_default_func(node)
def visit_OrNode(self, node: mparser.OrNode):
def visit_OrNode(self, node: mparser.OrNode) -> None:
self.visit_default_func(node)
node.left.accept(self)
node.right.accept(self)
def visit_AndNode(self, node: mparser.AndNode):
def visit_AndNode(self, node: mparser.AndNode) -> None:
self.visit_default_func(node)
node.left.accept(self)
node.right.accept(self)
def visit_ComparisonNode(self, node: mparser.ComparisonNode):
def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None:
self.visit_default_func(node)
node.left.accept(self)
node.right.accept(self)
def visit_ArithmeticNode(self, node: mparser.ArithmeticNode):
def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None:
self.visit_default_func(node)
node.left.accept(self)
node.right.accept(self)
def visit_NotNode(self, node: mparser.NotNode):
def visit_NotNode(self, node: mparser.NotNode) -> None:
self.visit_default_func(node)
node.value.accept(self)
def visit_CodeBlockNode(self, node: mparser.CodeBlockNode):
def visit_CodeBlockNode(self, node: mparser.CodeBlockNode) -> None:
self.visit_default_func(node)
for i in node.lines:
i.accept(self)
def visit_IndexNode(self, node: mparser.IndexNode):
def visit_IndexNode(self, node: mparser.IndexNode) -> None:
self.visit_default_func(node)
node.iobject.accept(self)
node.index.accept(self)
def visit_MethodNode(self, node: mparser.MethodNode):
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
self.visit_default_func(node)
node.source_object.accept(self)
node.args.accept(self)
def visit_FunctionNode(self, node: mparser.FunctionNode):
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
self.visit_default_func(node)
node.args.accept(self)
def visit_AssignmentNode(self, node: mparser.AssignmentNode):
def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
self.visit_default_func(node)
node.value.accept(self)
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode):
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
self.visit_default_func(node)
node.value.accept(self)
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode):
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
self.visit_default_func(node)
node.items.accept(self)
node.block.accept(self)
def visit_IfClauseNode(self, node: mparser.IfClauseNode):
def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
self.visit_default_func(node)
for i in node.ifs:
i.accept(self)
if node.elseblock:
node.elseblock.accept(self)
def visit_UMinusNode(self, node: mparser.UMinusNode):
def visit_UMinusNode(self, node: mparser.UMinusNode) -> None:
self.visit_default_func(node)
node.value.accept(self)
def visit_IfNode(self, node: mparser.IfNode):
def visit_IfNode(self, node: mparser.IfNode) -> None:
self.visit_default_func(node)
node.condition.accept(self)
node.block.accept(self)
def visit_TernaryNode(self, node: mparser.TernaryNode):
def visit_TernaryNode(self, node: mparser.TernaryNode) -> None:
self.visit_default_func(node)
node.condition.accept(self)
node.trueblock.accept(self)
node.falseblock.accept(self)
def visit_ArgumentNode(self, node: mparser.ArgumentNode):
def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None:
self.visit_default_func(node)
for i in node.arguments:
i.accept(self)
for val in node.kwargs.values():
for key, val in node.kwargs.items():
key.accept(self)
val.accept(self)

@ -978,7 +978,7 @@ class CMakeInterpreter:
if isinstance(value, str):
return string(value)
elif isinstance(value, bool):
return BooleanNode(token(), value)
return BooleanNode(token(val=value))
elif isinstance(value, int):
return number(value)
elif isinstance(value, list):
@ -1002,7 +1002,7 @@ class CMakeInterpreter:
if not isinstance(args, list):
args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items() if v is not None}
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n)
return func_n
@ -1013,7 +1013,7 @@ class CMakeInterpreter:
if not isinstance(args, list):
args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items() if v is not None}
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
return MethodNode(self.subdir, 0, 0, obj, name, args_n)
def assign(var_name: str, value: BaseNode) -> AssignmentNode:

@ -2182,13 +2182,12 @@ class Interpreter(InterpreterBase):
def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects',
modules = None, default_project_options=None, mock=False, ast=None):
super().__init__(build.environment.get_source_dir(), subdir)
super().__init__(build.environment.get_source_dir(), subdir, subproject)
self.an_unpicklable_object = mesonlib.an_unpicklable_object
self.build = build
self.environment = build.environment
self.coredata = self.environment.get_coredata()
self.backend = backend
self.subproject = subproject
self.summary = {}
if modules is None:
self.modules = {}
@ -4426,15 +4425,15 @@ Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_s
ef = extract_as_list(kwargs, 'extra_files')
kwargs['extra_files'] = self.source_strings_to_files(ef)
self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources)
if targetholder is ExecutableHolder:
if targetholder == ExecutableHolder:
targetclass = build.Executable
elif targetholder is SharedLibraryHolder:
elif targetholder == SharedLibraryHolder:
targetclass = build.SharedLibrary
elif targetholder is SharedModuleHolder:
elif targetholder == SharedModuleHolder:
targetclass = build.SharedModule
elif targetholder is StaticLibraryHolder:
elif targetholder == StaticLibraryHolder:
targetclass = build.StaticLibrary
elif targetholder is JarHolder:
elif targetholder == JarHolder:
targetclass = build.Jar
else:
mlog.debug('Unknown target type:', str(targetholder))
@ -4500,7 +4499,7 @@ This will become a hard error in the future.''', location=self.current_node)
raise InterpreterException('Tried to add non-existing source file %s.' % s)
# Only permit object extraction from the same subproject
def validate_extraction(self, buildtarget):
def validate_extraction(self, buildtarget: InterpreterObject) -> None:
if not self.subdir.startswith(self.subproject_dir):
if buildtarget.subdir.startswith(self.subproject_dir):
raise InterpreterException('Tried to extract objects from a subproject target.')
@ -4510,19 +4509,6 @@ This will become a hard error in the future.''', location=self.current_node)
if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
raise InterpreterException('Tried to extract objects from a different subproject.')
def check_contains(self, obj, args):
if len(args) != 1:
raise InterpreterException('Contains method takes exactly one argument.')
item = args[0]
for element in obj:
if isinstance(element, list):
found = self.check_contains(element, args)
if found:
return True
if element == item:
return True
return False
def is_subproject(self):
return self.subproject != ''

@ -19,19 +19,43 @@ from . import mparser, mesonlib, mlog
from . import environment, dependencies
import os, copy, re
import collections.abc
from functools import wraps
import typing as T
class ObjectHolder:
def __init__(self, obj, subproject=None):
self.held_object = obj
self.subproject = subproject
class InterpreterObject:
def __init__(self):
self.methods = {} # type: T.Dict[str, T.Callable]
# Current node set during a method call. This can be used as location
# when printing a warning message during a method call.
self.current_node = None # type: mparser.BaseNode
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']]):
if method_name in self.methods:
method = self.methods[method_name]
if not getattr(method, 'no-args-flattening', False):
args = flatten(args)
return method(args, kwargs)
raise InvalidCode('Unknown method "%s" in object.' % method_name)
TV_InterpreterObject = T.TypeVar('TV_InterpreterObject')
class ObjectHolder(T.Generic[TV_InterpreterObject]):
def __init__(self, obj: InterpreterObject, subproject: T.Optional[str] = None):
self.held_object = obj # type: InterpreterObject
self.subproject = subproject # type: str
def __repr__(self):
return '<Holder: {!r}>'.format(self.held_object)
TYPE_elementary = T.Union[str, int, float, bool]
TYPE_var = T.Union[TYPE_elementary, list, dict, InterpreterObject, ObjectHolder]
TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode]
TYPE_nkwargs = T.Dict[T.Union[mparser.BaseNode, str], TYPE_nvar]
# Decorators for method calls.
def check_stringlist(a, msg='Arguments must be strings.'):
def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None:
if not isinstance(a, list):
mlog.debug('Not a list:', str(a))
raise InvalidArguments('Argument not a list.')
@ -39,7 +63,7 @@ def check_stringlist(a, msg='Arguments must be strings.'):
mlog.debug('Element not a string:', str(a))
raise InvalidArguments(msg)
def _get_callee_args(wrapped_args, want_subproject=False):
def _get_callee_args(wrapped_args, want_subproject: bool = False):
s = wrapped_args[0]
n = len(wrapped_args)
# Raise an error if the codepaths are not there
@ -100,12 +124,13 @@ def _get_callee_args(wrapped_args, want_subproject=False):
kwargs = kwargs if kwargs is not None else {}
return s, node, args, kwargs, subproject
def flatten(args):
def flatten(args: T.Union[TYPE_nvar, T.List[TYPE_nvar]]) -> T.List[TYPE_nvar]:
if isinstance(args, mparser.StringNode):
return args.value
if isinstance(args, (int, str, mesonlib.File, InterpreterObject)):
return args
result = []
assert isinstance(args.value, str)
return [args.value]
if not isinstance(args, collections.abc.Sequence):
return [args]
result = [] # type: T.List[TYPE_nvar]
for a in args:
if isinstance(a, list):
rest = flatten(a)
@ -160,8 +185,8 @@ def disablerIfNotFound(f):
class permittedKwargs:
def __init__(self, permitted):
self.permitted = permitted
def __init__(self, permitted: T.Set[str]):
self.permitted = permitted # type: T.Set[str]
def __call__(self, f):
@wraps(f)
@ -178,18 +203,23 @@ class permittedKwargs:
class FeatureCheckBase:
"Base class for feature version checks"
def __init__(self, feature_name, version):
self.feature_name = feature_name
self.feature_version = version
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]]
def __init__(self, feature_name: str, version: str):
self.feature_name = feature_name # type: str
self.feature_version = version # type: str
@staticmethod
def get_target_version(subproject):
def get_target_version(subproject: str) -> str:
# Don't do any checks if project() has not been parsed yet
if subproject not in mesonlib.project_meson_versions:
return ''
return mesonlib.project_meson_versions[subproject]
def use(self, subproject):
def use(self, subproject: str) -> None:
tv = self.get_target_version(subproject)
# No target version
if tv == '':
@ -212,7 +242,7 @@ class FeatureCheckBase:
self.log_usage_warning(tv)
@classmethod
def report(cls, subproject):
def report(cls, subproject: str) -> None:
if subproject not in cls.feature_registry:
return
warning_str = cls.get_warning_str_prefix(cls.get_target_version(subproject))
@ -221,6 +251,13 @@ class FeatureCheckBase:
warning_str += '\n * {}: {}'.format(version, fv[version])
mlog.warning(warning_str)
def log_usage_warning(self, tv: str) -> None:
raise InterpreterException('log_usage_warning not implemented')
@staticmethod
def get_warning_str_prefix(tv: str) -> str:
raise InterpreterException('get_warning_str_prefix not implemented')
def __call__(self, f):
@wraps(f)
def wrapped(*wrapped_args, **wrapped_kwargs):
@ -233,38 +270,30 @@ class FeatureCheckBase:
class FeatureNew(FeatureCheckBase):
"""Checks for new features"""
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = {}
@staticmethod
def get_warning_str_prefix(tv):
def get_warning_str_prefix(tv: str) -> str:
return 'Project specifies a minimum meson_version \'{}\' but uses features which were added in newer versions:'.format(tv)
def log_usage_warning(self, tv):
def log_usage_warning(self, tv: str) -> None:
mlog.warning('Project targeting \'{}\' but tried to use feature introduced '
'in \'{}\': {}'.format(tv, self.feature_version, self.feature_name))
class FeatureDeprecated(FeatureCheckBase):
"""Checks for deprecated features"""
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = {}
@staticmethod
def get_warning_str_prefix(tv):
def get_warning_str_prefix(tv: str) -> str:
return 'Deprecated features used:'
def log_usage_warning(self, tv):
def log_usage_warning(self, tv: str) -> None:
mlog.deprecation('Project targeting \'{}\' but tried to use feature '
'deprecated since \'{}\': {}'
''.format(tv, self.feature_version, self.feature_name))
class FeatureCheckKwargsBase:
def __init__(self, feature_name, feature_version, kwargs):
def __init__(self, feature_name: str, feature_version: str, kwargs: T.List[str]):
self.feature_name = feature_name
self.feature_version = feature_version
self.kwargs = kwargs
@ -310,21 +339,6 @@ class ContinueRequest(BaseException):
class BreakRequest(BaseException):
pass
class InterpreterObject:
def __init__(self):
self.methods = {}
# Current node set during a method call. This can be used as location
# when printing a warning message during a method call.
self.current_node = None
def method_call(self, method_name, args, kwargs):
if method_name in self.methods:
method = self.methods[method_name]
if not getattr(method, 'no-args-flattening', False):
args = flatten(args)
return method(args, kwargs)
raise InvalidCode('Unknown method "%s" in object.' % method_name)
class MutableInterpreterObject(InterpreterObject):
def __init__(self):
super().__init__()
@ -359,19 +373,22 @@ def is_disabled(args, kwargs) -> bool:
return False
class InterpreterBase:
def __init__(self, source_root, subdir):
elementary_types = (int, float, str, bool, list)
def __init__(self, source_root: str, subdir: str, subproject: str):
self.source_root = source_root
self.funcs = {}
self.builtin = {}
self.funcs = {} # type: T.Dict[str, T.Callable[[mparser.BaseNode, T.List[TYPE_nvar], T.Dict[str, TYPE_nvar]], TYPE_var]]
self.builtin = {} # type: T.Dict[str, InterpreterObject]
self.subdir = subdir
self.variables = {}
self.subproject = subproject
self.variables = {} # type: T.Dict[str, TYPE_var]
self.argument_depth = 0
self.current_lineno = -1
# Current node set during a function call. This can be used as location
# when printing a warning message during a method call.
self.current_node = None
self.current_node = None # type: mparser.BaseNode
def load_root_meson_file(self):
def load_root_meson_file(self) -> None:
mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
if not os.path.isfile(mesonfile):
raise InvalidArguments('Missing Meson file in %s' % mesonfile)
@ -386,17 +403,17 @@ class InterpreterBase:
me.file = mesonfile
raise me
def join_path_strings(self, args):
def join_path_strings(self, args: T.Sequence[str]) -> str:
return os.path.join(*args).replace('\\', '/')
def parse_project(self):
def parse_project(self) -> None:
"""
Parses project() and initializes languages, compilers etc. Do this
early because we need this before we parse the rest of the AST.
"""
self.evaluate_codeblock(self.ast, end=1)
def sanity_check_ast(self):
def sanity_check_ast(self) -> None:
if not isinstance(self.ast, mparser.CodeBlockNode):
raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
if not self.ast.lines:
@ -405,7 +422,7 @@ class InterpreterBase:
if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
raise InvalidCode('First statement must be a call to project')
def run(self):
def run(self) -> None:
# Evaluate everything after the first line, which is project() because
# we already parsed that in self.parse_project()
try:
@ -413,7 +430,7 @@ class InterpreterBase:
except SubdirDoneRequest:
pass
def evaluate_codeblock(self, node, start=0, end=None):
def evaluate_codeblock(self, node: mparser.CodeBlockNode, start: int = 0, end: T.Optional[int] = None) -> None:
if node is None:
return
if not isinstance(node, mparser.CodeBlockNode):
@ -430,17 +447,18 @@ class InterpreterBase:
self.evaluate_statement(cur)
except Exception as e:
if not hasattr(e, 'lineno'):
e.lineno = cur.lineno
e.colno = cur.colno
e.file = os.path.join(self.source_root, self.subdir, environment.build_filename)
# We are doing the equivalent to setattr here and mypy does not like it
e.lineno = cur.lineno # type: ignore
e.colno = cur.colno # type: ignore
e.file = os.path.join(self.source_root, self.subdir, environment.build_filename) # type: ignore
raise e
i += 1 # In THE FUTURE jump over blocks and stuff.
def evaluate_statement(self, cur):
def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[TYPE_var]:
if isinstance(cur, mparser.FunctionNode):
return self.function_call(cur)
elif isinstance(cur, mparser.AssignmentNode):
return self.assignment(cur)
self.assignment(cur)
elif isinstance(cur, mparser.MethodNode):
return self.method_call(cur)
elif isinstance(cur, mparser.StringNode):
@ -470,9 +488,9 @@ class InterpreterBase:
elif isinstance(cur, mparser.ArithmeticNode):
return self.evaluate_arithmeticstatement(cur)
elif isinstance(cur, mparser.ForeachClauseNode):
return self.evaluate_foreach(cur)
self.evaluate_foreach(cur)
elif isinstance(cur, mparser.PlusAssignmentNode):
return self.evaluate_plusassign(cur)
self.evaluate_plusassign(cur)
elif isinstance(cur, mparser.IndexNode):
return self.evaluate_indexing(cur)
elif isinstance(cur, mparser.TernaryNode):
@ -481,75 +499,78 @@ class InterpreterBase:
raise ContinueRequest()
elif isinstance(cur, mparser.BreakNode):
raise BreakRequest()
elif self.is_elementary_type(cur):
elif isinstance(cur, self.elementary_types):
return cur
else:
raise InvalidCode("Unknown statement.")
return None
def evaluate_arraystatement(self, cur):
def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> list:
(arguments, kwargs) = self.reduce_arguments(cur.args)
if len(kwargs) > 0:
raise InvalidCode('Keyword arguments are invalid in array construction.')
return arguments
@FeatureNew('dict', '0.47.0')
def evaluate_dictstatement(self, cur):
(arguments, kwargs) = self.reduce_arguments(cur.args)
def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Dict[str, T.Any]:
(arguments, kwargs) = self.reduce_arguments(cur.args, resolve_key_nodes=False)
assert (not arguments)
result = {}
result = {} # type: T.Dict[str, T.Any]
self.argument_depth += 1
for key, value in kwargs.items():
if not isinstance(key, mparser.StringNode):
FeatureNew('Dictionary entry using non literal key', '0.53.0').use(self.subproject)
key = self.evaluate_statement(key)
if not isinstance(key, str):
assert isinstance(key, mparser.BaseNode) # All keys must be nodes due to resolve_key_nodes=False
str_key = self.evaluate_statement(key)
if not isinstance(str_key, str):
raise InvalidArguments('Key must be a string')
if key in result:
raise InvalidArguments('Duplicate dictionary key: {}'.format(key))
result[key] = value
if str_key in result:
raise InvalidArguments('Duplicate dictionary key: {}'.format(str_key))
result[str_key] = value
self.argument_depth -= 1
return result
def evaluate_notstatement(self, cur):
def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]:
v = self.evaluate_statement(cur.value)
if is_disabler(v):
if isinstance(v, Disabler):
return v
if not isinstance(v, bool):
raise InterpreterException('Argument to "not" is not a boolean.')
return not v
def evaluate_if(self, node):
def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
assert(isinstance(node, mparser.IfClauseNode))
for i in node.ifs:
result = self.evaluate_statement(i.condition)
if is_disabler(result):
if isinstance(result, Disabler):
return result
if not(isinstance(result, bool)):
raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
if result:
self.evaluate_codeblock(i.block)
return
return None
if not isinstance(node.elseblock, mparser.EmptyNode):
self.evaluate_codeblock(node.elseblock)
return None
def validate_comparison_types(self, val1, val2):
def validate_comparison_types(self, val1: T.Any, val2: T.Any) -> bool:
if type(val1) != type(val2):
return False
return True
def evaluate_in(self, val1, val2):
def evaluate_in(self, val1: T.Any, val2: T.Any) -> bool:
if not isinstance(val1, (str, int, float, ObjectHolder)):
raise InvalidArguments('lvalue of "in" operator must be a string, integer, float, or object')
if not isinstance(val2, (list, dict)):
raise InvalidArguments('rvalue of "in" operator must be an array or a dict')
return val1 in val2
def evaluate_comparison(self, node):
def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[bool, Disabler]:
val1 = self.evaluate_statement(node.left)
if is_disabler(val1):
if isinstance(val1, Disabler):
return val1
val2 = self.evaluate_statement(node.right)
if is_disabler(val2):
if isinstance(val2, Disabler):
return val2
if node.ctype == 'in':
return self.evaluate_in(val1, val2)
@ -572,68 +593,70 @@ The result of this is undefined and will become a hard error in a future Meson r
'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__,
type(val2).__name__,
node.ctype))
elif not self.is_elementary_type(val1):
raise InterpreterException('{} can only be compared for equality.'.format(node.left.value))
elif not self.is_elementary_type(val2):
raise InterpreterException('{} can only be compared for equality.'.format(node.right.value))
elif not isinstance(val1, self.elementary_types):
raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.left, 'value', '<ERROR>')))
elif not isinstance(val2, self.elementary_types):
raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.right, 'value', '<ERROR>')))
# Use type: ignore because mypy will complain that we are comparing two Unions,
# but we actually guarantee earlier that both types are the same
elif node.ctype == '<':
return val1 < val2
return val1 < val2 # type: ignore
elif node.ctype == '<=':
return val1 <= val2
return val1 <= val2 # type: ignore
elif node.ctype == '>':
return val1 > val2
return val1 > val2 # type: ignore
elif node.ctype == '>=':
return val1 >= val2
return val1 >= val2 # type: ignore
else:
raise InvalidCode('You broke my compare eval.')
def evaluate_andstatement(self, cur):
def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[bool, Disabler]:
l = self.evaluate_statement(cur.left)
if is_disabler(l):
if isinstance(l, Disabler):
return l
if not isinstance(l, bool):
raise InterpreterException('First argument to "and" is not a boolean.')
if not l:
return False
r = self.evaluate_statement(cur.right)
if is_disabler(r):
if isinstance(r, Disabler):
return r
if not isinstance(r, bool):
raise InterpreterException('Second argument to "and" is not a boolean.')
return r
def evaluate_orstatement(self, cur):
def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[bool, Disabler]:
l = self.evaluate_statement(cur.left)
if is_disabler(l):
if isinstance(l, Disabler):
return l
if not isinstance(l, bool):
raise InterpreterException('First argument to "or" is not a boolean.')
if l:
return True
r = self.evaluate_statement(cur.right)
if is_disabler(r):
if isinstance(r, Disabler):
return r
if not isinstance(r, bool):
raise InterpreterException('Second argument to "or" is not a boolean.')
return r
def evaluate_uminusstatement(self, cur):
def evaluate_uminusstatement(self, cur) -> T.Union[int, Disabler]:
v = self.evaluate_statement(cur.value)
if is_disabler(v):
if isinstance(v, Disabler):
return v
if not isinstance(v, int):
raise InterpreterException('Argument to negation is not an integer.')
return -v
@FeatureNew('/ with string arguments', '0.49.0')
def evaluate_path_join(self, l, r):
def evaluate_path_join(self, l: str, r: str) -> str:
if not isinstance(l, str):
raise InvalidCode('The division operator can only append to a string.')
if not isinstance(r, str):
raise InvalidCode('The division operator can only append a string.')
return self.join_path_strings((l, r))
def evaluate_division(self, 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):
@ -642,19 +665,20 @@ The result of this is undefined and will become a hard error in a future Meson r
return l // r
raise InvalidCode('Division works only with strings or integers.')
def evaluate_arithmeticstatement(self, cur):
def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[int, str, dict, list, Disabler]:
l = self.evaluate_statement(cur.left)
if is_disabler(l):
if isinstance(l, Disabler):
return l
r = self.evaluate_statement(cur.right)
if is_disabler(r):
if isinstance(r, Disabler):
return r
if cur.operation == 'add':
if isinstance(l, dict) and isinstance(r, dict):
return {**l, **r}
try:
return l + r
# MyPy error due to handling two Unions (we are catching all exceptions anyway)
return l + r # type: ignore
except Exception as e:
raise InvalidCode('Invalid use of addition: ' + str(e))
elif cur.operation == 'sub':
@ -674,10 +698,10 @@ The result of this is undefined and will become a hard error in a future Meson r
else:
raise InvalidCode('You broke me.')
def evaluate_ternary(self, node):
def evaluate_ternary(self, node: mparser.TernaryNode) -> TYPE_var:
assert(isinstance(node, mparser.TernaryNode))
result = self.evaluate_statement(node.condition)
if is_disabler(result):
if isinstance(result, Disabler):
return result
if not isinstance(result, bool):
raise InterpreterException('Ternary condition is not boolean.')
@ -686,14 +710,14 @@ The result of this is undefined and will become a hard error in a future Meson r
else:
return self.evaluate_statement(node.falseblock)
def evaluate_foreach(self, node):
def evaluate_foreach(self, node: mparser.ForeachClauseNode) -> None:
assert(isinstance(node, mparser.ForeachClauseNode))
items = self.evaluate_statement(node.items)
if isinstance(items, list):
if len(node.varnames) != 1:
raise InvalidArguments('Foreach on array does not unpack')
varname = node.varnames[0].value
varname = node.varnames[0]
for item in items:
self.set_variable(varname, item)
try:
@ -706,8 +730,8 @@ The result of this is undefined and will become a hard error in a future Meson r
if len(node.varnames) != 2:
raise InvalidArguments('Foreach on dict unpacks key and value')
for key, value in items.items():
self.set_variable(node.varnames[0].value, key)
self.set_variable(node.varnames[1].value, value)
self.set_variable(node.varnames[0], key)
self.set_variable(node.varnames[1], value)
try:
self.evaluate_codeblock(node.block)
except ContinueRequest:
@ -717,7 +741,7 @@ The result of this is undefined and will become a hard error in a future Meson r
else:
raise InvalidArguments('Items of foreach loop must be an array or a dict')
def evaluate_plusassign(self, node):
def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
assert(isinstance(node, mparser.PlusAssignmentNode))
varname = node.var_name
addition = self.evaluate_statement(node.value)
@ -727,6 +751,7 @@ The result of this is undefined and will become a hard error in a future Meson r
# 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]
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')
@ -749,10 +774,10 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints ')
self.set_variable(varname, new_value)
def evaluate_indexing(self, node):
def evaluate_indexing(self, node: mparser.IndexNode) -> TYPE_var:
assert(isinstance(node, mparser.IndexNode))
iobject = self.evaluate_statement(node.iobject)
if is_disabler(iobject):
if isinstance(iobject, Disabler):
return iobject
if not hasattr(iobject, '__getitem__'):
raise InterpreterException(
@ -770,26 +795,32 @@ The result of this is undefined and will become a hard error in a future Meson r
if not isinstance(index, int):
raise InterpreterException('Index value is not an integer.')
try:
return iobject[index]
# Ignore the MyPy error, since we don't know all indexable types here
# and we handle non indexable types with an exception
# TODO maybe find a better solution
return iobject[index] # type: ignore
except IndexError:
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject)))
# We are already checking for the existance of __getitem__, so this should be save
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore
def function_call(self, node):
def function_call(self, node: mparser.FunctionNode) -> T.Optional[TYPE_var]:
func_name = node.func_name
(posargs, kwargs) = self.reduce_arguments(node.args)
if is_disabled(posargs, kwargs) and func_name != 'set_variable' and func_name != 'is_disabler':
return Disabler()
if func_name in self.funcs:
func = self.funcs[func_name]
func_args = posargs # type: T.Any
if not getattr(func, 'no-args-flattening', False):
posargs = flatten(posargs)
func_args = flatten(posargs)
self.current_node = node
return func(node, posargs, kwargs)
return func(node, func_args, self.kwargs_string_keys(kwargs))
else:
self.unknown_function_called(func_name)
return None
def method_call(self, node):
def method_call(self, node: mparser.MethodNode) -> TYPE_var:
invokable = node.source_object
if isinstance(invokable, mparser.IdNode):
object_name = invokable.value
@ -797,7 +828,9 @@ The result of this is undefined and will become a hard error in a future Meson r
else:
obj = self.evaluate_statement(invokable)
method_name = node.name
args = node.args
(args, kwargs) = self.reduce_arguments(node.args)
if is_disabled(args, kwargs):
return Disabler()
if isinstance(obj, str):
return self.string_method_call(obj, method_name, args)
if isinstance(obj, bool):
@ -812,7 +845,6 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InvalidArguments('File object "%s" is not callable.' % obj)
if not isinstance(obj, InterpreterObject):
raise InvalidArguments('Variable "%s" is not callable.' % object_name)
(args, kwargs) = self.reduce_arguments(args)
# Special case. This is the only thing you can do with a disabler
# object. Every other use immediately returns the disabler object.
if isinstance(obj, Disabler):
@ -820,17 +852,14 @@ The result of this is undefined and will become a hard error in a future Meson r
return False
else:
return Disabler()
if is_disabled(args, kwargs):
return Disabler()
if method_name == 'extract_objects':
if not isinstance(obj, ObjectHolder):
raise InvalidArguments('Invalid operation "extract_objects" on variable "{}"'.format(object_name))
self.validate_extraction(obj.held_object)
obj.current_node = node
return obj.method_call(method_name, args, kwargs)
return obj.method_call(method_name, args, self.kwargs_string_keys(kwargs))
def bool_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_nvar]) -> T.Union[str, int]:
if method_name == 'to_string':
if not posargs:
if obj:
@ -852,10 +881,7 @@ The result of this is undefined and will become a hard error in a future Meson r
else:
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
def int_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_nvar]) -> T.Union[str, bool]:
if method_name == 'is_even':
if not posargs:
return obj % 2 == 0
@ -875,7 +901,7 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
@staticmethod
def _get_one_string_posarg(posargs, method_name):
def _get_one_string_posarg(posargs: T.List[TYPE_nvar], method_name: str) -> str:
if len(posargs) > 1:
m = '{}() must have zero or one arguments'
raise InterpreterException(m.format(method_name))
@ -887,17 +913,14 @@ The result of this is undefined and will become a hard error in a future Meson r
return s
return None
def string_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_nvar]) -> T.Union[str, int, bool, T.List[str]]:
if method_name == 'strip':
s = self._get_one_string_posarg(posargs, 'strip')
if s is not None:
return obj.strip(s)
s1 = self._get_one_string_posarg(posargs, 'strip')
if s1 is not None:
return obj.strip(s1)
return obj.strip()
elif method_name == 'format':
return self.format_string(obj, args)
return self.format_string(obj, posargs)
elif method_name == 'to_upper':
return obj.upper()
elif method_name == 'to_lower':
@ -905,19 +928,19 @@ The result of this is undefined and will become a hard error in a future Meson r
elif method_name == 'underscorify':
return re.sub(r'[^a-zA-Z0-9]', '_', obj)
elif method_name == 'split':
s = self._get_one_string_posarg(posargs, 'split')
if s is not None:
return obj.split(s)
s2 = self._get_one_string_posarg(posargs, 'split')
if s2 is not None:
return obj.split(s2)
return obj.split()
elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith':
s = posargs[0]
if not isinstance(s, str):
s3 = posargs[0]
if not isinstance(s3, str):
raise InterpreterException('Argument must be a string.')
if method_name == 'startswith':
return obj.startswith(s)
return obj.startswith(s3)
elif method_name == 'contains':
return obj.find(s) >= 0
return obj.endswith(s)
return obj.find(s3) >= 0
return obj.endswith(s3)
elif method_name == 'to_int':
try:
return int(obj)
@ -928,6 +951,7 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InterpreterException('Join() takes exactly one argument.')
strlist = posargs[0]
check_stringlist(strlist)
assert isinstance(strlist, list) # Required for mypy
return obj.join(strlist)
elif method_name == 'version_compare':
if len(posargs) != 1:
@ -938,12 +962,11 @@ The result of this is undefined and will become a hard error in a future Meson r
return mesonlib.version_compare(obj, cmpr)
raise InterpreterException('Unknown method "%s" for a string.' % method_name)
def format_string(self, templ, args):
if isinstance(args, mparser.ArgumentNode):
args = args.arguments
def format_string(self, templ: str, args: T.List[TYPE_nvar]) -> str:
arg_strings = []
for arg in args:
arg = self.evaluate_statement(arg)
if isinstance(arg, mparser.BaseNode):
arg = self.evaluate_statement(arg)
if isinstance(arg, bool): # Python boolean is upper case.
arg = str(arg).lower()
arg_strings.append(str(arg))
@ -956,15 +979,24 @@ The result of this is undefined and will become a hard error in a future Meson r
return re.sub(r'@(\d+)@', arg_replace, templ)
def unknown_function_called(self, func_name):
def unknown_function_called(self, func_name: str) -> None:
raise InvalidCode('Unknown function "%s".' % func_name)
def array_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
def array_method_call(self, obj: list, method_name: str, posargs: T.List[TYPE_nvar]) -> TYPE_var:
if method_name == 'contains':
return self.check_contains(obj, posargs)
def check_contains(el: list) -> bool:
if len(posargs) != 1:
raise InterpreterException('Contains method takes exactly one argument.')
item = posargs[0]
for element in el:
if isinstance(element, list):
found = check_contains(element)
if found:
return True
if element == item:
return True
return False
return check_contains(obj)
elif method_name == 'length':
return len(obj)
elif method_name == 'get':
@ -983,16 +1015,14 @@ The result of this is undefined and will become a hard error in a future Meson r
if fallback is None:
m = 'Array index {!r} is out of bounds for array of size {!r}.'
raise InvalidArguments(m.format(index, len(obj)))
if isinstance(fallback, mparser.BaseNode):
return self.evaluate_statement(fallback)
return fallback
return obj[index]
m = 'Arrays do not have a method called {!r}.'
raise InterpreterException(m.format(method_name))
def dict_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
def dict_method_call(self, obj: dict, method_name: str, posargs: T.List[TYPE_nvar]) -> TYPE_var:
if method_name in ('has_key', 'get'):
if method_name == 'has_key':
if len(posargs) != 1:
@ -1014,7 +1044,10 @@ The result of this is undefined and will become a hard error in a future Meson r
return obj[key]
if len(posargs) == 2:
return posargs[1]
fallback = posargs[1]
if isinstance(fallback, mparser.BaseNode):
return self.evaluate_statement(fallback)
return fallback
raise InterpreterException('Key {!r} is not in the dictionary.'.format(key))
@ -1025,21 +1058,27 @@ The result of this is undefined and will become a hard error in a future Meson r
raise InterpreterException('Dictionaries do not have a method called "%s".' % method_name)
def reduce_arguments(self, args):
def reduce_arguments(self, args: mparser.ArgumentNode, resolve_key_nodes: bool = True) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]:
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
self.argument_depth += 1
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
reduced_kw = {}
for key in args.kwargs.keys():
a = args.kwargs[key]
reduced_kw[key] = self.evaluate_statement(a)
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: T.List[TYPE_nvar]
reduced_kw = {} # type: TYPE_nkwargs
for key, val in args.kwargs.items():
reduced_key = key # type: T.Union[str, mparser.BaseNode]
reduced_val = val # type: TYPE_nvar
if resolve_key_nodes and isinstance(key, mparser.IdNode):
assert isinstance(key.value, str)
reduced_key = key.value
if isinstance(reduced_val, mparser.BaseNode):
reduced_val = self.evaluate_statement(reduced_val)
reduced_kw[reduced_key] = reduced_val
self.argument_depth -= 1
final_kw = self.expand_default_kwargs(reduced_kw)
return reduced_pos, final_kw
def expand_default_kwargs(self, kwargs):
def expand_default_kwargs(self, kwargs: TYPE_nkwargs) -> TYPE_nkwargs:
if 'kwargs' not in kwargs:
return kwargs
to_expand = kwargs.pop('kwargs')
@ -1053,7 +1092,15 @@ The result of this is undefined and will become a hard error in a future Meson r
kwargs[k] = v
return kwargs
def assignment(self, node):
def kwargs_string_keys(self, kwargs: TYPE_nkwargs) -> T.Dict[str, TYPE_nvar]:
kw = {} # type: T.Dict[str, TYPE_nvar]
for key, val in kwargs.items():
if not isinstance(key, str):
raise InterpreterException('Key of kwargs is not a string')
kw[key] = val
return kw
def assignment(self, node: mparser.AssignmentNode) -> None:
assert(isinstance(node, mparser.AssignmentNode))
if self.argument_depth != 0:
raise InvalidArguments('''Tried to assign values inside an argument list.
@ -1070,7 +1117,7 @@ To specify a keyword argument, use : instead of =.''')
self.set_variable(var_name, value)
return None
def set_variable(self, varname, variable):
def set_variable(self, varname: str, variable: TYPE_var) -> None:
if variable is None:
raise InvalidCode('Can not assign None to variable.')
if not isinstance(varname, str):
@ -1083,16 +1130,16 @@ To specify a keyword argument, use : instead of =.''')
raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
self.variables[varname] = variable
def get_variable(self, varname):
def get_variable(self, varname) -> TYPE_var:
if varname in self.builtin:
return self.builtin[varname]
if varname in self.variables:
return self.variables[varname]
raise InvalidCode('Unknown variable "%s".' % varname)
def is_assignable(self, value):
def is_assignable(self, value: T.Any) -> bool:
return isinstance(value, (InterpreterObject, dependencies.Dependency,
str, int, list, dict, mesonlib.File))
def is_elementary_type(self, v):
return isinstance(v, (int, float, str, bool, list))
def validate_extraction(self, buildtarget: InterpreterObject) -> None:
raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)')

@ -400,7 +400,7 @@ class PerThreeMachine(PerMachine[_T]):
class PerMachineDefaultable(PerMachine[T.Optional[_T]]):
"""Extends `PerMachine` with the ability to default from `None`s.
"""
def __init__(self) -> None:
def __init__(self):
super().__init__(None, None)
def default_missing(self) -> "PerMachine[T.Optional[_T]]":
@ -418,7 +418,7 @@ class PerMachineDefaultable(PerMachine[T.Optional[_T]]):
class PerThreeMachineDefaultable(PerMachineDefaultable, PerThreeMachine[T.Optional[_T]]):
"""Extends `PerThreeMachine` with the ability to default from `None`s.
"""
def __init__(self) -> None:
def __init__(self):
PerThreeMachine.__init__(self, None, None, None)
def default_missing(self) -> "PerThreeMachine[T.Optional[_T]]":

@ -25,7 +25,7 @@ from . import mesonlib
from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator
from . import mlog
from .backend import backends
from .mparser import FunctionNode, ArrayNode, ArgumentNode, StringNode
from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode
from .interpreter import Interpreter
from pathlib import PurePath
import typing as T
@ -110,7 +110,7 @@ def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[st
for i in intr.targets:
sources = [] # type: T.List[str]
for n in i['sources']:
args = [] # type: T.List[T.Union[str, StringNode]]
args = [] # type: T.List[BaseNode]
if isinstance(n, FunctionNode):
args = list(n.args.arguments)
if n.func_name in build_target_functions:
@ -121,6 +121,7 @@ def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[st
args = n.arguments
for j in args:
if isinstance(j, StringNode):
assert isinstance(j.value, str)
sources += [j.value]
elif isinstance(j, str):
sources += [j]

@ -14,10 +14,15 @@
import re
import codecs
import textwrap
import types
import typing as T
from .mesonlib import MesonException
from . import mlog
if T.TYPE_CHECKING:
from .ast import AstVisitor
# This is the regex for the supported escape sequences of a regular string
# literal, like 'abc\x00'
ESCAPE_SEQUENCE_SINGLE_RE = re.compile(r'''
@ -71,23 +76,25 @@ class BlockParseException(MesonException):
self.lineno = lineno
self.colno = colno
class Token:
def __init__(self, tid, filename, line_start, lineno, colno, bytespan, value):
self.tid = tid
self.filename = filename
self.line_start = line_start
self.lineno = lineno
self.colno = colno
self.bytespan = bytespan
self.value = value
TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool)
def __eq__(self, other):
class Token(T.Generic[TV_TokenTypes]):
def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: T.Tuple[int, int], value: TV_TokenTypes):
self.tid = tid # type: str
self.filename = filename # type: str
self.line_start = line_start # type: int
self.lineno = lineno # type: int
self.colno = colno # type: int
self.bytespan = bytespan # type: T.Tuple[int, int]
self.value = value # type: TV_TokenTypes
def __eq__(self, other) -> bool:
if isinstance(other, str):
return self.tid == other
return self.tid == other.tid
class Lexer:
def __init__(self, code):
def __init__(self, code: str):
self.code = code
self.keywords = {'true', 'false', 'if', 'else', 'elif',
'endif', 'and', 'or', 'not', 'foreach', 'endforeach',
@ -129,10 +136,10 @@ class Lexer:
('questionmark', re.compile(r'\?')),
]
def getline(self, line_start):
def getline(self, line_start: int) -> str:
return self.code[line_start:self.code.find('\n', line_start)]
def lex(self, filename):
def lex(self, filename: str) -> T.Generator[Token, None, None]:
line_start = 0
lineno = 1
loc = 0
@ -142,7 +149,7 @@ class Lexer:
col = 0
while loc < len(self.code):
matched = False
value = None
value = None # type: T.Union[str, bool, int]
for (tid, reg) in self.token_specification:
mo = reg.match(self.code, loc)
if mo:
@ -174,8 +181,14 @@ class Lexer:
elif tid == 'string':
# Handle here and not on the regexp to give a better error message.
if match_text.find("\n") != -1:
mlog.warning("""Newline character in a string detected, use ''' (three single quotes) for multiline strings instead.
This will become a hard error in a future Meson release.""", self.getline(line_start), lineno, col)
mlog.warning(textwrap.dedent("""\
Newline character in a string detected, use ''' (three single quotes) for multiline strings instead.
This will become a hard error in a future Meson release.\
"""),
self.getline(line_start),
str(lineno),
str(col)
)
value = match_text[1:-1]
try:
value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
@ -213,44 +226,53 @@ This will become a hard error in a future Meson release.""", self.getline(line_s
raise ParseException('lexer', self.getline(line_start), lineno, col)
class BaseNode:
def accept(self, visitor):
def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None):
self.lineno = lineno # type: int
self.colno = colno # type: int
self.filename = filename # type: str
self.end_lineno = end_lineno if end_lineno is not None else self.lineno
self.end_colno = end_colno if end_colno is not None else self.colno
# Attributes for the visitors
self.level = 0 # type: int
self.ast_id = '' # type: str
self.condition_level = 0 # type: int
def accept(self, visitor: 'AstVisitor') -> None:
fname = 'visit_{}'.format(type(self).__name__)
if hasattr(visitor, fname):
func = getattr(visitor, fname)
if callable(func):
func(self)
class ElementaryNode(BaseNode):
def __init__(self, token):
self.lineno = token.lineno
self.filename = token.filename
self.colno = token.colno
self.value = token.value
self.bytespan = token.bytespan
class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode):
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
self.value = token.value # type: TV_TokenTypes
self.bytespan = token.bytespan # type: T.Tuple[int, int]
class BooleanNode(ElementaryNode):
def __init__(self, token, value):
class BooleanNode(ElementaryNode[bool]):
def __init__(self, token: Token[bool]):
super().__init__(token)
self.value = value
assert(isinstance(self.value, bool))
assert isinstance(self.value, bool)
class IdNode(ElementaryNode):
def __init__(self, token):
class IdNode(ElementaryNode[str]):
def __init__(self, token: Token[str]):
super().__init__(token)
assert(isinstance(self.value, str))
assert isinstance(self.value, str)
def __str__(self):
return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
class NumberNode(ElementaryNode):
def __init__(self, token):
class NumberNode(ElementaryNode[int]):
def __init__(self, token: Token[int]):
super().__init__(token)
assert(isinstance(self.value, int))
assert isinstance(self.value, int)
class StringNode(ElementaryNode):
def __init__(self, token):
class StringNode(ElementaryNode[str]):
def __init__(self, token: Token[str]):
super().__init__(token)
assert(isinstance(self.value, str))
assert isinstance(self.value, str)
def __str__(self):
return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
@ -261,203 +283,163 @@ class ContinueNode(ElementaryNode):
class BreakNode(ElementaryNode):
pass
class ArgumentNode(BaseNode):
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
self.arguments = [] # type: T.List[BaseNode]
self.commas = [] # type: T.List[Token[TV_TokenTypes]]
self.kwargs = {} # type: T.Dict[BaseNode, BaseNode]
self.order_error = False
def prepend(self, statement: BaseNode) -> None:
if self.num_kwargs() > 0:
self.order_error = True
if not isinstance(statement, EmptyNode):
self.arguments = [statement] + self.arguments
def append(self, statement: BaseNode) -> None:
if self.num_kwargs() > 0:
self.order_error = True
if not isinstance(statement, EmptyNode):
self.arguments += [statement]
def set_kwarg(self, name: IdNode, value: BaseNode) -> None:
if name.value in [x.value for x in self.kwargs.keys() if isinstance(x, IdNode)]:
mlog.warning('Keyword argument "{}" defined multiple times.'.format(name.value), location=self)
mlog.warning('This will be an error in future Meson releases.')
self.kwargs[name] = value
def set_kwarg_no_check(self, name: BaseNode, value: BaseNode) -> None:
self.kwargs[name] = value
def num_args(self) -> int:
return len(self.arguments)
def num_kwargs(self) -> int:
return len(self.kwargs)
def incorrect_order(self) -> bool:
return self.order_error
def __len__(self) -> int:
return self.num_args() # Fixme
class ArrayNode(BaseNode):
def __init__(self, args, lineno, colno, end_lineno, end_colno):
self.filename = args.filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
self.end_colno = end_colno
self.args = args
def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
self.args = args # type: ArgumentNode
class DictNode(BaseNode):
def __init__(self, args, lineno, colno, end_lineno, end_colno):
self.filename = args.filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
self.end_colno = end_colno
def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
self.args = args
class EmptyNode(BaseNode):
def __init__(self, lineno, colno):
self.filename = ''
self.lineno = lineno
self.colno = colno
def __init__(self, lineno: int, colno: int, filename: str):
super().__init__(lineno, colno, filename)
self.value = None
class OrNode(BaseNode):
def __init__(self, left, right):
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
self.right = right
def __init__(self, left: BaseNode, right: BaseNode):
super().__init__(left.lineno, left.colno, left.filename)
self.left = left # type: BaseNode
self.right = right # type: BaseNode
class AndNode(BaseNode):
def __init__(self, left, right):
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
self.right = right
def __init__(self, left: BaseNode, right: BaseNode):
super().__init__(left.lineno, left.colno, left.filename)
self.left = left # type: BaseNode
self.right = right # type: BaseNode
class ComparisonNode(BaseNode):
def __init__(self, ctype, left, right):
self.lineno = left.lineno
self.colno = left.colno
self.filename = left.filename
self.left = left
self.right = right
self.ctype = ctype
def __init__(self, ctype: str, left: BaseNode, right: BaseNode):
super().__init__(left.lineno, left.colno, left.filename)
self.left = left # type: BaseNode
self.right = right # type: BaseNode
self.ctype = ctype # type: str
class ArithmeticNode(BaseNode):
def __init__(self, operation, left, right):
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
self.right = right
self.operation = operation
def __init__(self, operation: str, left: BaseNode, right: BaseNode):
super().__init__(left.lineno, left.colno, left.filename)
self.left = left # type: BaseNode
self.right = right # type: BaseNode
self.operation = operation # type: str
class NotNode(BaseNode):
def __init__(self, location_node, value):
self.filename = location_node.filename
self.lineno = location_node.lineno
self.colno = location_node.colno
self.value = value
def __init__(self, token: Token[TV_TokenTypes], value: BaseNode):
super().__init__(token.lineno, token.colno, token.filename)
self.value = value # type: BaseNode
class CodeBlockNode(BaseNode):
def __init__(self, location_node):
self.filename = location_node.filename
self.lineno = location_node.lineno
self.colno = location_node.colno
self.lines = []
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
self.lines = [] # type: T.List[BaseNode]
class IndexNode(BaseNode):
def __init__(self, iobject, index):
self.iobject = iobject
self.index = index
self.filename = iobject.filename
self.lineno = iobject.lineno
self.colno = iobject.colno
def __init__(self, iobject: BaseNode, index: BaseNode):
super().__init__(iobject.lineno, iobject.colno, iobject.filename)
self.iobject = iobject # type: BaseNode
self.index = index # type: BaseNode
class MethodNode(BaseNode):
def __init__(self, filename, lineno, colno, source_object, name, args):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.source_object = source_object
self.name = name
def __init__(self, filename: str, lineno: int, colno: int, source_object: BaseNode, name: str, args: ArgumentNode):
super().__init__(lineno, colno, filename)
self.source_object = source_object # type: BaseNode
self.name = name # type: str
assert(isinstance(self.name, str))
self.args = args
self.args = args # type: ArgumentNode
class FunctionNode(BaseNode):
def __init__(self, filename, lineno, colno, end_lineno, end_colno, func_name, args):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
self.end_colno = end_colno
self.func_name = func_name
def __init__(self, filename: str, lineno: int, colno: int, end_lineno: int, end_colno: int, func_name: str, args: ArgumentNode):
super().__init__(lineno, colno, filename, end_lineno=end_lineno, end_colno=end_colno)
self.func_name = func_name # type: str
assert(isinstance(func_name, str))
self.args = args
self.args = args # type: ArgumentNode
class AssignmentNode(BaseNode):
def __init__(self, filename, lineno, colno, var_name, value):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.var_name = var_name
def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
super().__init__(lineno, colno, filename)
self.var_name = var_name # type: str
assert(isinstance(var_name, str))
self.value = value
self.value = value # type: BaseNode
class PlusAssignmentNode(BaseNode):
def __init__(self, filename, lineno, colno, var_name, value):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.var_name = var_name
def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
super().__init__(lineno, colno, filename)
self.var_name = var_name # type: str
assert(isinstance(var_name, str))
self.value = value
self.value = value # type: BaseNode
class ForeachClauseNode(BaseNode):
def __init__(self, lineno, colno, varnames, items, block):
self.lineno = lineno
self.colno = colno
self.varnames = varnames
self.items = items
self.block = block
def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode):
super().__init__(token.lineno, token.colno, token.filename)
self.varnames = varnames # type: T.List[str]
self.items = items # type: BaseNode
self.block = block # type: CodeBlockNode
class IfNode(BaseNode):
def __init__(self, linenode: BaseNode, condition: BaseNode, block: CodeBlockNode):
super().__init__(linenode.lineno, linenode.colno, linenode.filename)
self.condition = condition # type: BaseNode
self.block = block # type: CodeBlockNode
class IfClauseNode(BaseNode):
def __init__(self, lineno, colno):
self.lineno = lineno
self.colno = colno
self.ifs = []
self.elseblock = EmptyNode(lineno, colno)
def __init__(self, linenode: BaseNode):
super().__init__(linenode.lineno, linenode.colno, linenode.filename)
self.ifs = [] # type: T.List[IfNode]
self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: T.Union[EmptyNode, CodeBlockNode]
class UMinusNode(BaseNode):
def __init__(self, current_location, value):
self.filename = current_location.filename
self.lineno = current_location.lineno
self.colno = current_location.colno
self.value = value
class IfNode(BaseNode):
def __init__(self, lineno, colno, condition, block):
self.lineno = lineno
self.colno = colno
self.condition = condition
self.block = block
def __init__(self, current_location: Token, value: BaseNode):
super().__init__(current_location.lineno, current_location.colno, current_location.filename)
self.value = value # type: BaseNode
class TernaryNode(BaseNode):
def __init__(self, filename, lineno, colno, condition, trueblock, falseblock):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.condition = condition
self.trueblock = trueblock
self.falseblock = falseblock
class ArgumentNode(BaseNode):
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.filename = token.filename
self.arguments = []
self.commas = []
self.kwargs = {}
self.order_error = False
def prepend(self, statement):
if self.num_kwargs() > 0:
self.order_error = True
if not isinstance(statement, EmptyNode):
self.arguments = [statement] + self.arguments
def append(self, statement):
if self.num_kwargs() > 0:
self.order_error = True
if not isinstance(statement, EmptyNode):
self.arguments += [statement]
def set_kwarg(self, name, value):
if name in self.kwargs:
mlog.warning('Keyword argument "{}" defined multiple times.'.format(name), location=self)
mlog.warning('This will be an error in future Meson releases.')
self.kwargs[name] = value
def num_args(self):
return len(self.arguments)
def num_kwargs(self):
return len(self.kwargs)
def incorrect_order(self):
return self.order_error
def __len__(self):
return self.num_args() # Fixme
def __init__(self, condition: BaseNode, trueblock: BaseNode, falseblock: BaseNode):
super().__init__(condition.lineno, condition.colno, condition.filename)
self.condition = condition # type: BaseNode
self.trueblock = trueblock # type: BaseNode
self.falseblock = falseblock # type: BaseNode
comparison_map = {'equal': '==',
'nequal': '!=',
@ -485,58 +467,60 @@ comparison_map = {'equal': '==',
# 9 plain token
class Parser:
def __init__(self, code, filename):
def __init__(self, code: str, filename: str):
self.lexer = Lexer(code)
self.stream = self.lexer.lex(filename)
self.current = Token('eof', '', 0, 0, 0, (0, 0), None)
self.current = Token('eof', '', 0, 0, 0, (0, 0), None) # type: Token
self.getsym()
self.in_ternary = False
def getsym(self):
def getsym(self) -> None:
try:
self.current = next(self.stream)
except StopIteration:
self.current = Token('eof', '', self.current.line_start, self.current.lineno, self.current.colno + self.current.bytespan[1] - self.current.bytespan[0], (0, 0), None)
def getline(self):
def getline(self) -> str:
return self.lexer.getline(self.current.line_start)
def accept(self, s):
def accept(self, s: str) -> bool:
if self.current.tid == s:
self.getsym()
return True
return False
def expect(self, s):
def expect(self, s: str) -> bool:
if self.accept(s):
return True
raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.getline(), self.current.lineno, self.current.colno)
def block_expect(self, s, block_start):
def block_expect(self, s: str, block_start: Token) -> bool:
if self.accept(s):
return True
raise BlockParseException('Expecting %s got %s.' % (s, self.current.tid), self.getline(), self.current.lineno, self.current.colno, self.lexer.getline(block_start.line_start), block_start.lineno, block_start.colno)
def parse(self):
def parse(self) -> CodeBlockNode:
block = self.codeblock()
self.expect('eof')
return block
def statement(self):
def statement(self) -> BaseNode:
return self.e1()
def e1(self):
def e1(self) -> BaseNode:
left = self.e2()
if self.accept('plusassign'):
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
return PlusAssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
elif self.accept('assign'):
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Assignment target must be an id.',
self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
return AssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
elif self.accept('questionmark'):
if self.in_ternary:
@ -547,10 +531,10 @@ class Parser:
self.expect('colon')
falseblock = self.e1()
self.in_ternary = False
return TernaryNode(left.filename, left.lineno, left.colno, left, trueblock, falseblock)
return TernaryNode(left, trueblock, falseblock)
return left
def e2(self):
def e2(self) -> BaseNode:
left = self.e3()
while self.accept('or'):
if isinstance(left, EmptyNode):
@ -559,7 +543,7 @@ class Parser:
left = OrNode(left, self.e3())
return left
def e3(self):
def e3(self) -> BaseNode:
left = self.e4()
while self.accept('and'):
if isinstance(left, EmptyNode):
@ -568,7 +552,7 @@ class Parser:
left = AndNode(left, self.e4())
return left
def e4(self):
def e4(self) -> BaseNode:
left = self.e5()
for nodename, operator_type in comparison_map.items():
if self.accept(nodename):
@ -577,47 +561,47 @@ class Parser:
return ComparisonNode('notin', left, self.e5())
return left
def e5(self):
def e5(self) -> BaseNode:
return self.e5add()
def e5add(self):
def e5add(self) -> BaseNode:
left = self.e5sub()
if self.accept('plus'):
return ArithmeticNode('add', left, self.e5add())
return left
def e5sub(self):
def e5sub(self) -> BaseNode:
left = self.e5mod()
if self.accept('dash'):
return ArithmeticNode('sub', left, self.e5sub())
return left
def e5mod(self):
def e5mod(self) -> BaseNode:
left = self.e5mul()
if self.accept('percent'):
return ArithmeticNode('mod', left, self.e5mod())
return left
def e5mul(self):
def e5mul(self) -> BaseNode:
left = self.e5div()
if self.accept('star'):
return ArithmeticNode('mul', left, self.e5mul())
return left
def e5div(self):
def e5div(self) -> BaseNode:
left = self.e6()
if self.accept('fslash'):
return ArithmeticNode('div', left, self.e5div())
return left
def e6(self):
def e6(self) -> BaseNode:
if self.accept('not'):
return NotNode(self.current, self.e7())
if self.accept('dash'):
return UMinusNode(self.current, self.e7())
return self.e7()
def e7(self):
def e7(self) -> BaseNode:
left = self.e8()
block_start = self.current
if self.accept('lparen'):
@ -626,6 +610,7 @@ class Parser:
if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id',
self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
left = FunctionNode(left.filename, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
go_again = True
while go_again:
@ -638,7 +623,7 @@ class Parser:
left = self.index_call(left)
return left
def e8(self):
def e8(self) -> BaseNode:
block_start = self.current
if self.accept('lparen'):
e = self.statement()
@ -655,27 +640,29 @@ class Parser:
else:
return self.e9()
def e9(self):
def e9(self) -> BaseNode:
t = self.current
if self.accept('true'):
return BooleanNode(t, True)
t.value = True
return BooleanNode(t)
if self.accept('false'):
return BooleanNode(t, False)
t.value = False
return BooleanNode(t)
if self.accept('id'):
return IdNode(t)
if self.accept('number'):
return NumberNode(t)
if self.accept('string'):
return StringNode(t)
return EmptyNode(self.current.lineno, self.current.colno)
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
def key_values(self):
s = self.statement()
a = ArgumentNode(s)
def key_values(self) -> ArgumentNode:
s = self.statement() # type: BaseNode
a = ArgumentNode(self.current)
while not isinstance(s, EmptyNode):
if self.accept('colon'):
a.set_kwarg(s, self.statement())
a.set_kwarg_no_check(s, self.statement())
potential = self.current
if not self.accept('comma'):
return a
@ -686,9 +673,9 @@ class Parser:
s = self.statement()
return a
def args(self):
s = self.statement()
a = ArgumentNode(s)
def args(self) -> ArgumentNode:
s = self.statement() # type: BaseNode
a = ArgumentNode(self.current)
while not isinstance(s, EmptyNode):
potential = self.current
@ -699,7 +686,7 @@ class Parser:
if not isinstance(s, IdNode):
raise ParseException('Dictionary key must be a plain identifier.',
self.getline(), s.lineno, s.colno)
a.set_kwarg(s.value, self.statement())
a.set_kwarg(s, self.statement())
potential = self.current
if not self.accept('comma'):
return a
@ -710,11 +697,12 @@ class Parser:
s = self.statement()
return a
def method_call(self, source_object):
def method_call(self, source_object) -> MethodNode:
methodname = self.e9()
if not(isinstance(methodname, IdNode)):
raise ParseException('Method name must be plain id',
self.getline(), self.current.lineno, self.current.colno)
assert isinstance(methodname.value, str)
self.expect('lparen')
args = self.args()
self.expect('rparen')
@ -723,68 +711,73 @@ class Parser:
return self.method_call(method)
return method
def index_call(self, source_object):
def index_call(self, source_object) -> IndexNode:
index_statement = self.statement()
self.expect('rbracket')
return IndexNode(source_object, index_statement)
def foreachblock(self):
def foreachblock(self) -> ForeachClauseNode:
t = self.current
self.expect('id')
assert isinstance(t.value, str)
varname = t
varnames = [t]
varnames = [t.value] # type: T.List[str]
if self.accept('comma'):
t = self.current
self.expect('id')
varnames.append(t)
assert isinstance(t.value, str)
varnames.append(t.value)
self.expect('colon')
items = self.statement()
block = self.codeblock()
return ForeachClauseNode(varname.lineno, varname.colno, varnames, items, block)
return ForeachClauseNode(varname, varnames, items, block)
def ifblock(self):
def ifblock(self) -> IfClauseNode:
condition = self.statement()
clause = IfClauseNode(condition.lineno, condition.colno)
clause = IfClauseNode(condition)
self.expect('eol')
block = self.codeblock()
clause.ifs.append(IfNode(clause.lineno, clause.colno, condition, block))
clause.ifs.append(IfNode(clause, condition, block))
self.elseifblock(clause)
clause.elseblock = self.elseblock()
elseblock = self.elseblock()
if elseblock:
clause.elseblock = elseblock
return clause
def elseifblock(self, clause):
def elseifblock(self, clause) -> None:
while self.accept('elif'):
s = self.statement()
self.expect('eol')
b = self.codeblock()
clause.ifs.append(IfNode(s.lineno, s.colno, s, b))
clause.ifs.append(IfNode(s, s, b))
def elseblock(self):
def elseblock(self) -> T.Optional[CodeBlockNode]:
if self.accept('else'):
self.expect('eol')
return self.codeblock()
return None
def line(self):
def line(self) -> BaseNode:
block_start = self.current
if self.current == 'eol':
return EmptyNode(self.current.lineno, self.current.colno)
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
if self.accept('if'):
block = self.ifblock()
ifblock = self.ifblock()
self.block_expect('endif', block_start)
return block
return ifblock
if self.accept('foreach'):
block = self.foreachblock()
forblock = self.foreachblock()
self.block_expect('endforeach', block_start)
return block
return forblock
if self.accept('continue'):
return ContinueNode(self.current)
if self.accept('break'):
return BreakNode(self.current)
return self.statement()
def codeblock(self):
def codeblock(self) -> CodeBlockNode:
block = CodeBlockNode(self.current)
cond = True
while cond:

@ -176,10 +176,10 @@ class OptionInterpreter:
reduced_pos = [self.reduce_single(arg) for arg in args.arguments]
reduced_kw = {}
for key in args.kwargs.keys():
if not isinstance(key, str):
if not isinstance(key, mparser.IdNode):
raise OptionException('Keyword argument name is not a string.')
a = args.kwargs[key]
reduced_kw[key] = self.reduce_single(a)
reduced_kw[key.value] = self.reduce_single(a)
return reduced_pos, reduced_kw
def evaluate_statement(self, node):

@ -113,7 +113,7 @@ class MTypeBase:
def _new_node(self):
# Overwrite in derived class
return BaseNode()
raise RewriterException('Internal error: _new_node of MTypeBase was called')
def can_modify(self):
return self.node_type is not None
@ -159,7 +159,7 @@ class MTypeBool(MTypeBase):
super().__init__(node)
def _new_node(self):
return StringNode(Token('', '', 0, 0, 0, None, False))
return BooleanNode(Token('', '', 0, 0, 0, None, False))
def supported_nodes(self):
return [BooleanNode]
@ -172,7 +172,7 @@ class MTypeID(MTypeBase):
super().__init__(node)
def _new_node(self):
return StringNode(Token('', '', 0, 0, 0, None, ''))
return IdNode(Token('', '', 0, 0, 0, None, ''))
def supported_nodes(self):
return [IdNode]
@ -189,7 +189,7 @@ class MTypeList(MTypeBase):
def _new_element_node(self, value):
# Overwrite in derived class
return BaseNode()
raise RewriterException('Internal error: _new_element_node of MTypeList was called')
def _ensure_array_node(self):
if not isinstance(self.node, ArrayNode):
@ -414,10 +414,10 @@ class Rewriter:
# Check the assignments
tgt = None
if target in self.interpreter.assignments:
node = self.interpreter.assignments[target][0]
node = self.interpreter.assignments[target]
if isinstance(node, FunctionNode):
if node.func_name in ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries']:
tgt = self.interpreter.assign_vals[target][0]
tgt = self.interpreter.assign_vals[target]
return tgt
@ -434,7 +434,7 @@ class Rewriter:
# Check the assignments
if dependency in self.interpreter.assignments:
node = self.interpreter.assignments[dependency][0]
node = self.interpreter.assignments[dependency]
if isinstance(node, FunctionNode):
if node.func_name in ['dependency']:
name = self.interpreter.flatten_args(node.args)[0]
@ -522,6 +522,8 @@ class Rewriter:
mlog.error('Unable to find the function node')
assert(isinstance(node, FunctionNode))
assert(isinstance(arg_node, ArgumentNode))
# Transform the key nodes to plain strings
arg_node.kwargs = {k.value: v for k, v in arg_node.kwargs.items()}
# Print kwargs info
if cmd['operation'] == 'info':
@ -585,11 +587,13 @@ class Rewriter:
arg_node.kwargs[key] = modifyer.get_node()
num_changed += 1
# Convert the keys back to IdNode's
arg_node.kwargs = {IdNode(Token('', '', 0, 0, 0, None, k)): v for k, v in arg_node.kwargs.items()}
if num_changed > 0 and node not in self.modefied_nodes:
self.modefied_nodes += [node]
def find_assignment_node(self, node: BaseNode) -> AssignmentNode:
if hasattr(node, 'ast_id') and node.ast_id in self.interpreter.reverse_assignment:
if node.ast_id and node.ast_id in self.interpreter.reverse_assignment:
return self.interpreter.reverse_assignment[node.ast_id]
return None

@ -1398,7 +1398,7 @@ class DataTests(unittest.TestCase):
'''
env = get_fake_env()
interp = Interpreter(FakeBuild(env), mock=True)
astint = AstInterpreter('.', '')
astint = AstInterpreter('.', '', '')
self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys()))

Loading…
Cancel
Save