1 module concepts.models;
2 
3 /**
4  * A static assertion that a type satisfies a given template constraint.
5  * It can be used as a $(LINK2 ../attribute.html#uda, UDA) or
6  * in a `static assert` to make sure that a type conforms
7  * to the compile-time interface the user expects it to.
8  * The difference between using `models` and a simple static assert
9  * with the template contraint is that `models` will instantiate the
10  * failing code when the constraint is not satisfied,
11  * yielding compiler error messages to aid the user.
12  *
13  * The template contraint predicate must start with the word `is`
14  * (e.g. `isInputRange`) and an associated template function
15  * with the "is" replaced by "check" should exist (e.g. `checkInputRange`)
16  * and be defined in the same module.
17  */
18 template models(alias T, alias P, A...)
19 {
20     static assert(P.stringof[0..2] == "is",
21                   P.stringof ~ " does not begin with 'is', which is require by `models`");
22 
23     static if(P!(T, A))
24     {
25         bool models()
26         {
27             return true;
28         }
29     }
30     else
31     {
32         bool models()
33         {
34             import std.algorithm: countUntil;
35             import std.traits: moduleName;
36 
37             enum openParenIndex = P.stringof.countUntil("(");
38             enum untilOpenParen = P.stringof["is".length .. openParenIndex];
39             enum checkName = "check" ~ untilOpenParen;
40             enum mixinStr = checkName ~ "!(T, A);";
41             mixin("import " ~ moduleName!(P) ~ ";"); //make it visible first
42             mixin(mixinStr);
43             return false;
44         }
45     }
46 }
47 
48 ///
49 @safe pure unittest
50 {
51     // can't assert that, e.g. !models!(Bar, isFoo) -
52     // the whole point of `models` is that it doesn't compile
53     // when the template constraint is not satisfied
54     static assert(!__traits(compiles, models!(Bar, isFoo)));
55     static assert(!__traits(compiles, models!(Foo, isBar, byte)));
56 }
57 
58 
59 @safe pure unittest {
60     enum weirdPred(T) = true;
61     //always true, this is a sanity check
62     static assert(weirdPred!Foo);
63     //shouldn't compile since weirdPred doesn't begin with the word "is"
64     static assert(!__traits(compiles, models!(Foo, weirdPred)));
65 }
66 
67 
68 @("@models can be applied to serialise")
69 @safe pure unittest {
70     static assert( __traits(compiles, models!(serialise,        isSerialisationFunction)));
71     static assert(!__traits(compiles, models!(doesNotSerialise, isSerialisationFunction)));
72 }
73 
74 @("@models can be applied to deserialise")
75 @safe pure unittest {
76     static assert( __traits(compiles, models!(deserialise, isDeserialisationFunction)));
77     static assert(!__traits(compiles, models!(deserialise, isSerialisationFunction)));
78 }
79 
80 
81 version(unittest) {
82 
83     private enum isFoo(T) = is(typeof(checkFoo!T));
84     @models!(Foo, isFoo) //as a UDA
85     private struct Foo {
86         void foo() {}
87         static assert(models!(Foo, isFoo)); //as a static assert
88     }
89 
90     private template isBar(T, U) {
91         enum isBar = is(typeof(checkBar!(T, U)));
92     }
93 
94     @models!(Bar, isBar, byte) //as a UDA
95     private struct Bar {
96         byte bar;
97         static assert(models!(Bar, isBar, byte)); //as a static assert
98     }
99 
100     private void checkFoo(T)()
101     {
102         T t = T.init;
103         t.foo();
104     }
105 
106     private void checkBar(T, U)()
107     {
108         U _bar = T.init.bar;
109     }
110 
111     private void checkSerialisationFunction(alias F)() {
112         ubyte[] bytes = F(5);
113     }
114 
115     private enum isSerialisationFunction(alias F) = is(typeof(checkSerialisationFunction!F));
116 
117     @models!(serialise, isSerialisationFunction)
118     private ubyte[] serialise(T)(in T val) {
119         return [42];
120     }
121 
122     private void doesNotSerialise(T)(in T val) {
123 
124     }
125 
126     private void checkDeserialisationFunction(alias F)() {
127         ubyte[] bytes;
128         int res = F!int(bytes);
129     }
130     private enum isDeserialisationFunction(alias F) = is(typeof(checkDeserialisationFunction!F));
131 
132     private T deserialise(T)(in ubyte[] bytes) {
133         return T.init;
134     }
135 }