Skip to content

Commit

Permalink
FeatureNew: add mypy type annotations for subproject arg
Browse files Browse the repository at this point in the history
Use a derived type when passing `subproject` around, so that mypy knows
it's actually a SubProject, not a str. This means that passing anything
other than a handle to the interpreter state's subproject attribute
becomes a type violation, specifically when the order of the *four*
different str arguments is typoed.
  • Loading branch information
eli-schwartz committed Feb 15, 2022
1 parent bab651a commit c0b8e02
Show file tree
Hide file tree
Showing 9 changed files with 28 additions and 12 deletions.
5 changes: 3 additions & 2 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from ._typing import ImmutableListProtocol, ImmutableSetProtocol
from .backend.backends import Backend, ExecutableSerialisation
from .interpreter.interpreter import Test, SourceOutputs, Interpreter
from .interpreterbase import SubProject
from .mesonlib import FileMode, FileOrString
from .modules import ModuleState
from .mparser import BaseNode
Expand Down Expand Up @@ -514,7 +515,7 @@ class Target(HoldableObject):

name: str
subdir: str
subproject: str
subproject: 'SubProject'
build_by_default: bool
for_machine: MachineChoice

Expand Down Expand Up @@ -675,7 +676,7 @@ class BuildTarget(Target):

install_dir: T.List[T.Union[str, bool]]

def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
def __init__(self, name: str, subdir: str, subproject: 'SubProject', for_machine: MachineChoice,
sources: T.List['SourceOutputs'], objects, environment: environment.Environment, kwargs):
super().__init__(name, subdir, subproject, True, for_machine)
unity_opt = environment.coredata.get_option(OptionKey('unity'))
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from . import kwargs
from .interpreter import Interpreter
from ..envconfig import MachineInfo
from ..interpreterbase import SubProject

from typing_extensions import TypedDict

Expand All @@ -40,7 +41,7 @@ class EnvironmentSeparatorKW(TypedDict):


def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired',
subproject: str,
subproject: 'SubProject',
feature_check: T.Optional[FeatureCheckBase] = None,
default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]:
val = kwargs.get('required', default)
Expand Down
5 changes: 4 additions & 1 deletion mesonbuild/interpreter/primitives/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
InvalidArguments,
)

if T.TYPE_CHECKING:
from ...interpreterbase import SubProject

class RangeHolder(MesonInterpreterObject, IterableObject):
def __init__(self, start: int, stop: int, step: int, *, subproject: str) -> None:
def __init__(self, start: int, stop: int, step: int, *, subproject: 'SubProject') -> None:
super().__init__(subproject=subproject)
self.range = range(start, stop, step)
self.operators.update({
Expand Down
4 changes: 4 additions & 0 deletions mesonbuild/interpreterbase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@

'InterpreterBase',

'SubProject',

'TV_fw_var',
'TV_fw_args',
'TV_fw_kwargs',
Expand Down Expand Up @@ -91,6 +93,8 @@
TYPE_key_resolver,
TYPE_HoldableTypes,

SubProject,

HoldableTypes,
)

Expand Down
6 changes: 4 additions & 2 deletions mesonbuild/interpreterbase/baseobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
TYPE_nkwargs = T.Dict[str, TYPE_nvar]
TYPE_key_resolver = T.Callable[[mparser.BaseNode], str]

SubProject = T.NewType('SubProject', str)

if T.TYPE_CHECKING:
from typing_extensions import Protocol
__T = T.TypeVar('__T', bound=TYPE_var, contravariant=True)
Expand All @@ -47,7 +49,7 @@ class OperatorCall(Protocol[__T]):
def __call__(self, other: __T) -> TYPE_var: ...

class InterpreterObject:
def __init__(self, *, subproject: T.Optional[str] = None) -> None:
def __init__(self, *, subproject: T.Optional['SubProject'] = None) -> None:
self.methods: T.Dict[
str,
T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var]
Expand All @@ -63,7 +65,7 @@ def __init__(self, *, subproject: T.Optional[str] = None) -> None:
# 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: mparser.BaseNode = None
self.subproject: str = subproject or ''
self.subproject = subproject or SubProject('')

# Some default operators supported by all objects
self.operators.update({
Expand Down
7 changes: 4 additions & 3 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
import typing as T
if T.TYPE_CHECKING:
from .. import mparser
from .interpreterbase import SubProject

def get_callee_args(wrapped_args: T.Sequence[T.Any]) -> T.Tuple['mparser.BaseNode', T.List['TYPE_var'], 'TYPE_kwargs', str]:
def get_callee_args(wrapped_args: T.Sequence[T.Any]) -> T.Tuple['mparser.BaseNode', T.List['TYPE_var'], 'TYPE_kwargs', 'SubProject']:
# First argument could be InterpreterBase, InterpreterObject or ModuleObject.
# In the case of a ModuleObject it is the 2nd argument (ModuleState) that
# contains the needed information.
Expand Down Expand Up @@ -600,7 +601,7 @@ def get_target_version(subproject: str) -> str:
def check_version(target_version: str, feature_version: str) -> bool:
pass

def use(self, subproject: str) -> None:
def use(self, subproject: 'SubProject') -> None:
tv = self.get_target_version(subproject)
# No target version
if tv == '':
Expand Down Expand Up @@ -668,7 +669,7 @@ def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
return T.cast(TV_func, wrapped)

@classmethod
def single_use(cls, feature_name: str, version: str, subproject: str,
def single_use(cls, feature_name: str, version: str, subproject: 'SubProject',
extra_message: str = '', location: T.Optional['mparser.BaseNode'] = None) -> None:
"""Oneline version that instantiates and calls use()."""
cls(feature_name, version, extra_message, location).use(subproject)
Expand Down
4 changes: 3 additions & 1 deletion mesonbuild/interpreterbase/interpreterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
ObjectHolder,
IterableObject,

SubProject,

TYPE_var,
TYPE_kwargs,

Expand Down Expand Up @@ -73,7 +75,7 @@
]

class InterpreterBase:
def __init__(self, source_root: str, subdir: str, subproject: str):
def __init__(self, source_root: str, subdir: str, subproject: 'SubProject'):
self.source_root = source_root
self.funcs: FunctionType = {}
self.builtin: T.Dict[str, InterpreterObject] = {}
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/optinterpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo, permittedKwargs
if T.TYPE_CHECKING:
from .interpreterbase import TYPE_var, TYPE_kwargs
from .interpreterbase import SubProject
from typing_extensions import TypedDict
FuncOptionArgs = TypedDict('FuncOptionArgs', {
'type': str,
Expand Down Expand Up @@ -50,7 +51,7 @@ class OptionException(mesonlib.MesonException):


class OptionInterpreter:
def __init__(self, subproject: str) -> None:
def __init__(self, subproject: 'SubProject') -> None:
self.options: 'coredata.KeyedOptionDictType' = {}
self.subproject = subproject
self.option_types = {'string': self.string_parser,
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/wrap/wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .. import coredata
from ..mesonlib import quiet_git, GIT, ProgressBar, MesonException, windows_proof_rmtree
from ..interpreterbase import FeatureNew
from ..interpreterbase import SubProject
from .. import mesonlib

if T.TYPE_CHECKING:
Expand Down Expand Up @@ -95,7 +96,7 @@ class WrapNotFoundException(WrapException):
class PackageDefinition:
def __init__(self, fname: str, subproject: str = ''):
self.filename = fname
self.subproject = subproject
self.subproject = SubProject(subproject)
self.type = None # type: T.Optional[str]
self.values = {} # type: T.Dict[str, str]
self.provided_deps = {} # type: T.Dict[str, T.Optional[str]]
Expand Down

0 comments on commit c0b8e02

Please sign in to comment.