Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
471facd
interpreter: Replace last uses of Feature*Kwargs
dcbaker Jul 13, 2026
57b44b9
interpreterbase/decorators: pull some helpers out of typed_kwargs
dcbaker Jul 21, 2026
85201f6
interpreterbase/decorators: share code between pos and kwarg checking
dcbaker Jul 21, 2026
7e4308f
decorators: share error formatting code between pos and kw arg checking
dcbaker Jul 22, 2026
407cd7a
decorators: normalize quoting of types in errors
dcbaker Jul 22, 2026
4b3dd8b
decorators: make the quoting of types fully standard
dcbaker Jul 22, 2026
263c844
decorators: consistently quote the name of functions in errors
dcbaker Jul 22, 2026
0a9ca80
decorators: Convert KwargInfo to a dataclass
dcbaker Jul 22, 2026
252fd63
decorators: use some `TypeAlias`es to make further changes easier
dcbaker Jul 22, 2026
6ad1137
decorators: allow tuples of types in KWargInfo since_values and depre…
dcbaker Jul 22, 2026
ed0ad23
modules/cmake: Use KwargInfo since_values instead of open coding
dcbaker Jul 22, 2026
a4c81a7
modules/qt: Use KWargInfo.since_values
dcbaker Jul 22, 2026
86248c5
modules/qt: Use typed_pos_args for qt.preprocess
dcbaker Jul 22, 2026
0ded72e
modules/sourceset: Use KwargInfo.since_values for new types
dcbaker Jul 22, 2026
08187b3
interpreterobjects: move pkgconfig_define FeatureNew to KWargInfo
dcbaker Jul 22, 2026
6f71961
interpreter: Use KWargInfo for some test arguments
dcbaker Jul 22, 2026
d6ea643
interpreter/type_checking: consistently quote in_set_validator errors
dcbaker Jul 23, 2026
6821d80
interpreter/type_checking: quote unknown langauge validator error
dcbaker Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions mesonbuild/interpreter/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from ..compilers import SUFFIX_TO_LANG, RunResult
from ..compilers.compilers import CompileCheckMode
from ..interpreterbase import (ObjectHolder, noPosargs, noKwargs,
FeatureNew, FeatureNewKwargs, disablerIfNotFound,
FeatureNew, disablerIfNotFound,
InterpreterException, InterpreterObject)
from ..interpreterbase.decorators import ContainerTypeInfo, typed_kwargs, KwargInfo, typed_pos_args
from ..options import OptionKey
Expand Down Expand Up @@ -881,12 +881,18 @@ def get_argument_syntax_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwa
return self.compiler.get_argument_syntax()

