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 }