diff options
author | Thomas Strobel <ts468@cam.ac.uk> | 2016-02-20 01:47:01 +0100 |
---|---|---|
committer | Thomas Strobel <ts468@cam.ac.uk> | 2016-02-29 01:09:00 +0100 |
commit | cad8957eabcbf73062226d28366fd446c15c8737 (patch) | |
tree | 0299e96391c14f612e7bd1cf3b2274198425fd61 /lib/modules.nix | |
parent | c483224c82c8e94324c03576e64c5dfbf16bd2f8 (diff) |
Add the tool "nixos-typecheck" that can check an option declaration to:
- Enforce that an option declaration has a "defaultText" if and only if the
type of the option derives from "package", "packageSet" or "nixpkgsConfig"
and if a "default" attribute is defined.
- Enforce that the value of the "example" attribute is wrapped with "literalExample"
if the type of the option derives from "package", "packageSet" or "nixpkgsConfig".
- Warn if a "defaultText" is defined in an option declaration if the type of
the option does not derive from "package", "packageSet" or "nixpkgsConfig".
- Warn if no "type" is defined in an option declaration.
Diffstat (limited to 'lib/modules.nix')
-rw-r--r-- | lib/modules.nix | 99 |
1 files changed, 82 insertions, 17 deletions
diff --git a/lib/modules.nix b/lib/modules.nix index 12ec7004d1ee..d08dc9f85a25 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -23,8 +23,6 @@ rec { specialArgs ? {} , # This would be remove in the future, Prefer _module.args option instead. args ? {} - , # This would be remove in the future, Prefer _module.check option instead. - check ? true }: let # This internal module declare internal options under the `_module' @@ -45,9 +43,22 @@ rec { _module.check = mkOption { type = types.bool; internal = true; - default = check; + default = true; description = "Whether to check whether all option definitions have matching declarations."; }; + + _module.typeInference = mkOption { + type = types.nullOr types.str; + internal = true; + default = null; # TODO: Move away from 'null' after enough testing. + description = '' + Mode of type inferencing. Possible values are: + null = Disable type inferencing completely. Use 'types.unspecified' for every option without type definition. + "silent" = Try to infer type of option without type definition, but do not print anything. + "printUnspecified" = Try to infer type of option without type definition and print options for which no full type could be inferred. + "printAll" = Try to infer type of option without type definition and print all options without type definition. + ''; + }; }; config = { @@ -60,7 +71,7 @@ rec { # Note: the list of modules is reversed to maintain backward # compatibility with the old module system. Not sure if this is # the most sensible policy. - options = mergeModules prefix (reverseList closed); + options = mergeModules (config._module) prefix (reverseList closed); # Traverse options and extract the option values into the final # config set. At the same time, check whether all option @@ -170,11 +181,11 @@ rec { At the same time, for each option declaration, it will merge the corresponding option definitions in all machines, returning them in the ‘value’ attribute of each option. */ - mergeModules = prefix: modules: - mergeModules' prefix modules + mergeModules = _module: prefix: modules: + mergeModules' _module prefix modules (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); - mergeModules' = prefix: options: configs: + mergeModules' = _module: prefix: options: configs: listToAttrs (map (name: { # We're descending into attribute ‘name’. inherit name; @@ -200,8 +211,8 @@ rec { (filter (m: m.config ? ${name}) configs); in if nrOptions == length decls then - let opt = fixupOptionType loc (mergeOptionDecls loc decls); - in evalOptionValue loc opt defns' + let opt = fixupOptionType _module.typeInference loc (mergeOptionDecls loc decls); + in evalOptionValue _module loc opt defns' else if nrOptions != 0 then let firstOption = findFirst (m: isOption m.options) "" decls; @@ -209,7 +220,7 @@ rec { in throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." else - mergeModules' loc decls defns; + mergeModules' _module loc decls defns; }) (concatMap (m: attrNames m.options) options)) // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; @@ -258,7 +269,7 @@ rec { /* Merge all the definitions of an option to produce the final config value. */ - evalOptionValue = loc: opt: defs: + evalOptionValue = _module: loc: opt: defs: let # Add in the default value for this option, if any. defs' = @@ -270,7 +281,7 @@ rec { if opt.readOnly or false && length defs' > 1 then throw "The option `${showOption loc}' is read-only, but it's set multiple times." else - mergeDefinitions loc opt.type defs'; + mergeDefinitions _module loc opt.type defs'; # Check whether the option is defined, and apply the ‘apply’ # function to the merged value. This allows options to yield a @@ -291,7 +302,7 @@ rec { }; # Merge definitions of a value of a given type. - mergeDefinitions = loc: type: defs: rec { + mergeDefinitions = _module: loc: type: defs: rec { defsFinal = let # Process mkMerge and mkIf properties. @@ -314,7 +325,7 @@ rec { mergedValue = foldl' (res: def: if type.check def.value then res else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.") - (type.merge loc defsFinal) defsFinal; + (type.merge _module loc defsFinal) defsFinal; isDefined = defsFinal != []; @@ -412,7 +423,7 @@ rec { /* Hack for backward compatibility: convert options of type optionSet to options of type submodule. FIXME: remove eventually. */ - fixupOptionType = loc: opt: + fixupOptionType = typeInference: loc: opt: let options = opt.options or (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); @@ -424,12 +435,66 @@ rec { else if tp.name == "list of option sets" then types.listOf (types.submodule options) else if tp.name == "null or option set" then types.nullOr (types.submodule options) else tp; + in if opt.type.getSubModules or null == null - then opt // { type = f (opt.type or types.unspecified); } + then opt // { type = f (opt.type or (inferType typeInference loc opt)); } else opt // { type = opt.type.substSubModules opt.options; options = []; }; + /* Function that tries to infer the type of an option from the default value of the option. */ + inferType = mode: loc: opt: + let + doc = x: elemAt x 0; + type = x: elemAt x 1; + containsUnspecified = x: elemAt x 2; + inferType' = def: + if isDerivation def then [ "package" types.package false ] + else if isBool def then [ "bool" types.bool false ] + else if builtins.isString def then [ "str" types.str false ] + else if isInt def then [ "int" types.int false ] + else if isFunction def then [ "functionTo unspecified" (types.functionTo types.unspecified) true ] + else if isList def then + let nestedType = if (length def > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head def)))) def) + then inferType' (head def) + else [ "unspecified" types.unspecified true ]; + in [ "listOf ${doc nestedType}" (types.listOf (type nestedType)) (containsUnspecified nestedType) ] + else if isAttrs def then + let list = mapAttrsToList (_: v: v) (removeAttrs def ["_args"]); + nestedType = if (length list > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head list)))) list) + then inferType' (head list) + else [ "unspecified" types.unspecified true ]; + in [ "attrsOf ${doc nestedType}" (types.attrsOf (type nestedType)) (containsUnspecified nestedType) ] + else [ "unspecified" types.unspecified true ]; + + inferDoc = x: '' + Inferring the type of "${showOption loc}" to "${doc x}". + Please verify the inferred type and define the type explicitely in ${showFiles opt.declarations}! + ''; + + inferredType = printMode: + let inferred = inferType' opt.default; + in if printMode == "silent" then type inferred + else if printMode == "printAll" then builtins.trace (inferDoc inferred) (type inferred) + else if printMode == "printUnspecified" && (containsUnspecified inferred) then builtins.trace (inferDoc inferred) (type inferred) + else type inferred; + + noInferDoc = '' + Could not infer a type for "${showOption loc}", using "unspecified" instead. + Please define the type explicitely in ${showFiles opt.declarations}! + ''; + + hasDefault = (opt ? default) && !(opt ? defaultText); + isExternalVisible = (opt.visible or true) && !(opt.internal or false); + in + + if isNull mode || !isExternalVisible + then types.unspecified + else if hasDefault + then inferredType mode /* Set to 'true' to see every type that is being inferred, not just those types that result in 'unspecified'. */ + else if mode != "silent" then builtins.trace noInferDoc types.unspecified else types.unspecified; + + /* Properties. */ mkIf = condition: content: @@ -497,7 +562,7 @@ rec { /* Compatibility. */ - fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; + fixMergeModules = modules: args: evalModules { inherit args; modules = (modules ++ [{ _module.check = false; }]); }; /* Return a module that causes a warning to be shown if the |