In commit eaf365cb3e we explicitly sorted
them for neatness, with the rationale that we were restoring intentional
behavior and we only need a set for stylistic purposes.
This actually wasn't true, because we never sorted them to begin with
(we did sort the version numbers), but sorting them is fine. The bigger
issue is that we actually used a set to avoid printing the same feature
type multiple times. Now we do print them multiple times -- because each
registered feature includes the unique node.
Fix this by using both sorted and a set.
Fix tests that should in retrospect have flagged this as an issue, but
were added later on in the same series to check something else entirely,
happen to cover this too, and were presumably copied directly from
stdout as-is...
Currently in our deprecated/new feature printing we carefully sort all
of the values, then put them in a set to print them. Which unsorts them.
I'm assuming this was done because a set looks nice when printed (which
is true). Let's keep the formatting, but print them in a stable order.
Some functions cannot be fully type checked, because our API allows
fully arbitrary kwargs and treats them as data to pass through to the
underlying feature. For example, hotdoc command line arguments.
This change allows us to type check some kwargs with known types and
possibly required status, and make their values consistent(ly defaultable),
while preserving the optional nature of the additional kwargs.
Given a kwarg value that is itself a dict/list, we would only check if
the since_values was present within that container. If the since_values
is itself the dict or list type, then we aren't looking for recursive
structures, we just want to know when the function began to accept that
type.
This is relevant in cases where a function accepted a dict, and at one
point began accepting a list (of strings in the form 'key=value'), or
vice versa.
"targetting" is verb-derived adjective, which sort-of-works here, but
makes the whole sentence awkward, because there's no verb. Let's just
use present simple.
"tried to use" implies that the attempt was not successful, i.e. that meson
ignored the feature. But that is not what happens, apart from the warning the
feature works just fine. The new message is also shorter ;)
These are only used for type checking, so don't bother importing them at
runtime.
Generally add future annotations at the same time, to make sure that
existing uses of these imports don't need to be quoted.
Using future annotations, type annotations become strings at runtime and
don't impact performance. This is not possible to do with T.cast though,
because it is a function argument instead of an annotation.
Quote the type argument everywhere in order to have the same effect as
future annotations. This also allows linters to better detect in some
cases that a given import is typing-only.
A bunch of files have several T.TYPE_CHECKING blocks that each do some
things which could just as well be done once, with a single `if`
statement. Make them do so.
The point of a .use() function is because we don't always have the
information we need to use a feature check, so we allow creating the
feature and then storing it for later use. When implementing location
checks, although it is optional, actually using it violated that design.
Move the location out of the init method for FeatureCheck itself. It
remains compatible with all cases of .single_use(), but fix the rest up.
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.
In some cases, init variables that accept None as a sentinel and
immediately overwrite with [], are migrated to dataclass field
factories. \o/
Note: dataclasses by default cannot provide eq methods, as they then
become unhashable. In the future we may wish to opt into declaring them
frozen, instead/additionally.
FeatureCheck always immediately sets extra_message to '' if it isn't
explicitly passed, so there is really no point in using None as a
sentinel that is never used.
Names used in init functions are sometimes pointlessly different from
the class instance attributes they are immediately assigned to. They
would make more sense if defined properly.
Currently, if you pass a `[]string`, but the argument expects
`[]number`, then you get a message like `expected list[str] but got
list`. That isn't helpful. With this patch arrays and dictionaries will
both print messages with the types provided.
The inner closure of the typed_kwargs function is already complicated
enough without defining closures in the middle of a loop. Let's just
pass the types_tuple as an argument to both avoid redefining the
function over and over, and also make the whole thing easier to read.
Since these aren't warnings, per se, we don't note every single call
site that has one. And we raise mlog.notice in non-fatal mode to avoid
either:
- being too threatening
- making builds fail with --fatal-meson-warnings
Nevertheless, it is useful to give people a heads-up that there is an
upgrade opportunity, rather than waiting until they upgrade and then
causing projects to begin printing fatal warnings.
mlog can already print location info, and we use this often -- including
for custom feature warnings already. Make this work everywhere, so that
it is feasible to move such custom warnings to globally tracked
Features.
info.types could be a tuple like (str, ContainerTypeInfo()). That means
we have to check types one by one and only print error if none of them
matched.
Also fix the case when default value is None for a container type, it
should leave the value to None to be able to distinguish between unset
and empty list.
I've used any because it needs to be infinitely recursive, something
that we simply can't model. But basically until it goes into validator
we have no way of knowing what's going on, since one can write code
like:
```python
KwargInfo[str]('arg', object, validator=_some_very_complex_logic_to_get_specific_string)
```
As such, we can't assume that validator is receiving a type _T, it could
be anything.
We have a lot of these. Some of them are harmless, if unidiomatic, such
as `if (condition)`, others are potentially dangerous `assert(...)`, as
`assert(condtion)` works as expected, but `assert(condition, message)`
will result in an assertion that never triggers, as what you're actually
asserting is `bool(tuple[2])`, which will always be true.
Because currently you can write something like:
```python
KwargInfo('capture', bool)
```
Which proclaims "this must be bool", but the default is then not valid.
Because of the convertor function we have no guarantee that what we're
getting is in fact a `Dict[str, TYPE_var]`, it might well be anything in
the values, so we need to do some casting and set the return type to
object. This works out fine in practice as our declared `TypeDict`
inputs in the actual function signatures will be used instead.
This commit introduces a new type of `HoldableObject`: The
`SecondLevelHolder`. The primary purpose of this class is
to handle cases where two (or more) `HoldableObject`s are
stored at the same time (with one default object). The
best (and currently only) example here is the `BothLibraries`
class.