@FeatureNew('compiler.preprocess', '0.64.0')
@FeatureNewKwargs('compiler.preprocess', '1.3.2', ['compile_args'], extra_message='compile_args were ignored before this version')
@typed_pos_args('compiler.preprocess', varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), min_varargs=1)
@typed_kwargs(
'compiler.preprocess',
KwargInfo('output', str, default='@PLAINNAME@.i'),
KwargInfo('compile_args', ContainerTypeInfo(list, str), listify=True, default=[]),
KwargInfo(
'compile_args',
ContainerTypeInfo(list, str),
listify=True,
default=[],
since='1.3.2',
since_message='compile_args were ignored before this version',
),
_INCLUDE_DIRECTORIES_KW,
_DEPENDENCIES_KW.evolve(since='1.1.0'),
_DEPENDS_KW.evolve(since='1.4.0'),
Expand Down
3 changes: 1 addition & 2 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .decorators import apply_machine_map
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken, FeatureNewKwargs
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken
from ..interpreterbase import ObjectHolder, ContextManagerObject, DefaultObject
from ..interpreterbase import stringifyUserArguments, Feature, FeatureValue
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule, __path__ as modules_path
Expand Down Expand Up @@ -2037,7 +2037,6 @@ def func_jar(self, node: mparser.BaseNode,
kwargs: kwtypes.Jar) -> build.Jar:
return self.build_target(node, T.cast('tuple[str, SourcesVarargsType]', args), kwargs, build.Jar)

@FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
@typed_pos_args('build_target', str, varargs=SOURCES_VARARGS)
@typed_kwargs('build_target', *BUILD_TARGET_KWS)
def func_build_target(self, node: mparser.BaseNode,
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ def _bt_install_dir_deprecated(args: T.List[T.Union[str, bool]]) -> T.Iterator[F

# Applies to all build_target like classes
_ALL_TARGET_KWS: T.List[KwargInfo] = [
OVERRIDE_OPTIONS_KW,
OVERRIDE_OPTIONS_KW.evolve(since='0.40.0'),
KwargInfo('build_by_default', bool, default=True, since='0.38.0'),
DEPENDENCIES_KW,
KwargInfo(
Expand Down Expand Up @@ -784,6 +784,7 @@ def _pch_convertor(args: T.List[str]) -> T.Optional[T.Tuple[str, T.Optional[str]
INCLUDE_DIRECTORIES.evolve(name='d_import_dirs'),
LINK_ARGS_KW,
LINK_WHOLE_KW.evolve(
since='0.40.0',
as_default=[('', ('1.11.0', "Replace an empty string with an empty array: `link_whole : ''` -> `link_whole : []`"))],
),
_NAME_PREFIX_KW,
Expand Down
4 changes: 0 additions & 4 deletions mesonbuild/interpreterbase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@
'FeatureNew',
'FeatureDeprecated',
'FeatureBroken',
'FeatureNewKwargs',
'FeatureDeprecatedKwargs',

'InterpreterBase',

Expand Down Expand Up @@ -104,8 +102,6 @@
FeatureNew,
FeatureDeprecated,
FeatureBroken,
FeatureNewKwargs,
FeatureDeprecatedKwargs,
)

from .exceptions import (
Expand Down
141 changes: 58 additions & 83 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,55 @@ def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
return inner


def _types_description(types: tuple[type | ContainerTypeInfo, ...] | type | ContainerTypeInfo) -> str:
candidates: list[str] = []
types_tuple = types if isinstance(types, tuple) else (types, )
for t in types_tuple:
if isinstance(t, ContainerTypeInfo):
candidates.append(t.description())
else:
candidates.append(t.__name__)
shouldbe = 'one of: ' if len(candidates) > 1 else ''
shouldbe += ', '.join(f'"{c}"' for c in candidates)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, the quotes are coming back here...

return shouldbe


def _raw_description(t: object) -> str:
"""describe a raw type (ie, one that is not a ContainerTypeInfo)."""
if isinstance(t, list):
if t:
return f"array[{' | '.join(sorted(mesonlib.OrderedSet(type(v).__name__ for v in t)))}]"
return 'array[]'
elif isinstance(t, dict):
if t:
return f"dict[{' | '.join(sorted(mesonlib.OrderedSet(type(v).__name__ for v in t.values())))}]"
return 'dict[]'
return type(t).__name__


def _check_value_type(types: tuple[type | ContainerTypeInfo, ...] | type | ContainerTypeInfo,
value: T.Any) -> bool:
types_tuple = types if isinstance(types, tuple) else (types, )
for t in types_tuple:
if isinstance(t, ContainerTypeInfo):
if t.check(value):
return True
elif isinstance(value, t):
return True
return False


def _shouldbe_format(name: str, argument_type: T.Literal['positional', 'keyword'],
argument_name: str, argument: object,
types: tuple[type | ContainerTypeInfo, ...] | type | ContainerTypeInfo,
extra: str | None = None) -> str:
should_be = _types_description(types)
if extra:
should_be = f'{should_be}. {extra}'
return (f'{name} {argument_type} argument "{argument_name}" was of type '
f'"{_raw_description(argument)}" but should have been {should_be}')


def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,
optargs: T.Optional[T.List[T.Union[T.Type, T.Tuple[T.Type, ...]]]] = None,
Expand Down Expand Up @@ -251,12 +300,7 @@ def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
else:
msg = 'not allowed for required positional arguments'
raise InvalidArguments(f'default() objects are {msg}')

if isinstance(type_, tuple):
shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in type_))
else:
shouldbe = f'"{type_.__name__}"'
raise InvalidArguments(f'{name} argument {i} was of type "{type(arg).__name__}" but should have been {shouldbe}')
raise InvalidArguments(_shouldbe_format(name, 'positional', str(i), arg, type_))

# Ensure that we're actually passing a tuple.
# Depending on what kind of function we're calling the length of
Expand Down Expand Up @@ -504,39 +548,6 @@ def typed_kwargs(name: str, *types: KwargInfo, allow_unknown: bool = False) -> T
"""
def inner(f: TV_func) -> TV_func:

def types_description(types_tuple: T.Tuple[T.Union[T.Type, ContainerTypeInfo], ...]) -> str:
candidates = []
for t in types_tuple:
if isinstance(t, ContainerTypeInfo):
candidates.append(t.description())
else:
candidates.append(t.__name__)
shouldbe = 'one of: ' if len(candidates) > 1 else ''
shouldbe += ', '.join(candidates)
return shouldbe

def raw_description(t: object) -> str:
"""describe a raw type (ie, one that is not a ContainerTypeInfo)."""
if isinstance(t, list):
if t:
return f"array[{' | '.join(sorted(mesonlib.OrderedSet(type(v).__name__ for v in t)))}]"
return 'array[]'
elif isinstance(t, dict):
if t:
return f"dict[{' | '.join(sorted(mesonlib.OrderedSet(type(v).__name__ for v in t.values())))}]"
return 'dict[]'
return type(t).__name__

def check_value_type(types_tuple: T.Tuple[T.Union[T.Type, ContainerTypeInfo], ...],
value: T.Any) -> bool:
for t in types_tuple:
if isinstance(t, ContainerTypeInfo):
if t.check(value):
return True
elif isinstance(value, t):
return True
return False

@wraps(f)
def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:

Expand Down Expand Up @@ -589,6 +600,7 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
value = None

if value is not None:
extra: str | None
if info.since:
feature_name = info.name + ' arg in ' + name
FeatureNew.single_use(feature_name, info.since, subproject, info.since_message, location=node)
Expand All @@ -606,9 +618,10 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
value = copy.copy(info.default)
if info.listify:
kwargs[info.name] = value = mesonlib.listify(value)
if not check_value_type(types_tuple, value):
extra_desc: T.List[str] = []
if not _check_value_type(types_tuple, value):
extra = None
if info.extra_types:
extra_desc: T.List[str] = []
if isinstance(value, list):
for (t, cb), v in itertools.product(info.extra_types.items(), value):
if isinstance(v, t):
Expand All @@ -617,11 +630,10 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
for t, cb in info.extra_types.items():
if isinstance(value, t):
extra_desc.append(cb(value))
extra = '. '.join(extra_desc)

shouldbe = types_description(types_tuple)
if extra_desc:
shouldbe = '{}. {}'.format(shouldbe, '. '.join(extra_desc))
raise InvalidArguments(f'{name} keyword argument {info.name!r} was of type {raw_description(value)} but should have been {shouldbe}')
raise InvalidArguments(
_shouldbe_format(name, 'keyword', info.name, value, types_tuple, extra))

if info.validator is not None:
msg = info.validator(value)
Expand All @@ -643,7 +655,7 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
else:
# set the value to the default, this ensuring all kwargs are present
# This both simplifies the typing checking and the usage
assert check_value_type(types_tuple, info.default), f'In function {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {types_description(types_tuple)}'
assert _check_value_type(types_tuple, info.default), f'In function {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {_types_description(types_tuple)}'
# Create a shallow copy of the container. This allows mutable
# types to be used safely as default values
kwargs[info.name] = copy.copy(info.default)
Expand Down Expand Up @@ -883,40 +895,3 @@ def log_usage_warning(self, tv: MesonVersionTarget, location: T.Optional['mparse
if self.extra_message:
args.append(self.extra_message)
mlog.deprecation(*args, location=location)


# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374
class FeatureCheckKwargsBase(metaclass=mesonlib.SimpleABC):

@property
@abc.abstractmethod
def feature_check_class(self) -> T.Type[FeatureCheckBase]:
pass

def __init__(self, feature_name: str, feature_version: str,
kwargs: T.List[str], extra_message: T.Optional[str] = None):
self.feature_name = feature_name
self.feature_version = feature_version
self.kwargs = kwargs
self.extra_message = extra_message

def __call__(self, f: TV_func) -> TV_func:
@wraps(f)
def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
node, _, kwargs, subproject = get_callee_args(wrapped_args)
if subproject is None:
raise AssertionError(f'{wrapped_args!r}')
for arg in self.kwargs:
if arg not in kwargs:
continue
name = arg + ' arg in ' + self.feature_name
self.feature_check_class.single_use(
name, self.feature_version, subproject, self.extra_message, node)
return f(*wrapped_args, **wrapped_kwargs)
return T.cast('TV_func', wrapped)

class FeatureNewKwargs(FeatureCheckKwargsBase):
feature_check_class = FeatureNew

class FeatureDeprecatedKwargs(FeatureCheckKwargsBase):
feature_check_class = FeatureDeprecated
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ if not ['pgi', 'msvc', 'clang-cl', 'intel-cl'].contains(cc.get_id())
assert(not cc.has_function_attribute(a, required: opt))
endif

testcase expect_error('''compiler.has_function keyword argument 'required' was of type str but should have been one of: bool, Feature''')
testcase expect_error('''compiler.has_function keyword argument "required" was of type "str" but should have been one of: bool, Feature''')
cc.has_function('printf', required : 'not a bool')
endtestcase

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if meson.can_run_host_binaries()
assert(res.stderr() == '')
endif

testcase expect_error('''compiler.compiles keyword argument 'required' was of type str but should have been one of: bool, Feature''')
testcase expect_error('''compiler.compiles keyword argument "required" was of type "str" but should have been one of: bool, Feature''')
cc.compiles(valid, name: 'valid', required : 'not a bool')
endtestcase

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
project('test', 'c')

testcase expect_error('executable keyword argument \'dependencies\' was of type array[SubprojectHolder] but should have been array[Dependency | InternalDependency]')
testcase expect_error('executable keyword argument "dependencies" was of type "array[SubprojectHolder]" but should have been array[Dependency | InternalDependency]')
executable('main', 'main.c', dependencies: subproject('sub'))
endtestcase

lib = static_library('lib', 'lib.c')

testcase expect_error('executable keyword argument \'dependencies\' was of type array[StaticLibrary] but should have been array[Dependency | InternalDependency]. Tried to use a build_target "lib" as a dependency. This should be in `link_with` or `link_whole` instead.')
testcase expect_error('executable keyword argument "dependencies" was of type "array[StaticLibrary]" but should have been array[Dependency | InternalDependency]. Tried to use a build_target "lib" as a dependency. This should be in `link_with` or `link_whole` instead.')
executable('main', 'main.c', dependencies : lib)
endtestcase
4 changes: 2 additions & 2 deletions test cases/common/80 declare dep/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ test('dep', exe)
executable('dummy', 'main.c',
dependencies : [entity_dep, []])

testcase expect_error('declare_dependency keyword argument \'link_with\' was of type array[InternalDependency] but should have been array[BothLibraries | SharedLibrary | StaticLibrary | CustomTarget | CustomTargetIndex | Jar | Executable]. Dependency and external_library objects must go in the "dependencies" keyword argument')
testcase expect_error('declare_dependency keyword argument "link_with" was of type "array[InternalDependency]" but should have been array[BothLibraries | SharedLibrary | StaticLibrary | CustomTarget | CustomTargetIndex | Jar | Executable]. Dependency and external_library objects must go in the "dependencies" keyword argument')
declare_dependency(link_with : entity_dep)
endtestcase

testcase expect_error('declare_dependency keyword argument \'link_whole\' was of type array[InternalDependency] but should have been array[BothLibraries | StaticLibrary | CustomTarget | CustomTargetIndex]. Dependency and external_library objects must go in the "dependencies" keyword argument')
testcase expect_error('declare_dependency keyword argument "link_whole" was of type "array[InternalDependency]" but should have been array[BothLibraries | StaticLibrary | CustomTarget | CustomTargetIndex]. Dependency and external_library objects must go in the "dependencies" keyword argument')
declare_dependency(link_whole : entity_dep)
endtestcase

Expand Down
2 changes: 1 addition & 1 deletion test cases/failing/110 run_target in test/test.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/110 run_target in test/meson.build:7:0: ERROR: test keyword argument 'args' was of type array[RunTarget] but should have been array[str | File | BuildTarget | CustomTarget | CustomTargetIndex | Program]"
"line": "test cases/failing/110 run_target in test/meson.build:7:0: ERROR: test keyword argument \"args\" was of type \"array[RunTarget]\" but should have been \"array[str | File | BuildTarget | CustomTarget | CustomTargetIndex | Program]\""
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/111 run_target in add_install_script/meson.build:7:6: ERROR: meson.add_install_script argument 2 was of type \"RunTarget\" but should have been one of: \"str\", \"File\", \"BuildTarget\", \"CustomTarget\", \"CustomTargetIndex\", \"Program\""
"line": "test cases/failing/111 run_target in add_install_script/meson.build:7:6: ERROR: meson.add_install_script positional argument \"2\" was of type \"RunTarget\" but should have been one of: \"str\", \"File\", \"BuildTarget\", \"CustomTarget\", \"CustomTargetIndex\", \"Program\""
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion test cases/failing/59 string as link target/test.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/59 string as link target/meson.build:2:0: ERROR: executable keyword argument 'link_with' was of type array[str] but should have been array[BothLibraries | SharedLibrary | StaticLibrary | CustomTarget | CustomTargetIndex | Jar | Executable]"
"line": "test cases/failing/59 string as link target/meson.build:2:0: ERROR: executable keyword argument \"link_with\" was of type \"array[str]\" but should have been \"array[BothLibraries | SharedLibrary | StaticLibrary | CustomTarget | CustomTargetIndex | Jar | Executable]\""
}
]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/88 custom target install data/meson.build:11:0: ERROR: install_data argument 1 was of type \"CustomTarget\" but should have been one of: \"str\", \"File\""
"line": "test cases/failing/88 custom target install data/meson.build:11:0: ERROR: install_data positional argument \"1\" was of type \"CustomTarget\" but should have been one of: \"str\", \"File\""
}
]
}
Loading