Skip to content

type safe option value getters#15678

Draft
dcbaker wants to merge 22 commits intomesonbuild:masterfrom
dcbaker:submit/type-safe-option-getters
Draft

type safe option value getters#15678
dcbaker wants to merge 22 commits intomesonbuild:masterfrom
dcbaker:submit/type-safe-option-getters

Conversation

@dcbaker
Copy link
Copy Markdown
Member

@dcbaker dcbaker commented Apr 3, 2026

This adds a couple of methods to the OptionStore to work with options value in a type safe manner. This works by putting the type in the getter itself, this avoids the need for the pattern of:

opt = optstore.get_value_for('b_foo')
assert isinstance(opt, str), 'for mypy'

While also giving better error messages. Additionally, I've added a default= parameter, to allow not erroring when an options isn't available (a per-platform or per-compiler option). The end result is the following:

  • get_value_for a type safe getter for a system value
  • get_value_for_untyped a type unsafe getter for system values, works like get_value_for in current main
  • get_value_for_target a type safe getter for an option that could be overwritten by a target
  • get_value_for_target_untyped a type unsafe getter for an option that could be overwritten by a target, same as get_value_for_target in current main branch
  • get_value_for_maybe_target a type safe getter that allows a target to be optional. This is a bit ugly, but I don't see a better way to handle this without duplicating code in a way that would lead to bugs.

this allows the following patterns to be eliminated:

opt = optstore.get_value_for_untyped(OptionKey('b_foo'))
assert isinstance(opt, str), 'for mypy'

with

opt = opstore.get_value_for(OptionKey('b_foo'), str)

(and the same for targets)

and

key = OptionKey('b_foo')
if target is not None:
    opt = opstore.get_value_for_target(target, key, str)
else:
    opt = opstore.get_value_for(key, str)

with

opt = optstore.get_value_for_maybe_target(target, OptionKey('b_foo'), str)

and

try:
    opt = opstore.get_value_for(OptionKey('b_foo'), bool)
except KeyError:
    opt = False

with

opt = optstore.get_value_for(OptionKey('b_foo'), bool, default=False)

This has been set to draft as I'm sure there will be some issues with Windows and the VS backend that need to be resolved.

dcbaker added 22 commits April 6, 2026 09:03
There are some missing values here, and some places where we are casting
back to `dict[str, Any]` because of this. Let's fix the annotations
Compilers and Linkers already have an Environment reference, so passing
them in complicates the API needlessly.
This leaves a wrapper in the coredata struct, which will allow us to
have one big mechanical change in the next commit
This is mostly generated through search and replace, but with the
wrapper in CoreData removed manually
Leaves a wrapper in place that will be removed later
These provide unconstrained ElementryOptionValues variants. The next
patches will provide typed versions using the normal name, that do
automatic type validation
These use a TypeVar to constrain the type, and then do a runtime check
to ensure that the option is of the correct type.

One has not been added for `get_option_and_value_for` because we need to
further constrain the types via overloads, but I can't figure out how to
make the type checker happy with it.
This allows use to replace patterns like:
```python
if key in opstore and opstore.get_value_for(...)
```
with
```python
if opstore.get_value_for(..., default=...)
```
This replaces most of the uses of the unsafe get_value_for, with a
couple of exceptions:

- There is code in the Compiler that does some interesting BuildTarget |
  None checking that should be wrapped
- the `install_umask` option cannot use this helper due to it's variant
  type
- There's a wrapper in the Module code that needs to be replaced
There are still a few uses of the unsafe variant, manly around compilers
and some wrappers that need to be removed. Additionally, this work
allows some cleanups to remove try/except blocks
These are allowed to take None for target. This is the only case where
the subproject argument is not `target.subproject`. So, to prevent
issues where the subproject doesn't match, I've used `typing.overload`
to create two signatures. The subproject is only allowed in the case
where the target is None, and not in the case where Target is defined.
This allows the type checker to enforce that you must have a subproject
without a target, and may not have one with.
There is some added complexity because the langauge of an option was
merged back into the name of the key instead of being a separate field.

This method was basically doing the same thing as
get_option_for_maybe_target, plus building a key. Some of the uses
didn't make sense, as we knew we didn't have a target and just wanted a
global value, plus we lost our type checking.
It was just another layer of indirection, and doesn't provide type
safety
This is just a wrapper around `get_value_for_target` with `default=False`.
@dcbaker dcbaker force-pushed the submit/type-safe-option-getters branch from baed5ae to 12bbf92 Compare April 6, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant