interpreter: Add a default() function

This gives a way to conditionally override a keyword argument, such as
when setting a value specifically for one platform. For example,
```meson
library(
  ...,
  name_prefix : host_machine.system() == 'windows' ? '' : default(),
)
```
This commit is contained in:
Dylan Baker authored and Paolo Bonzini committed 2026-07-04 19:04:10 +02:00
1 parent 15231ceccd
commit e4b644b9c7
12 files changed
+126 -4

No files matched your search

@@ -86,6 +86,7 @@ syn keyword mesonBuiltin
\ custom_target
\ debug
\ declare_dependency
\ default
\ dependency
\ disabler
\ environment
+13
View File
@@ -0,0 +1,13 @@
## Add a new `default()` function and object
This function and object allow setting a keyword argument to it's default value.
This is especially useful for cases when a non-default value is wanted in some
cases but not all.
For example:
```meson
library(
...
name_prefix : host_machine.system() == 'windows' ? '' : default(),
)
```
+4
View File
@@ -0,0 +1,4 @@
name: default
returns: default
description: Returns a [[@default]] object.
since: 1.12.0
+5
View File
@@ -0,0 +1,5 @@
name: default
long_name: Default
description: |
A default object can be passed to keyword arguments to get the default value.
+1
View File
@@ -242,6 +242,7 @@ class AstInterpreter(InterpreterBase):
'is_disabler': self.func_do_nothing,
'is_variable': self.func_do_nothing,
'disabler': self.func_do_nothing,
'default': self.func_do_nothing,
'jar': self.func_do_nothing,
'warning': self.func_do_nothing,
'shared_module': self.func_do_nothing,
+10 -3
View File
@@ -32,7 +32,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 ObjectHolder, ContextManagerObject
from ..interpreterbase import ObjectHolder, ContextManagerObject, DefaultObject
from ..interpreterbase import stringifyUserArguments, Feature, FeatureValue
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule, __path__ as modules_path
from ..optinterpreter import optname_regex
@@ -390,6 +390,7 @@ class Interpreter(InterpreterBase, HoldableObject):
'declare_dependency': self.func_declare_dependency,
'dependency': self.func_dependency,
'disabler': self.func_disabler,
'default': self.func_default,
'environment': self.func_environment,
'error': self.func_error,
'executable': self.func_executable,
@@ -1972,6 +1973,12 @@ class Interpreter(InterpreterBase, HoldableObject):
f.use(self.subproject, node)
return d
@FeatureNew('default', '1.12.0')
@noKwargs
@noPosargs
def func_default(self, node: mparser.BaseNode, args: list[TYPE_var], kwargs: TYPE_kwargs) -> DefaultObject:
return DefaultObject()
@FeatureNew('disabler', '0.44.0')
@noKwargs
@noPosargs
@@ -4056,7 +4063,7 @@ class Interpreter(InterpreterBase, HoldableObject):
def is_subproject(self) -> bool:
return self.subproject != ''
@typed_pos_args('set_variable', str, object)
@typed_pos_args('set_variable', str, (object, DefaultObject))
@noKwargs
@noArgsFlattening
@noSecondLevelHolderResolving
@@ -4066,7 +4073,7 @@ class Interpreter(InterpreterBase, HoldableObject):
raise InvalidCode('Invalid variable name: ' + varname)
self.set_variable(varname, value, holderify=True)
@typed_pos_args('get_variable', (str, Disabler), optargs=[object])
@typed_pos_args('get_variable', (str, Disabler, DefaultObject), optargs=[object])
@noKwargs
@noArgsFlattening
@unholder_return
+2
View File
@@ -8,6 +8,7 @@ __all__ = [
'IterableObject',
'MutableInterpreterObject',
'ContextManagerObject',
'DefaultObject',
'MesonOperator',
@@ -70,6 +71,7 @@ from .baseobjects import (
IterableObject,
MutableInterpreterObject,
ContextManagerObject,
DefaultObject,
TV_func,
TYPE_elementary,
@@ -233,3 +233,7 @@ class IterableObject(metaclass=SimpleABC):
class ContextManagerObject(MesonInterpreterObject, AbstractContextManager):
def __init__(self, subproject: 'SubProject') -> None:
super().__init__(subproject=subproject)
class DefaultObject(MesonInterpreterObject):
"""A default value to a keyword argument."""
+23
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
from .. import coredata, mesonlib, mlog
from .disabler import Disabler
from .baseobjects import DefaultObject
from .exceptions import InterpreterException, InvalidArguments
from ._unholder import _unholder
@@ -216,6 +217,7 @@ def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
num_args = len(args)
num_types = len(types)
a_types = types
last_pos = num_args
if varargs:
min_args = num_types + min_varargs
@@ -238,6 +240,18 @@ def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
for i, (arg, type_) in enumerate(itertools.zip_longest(args, a_types, fillvalue=varargs), start=1):
if not isinstance(arg, type_):
# if DefaultObject is an explicit allowed allowed type allow
# it through.
if isinstance(arg, DefaultObject):
if i >= last_pos:
if varargs:
msg = 'not allowed for variadic arguments'
else:
msg = 'not allowed for optional positional arguments'
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:
@@ -565,6 +579,15 @@ def typed_kwargs(name: str, *types: KwargInfo, allow_unknown: bool = False) -> T
for info in types:
types_tuple = info.types if isinstance(info.types, tuple) else (info.types,)
value = kwargs.get(info.name)
if isinstance(value, DefaultObject):
# Ensure that default() is not used for required options
# Otherwise, set the value to None, which will send us down
# the "unset" path
if info.required:
raise InvalidArguments(f'"{name}" got a default() value for the required keyword argument "{info.name}". '
'default() may not be used for required keyword arguments.')
value = None
if value is not None:
if info.since:
feature_name = info.name + ' arg in ' + name
@@ -14,6 +14,7 @@ from .baseobjects import (
ObjectHolder,
IterableObject,
ContextManagerObject,
DefaultObject,
HoldableTypes,
)
@@ -612,7 +613,12 @@ class InterpreterBase:
def expand_default_kwargs(self, kwargs: T.Dict[str, T.Optional[InterpreterObject]]) -> T.Dict[str, T.Optional[InterpreterObject]]:
if 'kwargs' not in kwargs:
return kwargs
to_expand = _unholder(kwargs.pop('kwargs'))
kwargs_var = kwargs.pop('kwargs')
if isinstance(kwargs_var, DefaultObject):
return kwargs
to_expand = _unholder(kwargs_var)
if not isinstance(to_expand, dict):
raise InterpreterException('Value of "kwargs" must be dictionary.')
if 'kwargs' in to_expand:
+48
View File
@@ -0,0 +1,48 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2026 Intel Corporation
project(
'default',
'cpp',
meson_version : '>= 1.12.0',
default_options : {'cpp_std' : 'c++14'},
)
exe = executable(
'prog',
'prog.cpp',
override_options : default(),
)
exe2 = executable(
'prog2',
'prog.cpp',
kwargs : default(),
)
test_kwargs = {
'protocol' : default(),
}
test('exe_test', exe, kwargs : test_kwargs)
testcase expect_error('default() objects are not allowed for required positional arguments')
executable(default(), 'prog.cpp')
endtestcase
testcase expect_error('default() objects are not allowed for variadic arguments')
add_languages(default())
endtestcase
testcase expect_error('default() objects are not allowed for optional positional arguments')
assert(true, default())
endtestcase
testcase expect_error('"install_symlink" got a default() value for the required keyword argument "install_dir". default() may not be used for required keyword arguments.')
install_symlink('foo', pointing_to : '/foo', install_dir : default())
endtestcase
foo = default()
get_variable('foo')
unset_variable('foo')
set_variable('var', default())
+8
View File
@@ -0,0 +1,8 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright © 2026 Intel Corporation
*/
int main() {
return __cplusplus == 201402L ? 0 : 1;
}