Skip to content

Commit

Permalink
lib/types-simple: add restrict
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
Profpatsch committed Mar 19, 2018
1 parent eefcd09 commit 85f28ff
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 8 deletions.
92 changes: 90 additions & 2 deletions lib/tests/types-simple.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ let
# TODO test the return type of checkType to be
# nested attrs (product { should = string; val = any; })

in lib.runTests {
in lib.runTests ({


# -- Scalars --

testVoid = test void 23 (err void 23);

Expand All @@ -56,6 +59,9 @@ in lib.runTests {
testFloatNotInt = test float 23 (err float 23);
testFloatFoo = test float [ "nope" ] (err float [ "nope" ]);


# -- Recursives --

testListEmpty = test (list void) [] ok;
testListIntOk = test (list int) [ 1 2 3 ] ok;
testListPosFoo = test (list int) [ 1 "ahh" 3 true ] {
Expand Down Expand Up @@ -93,6 +99,9 @@ in lib.runTests {
"2".x = err unit [];
};


# -- Products --

testProductOk = test (product { name = string; age = int; })
{ name = "hans"; age = 42; } ok;
testProductWrongTypes = test (product { name = string; age = int; })
Expand Down Expand Up @@ -129,6 +138,9 @@ in lib.runTests {
{ p = { x = 23; }; } # missing the required p.y
{ p = (err (product { x = int; y = bool; }) { x = 23; }); };


# -- Sums --

testSumLeftOk = test (sum { left = string; right = unit; })
{ left = "errör!"; } ok;
testSumRightOk = test (sum { left = string; right = unit; })
Expand All @@ -143,6 +155,9 @@ in lib.runTests {
{ a = 21; b = {}; }
(err (sum { a = int; b = unit; }) { a = 21; b = {}; });


# -- Unions --

testUnionOk1 = test (union [ int string (list unit) ]) 23 ok;
testUnionOk2 = test (union [ int string (list unit) ]) "foo" ok;
testUnionOk3 = test (union [ int string (list unit) ]) [{}{}] ok;
Expand All @@ -152,4 +167,77 @@ in lib.runTests {
testUnionSimilar = test (union [ (list string) (attrs string) ])
{ foo = "string"; } ok;

}
} //


# -- Restrictions --

(let
even = restrict {
type = int;
check = v: lib.mod v 2 == 0;
description = "even integer";
};

intBetween = min: max: restrict {
type = int;
check = v: v >= min && v <= max;
description = "int between ${toString min} ${toString max}";
};

thirdElementIsListOf23 = restrict {
type = list (list int);
check = v: builtins.length v >= 3 && builtins.elemAt v 2 == [23];
description = "third element is [23]";
};

enum = t: xs: restrict {
type = t;
check = v: lib.any (x: x == v) xs;
description = "one of values [ " +
lib.concatMapStringsSep ", " (lib.generators.toPretty {}) xs
+ " ]";
};

in {
testRestrictEvenOk = test (list even)
[ 2 4 128 42 ] ok;
testRestrictEvenFoo = test (list even)
[ 42 23 ]
{ "1" = err even 23; };

testRestrictTypeCheckFirst =
let t = restrict {
type = void;
# will crash if "23" is checked here
# before the check for its type
check = v: (v + 19) == 42;
description = "is worthy";
};
# we actually want it to always give the type description
# of the restricted type when the general type check fails
# e.g. 23 should return "even integer" instead of "integer"
# when checking for the int restricted to even integers
in test t "23" (err t "23");

testDeepRestriction = test thirdElementIsListOf23
[ [1] ["foo"] [23] ]
{ "1"."0" = err int "foo"; };
testDeepRestrictionFoo = test thirdElementIsListOf23
[ [] [] [24] [] [] ]
(err thirdElementIsListOf23 [ [] [] [24] [] [] ]);

testRestrictIntBetweenOk = test (list (intBetween (-2) 3))
[ (-2) (-1) 0 1 2 3 ] ok;
testRestrictIntBetweenFoo = test (list (intBetween 0 0))
[ (-23) 42 ]
{ "0" = err (intBetween 0 0) (-23);
"1" = err (intBetween 0 0) 42; };

testRestrictEnumOk = test (list (enum string [ "a" "b" "c" ]))
[ "b" "c" "a" ] ok;
testRestrictEnumFoo = test (list (enum string [ "a" "b" "c" ]))
[ "b" "d" "a" ]
{ "1" = err (enum string [ "a" "b" "c" ]) "d"; };

}))
38 changes: 32 additions & 6 deletions lib/types-simple.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ let
recursive = 1;
sum = 2;
product = 3;
# TODO: it feels like union (or sum or product) is not axiomatic
union = 4;
};

Expand Down Expand Up @@ -154,8 +153,6 @@ let
# the (displayable) type description
description,
# a function to check the outermost type, given a value (Val -> Bool)
# TODO: this is value-specific, maybe this should be inside the type checker
# logic instead of the type definiton? There’s some repetition.
check,
# the variant of this type
variant,
Expand Down Expand Up @@ -370,8 +367,36 @@ let
check = v: lib.any (t: t.check v) altList;
variant = variants.union;
extraFields = {
inherit altList;
};
inherit altList;
};
};

# restrict applies a further check to values of type
# the idea is simple, but some crazy things are possible, like
# * even integers
# * integers between 23 and 42
# * enumerations
# * lists with exactly three elements where the second is the string "bla"
# type errors from the base type checks are retained.
#
# restrict { type = int; check = isEven; … }:
# 2
# 42
# see tests for further examples
restrict = {
# type that should be restricted
type,
# takes a value of type
# return true for values of type that are valid
check,
# the (displayable) restricted type description
description
}: type // {
inherit description;
# first the general type is checked,
# then the restriction check is tried
# this way the restriction check can assume the correct type
check = v: type.check v && check v;
};

# TODO: should scalars be allowed as nest types?
Expand Down Expand Up @@ -410,7 +435,8 @@ in {
# Constructor functions for types.
# Their internal structure/fields are an *implementation detail*.
inherit void any unit bool string int float
list attrs product productOpt sum union;
list attrs product productOpt sum union
restrict;
# Type checking.
inherit checkType;
# Functions.
Expand Down

0 comments on commit 85f28ff

Please sign in to comment.