From c14aea2812fd2be94998bdb174e9a4681aeea394 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 7 Dec 2019 14:42:23 +0100 Subject: [PATCH 1/8] types: Annotate mparser.py This also fixes that the keys in ArgumentNode.kwargs are all of the type BaseNode now. Before this commit, it was possible that both strings and Nodes where used as keys. --- .github/workflows/lint_mypy.yml | 2 +- mesonbuild/ast/interpreter.py | 10 +- mesonbuild/ast/printer.py | 7 +- mesonbuild/cmake/interpreter.py | 4 +- mesonbuild/interpreterbase.py | 17 +- mesonbuild/mintro.py | 5 +- mesonbuild/mparser.py | 452 ++++++++++++++++---------------- mesonbuild/optinterpreter.py | 4 +- mesonbuild/rewriter.py | 8 +- 9 files changed, 255 insertions(+), 254 deletions(-) diff --git a/.github/workflows/lint_mypy.yml b/.github/workflows/lint_mypy.yml index d74043bec..5f98068c4 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/mtest.py mesonbuild/minit.py mesonbuild/mintro.py mesonbuild/mparser.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 diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 5f47ad340..09d8d32c7 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -29,6 +29,7 @@ from ..mparser import ( ElementaryNode, EmptyNode, IdNode, + StringNode, MethodNode, PlusAssignmentNode, TernaryNode, @@ -201,9 +202,16 @@ class AstInterpreter(interpreterbase.InterpreterBase): def reduce_arguments(self, args): if isinstance(args, ArgumentNode): + kwargs = {} # type: T.Dict[T.Union[str, BaseNode], BaseNode] + 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), {} diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py index f245a3629..033b1d58b 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -143,7 +143,7 @@ class AstPrinter(AstVisitor): node.value.accept(self) def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): - varnames = [x.value for x in node.varnames] + varnames = [x for x in node.varnames] self.append_padded('foreach', node) self.append_padded(', '.join(varnames), node) self.append_padded(':', node) @@ -192,10 +192,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/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 941baeddf..fdcd287e4 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -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/interpreterbase.py b/mesonbuild/interpreterbase.py index d723da554..152c65b1d 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -20,6 +20,7 @@ from . import environment, dependencies import os, copy, re from functools import wraps +from typing import Union, Optional class ObjectHolder: def __init__(self, obj, subproject=None): @@ -494,7 +495,7 @@ class InterpreterBase: @FeatureNew('dict', '0.47.0') def evaluate_dictstatement(self, cur): - (arguments, kwargs) = self.reduce_arguments(cur.args) + (arguments, kwargs) = self.reduce_arguments(cur.args, resolve_key_nodes=False) assert (not arguments) result = {} self.argument_depth += 1 @@ -693,7 +694,7 @@ The result of this is undefined and will become a hard error in a future Meson r 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 +707,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: @@ -1025,7 +1026,7 @@ 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: Optional[bool] = True): assert(isinstance(args, mparser.ArgumentNode)) if args.incorrect_order(): raise InvalidArguments('All keyword arguments must be after positional arguments.') @@ -1033,8 +1034,12 @@ The result of this is undefined and will become a hard error in a future Meson r reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] reduced_kw = {} for key in args.kwargs.keys(): + reduced_key = key # type: Union[str, mparser.BaseNode] + if resolve_key_nodes and isinstance(key, mparser.IdNode): + assert isinstance(key.value, str) + reduced_key = key.value a = args.kwargs[key] - reduced_kw[key] = self.evaluate_statement(a) + reduced_kw[reduced_key] = self.evaluate_statement(a) self.argument_depth -= 1 final_kw = self.expand_default_kwargs(reduced_kw) return reduced_pos, final_kw diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index cfa457436..cca437225 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 430c89e73..84b686ec3 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -14,10 +14,16 @@ import re import codecs +import textwrap import types +import typing as T +from typing import Dict, List, Tuple, Optional, Union, TYPE_CHECKING from .mesonlib import MesonException from . import mlog +if 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''' @@ -72,22 +78,22 @@ class BlockParseException(MesonException): 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 - - def __eq__(self, other): + def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: Tuple[int, int], value: Union[str, bool, int]) -> None: + 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: Tuple[int, int] + self.value = value # type: Union[str, bool, int] + + 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) -> None: self.code = code self.keywords = {'true', 'false', 'if', 'else', 'elif', 'endif', 'and', 'or', 'not', 'foreach', 'endforeach', @@ -129,10 +135,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 +148,7 @@ class Lexer: col = 0 while loc < len(self.code): matched = False - value = None + value = None # type: Union[str, bool, int] for (tid, reg) in self.token_specification: mo = reg.match(self.code, loc) if mo: @@ -174,8 +180,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,7 +225,14 @@ 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: Optional[int] = None, end_colno: Optional[int] = None) -> 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 + + def accept(self, visitor: 'AstVisitor') -> None: fname = 'visit_{}'.format(type(self).__name__) if hasattr(visitor, fname): func = getattr(visitor, fname) @@ -221,21 +240,19 @@ class BaseNode: func(self) class ElementaryNode(BaseNode): - def __init__(self, token): - self.lineno = token.lineno - self.filename = token.filename - self.colno = token.colno + def __init__(self, token: Token) -> None: + super().__init__(token.lineno, token.colno, token.filename) self.value = token.value self.bytespan = token.bytespan class BooleanNode(ElementaryNode): - def __init__(self, token, value): + def __init__(self, token: Token, value: bool) -> None: super().__init__(token) self.value = value assert(isinstance(self.value, bool)) class IdNode(ElementaryNode): - def __init__(self, token): + def __init__(self, token: Token) -> None: super().__init__(token) assert(isinstance(self.value, str)) @@ -243,12 +260,12 @@ class IdNode(ElementaryNode): return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) class NumberNode(ElementaryNode): - def __init__(self, token): + def __init__(self, token: Token) -> None: super().__init__(token) assert(isinstance(self.value, int)) class StringNode(ElementaryNode): - def __init__(self, token): + def __init__(self, token: Token) -> None: super().__init__(token) assert(isinstance(self.value, str)) @@ -261,203 +278,163 @@ class ContinueNode(ElementaryNode): class BreakNode(ElementaryNode): pass +class ArgumentNode(BaseNode): + def __init__(self, token: Token) -> None: + super().__init__(token.lineno, token.colno, token.filename) + self.arguments = [] # type: List[BaseNode] + self.commas = [] # type: List[Token] + self.kwargs = {} # type: 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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, value: BaseNode) -> None: + 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) -> None: + super().__init__(token.lineno, token.colno, token.filename) + self.lines = [] # type: 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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) -> None: + 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: List[str], items: BaseNode, block: CodeBlockNode) -> None: + super().__init__(token.lineno, token.colno, token.filename) + self.varnames = varnames # type: 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) -> None: + super().__init__(linenode.lineno, linenode.colno, linenode.filename) + self.ifs = [] # type: List[IfNode] + self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: 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 +462,60 @@ comparison_map = {'equal': '==', # 9 plain token class Parser: - def __init__(self, code, filename): + def __init__(self, code: str, filename: str) -> None: self.lexer = Lexer(code) self.stream = self.lexer.lex(filename) self.current = Token('eof', '', 0, 0, 0, (0, 0), None) 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 +526,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 +538,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 +547,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 +556,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 +605,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 +618,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,7 +635,7 @@ class Parser: else: return self.e9() - def e9(self): + def e9(self) -> BaseNode: t = self.current if self.accept('true'): return BooleanNode(t, True) @@ -667,15 +647,15 @@ class Parser: 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 +666,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 +679,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 +690,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 +704,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: 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) -> 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 fd0a8c95e..2695a269c 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 39d8337d9..6aaa269bb 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 @@ -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): @@ -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,6 +587,8 @@ 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] From a75255bc4ce992721bf9f94c3d64ee10c77d7191 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 7 Dec 2019 19:18:26 +0100 Subject: [PATCH 2/8] types: Annotate the AST visitors --- mesonbuild/ast/interpreter.py | 6 +-- mesonbuild/ast/introspection.py | 5 +-- mesonbuild/ast/postprocess.py | 35 ++++++++-------- mesonbuild/ast/printer.py | 71 ++++++++++++++++----------------- mesonbuild/ast/visitor.py | 59 +++++++++++++-------------- mesonbuild/mparser.py | 5 +++ mesonbuild/rewriter.py | 2 +- 7 files changed, 93 insertions(+), 90 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 09d8d32c7..e2f4e1230 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -190,7 +190,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): 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'): + if node.value.ast_id: self.reverse_assignment[node.value.ast_id] = node self.assign_vals[node.var_name] += [self.evaluate_statement(node.value)] @@ -250,7 +250,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): def assignment(self, node): assert(isinstance(node, AssignmentNode)) self.assignments[node.var_name] = [node.value] # Save a reference to the value node - if hasattr(node.value, 'ast_id'): + 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 @@ -274,7 +274,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] diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 709dbac4e..b191be3ae 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -145,7 +145,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,8 +154,8 @@ 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): diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py index 8e8732f3e..aa19916f3 100644 --- a/mesonbuild/ast/postprocess.py +++ b/mesonbuild/ast/postprocess.py @@ -17,48 +17,49 @@ from . import AstVisitor from .. import mparser +from typing import Dict class AstIndentationGenerator(AstVisitor): - def __init__(self): + def __init__(self) -> None: 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) @@ -75,10 +76,10 @@ class AstIndentationGenerator(AstVisitor): self.level -= 1 class AstIDGenerator(AstVisitor): - def __init__(self): - self.counter = {} + def __init__(self) -> None: + self.counter = {} # type: 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 @@ -86,20 +87,20 @@ class AstIDGenerator(AstVisitor): self.counter[name] += 1 class AstConditionLevel(AstVisitor): - def __init__(self): + def __init__(self) -> None: 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 033b1d58b..06f3c6234 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -28,7 +28,7 @@ arithmic_map = { } class AstPrinter(AstVisitor): - def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5): + def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5) -> None: self.result = '' self.indent = indent self.arg_newline_cutoff = arg_newline_cutoff @@ -36,113 +36,110 @@ 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): + 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) @@ -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)): diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py index de13daeac..ec5101426 100644 --- a/mesonbuild/ast/visitor.py +++ b/mesonbuild/ast/visitor.py @@ -18,122 +18,123 @@ from .. import mparser class AstVisitor: - def __init__(self): + def __init__(self) -> None: 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/mparser.py b/mesonbuild/mparser.py index 84b686ec3..ad871f044 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -232,6 +232,11 @@ class BaseNode: 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): diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 6aaa269bb..a6d225677 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -593,7 +593,7 @@ class Rewriter: 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 From c48b0dea275a4a4aa80d4fd2700e82c230f27467 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 7 Dec 2019 20:48:44 +0100 Subject: [PATCH 3/8] types: Annotate ast/interpreter.py --- mesonbuild/ast/interpreter.py | 84 ++++++++++++++++++--------------- mesonbuild/ast/introspection.py | 7 ++- mesonbuild/interpreter.py | 3 +- mesonbuild/interpreterbase.py | 4 +- mesonbuild/rewriter.py | 6 +-- run_unittests.py | 2 +- 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index e2f4e1230..5cf6f284a 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -21,18 +21,27 @@ from .. import environment from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest from ..mparser import ( + AndNode, ArgumentNode, ArithmeticNode, ArrayNode, AssignmentNode, BaseNode, + ComparisonNode, ElementaryNode, EmptyNode, + ForeachClauseNode, IdNode, - StringNode, + IfClauseNode, + IfNode, + IndexNode, MethodNode, + NotNode, + OrNode, PlusAssignmentNode, + StringNode, TernaryNode, + UMinusNode, ) import os, sys @@ -60,13 +69,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) -> 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, @@ -123,21 +132,21 @@ 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[T.Union[BaseNode, str, int, float, bool]], kwargs: T.Dict[str, T.Union[BaseNode, str, int, float, bool]]) -> 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[T.Union[BaseNode, str, int, float, bool]], kwargs: T.Dict[str, T.Union[BaseNode, str, int, float, bool]]) -> 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)) return - prev_subdir = self.subdir + prev_subdir = self.subdir # type: str subdir = os.path.join(prev_subdir, args[0]) absdir = os.path.join(self.source_root, subdir) buildfilename = os.path.join(subdir, environment.build_filename) @@ -166,41 +175,39 @@ 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 + # 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[list, dict]: if isinstance(args, ArgumentNode): kwargs = {} # type: T.Dict[T.Union[str, BaseNode], BaseNode] for key, val in args.kwargs.items(): @@ -215,22 +222,22 @@ class AstInterpreter(interpreterbase.InterpreterBase): 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: @@ -238,30 +245,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 + 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: @@ -323,7 +331,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[T.Union[BaseNode, str, bool, int, float]] for i in result: if isinstance(i, BaseNode): resolved = self.resolve_node(i, include_unknown_args, id_loop_detect) @@ -335,12 +343,12 @@ 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: T.Any, include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[T.Union[BaseNode, str, bool, int, float]]: # Make sure we are always dealing with lists if not isinstance(args, list): args = [args] - flattend_args = [] + flattend_args = [] # type: T.List[T.Union[BaseNode, str, bool, int, float]] # Resolve the contents of args for i in args: @@ -354,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, T.Union[BaseNode, str, bool, int, float]], include_unknown_args: bool = False) -> T.Dict[str, T.Union[BaseNode, str, bool, int, float]]: 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 b191be3ae..083725329 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -38,7 +38,7 @@ class IntrospectionInterpreter(AstInterpreter): # 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): 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,7 +46,6 @@ 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') @@ -183,8 +182,8 @@ class IntrospectionInterpreter(AstInterpreter): elif isinstance(curr, IdNode): # Try to resolve the ID and append the node to the queue 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): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 2769c2d04..6b40815a2 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 = {} diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 152c65b1d..2865a955c 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -360,12 +360,12 @@ def is_disabled(args, kwargs) -> bool: return False class InterpreterBase: - def __init__(self, source_root, subdir): + def __init__(self, source_root: str, subdir: str, subproject: str) -> None: self.source_root = source_root self.funcs = {} self.builtin = {} self.subdir = subdir - self.variables = {} + self.subproject = subproject self.argument_depth = 0 self.current_lineno = -1 # Current node set during a function call. This can be used as location diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index a6d225677..46ce56cda 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -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] diff --git a/run_unittests.py b/run_unittests.py index 56551655e..3f5c87e45 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())) From 5a89a6f80481a9c17e00f102d341bce8bfc56b17 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 8 Dec 2019 19:22:14 +0100 Subject: [PATCH 4/8] types: (partially) annotate interpreterbase.py This commit annotates most of interpreterbase.py. However, there are stil the @wraps missing, since I am unsure what the types are supposed to be here. --- mesonbuild/ast/interpreter.py | 34 +-- mesonbuild/interpreter.py | 15 +- mesonbuild/interpreterbase.py | 389 +++++++++++++++++++--------------- 3 files changed, 236 insertions(+), 202 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 5cf6f284a..0eb76ccb7 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -19,7 +19,7 @@ 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, @@ -33,10 +33,8 @@ from ..mparser import ( ForeachClauseNode, IdNode, IfClauseNode, - IfNode, IndexNode, MethodNode, - NotNode, OrNode, PlusAssignmentNode, StringNode, @@ -132,7 +130,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): 'summary': self.func_do_nothing, }) - def func_do_nothing(self, node: BaseNode, args: T.List[T.Union[BaseNode, str, int, float, bool]], kwargs: T.Dict[str, T.Union[BaseNode, str, int, float, bool]]) -> bool: + 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) -> None: @@ -140,7 +138,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): for i in self.visitors: self.ast.accept(i) - def func_subdir(self, node: BaseNode, args: T.List[T.Union[BaseNode, str, int, float, bool]], kwargs: T.Dict[str, T.Union[BaseNode, str, int, float, bool]]) -> None: + 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)) @@ -312,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) @@ -331,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 = [] # type: T.List[T.Union[BaseNode, str, bool, int, float]] + 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) @@ -343,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.Union[BaseNode, str, bool, int, float]]: + def flatten_args(self, args_raw: 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 = [] # type: T.List[T.Union[BaseNode, str, bool, int, float]] + flattend_args = [] # type: T.List[TYPE_nvar] # Resolve the contents of args for i in args: @@ -362,9 +362,17 @@ class AstInterpreter(interpreterbase.InterpreterBase): flattend_args += [i] return flattend_args - def flatten_kwargs(self, kwargs: T.Dict[str, T.Union[BaseNode, str, bool, int, float]], include_unknown_args: bool = False) -> T.Dict[str, T.Union[BaseNode, str, bool, int, float]]: + def flatten_kwargs(self, kwargs: TYPE_nkwargs, include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]: flattend_kwargs = {} - for key, val in kwargs.items(): + for key_node, val in kwargs.items(): + key = None # type: str + if isinstance(key_node, str): + key = key_node + elif isinstance(key_node, StringNode): + assert isinstance(key_node.value, str) + key = key_node.value + else: + continue if isinstance(val, BaseNode): resolved = self.resolve_node(val, include_unknown_args) if resolved is not None: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 6b40815a2..cac58314e 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -4499,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.') @@ -4509,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 2865a955c..a3f0fb916 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -20,19 +20,39 @@ from . import environment, dependencies import os, copy, re from functools import wraps -from typing import Union, Optional +from typing import Any, Callable, Dict, List, Set, Sequence, Tuple, Optional, Union + +class InterpreterObject: + def __init__(self): + self.methods = {} # type: Dict[str, 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: List[Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']], kwargs: Dict[str, 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) class ObjectHolder: - def __init__(self, obj, subproject=None): - self.held_object = obj - self.subproject = subproject + def __init__(self, obj: InterpreterObject, subproject: Optional[str] = None): + self.held_object = obj # type: InterpreterObject + self.subproject = subproject # type: str def __repr__(self): return ''.format(self.held_object) +TYPE_elementary = Union[str, int, float, bool] +TYPE_var = Union[TYPE_elementary, list, dict, InterpreterObject, ObjectHolder] +TYPE_nvar = Union[TYPE_var, mparser.BaseNode] +TYPE_nkwargs = Dict[Union[mparser.BaseNode, str], TYPE_nvar] + # Decorators for method calls. -def check_stringlist(a, msg='Arguments must be strings.'): +def check_stringlist(a: 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.') @@ -40,7 +60,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 @@ -101,12 +121,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: Union[TYPE_nvar, List[TYPE_nvar]]) -> 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 isinstance(args, (int, float, bool, str, ObjectHolder, mparser.BaseNode, mesonlib.File, InterpreterObject)): + return [args] + result = [] # type: List[TYPE_nvar] for a in args: if isinstance(a, list): rest = flatten(a) @@ -161,8 +182,8 @@ def disablerIfNotFound(f): class permittedKwargs: - def __init__(self, permitted): - self.permitted = permitted + def __init__(self, permitted: Set[str]) -> None: + self.permitted = permitted # type: Set[str] def __call__(self, f): @wraps(f) @@ -179,18 +200,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: Dict[str, Dict[str, Set[str]]] + + def __init__(self, feature_name: str, version: str) -> None: + 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 == '': @@ -213,7 +239,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)) @@ -222,6 +248,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): @@ -234,38 +267,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: List[str]) -> None: self.feature_name = feature_name self.feature_version = feature_version self.kwargs = kwargs @@ -311,21 +336,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__() @@ -360,19 +370,22 @@ def is_disabled(args, kwargs) -> bool: return False class InterpreterBase: + elementary_types = (int, float, str, bool, list) + def __init__(self, source_root: str, subdir: str, subproject: str) -> None: self.source_root = source_root - self.funcs = {} - self.builtin = {} + self.funcs = {} # type: Dict[str, Callable[[mparser.BaseNode, List[TYPE_nvar], Dict[str, TYPE_nvar]], TYPE_var]] + self.builtin = {} # type: Dict[str, InterpreterObject] self.subdir = subdir self.subproject = subproject + self.variables = {} # type: 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) @@ -387,17 +400,17 @@ class InterpreterBase: me.file = mesonfile raise me - def join_path_strings(self, args): + def join_path_strings(self, args: 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: @@ -406,7 +419,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: @@ -414,7 +427,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: Optional[int] = None) -> None: if node is None: return if not isinstance(node, mparser.CodeBlockNode): @@ -431,17 +444,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) -> 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): @@ -471,9 +485,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): @@ -482,75 +496,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): + def evaluate_dictstatement(self, cur: mparser.DictNode) -> Dict[str, Any]: (arguments, kwargs) = self.reduce_arguments(cur.args, resolve_key_nodes=False) assert (not arguments) - result = {} + result = {} # type: Dict[str, 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) -> 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) -> 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: Any, val2: Any) -> bool: if type(val1) != type(val2): return False return True - def evaluate_in(self, val1, val2): + def evaluate_in(self, val1: Any, val2: 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) -> 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) @@ -573,68 +590,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', ''))) + elif not isinstance(val2, self.elementary_types): + raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.right, 'value', ''))) + # 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) -> 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) -> 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) -> 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: Any, r: Any) -> 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): @@ -643,19 +662,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) -> 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': @@ -675,10 +695,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.') @@ -687,7 +707,7 @@ 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) @@ -718,7 +738,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) @@ -728,6 +748,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: 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') @@ -750,10 +771,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( @@ -771,26 +792,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) -> 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: 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 @@ -798,7 +825,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): @@ -813,7 +842,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): @@ -821,17 +849,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: List[TYPE_nvar]) -> Union[str, int]: if method_name == 'to_string': if not posargs: if obj: @@ -853,10 +878,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: List[TYPE_nvar]) -> Union[str, bool]: if method_name == 'is_even': if not posargs: return obj % 2 == 0 @@ -876,7 +898,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: List[TYPE_nvar], method_name: str) -> str: if len(posargs) > 1: m = '{}() must have zero or one arguments' raise InterpreterException(m.format(method_name)) @@ -888,17 +910,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: List[TYPE_nvar]) -> Union[str, int, bool, 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': @@ -906,19 +925,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) @@ -929,6 +948,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: @@ -939,12 +959,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: 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)) @@ -957,15 +976,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: 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': @@ -984,16 +1012,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: List[TYPE_nvar]) -> TYPE_var: if method_name in ('has_key', 'get'): if method_name == 'has_key': if len(posargs) != 1: @@ -1015,7 +1041,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)) @@ -1026,25 +1055,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: mparser.ArgumentNode, resolve_key_nodes: Optional[bool] = True): + def reduce_arguments(self, args: mparser.ArgumentNode, resolve_key_nodes: bool = True) -> Tuple[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(): + reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: List[TYPE_nvar] + reduced_kw = {} # type: TYPE_nkwargs + for key, val in args.kwargs.items(): reduced_key = key # type: 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 - a = args.kwargs[key] - reduced_kw[reduced_key] = self.evaluate_statement(a) + 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') @@ -1058,7 +1089,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) -> Dict[str, TYPE_nvar]: + kw = {} # type: 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. @@ -1075,7 +1114,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): @@ -1088,16 +1127,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: 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)') From ad5df1b9c3fd9e853a6dc9a06964e2624cabb85b Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 8 Dec 2019 20:18:41 +0100 Subject: [PATCH 5/8] types: Annotate ast/introspection.py --- .github/workflows/lint_mypy.yml | 2 +- mesonbuild/ast/interpreter.py | 16 ++---- mesonbuild/ast/introspection.py | 92 ++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/.github/workflows/lint_mypy.yml b/.github/workflows/lint_mypy.yml index 5f98068c4..8f6d917eb 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/mparser.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 0eb76ccb7..fd04efd09 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -19,7 +19,7 @@ from .visitor import AstVisitor from .. import interpreterbase, mparser, mesonlib from .. import environment -from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar, TYPE_nkwargs +from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar from ..mparser import ( AndNode, ArgumentNode, @@ -341,7 +341,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): return result - def flatten_args(self, args_raw: T.Sequence[TYPE_nvar], include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[TYPE_nvar]: + 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 isinstance(args_raw, list): args = args_raw @@ -362,17 +362,9 @@ class AstInterpreter(interpreterbase.InterpreterBase): flattend_args += [i] return flattend_args - def flatten_kwargs(self, kwargs: TYPE_nkwargs, include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]: + def flatten_kwargs(self, kwargs: T.Dict[str, TYPE_nvar], include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]: flattend_kwargs = {} - for key_node, val in kwargs.items(): - key = None # type: str - if isinstance(key_node, str): - key = key_node - elif isinstance(key_node, StringNode): - assert isinstance(key_node.value, str) - key = key_node.value - else: - continue + for key, val in kwargs.items(): if isinstance(val, BaseNode): resolved = self.resolve_node(val, include_unknown_args) if resolved is not None: diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 083725329..c60c24b3b 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -15,28 +15,38 @@ # 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 +from typing import Any, Dict, List, Optional 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) -> None: + self.cross_file = cross_file # type: str + self.native_file = None # type: str + self.cmd_line_options = {} # type: 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: Optional[List[AstVisitor]] = None, + cross_file: Optional[str] = None, + subproject: str = '', + subproject_dir: str = 'subprojects', + env: Optional[environment.Environment] = None) -> None: visitors = visitors if visitors is not None else [] super().__init__(source_root, subdir, subproject, visitors=visitors) @@ -51,10 +61,10 @@ class IntrospectionInterpreter(AstInterpreter): 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: Dict[str, Any] + self.targets = [] # type: List[Dict[str, Any]] + self.dependencies = [] # type: List[Dict[str, Any]] + self.project_node = None # type: BaseNode self.funcs.update({ 'add_languages': self.func_add_languages, @@ -69,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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> None: if self.project_node: raise InvalidArguments('Second call to project()') self.project_node = node @@ -98,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'] = [] @@ -114,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: @@ -125,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: List[TYPE_nvar], kwargs: 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> None: args = self.flatten_args(args) kwargs = self.flatten_kwargs(kwargs) if not args: @@ -157,20 +173,20 @@ class IntrospectionInterpreter(AstInterpreter): 'node': node }] - def build_target(self, node, args, kwargs, targetclass): + def build_target(self, node: BaseNode, args: List[TYPE_nvar], kwargs_raw: Dict[str, TYPE_nvar], targetclass) -> Optional[Dict[str, 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: List[BaseNode] while srcqueue: curr = srcqueue.pop(0) arg_node = None @@ -181,6 +197,7 @@ 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: tmp_node = self.assignments[var_name] @@ -204,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: List[Any] + empty_sources = [] # type: List[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 = { @@ -225,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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: default_library = self.coredata.get_builtin_option('default_library') if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibrary) @@ -233,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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: return self.build_target(node, args, kwargs, SharedModule) - def func_library(self, node, args, kwargs): + def func_library(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: return self.build_library(node, args, kwargs) - def func_jar(self, node, args, kwargs): + def func_jar(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, 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 @@ -273,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() From ab988198c7aa5bec33057fabf2b5f68e847d67d2 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 9 Dec 2019 23:17:25 +0100 Subject: [PATCH 6/8] review: Initial fixup --- mesonbuild/ast/interpreter.py | 10 +++--- mesonbuild/cmake/interpreter.py | 2 +- mesonbuild/interpreter.py | 10 +++--- mesonbuild/interpreterbase.py | 11 +++--- mesonbuild/mparser.py | 59 +++++++++++++++++---------------- mesonbuild/rewriter.py | 4 +-- 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index fd04efd09..2839f54df 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -19,7 +19,7 @@ from .visitor import AstVisitor from .. import interpreterbase, mparser, mesonlib from .. import environment -from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar +from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar, TYPE_nkwargs from ..mparser import ( AndNode, ArgumentNode, @@ -144,7 +144,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): sys.stderr.write('Unable to evaluate subdir({}) in AstInterpreter --> Skipping\n'.format(args)) return - prev_subdir = self.subdir # type: str + prev_subdir = self.subdir subdir = os.path.join(prev_subdir, args[0]) absdir = os.path.join(self.source_root, subdir) buildfilename = os.path.join(subdir, environment.build_filename) @@ -194,7 +194,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): def evaluate_plusassign(self, node: PlusAssignmentNode) -> None: assert(isinstance(node, PlusAssignmentNode)) # Cheat by doing a reassignment - self.assignments[node.var_name] = node.value # Save a reference to the value node + 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) @@ -205,9 +205,9 @@ class AstInterpreter(interpreterbase.InterpreterBase): def unknown_function_called(self, func_name: str) -> None: pass - def reduce_arguments(self, args: ArgumentNode, resolve_key_nodes: bool = True) -> T.Tuple[list, dict]: + 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], BaseNode] + 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) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index fdcd287e4..efdb840b6 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): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index cac58314e..dad293358 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -4425,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)) diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index a3f0fb916..db77266a6 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -19,8 +19,9 @@ from . import mparser, mesonlib, mlog from . import environment, dependencies import os, copy, re +import collections.abc from functools import wraps -from typing import Any, Callable, Dict, List, Set, Sequence, Tuple, Optional, Union +from typing import Any, Callable, ClassVar, Dict, Generic, List, Set, Sequence, Tuple, TypeVar, Optional, Union class InterpreterObject: def __init__(self): @@ -37,7 +38,9 @@ class InterpreterObject: return method(args, kwargs) raise InvalidCode('Unknown method "%s" in object.' % method_name) -class ObjectHolder: +TV_InterpreterObject = TypeVar('TV_InterpreterObject') + +class ObjectHolder(Generic[TV_InterpreterObject]): def __init__(self, obj: InterpreterObject, subproject: Optional[str] = None): self.held_object = obj # type: InterpreterObject self.subproject = subproject # type: str @@ -125,7 +128,7 @@ def flatten(args: Union[TYPE_nvar, List[TYPE_nvar]]) -> List[TYPE_nvar]: if isinstance(args, mparser.StringNode): assert isinstance(args.value, str) return [args.value] - if isinstance(args, (int, float, bool, str, ObjectHolder, mparser.BaseNode, mesonlib.File, InterpreterObject)): + if not isinstance(args, collections.abc.Sequence): return [args] result = [] # type: List[TYPE_nvar] for a in args: @@ -203,7 +206,7 @@ class FeatureCheckBase: # Class variable, shared across all instances # # Format: {subproject: {feature_version: set(feature_names)}} - feature_registry = {} # type: Dict[str, Dict[str, Set[str]]] + feature_registry = {} # type: ClassVar[Dict[str, Dict[str, Set[str]]]] def __init__(self, feature_name: str, version: str) -> None: self.feature_name = feature_name # type: str diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index ad871f044..9e6226e56 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -17,7 +17,7 @@ import codecs import textwrap import types import typing as T -from typing import Dict, List, Tuple, Optional, Union, TYPE_CHECKING +from typing import Dict, Generic, Generator, List, Tuple, TypeVar, Optional, Union, TYPE_CHECKING from .mesonlib import MesonException from . import mlog @@ -77,15 +77,17 @@ class BlockParseException(MesonException): self.lineno = lineno self.colno = colno -class Token: - def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: Tuple[int, int], value: Union[str, bool, int]) -> None: +TV_TokenTypes = TypeVar('TV_TokenTypes', int, str, bool) + +class Token(Generic[TV_TokenTypes]): + def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: Tuple[int, int], value: TV_TokenTypes) -> None: 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: Tuple[int, int] - self.value = value # type: Union[str, bool, int] + self.value = value # type: TV_TokenTypes def __eq__(self, other) -> bool: if isinstance(other, str): @@ -244,35 +246,34 @@ class BaseNode: if callable(func): func(self) -class ElementaryNode(BaseNode): - def __init__(self, token: Token) -> None: +class ElementaryNode(Generic[TV_TokenTypes], BaseNode): + def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) - self.value = token.value - self.bytespan = token.bytespan + self.value = token.value # type: TV_TokenTypes + self.bytespan = token.bytespan # type: Tuple[int, int] -class BooleanNode(ElementaryNode): - def __init__(self, token: Token, value: bool) -> None: +class BooleanNode(ElementaryNode[bool]): + def __init__(self, token: Token[bool]) -> None: super().__init__(token) - self.value = value - assert(isinstance(self.value, bool)) + assert isinstance(self.value, bool) -class IdNode(ElementaryNode): - def __init__(self, token: Token) -> None: +class IdNode(ElementaryNode[str]): + def __init__(self, token: Token[str]) -> None: 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: Token) -> None: +class NumberNode(ElementaryNode[int]): + def __init__(self, token: Token[int]) -> None: super().__init__(token) - assert(isinstance(self.value, int)) + assert isinstance(self.value, int) -class StringNode(ElementaryNode): - def __init__(self, token: Token) -> None: +class StringNode(ElementaryNode[str]): + def __init__(self, token: Token[str]) -> None: 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) @@ -284,10 +285,10 @@ class BreakNode(ElementaryNode): pass class ArgumentNode(BaseNode): - def __init__(self, token: Token) -> None: + def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) self.arguments = [] # type: List[BaseNode] - self.commas = [] # type: List[Token] + self.commas = [] # type: List[Token[TV_TokenTypes]] self.kwargs = {} # type: Dict[BaseNode, BaseNode] self.order_error = False @@ -366,12 +367,12 @@ class ArithmeticNode(BaseNode): self.operation = operation # type: str class NotNode(BaseNode): - def __init__(self, token: Token, value: BaseNode) -> None: + def __init__(self, token: Token[TV_TokenTypes], value: BaseNode) -> None: super().__init__(token.lineno, token.colno, token.filename) self.value = value # type: BaseNode class CodeBlockNode(BaseNode): - def __init__(self, token: Token) -> None: + def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) self.lines = [] # type: List[BaseNode] @@ -470,7 +471,7 @@ class Parser: def __init__(self, code: str, filename: str) -> None: 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 @@ -643,9 +644,11 @@ class Parser: 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'): diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 46ce56cda..785451fd2 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -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] From 0302a697b8c232b21a4a8e14db93e79df485a97d Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 8 Jan 2020 17:44:50 +0100 Subject: [PATCH 7/8] types: Use import typing as T --- mesonbuild/ast/introspection.py | 48 +++++++-------- mesonbuild/ast/postprocess.py | 4 +- mesonbuild/interpreterbase.py | 102 ++++++++++++++++---------------- mesonbuild/mparser.py | 39 ++++++------ 4 files changed, 96 insertions(+), 97 deletions(-) diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index c60c24b3b..d51b0994c 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -23,7 +23,7 @@ from ..mesonlib import MachineChoice from ..interpreterbase import InvalidArguments, TYPE_nvar from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode -from typing import Any, Dict, List, Optional +import typing as T import os build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries'] @@ -33,7 +33,7 @@ class IntrospectionHelper: def __init__(self, cross_file: str) -> None: self.cross_file = cross_file # type: str self.native_file = None # type: str - self.cmd_line_options = {} # type: Dict[str, str] + self.cmd_line_options = {} # type: T.Dict[str, str] class IntrospectionInterpreter(AstInterpreter): # Interpreter to detect the options without a build directory @@ -42,11 +42,11 @@ class IntrospectionInterpreter(AstInterpreter): source_root: str, subdir: str, backend: str, - visitors: Optional[List[AstVisitor]] = None, - cross_file: Optional[str] = None, + visitors: T.Optional[T.List[AstVisitor]] = None, + cross_file: T.Optional[str] = None, subproject: str = '', subproject_dir: str = 'subprojects', - env: Optional[environment.Environment] = None) -> None: + env: T.Optional[environment.Environment] = None) -> None: visitors = visitors if visitors is not None else [] super().__init__(source_root, subdir, subproject, visitors=visitors) @@ -61,9 +61,9 @@ class IntrospectionInterpreter(AstInterpreter): 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 = {} # type: Dict[str, Any] - self.targets = [] # type: List[Dict[str, Any]] - self.dependencies = [] # type: List[Dict[str, Any]] + 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({ @@ -79,7 +79,7 @@ class IntrospectionInterpreter(AstInterpreter): 'both_libraries': self.func_both_lib, }) - def func_project(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> None: + 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 @@ -136,7 +136,7 @@ class IntrospectionInterpreter(AstInterpreter): except (mesonlib.MesonException, RuntimeError): return - def func_add_languages(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> None: + 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): @@ -149,7 +149,7 @@ class IntrospectionInterpreter(AstInterpreter): if lang not in self.coredata.compilers[for_machine]: self.environment.detect_compiler_for(lang, for_machine) - def func_dependency(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> None: + 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: @@ -173,7 +173,7 @@ class IntrospectionInterpreter(AstInterpreter): 'node': node }] - def build_target(self, node: BaseNode, args: List[TYPE_nvar], kwargs_raw: Dict[str, TYPE_nvar], targetclass) -> Optional[Dict[str, Any]]: + 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 None @@ -186,7 +186,7 @@ class IntrospectionInterpreter(AstInterpreter): kwargs = self.flatten_kwargs(kwargs_raw, True) - source_nodes = [] # type: List[BaseNode] + source_nodes = [] # type: T.List[BaseNode] while srcqueue: curr = srcqueue.pop(0) arg_node = None @@ -221,8 +221,8 @@ 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 = [] # type: List[Any] - empty_sources = [] # type: List[Any] + 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) @@ -243,7 +243,7 @@ class IntrospectionInterpreter(AstInterpreter): self.targets += [new_target] return new_target - def build_library(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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) @@ -253,28 +253,28 @@ class IntrospectionInterpreter(AstInterpreter): return self.build_target(node, args, kwargs, SharedLibrary) return None - def func_executable(self, node: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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: BaseNode, args: List[TYPE_nvar], kwargs: Dict[str, TYPE_nvar]) -> Optional[Dict[str, Any]]: + 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 None target_type = kwargs.pop('target_type') diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py index aa19916f3..6d808be57 100644 --- a/mesonbuild/ast/postprocess.py +++ b/mesonbuild/ast/postprocess.py @@ -17,7 +17,7 @@ from . import AstVisitor from .. import mparser -from typing import Dict +import typing as T class AstIndentationGenerator(AstVisitor): def __init__(self) -> None: @@ -77,7 +77,7 @@ class AstIndentationGenerator(AstVisitor): class AstIDGenerator(AstVisitor): def __init__(self) -> None: - self.counter = {} # type: Dict[str, int] + self.counter = {} # type: T.Dict[str, int] def visit_default_func(self, node: mparser.BaseNode) -> None: name = type(node).__name__ diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index db77266a6..e3c2c6401 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -21,16 +21,16 @@ from . import environment, dependencies import os, copy, re import collections.abc from functools import wraps -from typing import Any, Callable, ClassVar, Dict, Generic, List, Set, Sequence, Tuple, TypeVar, Optional, Union +import typing as T class InterpreterObject: def __init__(self): - self.methods = {} # type: Dict[str, Callable] + 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: List[Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']], kwargs: Dict[str, Union[mparser.BaseNode, str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']]): + 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): @@ -38,24 +38,24 @@ class InterpreterObject: return method(args, kwargs) raise InvalidCode('Unknown method "%s" in object.' % method_name) -TV_InterpreterObject = TypeVar('TV_InterpreterObject') +TV_InterpreterObject = T.TypeVar('TV_InterpreterObject') -class ObjectHolder(Generic[TV_InterpreterObject]): - def __init__(self, obj: InterpreterObject, subproject: Optional[str] = None): +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 ''.format(self.held_object) -TYPE_elementary = Union[str, int, float, bool] -TYPE_var = Union[TYPE_elementary, list, dict, InterpreterObject, ObjectHolder] -TYPE_nvar = Union[TYPE_var, mparser.BaseNode] -TYPE_nkwargs = Dict[Union[mparser.BaseNode, str], TYPE_nvar] +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: Any, msg: str = 'Arguments must be strings.') -> None: +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.') @@ -124,13 +124,13 @@ def _get_callee_args(wrapped_args, want_subproject: bool = False): kwargs = kwargs if kwargs is not None else {} return s, node, args, kwargs, subproject -def flatten(args: Union[TYPE_nvar, List[TYPE_nvar]]) -> List[TYPE_nvar]: +def flatten(args: T.Union[TYPE_nvar, T.List[TYPE_nvar]]) -> T.List[TYPE_nvar]: if isinstance(args, mparser.StringNode): assert isinstance(args.value, str) return [args.value] if not isinstance(args, collections.abc.Sequence): return [args] - result = [] # type: List[TYPE_nvar] + result = [] # type: T.List[TYPE_nvar] for a in args: if isinstance(a, list): rest = flatten(a) @@ -185,8 +185,8 @@ def disablerIfNotFound(f): class permittedKwargs: - def __init__(self, permitted: Set[str]) -> None: - self.permitted = permitted # type: Set[str] + def __init__(self, permitted: T.Set[str]) -> None: + self.permitted = permitted # type: T.Set[str] def __call__(self, f): @wraps(f) @@ -206,7 +206,7 @@ class FeatureCheckBase: # Class variable, shared across all instances # # Format: {subproject: {feature_version: set(feature_names)}} - feature_registry = {} # type: ClassVar[Dict[str, Dict[str, Set[str]]]] + feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] def __init__(self, feature_name: str, version: str) -> None: self.feature_name = feature_name # type: str @@ -293,7 +293,7 @@ class FeatureDeprecated(FeatureCheckBase): class FeatureCheckKwargsBase: - def __init__(self, feature_name: str, feature_version: str, kwargs: List[str]) -> None: + def __init__(self, feature_name: str, feature_version: str, kwargs: T.List[str]) -> None: self.feature_name = feature_name self.feature_version = feature_version self.kwargs = kwargs @@ -377,11 +377,11 @@ class InterpreterBase: def __init__(self, source_root: str, subdir: str, subproject: str) -> None: self.source_root = source_root - self.funcs = {} # type: Dict[str, Callable[[mparser.BaseNode, List[TYPE_nvar], Dict[str, TYPE_nvar]], TYPE_var]] - self.builtin = {} # type: Dict[str, InterpreterObject] + 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.subproject = subproject - self.variables = {} # type: Dict[str, TYPE_var] + 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 @@ -403,7 +403,7 @@ class InterpreterBase: me.file = mesonfile raise me - def join_path_strings(self, args: Sequence[str]) -> str: + def join_path_strings(self, args: T.Sequence[str]) -> str: return os.path.join(*args).replace('\\', '/') def parse_project(self) -> None: @@ -430,7 +430,7 @@ class InterpreterBase: except SubdirDoneRequest: pass - def evaluate_codeblock(self, node: mparser.CodeBlockNode, start: int = 0, end: Optional[int] = None) -> 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): @@ -454,7 +454,7 @@ class InterpreterBase: raise e i += 1 # In THE FUTURE jump over blocks and stuff. - def evaluate_statement(self, cur: mparser.BaseNode) -> Optional[TYPE_var]: + 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): @@ -512,10 +512,10 @@ class InterpreterBase: return arguments @FeatureNew('dict', '0.47.0') - def evaluate_dictstatement(self, cur: mparser.DictNode) -> Dict[str, Any]: + 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 = {} # type: Dict[str, Any] + result = {} # type: T.Dict[str, T.Any] self.argument_depth += 1 for key, value in kwargs.items(): if not isinstance(key, mparser.StringNode): @@ -530,7 +530,7 @@ class InterpreterBase: self.argument_depth -= 1 return result - def evaluate_notstatement(self, cur: mparser.NotNode) -> Union[bool, Disabler]: + def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return v @@ -538,7 +538,7 @@ class InterpreterBase: raise InterpreterException('Argument to "not" is not a boolean.') return not v - def evaluate_if(self, node: mparser.IfClauseNode) -> Optional[Disabler]: + 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) @@ -553,19 +553,19 @@ class InterpreterBase: self.evaluate_codeblock(node.elseblock) return None - def validate_comparison_types(self, val1: Any, val2: Any) -> bool: + 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: Any, val2: Any) -> bool: + 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: mparser.ComparisonNode) -> Union[bool, Disabler]: + def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[bool, Disabler]: val1 = self.evaluate_statement(node.left) if isinstance(val1, Disabler): return val1 @@ -610,7 +610,7 @@ The result of this is undefined and will become a hard error in a future Meson r else: raise InvalidCode('You broke my compare eval.') - def evaluate_andstatement(self, cur: mparser.AndNode) -> Union[bool, Disabler]: + def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[bool, Disabler]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l @@ -625,7 +625,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Second argument to "and" is not a boolean.') return r - def evaluate_orstatement(self, cur: mparser.OrNode) -> Union[bool, Disabler]: + def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[bool, Disabler]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l @@ -640,7 +640,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Second argument to "or" is not a boolean.') return r - def evaluate_uminusstatement(self, cur) -> Union[int, Disabler]: + def evaluate_uminusstatement(self, cur) -> T.Union[int, Disabler]: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return v @@ -656,7 +656,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InvalidCode('The division operator can only append a string.') return self.join_path_strings((l, r)) - def evaluate_division(self, l: Any, r: Any) -> Union[int, str]: + 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): @@ -665,7 +665,7 @@ 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: mparser.ArithmeticNode) -> Union[int, str, dict, list, Disabler]: + def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[int, str, dict, list, Disabler]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l @@ -751,7 +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: Union[str, int, float, bool, dict, list] + 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') @@ -803,14 +803,14 @@ The result of this is undefined and will become a hard error in a future Meson r # 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: mparser.FunctionNode) -> Optional[TYPE_var]: + 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: Any + func_args = posargs # type: T.Any if not getattr(func, 'no-args-flattening', False): func_args = flatten(posargs) @@ -859,7 +859,7 @@ The result of this is undefined and will become a hard error in a future Meson r obj.current_node = node return obj.method_call(method_name, args, self.kwargs_string_keys(kwargs)) - def bool_method_call(self, obj: bool, method_name: str, posargs: List[TYPE_nvar]) -> Union[str, int]: + 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: @@ -881,7 +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: int, method_name: str, posargs: List[TYPE_nvar]) -> Union[str, bool]: + 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 @@ -901,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: List[TYPE_nvar], method_name: str) -> str: + 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)) @@ -913,7 +913,7 @@ 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: str, method_name: str, posargs: List[TYPE_nvar]) -> Union[str, int, bool, List[str]]: + 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': s1 = self._get_one_string_posarg(posargs, 'strip') if s1 is not None: @@ -962,7 +962,7 @@ 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: str, args: List[TYPE_nvar]) -> str: + def format_string(self, templ: str, args: T.List[TYPE_nvar]) -> str: arg_strings = [] for arg in args: if isinstance(arg, mparser.BaseNode): @@ -982,7 +982,7 @@ The result of this is undefined and will become a hard error in a future Meson r def unknown_function_called(self, func_name: str) -> None: raise InvalidCode('Unknown function "%s".' % func_name) - def array_method_call(self, obj: list, method_name: str, posargs: List[TYPE_nvar]) -> TYPE_var: + def array_method_call(self, obj: list, method_name: str, posargs: T.List[TYPE_nvar]) -> TYPE_var: if method_name == 'contains': def check_contains(el: list) -> bool: if len(posargs) != 1: @@ -1022,7 +1022,7 @@ The result of this is undefined and will become a hard error in a future Meson r m = 'Arrays do not have a method called {!r}.' raise InterpreterException(m.format(method_name)) - def dict_method_call(self, obj: dict, method_name: str, posargs: List[TYPE_nvar]) -> TYPE_var: + 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: @@ -1058,15 +1058,15 @@ 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: mparser.ArgumentNode, resolve_key_nodes: bool = True) -> Tuple[List[TYPE_nvar], TYPE_nkwargs]: + 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] # type: List[TYPE_nvar] + 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: Union[str, mparser.BaseNode] + 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) @@ -1092,8 +1092,8 @@ The result of this is undefined and will become a hard error in a future Meson r kwargs[k] = v return kwargs - def kwargs_string_keys(self, kwargs: TYPE_nkwargs) -> Dict[str, TYPE_nvar]: - kw = {} # type: Dict[str, TYPE_nvar] + 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') @@ -1137,7 +1137,7 @@ To specify a keyword argument, use : instead of =.''') return self.variables[varname] raise InvalidCode('Unknown variable "%s".' % varname) - def is_assignable(self, value: Any) -> bool: + def is_assignable(self, value: T.Any) -> bool: return isinstance(value, (InterpreterObject, dependencies.Dependency, str, int, list, dict, mesonlib.File)) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 9e6226e56..d0e7525e3 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -17,11 +17,10 @@ import codecs import textwrap import types import typing as T -from typing import Dict, Generic, Generator, List, Tuple, TypeVar, Optional, Union, TYPE_CHECKING from .mesonlib import MesonException from . import mlog -if TYPE_CHECKING: +if T.TYPE_CHECKING: from .ast import AstVisitor # This is the regex for the supported escape sequences of a regular string @@ -77,16 +76,16 @@ class BlockParseException(MesonException): self.lineno = lineno self.colno = colno -TV_TokenTypes = TypeVar('TV_TokenTypes', int, str, bool) +TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool) -class Token(Generic[TV_TokenTypes]): - def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: Tuple[int, int], value: TV_TokenTypes) -> None: +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) -> None: 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: Tuple[int, int] + self.bytespan = bytespan # type: T.Tuple[int, int] self.value = value # type: TV_TokenTypes def __eq__(self, other) -> bool: @@ -150,7 +149,7 @@ class Lexer: col = 0 while loc < len(self.code): matched = False - value = None # type: Union[str, bool, int] + value = None # type: T.Union[str, bool, int] for (tid, reg) in self.token_specification: mo = reg.match(self.code, loc) if mo: @@ -227,7 +226,7 @@ class Lexer: raise ParseException('lexer', self.getline(line_start), lineno, col) class BaseNode: - def __init__(self, lineno: int, colno: int, filename: str, end_lineno: Optional[int] = None, end_colno: Optional[int] = None) -> None: + def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None) -> None: self.lineno = lineno # type: int self.colno = colno # type: int self.filename = filename # type: str @@ -246,11 +245,11 @@ class BaseNode: if callable(func): func(self) -class ElementaryNode(Generic[TV_TokenTypes], BaseNode): +class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode): def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) self.value = token.value # type: TV_TokenTypes - self.bytespan = token.bytespan # type: Tuple[int, int] + self.bytespan = token.bytespan # type: T.Tuple[int, int] class BooleanNode(ElementaryNode[bool]): def __init__(self, token: Token[bool]) -> None: @@ -287,9 +286,9 @@ class BreakNode(ElementaryNode): class ArgumentNode(BaseNode): def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) - self.arguments = [] # type: List[BaseNode] - self.commas = [] # type: List[Token[TV_TokenTypes]] - self.kwargs = {} # type: Dict[BaseNode, BaseNode] + 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: @@ -374,7 +373,7 @@ class NotNode(BaseNode): class CodeBlockNode(BaseNode): def __init__(self, token: Token[TV_TokenTypes]) -> None: super().__init__(token.lineno, token.colno, token.filename) - self.lines = [] # type: List[BaseNode] + self.lines = [] # type: T.List[BaseNode] class IndexNode(BaseNode): def __init__(self, iobject: BaseNode, index: BaseNode) -> None: @@ -412,9 +411,9 @@ class PlusAssignmentNode(BaseNode): self.value = value # type: BaseNode class ForeachClauseNode(BaseNode): - def __init__(self, token: Token, varnames: List[str], items: BaseNode, block: CodeBlockNode) -> None: + def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode) -> None: super().__init__(token.lineno, token.colno, token.filename) - self.varnames = varnames # type: List[str] + self.varnames = varnames # type: T.List[str] self.items = items # type: BaseNode self.block = block # type: CodeBlockNode @@ -427,8 +426,8 @@ class IfNode(BaseNode): class IfClauseNode(BaseNode): def __init__(self, linenode: BaseNode) -> None: super().__init__(linenode.lineno, linenode.colno, linenode.filename) - self.ifs = [] # type: List[IfNode] - self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: Union[EmptyNode, CodeBlockNode] + 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: Token, value: BaseNode): @@ -722,7 +721,7 @@ class Parser: self.expect('id') assert isinstance(t.value, str) varname = t - varnames = [t.value] # type: List[str] + varnames = [t.value] # type: T.List[str] if self.accept('comma'): t = self.current @@ -754,7 +753,7 @@ class Parser: b = self.codeblock() clause.ifs.append(IfNode(s, s, b)) - def elseblock(self) -> Optional[CodeBlockNode]: + def elseblock(self) -> T.Optional[CodeBlockNode]: if self.accept('else'): self.expect('eol') return self.codeblock() From d67888bf9b398b9ff9709d396569260f98971e8a Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Tue, 28 Jan 2020 20:57:07 +0100 Subject: [PATCH 8/8] types: Remove redundant __init__() -> None annotation --- mesonbuild/ast/interpreter.py | 2 +- mesonbuild/ast/introspection.py | 4 +-- mesonbuild/ast/postprocess.py | 6 ++-- mesonbuild/ast/printer.py | 2 +- mesonbuild/ast/visitor.py | 2 +- mesonbuild/interpreterbase.py | 8 ++--- mesonbuild/mesonlib.py | 4 +-- mesonbuild/mparser.py | 52 ++++++++++++++++----------------- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 2839f54df..25dfb8030 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -67,7 +67,7 @@ ADD_SOURCE = 0 REMOVE_SOURCE = 1 class AstInterpreter(interpreterbase.InterpreterBase): - def __init__(self, source_root: str, subdir: str, subproject: str, visitors: T.Optional[T.List[AstVisitor]] = None) -> None: + 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 = {} # type: T.Dict[str, bool] diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index d51b0994c..c9a51bf5a 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -30,7 +30,7 @@ build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'sha class IntrospectionHelper: # mimic an argparse namespace - def __init__(self, cross_file: str) -> None: + 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] @@ -46,7 +46,7 @@ class IntrospectionInterpreter(AstInterpreter): cross_file: T.Optional[str] = None, subproject: str = '', subproject_dir: str = 'subprojects', - env: T.Optional[environment.Environment] = None) -> None: + env: T.Optional[environment.Environment] = None): visitors = visitors if visitors is not None else [] super().__init__(source_root, subdir, subproject, visitors=visitors) diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py index 6d808be57..35fe1d317 100644 --- a/mesonbuild/ast/postprocess.py +++ b/mesonbuild/ast/postprocess.py @@ -20,7 +20,7 @@ from .. import mparser import typing as T class AstIndentationGenerator(AstVisitor): - def __init__(self) -> None: + def __init__(self): self.level = 0 def visit_default_func(self, node: mparser.BaseNode) -> None: @@ -76,7 +76,7 @@ class AstIndentationGenerator(AstVisitor): self.level -= 1 class AstIDGenerator(AstVisitor): - def __init__(self) -> None: + def __init__(self): self.counter = {} # type: T.Dict[str, int] def visit_default_func(self, node: mparser.BaseNode) -> None: @@ -87,7 +87,7 @@ class AstIDGenerator(AstVisitor): self.counter[name] += 1 class AstConditionLevel(AstVisitor): - def __init__(self) -> None: + def __init__(self): self.condition_level = 0 def visit_default_func(self, node: mparser.BaseNode) -> None: diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py index 06f3c6234..39e2ccae4 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -28,7 +28,7 @@ arithmic_map = { } class AstPrinter(AstVisitor): - def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5) -> None: + def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5): self.result = '' self.indent = indent self.arg_newline_cutoff = arg_newline_cutoff diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py index ec5101426..37be4632a 100644 --- a/mesonbuild/ast/visitor.py +++ b/mesonbuild/ast/visitor.py @@ -18,7 +18,7 @@ from .. import mparser class AstVisitor: - def __init__(self) -> None: + def __init__(self): pass def visit_default_func(self, node: mparser.BaseNode) -> None: diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index e3c2c6401..807113677 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -185,7 +185,7 @@ def disablerIfNotFound(f): class permittedKwargs: - def __init__(self, permitted: T.Set[str]) -> None: + def __init__(self, permitted: T.Set[str]): self.permitted = permitted # type: T.Set[str] def __call__(self, f): @@ -208,7 +208,7 @@ class FeatureCheckBase: # 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) -> None: + def __init__(self, feature_name: str, version: str): self.feature_name = feature_name # type: str self.feature_version = version # type: str @@ -293,7 +293,7 @@ class FeatureDeprecated(FeatureCheckBase): class FeatureCheckKwargsBase: - def __init__(self, feature_name: str, feature_version: str, kwargs: T.List[str]) -> None: + 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 @@ -375,7 +375,7 @@ def is_disabled(args, kwargs) -> bool: class InterpreterBase: elementary_types = (int, float, str, bool, list) - def __init__(self, source_root: str, subdir: str, subproject: str) -> None: + def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root 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] diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 854c934e3..2298c861b 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/mparser.py b/mesonbuild/mparser.py index d0e7525e3..8753b4038 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -79,7 +79,7 @@ class BlockParseException(MesonException): TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool) 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) -> None: + 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 @@ -94,7 +94,7 @@ class Token(T.Generic[TV_TokenTypes]): return self.tid == other.tid class Lexer: - def __init__(self, code: str) -> None: + def __init__(self, code: str): self.code = code self.keywords = {'true', 'false', 'if', 'else', 'elif', 'endif', 'and', 'or', 'not', 'foreach', 'endforeach', @@ -226,7 +226,7 @@ class Lexer: raise ParseException('lexer', self.getline(line_start), lineno, col) class BaseNode: - def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None) -> None: + 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 @@ -246,18 +246,18 @@ class BaseNode: func(self) class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode): - def __init__(self, token: Token[TV_TokenTypes]) -> None: + 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[bool]): - def __init__(self, token: Token[bool]) -> None: + def __init__(self, token: Token[bool]): super().__init__(token) assert isinstance(self.value, bool) class IdNode(ElementaryNode[str]): - def __init__(self, token: Token[str]) -> None: + def __init__(self, token: Token[str]): super().__init__(token) assert isinstance(self.value, str) @@ -265,12 +265,12 @@ class IdNode(ElementaryNode[str]): return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) class NumberNode(ElementaryNode[int]): - def __init__(self, token: Token[int]) -> None: + def __init__(self, token: Token[int]): super().__init__(token) assert isinstance(self.value, int) class StringNode(ElementaryNode[str]): - def __init__(self, token: Token[str]) -> None: + def __init__(self, token: Token[str]): super().__init__(token) assert isinstance(self.value, str) @@ -284,7 +284,7 @@ class BreakNode(ElementaryNode): pass class ArgumentNode(BaseNode): - def __init__(self, token: Token[TV_TokenTypes]) -> None: + 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]] @@ -325,64 +325,64 @@ class ArgumentNode(BaseNode): return self.num_args() # Fixme class ArrayNode(BaseNode): - def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int) -> None: + 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: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int) -> None: + 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: int, colno: int, filename: str) -> None: + def __init__(self, lineno: int, colno: int, filename: str): super().__init__(lineno, colno, filename) self.value = None class OrNode(BaseNode): - def __init__(self, left: BaseNode, right: BaseNode) -> None: + 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: BaseNode, right: BaseNode) -> None: + 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: str, left: BaseNode, right: BaseNode) -> None: + 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: str, left: BaseNode, right: BaseNode) -> None: + 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, token: Token[TV_TokenTypes], value: BaseNode) -> None: + 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, token: Token[TV_TokenTypes]) -> None: + 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: BaseNode, index: BaseNode) -> None: + 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: str, lineno: int, colno: int, source_object: BaseNode, name: str, args: ArgumentNode) -> None: + 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 @@ -390,28 +390,28 @@ class MethodNode(BaseNode): self.args = args # type: ArgumentNode class FunctionNode(BaseNode): - def __init__(self, filename: str, lineno: int, colno: int, end_lineno: int, end_colno: int, func_name: str, args: ArgumentNode) -> None: + 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 # type: ArgumentNode class AssignmentNode(BaseNode): - def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode) -> None: + 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 # type: BaseNode class PlusAssignmentNode(BaseNode): - def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode) -> None: + 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 # type: BaseNode class ForeachClauseNode(BaseNode): - def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode) -> None: + 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 @@ -424,7 +424,7 @@ class IfNode(BaseNode): self.block = block # type: CodeBlockNode class IfClauseNode(BaseNode): - def __init__(self, linenode: BaseNode) -> None: + 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] @@ -467,7 +467,7 @@ comparison_map = {'equal': '==', # 9 plain token class Parser: - def __init__(self, code: str, filename: str) -> None: + 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) # type: Token