1 /** 2 Provides utilites that allow you to enforce signatures - a specification for a structure 3 */ 4 module bolts.experimental.signatures; 5 6 /// 7 unittest { 8 interface InputRange(T) { 9 @property bool empty(); 10 @property T front(); 11 @ignoreAttributes void popFront(); 12 } 13 14 struct R(T) { 15 mixin Models!(InputRange!T); 16 17 T[] values; 18 int index; 19 this(T[] arr) { 20 values = arr; 21 } 22 @property bool empty() { 23 return values.length == index; 24 } 25 @property T front() { 26 return values[index]; 27 } 28 void popFront() { 29 index++; 30 } 31 } 32 33 import std.range: array; 34 auto r = R!int([1, 4, 2, 3]); 35 assert(r.array == [1, 4, 2, 3]); 36 } 37 38 private enum Report { 39 all, 40 one, 41 } 42 43 private auto checkSignatureOf(alias Model, alias Sig, Report report = Report.one, string path = "")() { 44 import bolts.traits: StringOf; 45 import bolts.meta: RemoveAttributes; 46 import std.traits: hasMember, isAggregateType, isNested, OriginalType; 47 import std.conv: to; 48 49 alias sigMember(string member) = __traits(getMember, Sig, member); 50 alias modelMember(string member) = __traits(getMember, Model, member); 51 52 string typeToString(T)() { 53 import std.traits: isFunction; 54 static if (is(T == struct)) { 55 return "struct"; 56 } else static if (is(T == class)) { 57 return "class"; 58 } else static if (is(T == union)) { 59 return "union"; 60 } else static if (is(T == interface)) { 61 return "interface"; 62 } else static if (is(T == enum)) { 63 return "enum"; 64 } else static if (isFunction!T) { 65 return "function type"; 66 } else { 67 return "type"; 68 } 69 } 70 71 string errorPrefix(string prefix)() { 72 string lower() { 73 if (prefix[0] >= 'A' && prefix[0] <= 'Z') { 74 return prefix[0] + 32 ~ prefix[1 .. $]; 75 } else { 76 return prefix; 77 } 78 } 79 80 string upper() { 81 if (prefix[0] >= 'a' && prefix[0] <= 'z') { 82 return prefix[0] - 32 ~ prefix[1 .. $]; 83 } else { 84 return prefix; 85 } 86 } 87 88 if (path.length) { 89 return "Type `" ~ path ~ "` " ~ lower(); 90 } else { 91 return upper(); 92 } 93 } 94 95 string checkTypedIdentifier(string member, SigMember)() { 96 97 import std.traits: isFunction, hasUDA, hasStaticMember; 98 99 enum error = errorPrefix!"Missing identifier `" 100 ~ member 101 ~ "` of " 102 ~ typeToString!SigMember 103 ~ " `" 104 ~ (hasStaticMember!(Sig, member) ? "static " : "") 105 ~ StringOf!SigMember 106 ~ "`."; 107 108 bool staticCheckPass() { 109 return !hasStaticMember!(Sig, member) 110 || hasStaticMember!(Model, member); 111 } 112 113 static if (is(typeof(modelMember!member) ModelMember)) { 114 115 if (!staticCheckPass()) { 116 return error; 117 } 118 119 static if (hasUDA!(sigMember!member, ignoreAttributes)) { 120 alias T = RemoveAttributes!SigMember; 121 alias U = RemoveAttributes!ModelMember; 122 } else { 123 static if (hasUDA!(sigMember!member, ignoreQualifiers)) { 124 import std.traits: Unqual; 125 alias T = Unqual!SigMember; 126 alias U = Unqual!ModelMember; 127 } else { 128 alias T = SigMember; 129 alias U = ModelMember; 130 } 131 } 132 133 static if (is(T == U)) { 134 return null; 135 } else { 136 return error; 137 } 138 139 } else { 140 return error; 141 } 142 } 143 144 string checkEnum(string member, ModelMember, SigMember)() { 145 static if (is(ModelMember == enum) && is(OriginalType!SigMember == OriginalType!ModelMember)) { 146 import std.algorithm: sort, setDifference; 147 auto sigMembers = [__traits(allMembers, SigMember)].sort; 148 auto modelMembers = [__traits(allMembers, ModelMember)].sort; 149 if (sigMembers != modelMembers) { 150 return errorPrefix!"Enum `" 151 ~ member 152 ~ "` is missing members: " 153 ~ sigMembers.setDifference(modelMembers).to!string; 154 } 155 return null; 156 } else { 157 return errorPrefix!"Missing enum named `" 158 ~ member 159 ~ "` of type `" 160 ~ StringOf!SigMember 161 ~ " with original type `" 162 ~ StringOf!(OriginalType!SigMember) 163 ~ "`."; 164 } 165 } 166 167 string checkAlias(string member, ModelMember, SigMember)() { 168 static if (!is(SigMember == ModelMember)) { 169 return errorPrefix!"Found alias `" 170 ~ member 171 ~ "` of wrong type. Expected alias to " 172 ~ typeToString!SigMember 173 ~ " `" 174 ~ StringOf!SigMember 175 ~ "`."; 176 } else { 177 return null; 178 } 179 } 180 181 auto checkType(string member, SigMember)() { 182 static if (is(modelMember!member ModelMember)) { 183 static if (member != StringOf!SigMember) { 184 if (auto error = checkAlias!(member, ModelMember, SigMember)) { 185 return error; 186 } 187 } else static if (is(SigMember == enum)) { 188 if (auto error = checkEnum!(member, ModelMember, SigMember)) { 189 return error; 190 } 191 } else static if (isAggregateType!SigMember) { 192 if (auto error = checkSignatureOf!(ModelMember, SigMember, report, path ~ "." ~ StringOf!ModelMember)) { 193 return error; 194 } 195 } 196 return null; 197 } else { 198 static if (StringOf!SigMember != member) { 199 return errorPrefix!"Missing alias named `" 200 ~ member 201 ~ "` to " 202 ~ typeToString!SigMember 203 ~ " `" 204 ~ StringOf!SigMember 205 ~ "`."; 206 } else { 207 return errorPrefix!"Missing " 208 ~ typeToString!SigMember 209 ~ " named `" 210 ~ member 211 ~ "`"; 212 } 213 } 214 } 215 216 string checkUnknown(string member)() { 217 static if (isNested!Sig && member == "this") { 218 return null; 219 } else { 220 return "Don`t know member `" ~ member ~ "` of type `" ~ StringOf!Model ~ "`"; 221 } 222 } 223 224 static if (report == Report.all) { 225 string[] result; 226 } else { 227 string result; 228 } 229 230 immutable storeResult = q{ 231 static if (report == Report.one) { 232 result = error; 233 break; 234 } else { 235 result ~= error; 236 } 237 }; 238 239 foreach (member; __traits(allMembers, Sig)) { 240 static if (is(typeof(sigMember!member) T)) { 241 if (auto error = checkTypedIdentifier!(member, T)) { 242 mixin(storeResult); 243 } 244 } else static if (is(sigMember!member T)) { 245 if (auto error = checkType!(member, T)) { 246 mixin(storeResult); 247 } 248 } else { 249 if (auto error = checkUnknown!member) { 250 mixin(storeResult); 251 } 252 } 253 } 254 return result; 255 } 256 257 unittest { 258 struct X { 259 alias b = int; alias c = float; enum E1 { one } void f(int) {} 260 enum E2 { a, b } int x; float y; short z; enum E3 { a, b } 261 struct Inner { 262 struct AnotherInner { 263 int a; 264 int b; 265 } 266 } 267 struct MissingInner { 268 struct A {} 269 } 270 static int s; 271 } 272 struct Y { 273 alias a = int; alias c = int; 274 enum E2 { a } int x; int z; enum E3 { a, b } 275 struct Inner { 276 struct AnotherInner { 277 int a; 278 int b; 279 } 280 } 281 struct MissingInner {} 282 } 283 284 const expectedErrors = [ 285 "Missing alias named `b` to type `int`.", 286 "Found alias `c` of wrong type. Expected alias to type `float`.", 287 "Missing enum named `E1`", 288 "Missing identifier `f` of function type `void(int)`.", 289 "Enum `E2` is missing members: [\"b\"]", 290 "Missing identifier `y` of type `float`.", 291 "Missing identifier `z` of type `short`.", 292 "Type `.MissingInner` missing struct named `A`", 293 "Missing identifier `s` of type `static int`.", 294 ]; 295 296 assert(checkSignatureOf!(Y, X, Report.all) == expectedErrors); 297 } 298 299 /** 300 Checks if type `Model` is a model of type `Sig` 301 */ 302 template isModelOf(alias _Model, alias _Sig) { 303 import bolts.traits: TypesOf; 304 alias Model = TypesOf!_Model[0]; 305 alias Sig = TypesOf!_Sig[0]; 306 enum isModelOf = checkSignatureOf!(Model, Sig, Report.one) == null; 307 } 308 309 /** 310 Asserts that the given model follows the specification of the given signature 311 */ 312 template AssertModelOf(alias _Model, alias _Sig, string file = __FILE__, int line = __LINE__) { 313 import std.algorithm: map, joiner; 314 import std.range: array; 315 import std.conv: to; 316 import bolts.traits: StringOf, TypesOf; 317 318 alias Model = TypesOf!_Model[0]; 319 alias Sig = TypesOf!_Sig[0]; 320 321 string addLocation(string str) { 322 template symLoc(alias sym) { 323 template format(string file, int line, int _) { 324 enum format = file ~ "(" ~ to!string(line) ~ ")"; 325 } 326 enum symLoc = format!(__traits(getLocation, sym)); 327 } 328 enum assertLoc = file ~ "(" ~ to!string(line) ~ ")"; 329 return str 330 ~ "\n " 331 ~ symLoc!Sig 332 ~ ": <-- Signature `" 333 ~ StringOf!Sig 334 ~ "` defined here.\n " 335 ~ assertLoc 336 ~ ": <-- Checked here."; 337 } 338 339 immutable errors = checkSignatureOf!(Model, Sig, Report.all); 340 341 static assert( 342 errors.length == 0, 343 "Type `" ~ StringOf!Model ~ "` does not comply to signature `" ~ StringOf!Sig ~ "`" 344 ~ errors 345 .map!(s => "\n " ~ s) 346 .joiner 347 .to!string 348 .addLocation 349 ); 350 351 enum AssertModelOf = true; 352 } 353 354 /// 355 unittest { 356 struct X { int a; float z; } 357 struct Y { int a; float z; } 358 struct Z { int b; float z; } 359 360 static assert(isModelOf!(Y, X)); 361 } 362 363 /** 364 Mixin that ensures a type models the desired signature of a structure 365 */ 366 mixin template Models(alias Sig, string file = __FILE__, int line = __LINE__) { 367 static import bolts.experimental; 368 static assert(bolts.experimental.signatures.AssertModelOf!(typeof(this), Sig, file, line)); 369 } 370 371 /// 372 unittest { 373 struct Sig { 374 alias I = int; 375 int x; 376 float y; 377 struct Inner { int a; struct X { int b; } } 378 int f(int) { return 0; } 379 enum X { one, two } 380 union L { int a; } 381 } 382 383 struct Y { 384 mixin Models!Sig; 385 alias I = int; 386 int x; 387 float y; 388 struct Inner { int a; struct X { int b; } } 389 int f(int) { return 0; } 390 enum X { one, two } 391 union L { int a; } 392 } 393 394 static assert(isModelOf!(Y, Sig)); 395 } 396 397 unittest { 398 struct TemplatedSig(T) { 399 T value; 400 } 401 402 struct Y(T) { 403 mixin Models!(TemplatedSig!T); 404 T value; 405 } 406 407 static assert(__traits(compiles, { 408 Y!int x; 409 })); 410 } 411 412 unittest { 413 struct Sig { 414 alias I = int; 415 int x; 416 float y; 417 } 418 419 static assert(!__traits(compiles, { 420 struct X { 421 mixin Models!Sig; 422 alias I = float; 423 int x; 424 float y; 425 } 426 })); 427 428 static assert(!__traits(compiles, { 429 struct X { 430 mixin Models!Sig; 431 int I; 432 int x; 433 float y; 434 } 435 })); 436 437 static assert(!__traits(compiles, { 438 struct X { 439 mixin Models!Sig; 440 alias M = int; 441 int x; 442 float y; 443 } 444 })); 445 } 446 447 unittest { 448 struct Sig { 449 alias I = int; 450 int x; 451 float y; 452 } 453 454 static assert(!__traits(compiles, { 455 struct X { 456 mixin Models!Sig; 457 alias I = float; 458 float x; 459 float y; 460 } 461 })); 462 463 static assert(!__traits(compiles, { 464 struct X { 465 mixin Models!Sig; 466 int I; 467 alias x = int; 468 float y; 469 } 470 })); 471 472 static assert(!__traits(compiles, { 473 struct X { 474 mixin Models!Sig; 475 alias M = int; 476 int x; 477 } 478 })); 479 } 480 481 /** 482 Attribute that can be applied on identifiers in a signature that will let the model checker know not to 483 take attributes in to account 484 */ 485 struct ignoreAttributes {} 486 487 /** 488 Attribute that can be applied on identifiers in a signature that will let the model checker know not to 489 take type qualifiers in to account 490 */ 491 struct ignoreQualifiers {} 492 493 unittest { 494 interface Sig { 495 static @property string name(); 496 @ignoreQualifiers static @property string help(); 497 int run(string[]); 498 } 499 500 struct X { 501 mixin Models!Sig; 502 503 static string name = "hello"; 504 immutable static string help = "help"; 505 int run(string[] args) { 506 return 0; 507 } 508 } 509 }