aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint_mypy.yml2
-rw-r--r--mesonbuild/ast/interpreter.py104
-rw-r--r--mesonbuild/ast/introspection.py104
-rw-r--r--mesonbuild/ast/postprocess.py29
-rw-r--r--mesonbuild/ast/printer.py76
-rw-r--r--mesonbuild/ast/visitor.py57
-rw-r--r--mesonbuild/cmake/interpreter.py6
-rw-r--r--mesonbuild/interpreter.py28
-rw-r--r--mesonbuild/interpreterbase.py409
-rw-r--r--mesonbuild/mesonlib.py4
-rw-r--r--mesonbuild/mintro.py5
-rw-r--r--mesonbuild/mparser.py489
-rw-r--r--mesonbuild/optinterpreter.py4
-rw-r--r--mesonbuild/rewriter.py20
-rwxr-xr-xrun_unittests.py2
15 files changed, 700 insertions, 639 deletions
diff --git a/.github/workflows/lint_mypy.yml b/.github/workflows/lint_mypy.yml
index d74043b..8f6d917 100644
--- a/.github/workflows/lint_mypy.yml
+++ b/.github/workflows/lint_mypy.yml
@@ -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
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index 5f47ad3..25dfb80 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.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):
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 709dbac..c9a51bf 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -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()
diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py
index 8e8732f..35fe1d3 100644
--- a/mesonbuild/ast/postprocess.py
+++ b/mesonbuild/ast/postprocess.py
@@ -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)
diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py
index f245a36..39e2cca 100644
--- a/mesonbuild/ast/printer.py
+++ b/mesonbuild/ast/printer.py
@@ -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)
diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py
index de13dae..37be463 100644
--- a/mesonbuild/ast/visitor.py
+++ b/mesonbuild/ast/visitor.py
@@ -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)
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py
index 941baed..efdb840 100644
--- a/mesonbuild/cmake/interpreter.py
+++ b/mesonbuild/cmake/interpreter.py
@@ -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:
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 2769c2d..dad2933 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -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 != ''
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
index d723da5..8071136 100644
--- a/mesonbuild/interpreterbase.py
+++ b/mesonbuild/interpreterbase.py
@@ -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)')
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index 854c934..2298c86 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -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]]":
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index cfa4574..cca4372 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -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]
diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py
index 430c89e..8753b40 100644
--- a/mesonbuild/mparser.py
+++ b/mesonbuild/mparser.py
@@ -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:
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index fd0a8c9..2695a26 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -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):
diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py
index 39d8337..785451f 100644
--- a/mesonbuild/rewriter.py
+++ b/mesonbuild/rewriter.py
@@ -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
diff --git a/run_unittests.py b/run_unittests.py
index 5655165..3f5c87e 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -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()))