Rewrite meta-check.nix type checking logic.#37252
Rewrite meta-check.nix type checking logic.#37252Profpatsch wants to merge 14 commits intoNixOS:masterfrom
Conversation
f75fee9 to
ffab4b4
Compare
|
Okay, with the latest commit I recommend looking at |
aszlig
left a comment
There was a problem hiding this comment.
Great work on the type system, although I'm not quite sure whether this is a good idea to use for checking meta, because we already have to fight against eval time.
Apart from that, it could also make sense to replace the type checking for the NixOS module system to use this instead (as it avoids the need to use tryEval for checking) on everything but submodules, maybe @nbp could chime in for that as well.
Other than that, I think adding another type system needs to have more eyes on this, so I'd suggest adding this as a separate pull request.
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
Why not concatLists (which is a primop)?
There was a problem hiding this comment.
Good catch, will change.
pkgs/stdenv/generic/check-meta.nix
Outdated
There was a problem hiding this comment.
Did you actually measure the evaluation time overhead this has?
There was a problem hiding this comment.
Nope, how would I measure it? Maybe cc @grahamc.
Right now the checks are not enabled by default, of course that would be a final goal.
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
This is not used anywhere, maybe remove?
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
So why have it in the first place?
There was a problem hiding this comment.
Ah, it’s used in the tests for example: (list void) is a type with exactly one inhabitant, []. One wouldn’t believe it, but void is a pretty handy type to have (the description is a bit tongue-in-cheek).
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
Similar to void this doesn’t seem useful at first, but it’s even more elementar than bool in a sense; e.g. the void you find in languages like C(++) is really a unit.
A great use-case is for sum types:
let mode = sum { off = unit; level = int; };
in checkType mode { off = {}; }which corresponds to the Haskell type:
data Mode = Off | Level Int
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
Maybe call this listOf so we use the same naming as with NixOS modules.
There was a problem hiding this comment.
I deliberately changed the names to be internally consistent; the module system has string and str, because string did strange concatenation. listOf and attrsOf have the same history with attrs and list, which are not able to recursively check their contents.
Compare the correspondence table below.
lib/types-simple.nix
Outdated
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
I'd use intersectAttrs (especially because it's a primop) here instead of converting to list and use subtractLists.
There was a problem hiding this comment.
To check for then reqfs == vfs in the line below, I’d have to use builtins.intersectAttrs v req == req, which has to do a deep equality check for req (or v if I turn the arguments around).
lib/types-simple.nix
Outdated
There was a problem hiding this comment.
Hm, I don't think this has any more benefit to having to specify the defaults directly, especially if it comes to code readability. You need to look up the default here instead of just following the code you're already looking at.
There was a problem hiding this comment.
I implemented that as a crutch for optional fields originally, but productOpt kind of replaced it pretty quickly. It might make sense to remove it again, yes.
|
Here’s a (non-exhaustive) correspondence table.
Some of the module types come from the fact that they also contain merging logic and submodule logic, which complicates the base design and lead to the deprecation of some types. |
A simple type system to check plain nix values. Please see the module comments for further explanations. Will be used to correct the buggy meta-field checks and help in sanity-checking other parts of nixpkgs.
productOpt: a product type with the possibility for optional fields defaults: generate (semi-arbitrary) default values for a given type In preparation for the rewrite of `check-meta.nix`.
The previous code used some ad-hoc attribute enumeration to check for missing
fields and did not really type check the attributes, because it used `t.check`
from the module system types, which only does shallow checks for nested types,
e.g. just `builtins.isList` for (listOf strings).
This new version uses `types-simple` to recursively check every attribute,
outputting accurate information for type errors in nested fields, e.g.:
Package ‘hello-2.10’ in … has an invalid meta attrset:
maintainers.1.email should be: string
but is: null
When we set
meta.maintainers [ eelco foobar ];
in `hello/default.nix` and foobar is defined as
{ name = "foobar"; email = null; };
in `maintainers-list.nix`.
The path accurately pins down where the type check failed:
In the `maintainers` field, the second element of the list (counting from 0)
and the field email in that element.
Further work: If an unsupported meta field is used, the type error will print
the complete type of the meta product, which is quite big. The type error should
show a diff of the problematic fields in that case.
This is possible but not yet integrated into the type checking logic.
The meta type is changed to either a string, a license or a list of licenses, where license is an attrset with required `fullName`, `shortName` and `free` fields, and two optional fields. Every package that does not conform to this union type is fixed; I noticed that only a few packages used `list of strings`, so I corrected them and removed the alternative.
Especially document the inner workings of the `checkType` function.
`defaults` originally only was a crutch to make products accept optional attributes shallowly, but that has been replaced by `productOpt`, so we don’t need it anymore. Also a few other changes based on input by @aszlig.
f0e032b to
eefcd09
Compare
|
Okay, rebase onto current master after the changes in @Ericson2314 I noticed you create a lot of types in |
| # TODO: use the types from lib/systems/parse.nix | ||
| # any should be lib.systems.parsed.types.system | ||
| system = any; | ||
| platform = union [ string any ]; |
There was a problem hiding this comment.
@Ericson2314 This is where I stub out the platform types at the moment.
`restrict` is able to restrict the inhabitants of a type, meaning only a subset of a type is allowed, the new type is “smaller”, but still gives nice type errors if a nested value has the wrong type. See the tests for some usage examples.
e6f1908 to
85f28ff
Compare
|
Update: |
| types = callLibs ./types.nix; | ||
|
|
||
| # type checking plain nix values | ||
| types-simple = callLibs ./types-simple.nix; |
There was a problem hiding this comment.
Please use camelCase for attribute names.
|
What is the memory / evaluation time impact of this? |
| }; | ||
|
|
||
|
|
||
| # -- Products -- |
There was a problem hiding this comment.
What are "products"? Seems like a pretty strange terminology.
There was a problem hiding this comment.
types-simple is essentially defining algebraic data types, like ML or Haskell have; "products" is the standard terminology for tuple- or record-like things in this setting.
|
A couple of clarifications.
In
I think `types-simple` should replace `types`
I mean that either `types-simple` should expose the merging NixOS modules would use, or `types` should `types-simple` for type checking.
In
without duplicating already inferable information
and related I mean the following kind of duplication
```imaginary-typed-nix
option :: builtintypes.str
option = mkOption {
merge = types.str.merge;
};
```
Similarly, in
a semantically very different nix
and related I also mean that it's unclear how you would typecheck
```nix
{
require = [
<module that defines `option`>
];
config = {
option = value;
};
}
```
without merging NixOS modules and nix (currently non-existent) modules into a single concept.
Also, an idea I wanted to use when I planned implementing pretty much the same thing: I planned to make `bool` into a non-mergable bool and split other uses into `types.any` and `types.all` since `bool` has two different monoids. E.g. you would then use `all` for normal `enable` options (because if somebody disabled it, they probably know what they are doing, why make them use `mkForce`?) and `any` for security-related options (because to make things insecure everyone should agree).
|
Ah, the |
|
ping! Progress? Do you have an updated/rebased version of this to play with? |
|
@oxij I have just pushed a branch which does away with the in-file changes of I intend to commit all improvements to that branch (types-simple-standalone) first. |
`names` was originally intended to be able to match on the specific instance of a type, but it breaks the abstraction in a way. Since the function that used it was removed, we can remove the name attributes as well.
|
@Profpatsch awesome, thanks! |
|
Wow, this is great work ! I really like having the typeckecking logic separate from the merging logic. Is there a plan to make nixos types use this for typechecking under the hood ? |
|
@Profpatsch this is perhaps somewhat off-topic, but I read through From the context, I gather that the design is based on ideas from category theory, and would (I assume) be easily comprehensible to people familiar with it. As a result, I am curious: are there other benefits in this specific instance to keeping the implementation so close to the abstract design? Those comments notwithstanding, I still found it easier to understand than the existing module type system, and having a self-contained type system in place would be very useful. |
No, not really, it’s from the classic bananas & barbed wire paper. You can find an explanation here: http://reasonablypolymorphic.com/blog/recursion-schemes/ |
…g` of NixOS to it This types `nixpkgs.config` of NixOS with `types.opaque` type and applies the resulting evaluated value to the newly introduced `configs` argument of `pkgs`. The new `configs` argument is like the old `config` argument (still available for convenience) but a list. This design was chosen because we can't just make a submodule out of `pkgs/top-level/config.nix` in NixOS because - firstly, we would have to duplicate all the magic `pkgs/top-level/default.nix` does to `config` in NixOS, - secondly, in principle, we might not have all the arguments needed by `pkgs/top-level/config.nix` in NixOS because some of them can be computed internally by `pkgs` (this is not a problem now but it will be a problem in use-flags patchset that comes after), thus all of those computations will need to be duplicated too, - thirdly, doing it this way keeps NixOS and pkgs separated, so that, in principle, one could replace NixOS with an alternative thing without duplicating all of the above once again and/or one could use separate module and type systems between NixOS and pkgs (`types.opaque` is trivial to implement in any type system and pkgs can use "types-simple" of NixOS#37252 today, if we were to wish it so). Note that since this design removes the need to do any preprocessing of `nixpkgs.config` in NixOS all the ad-hoc merging code was removed. Yay!
…g` of NixOS to it This types `nixpkgs.config` of NixOS with `types.opaque` type and applies the resulting evaluated value to the newly introduced `configs` argument of `pkgs`. The new `configs` argument is like the old `config` argument (still available for convenience) but a list. This design was chosen because we can't just make a submodule out of `pkgs/top-level/config.nix` in NixOS because - firstly, we would have to duplicate all the magic `pkgs/top-level/default.nix` does to `config` in NixOS, - secondly, in principle, we might not have all the arguments needed by `pkgs/top-level/config.nix` in NixOS because some of them can be computed internally by `pkgs` (this is not a problem now but it will be a problem in use-flags patchset that comes after), thus all of those computations will need to be duplicated too, - thirdly, doing it this way keeps NixOS and pkgs separated, so that, in principle, one could replace NixOS with an alternative thing without duplicating all of the above once again and/or one could use separate module and type systems between NixOS and pkgs (`types.opaque` is trivial to implement in any type system and pkgs can use "types-simple" of NixOS#37252 today, if we were to wish it so). Note that since this design removes the need to do any preprocessing of `nixpkgs.config` in NixOS all the ad-hoc merging code was removed. Yay!
|
Are there any updates on this pull request, please? |
I still think it’s valuable, but I don’t know of any way out of the “we don’t want two type systems in nixpkgs” issue. |
|
Thank you for your contributions.
|
Description of the main commit:
Please go trough the commits one-by-one, since they all add some pretty different changes and are built on each other as single units.Adding the type library might be a bit controversial, but I hope I have provided enough incentive in the docstrings and the commit messages to persuade you that it is a good addition.
Meta can be checked with this invocation from the ofborg readme.