blob: e7b758f4b955c60c9d667b5ed1c1a5822be1ce3a (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
# Provides a "type-system" for Nix that provides various primitive &
# polymorphic types as well as the ability to define & check records.
#
# All types (should) compose as expected.
#
# TODO:
# - enums?
{ toPretty ? ((import <nixpkgs> {}).lib.generators.toPretty {}) }:
# Checks are simply functions of the type `a -> bool` which check
# whether `a` conforms to the specification.
with builtins; let
# Internal utilities:
typeError = type: val:
throw "Expected type '${type}', but value '${toPretty val}' is of type '${typeOf val}'";
typedef = name: check: {
inherit name check;
__functor = self: value:
if check value then value
else typeError name value;
};
poly = n: c: { "${n}" = t: typedef "${n}<${t.name}>" (c t); };
poly2 = n: c: {
"${n}" = t1: t2: typedef "${n}<${t1.name},${t2.name}>" (c t1 t2);
};
ofType = t: x: isAttrs x && x ? "type" && x.type == t;
typeSet = foldl' (s: t: s // (if t ? "name" then { "${t.name}" = t; } else t)) {};
# Struct checker implementation
#
# Checks performed:
# 1. All existing fields match their types
# 2. No non-optional fields are missing.
# 3. No unexpected fields are in the struct.
#
# Anonymous structs are supported (e.g. for nesting) by omitting the
# name.
checkField = def: value: current: field:
let
fieldVal = if hasAttr field value then value."${field}" else null;
type = def."${field}";
checked = type.check fieldVal;
in if checked then (current && true)
else (throw "Field ${field} is of type ${typeOf fieldVal}, but expected ${type.name}");
checkExtraneous = name: def: present:
if (length present) == 0 then true
else if (hasAttr (head present) def)
then checkExtraneous name def (tail present)
else (throw "Found unexpected field '${head present}' in struct '${name}'");
struct' = name: def: {
inherit name def;
check = value:
let
fieldMatch = foldl' (checkField def value) true (attrNames def);
noExtras = checkExtraneous name def (attrNames value);
in (isAttrs value && fieldMatch && noExtras);
__functor = self: value: if self.check value
then value
else (throw "Expected '${self.name}'-struct, but ${toPretty value} is of type ${typeOf value}");
};
struct = arg:
if isString arg then (struct' arg)
else (struct' "anonymous" arg);
in (typeSet [
# Primitive types
(typedef "any" (_: true))
(typedef "int" isInt)
(typedef "bool" isBool)
(typedef "float" isFloat)
(typedef "string" isString)
(typedef "derivation" (x: isAttrs x && x ? "type" && x.type == "derivation"))
(typedef "function" (x: isFunction x || (isAttrs x && x ? "__functor"
&& isFunction x.__functor)))
# Polymorphic types
(poly "option" (t: v: (isNull v) || t.check v))
(poly "list" (t: v: isList v && (foldl' (s: e: s && (
if t.check e then true
else throw "Expected list element of type '${t.name}', but '${toPretty e}' is of type '${typeOf e}'"
)) true v)))
(poly "attrs" (t: v:
isAttrs v && (foldl' (s: e: s && (
if t.check e then true
else throw "Expected attribute set element of type '${t.name}', but '${toPretty e}' is of type '${typeOf e}'"
)) true (attrValues v))
))
(poly2 "either" (t1: t2: v: t1.check v || t2.check v))
]) // { inherit struct; }
|