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 /// 50 unittest 51 { 52 53 enum isFoo(T) = is(typeof(checkFoo!T)); 54 55 56 template isBar(T, U) 57 { 58 enum isBar = is(typeof(checkBar!(T, U))); 59 } 60 61 @models!(Foo, isFoo) //as a UDA 62 struct Foo 63 { 64 void foo() {} 65 static assert(models!(Foo, isFoo)); //as a static assert 66 } 67 68 @models!(Bar, isBar, byte) //as a UDA 69 struct Bar 70 { 71 byte bar; 72 static assert(models!(Bar, isBar, byte)); //as a static assert 73 } 74 75 // can't assert that, e.g. !models!(Bar, isFoo) - 76 // the whole point of `models` is that it doesn't compile 77 // when the template constraint is not satisfied 78 static assert(!__traits(compiles, models!(Bar, isFoo))); 79 static assert(!__traits(compiles, models!(Foo, isBar, byte))); 80 } 81 82 83 @safe pure unittest 84 { 85 struct Foo {} 86 enum weirdPred(T) = true; 87 //always true, this is a sanity check 88 static assert(weirdPred!Foo); 89 //shouldn't compile since weirdPred doesn't begin with the word "is" 90 static assert(!__traits(compiles, models!(Foo, weirdPred))); 91 } 92 93 94 @("@models can be applied to serialise") 95 @safe pure unittest { 96 static assert(__traits(compiles, models!(serialise, isSerialisationFunction))); 97 static assert(!__traits(compiles, models!(doesNotSerialise, isSerialisationFunction))); 98 } 99 100 // FIXME 101 // @("@models can be applied to deserialise") 102 // @safe pure unittest { 103 // static assert(__traits(compiles, models!(deserialise, isDeserialisationFunction))); 104 // } 105 106 107 version(unittest) { 108 void checkFoo(T)() 109 { 110 T t = T.init; 111 t.foo(); 112 } 113 114 void checkBar(T, U)() 115 { 116 U _bar = T.init.bar; 117 } 118 119 void checkSerialisationFunction(alias F)() { 120 ubyte[] bytes = F(5); 121 } 122 123 enum isSerialisationFunction(alias F) = is(typeof(checkSerialisationFunction!F)); 124 125 @models!(serialise, isSerialisationFunction) 126 ubyte[] serialise(T)(in T val) { 127 return [42]; 128 } 129 130 void doesNotSerialise(T)(in T val) { 131 132 } 133 134 void checkDeserialisationFunction(alias F)() { 135 ubyte[] bytes; 136 Request = F!int(bytes); 137 } 138 void isDeserialisationFunction(alias F) = is(typeof(isDeserialisationFunction!F)); 139 140 T deserialise(T)(in ubyte[] bytes) { 141 return T.init; 142 } 143 }