Newer
Older
dub_jkp / source / dub / internal / vibecompat / data / utils.d
  1. /**
  2. Utility functions for data serialization
  3.  
  4. Copyright: © 2012 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig
  7. */
  8. module dub.internal.vibecompat.data.utils;
  9.  
  10. version (Have_vibe_d) {}
  11. else:
  12.  
  13. public import std.traits;
  14.  
  15. /**
  16. Checks if given type is a getter function type
  17.  
  18. Returns: `true` if argument is a getter
  19. */
  20. template isPropertyGetter(T...)
  21. if (T.length == 1)
  22. {
  23. import std.traits : functionAttributes, FunctionAttribute, ReturnType,
  24. isSomeFunction;
  25. static if (isSomeFunction!(T[0])) {
  26. enum isPropertyGetter =
  27. (functionAttributes!(T[0]) & FunctionAttribute.property) != 0
  28. && !is(ReturnType!T == void);
  29. }
  30. else
  31. enum isPropertyGetter = false;
  32. }
  33.  
  34. ///
  35. unittest
  36. {
  37. interface Test
  38. {
  39. @property int getter();
  40. @property void setter(int);
  41. int simple();
  42. }
  43.  
  44. static assert(isPropertyGetter!(typeof(&Test.getter)));
  45. static assert(!isPropertyGetter!(typeof(&Test.setter)));
  46. static assert(!isPropertyGetter!(typeof(&Test.simple)));
  47. static assert(!isPropertyGetter!int);
  48. }
  49.  
  50. /**
  51. Checks if given type is a setter function type
  52.  
  53. Returns: `true` if argument is a setter
  54. */
  55. template isPropertySetter(T...)
  56. if (T.length == 1)
  57. {
  58. import std.traits : functionAttributes, FunctionAttribute, ReturnType,
  59. isSomeFunction;
  60.  
  61. static if (isSomeFunction!(T[0])) {
  62. enum isPropertySetter =
  63. (functionAttributes!(T) & FunctionAttribute.property) != 0
  64. && is(ReturnType!(T[0]) == void);
  65. }
  66. else
  67. enum isPropertySetter = false;
  68. }
  69.  
  70. ///
  71. unittest
  72. {
  73. interface Test
  74. {
  75. @property int getter();
  76. @property void setter(int);
  77. int simple();
  78. }
  79.  
  80. static assert(isPropertySetter!(typeof(&Test.setter)));
  81. static assert(!isPropertySetter!(typeof(&Test.getter)));
  82. static assert(!isPropertySetter!(typeof(&Test.simple)));
  83. static assert(!isPropertySetter!int);
  84. }
  85.  
  86. /**
  87. Deduces single base interface for a type. Multiple interfaces
  88. will result in compile-time error.
  89.  
  90. Params:
  91. T = interface or class type
  92.  
  93. Returns:
  94. T if it is an interface. If T is a class, interface it implements.
  95. */
  96. template baseInterface(T)
  97. if (is(T == interface) || is(T == class))
  98. {
  99. import std.traits : InterfacesTuple;
  100.  
  101. static if (is(T == interface)) {
  102. alias baseInterface = T;
  103. }
  104. else
  105. {
  106. alias Ifaces = InterfacesTuple!T;
  107. static assert (
  108. Ifaces.length == 1,
  109. "Type must be either provided as an interface or implement only one interface"
  110. );
  111. alias baseInterface = Ifaces[0];
  112. }
  113. }
  114.  
  115. ///
  116. unittest
  117. {
  118. interface I1 { }
  119. class A : I1 { }
  120. interface I2 { }
  121. class B : I1, I2 { }
  122.  
  123. static assert (is(baseInterface!I1 == I1));
  124. static assert (is(baseInterface!A == I1));
  125. static assert (!is(typeof(baseInterface!B)));
  126. }
  127.  
  128.  
  129. /**
  130. Determins if a member is a public, non-static data field.
  131. */
  132. template isRWPlainField(T, string M)
  133. {
  134. static if (!isRWField!(T, M)) enum isRWPlainField = false;
  135. else {
  136. //pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof);
  137. enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
  138. }
  139. }
  140.  
  141. /**
  142. Determines if a member is a public, non-static, de-facto data field.
  143.  
  144. In addition to plain data fields, R/W properties are also accepted.
  145. */
  146. template isRWField(T, string M)
  147. {
  148. import std.traits;
  149. import std.typetuple;
  150.  
  151. static void testAssign()() {
  152. T t = void;
  153. __traits(getMember, t, M) = __traits(getMember, t, M);
  154. }
  155.  
  156. // reject type aliases
  157. static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false;
  158. // reject non-public members
  159. else static if (!isPublicMember!(T, M)) enum isRWField = false;
  160. // reject static members
  161. else static if (!isNonStaticMember!(T, M)) enum isRWField = false;
  162. // reject non-typed members
  163. else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false;
  164. // reject void typed members (includes templates)
  165. else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false;
  166. // reject non-assignable members
  167. else static if (!__traits(compiles, testAssign!()())) enum isRWField = false;
  168. else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) {
  169. // If M is a function, reject if not @property or returns by ref
  170. private enum FA = functionAttributes!(__traits(getMember, T, M));
  171. enum isRWField = (FA & FunctionAttribute.property) != 0;
  172. } else {
  173. enum isRWField = true;
  174. }
  175. }
  176.  
  177. unittest {
  178. import std.algorithm;
  179.  
  180. struct S {
  181. alias a = int; // alias
  182. int i; // plain RW field
  183. enum j = 42; // manifest constant
  184. static int k = 42; // static field
  185. private int privateJ; // private RW field
  186.  
  187. this(Args...)(Args args) {}
  188.  
  189. // read-write property (OK)
  190. @property int p1() { return privateJ; }
  191. @property void p1(int j) { privateJ = j; }
  192. // read-only property (NO)
  193. @property int p2() { return privateJ; }
  194. // write-only property (NO)
  195. @property void p3(int value) { privateJ = value; }
  196. // ref returning property (OK)
  197. @property ref int p4() { return i; }
  198. // parameter-less template property (OK)
  199. @property ref int p5()() { return i; }
  200. // not treated as a property by DMD, so not a field
  201. @property int p6()() { return privateJ; }
  202. @property void p6(int j)() { privateJ = j; }
  203.  
  204. static @property int p7() { return k; }
  205. static @property void p7(int value) { k = value; }
  206.  
  207. ref int f1() { return i; } // ref returning function (no field)
  208.  
  209. int f2(Args...)(Args args) { return i; }
  210.  
  211. ref int f3(Args...)(Args args) { return i; }
  212.  
  213. void someMethod() {}
  214.  
  215. ref int someTempl()() { return i; }
  216. }
  217.  
  218. enum plainFields = ["i"];
  219. enum fields = ["i", "p1", "p4", "p5"];
  220.  
  221. foreach (mem; __traits(allMembers, S)) {
  222. static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field.");
  223. else static assert(!fields.canFind(mem), mem~" not detected as field.");
  224.  
  225. static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field.");
  226. else static assert(!plainFields.canFind(mem), mem~" not detected as plain field.");
  227. }
  228. }
  229.  
  230. package T Tgen(T)(){ return T.init; }
  231.  
  232.  
  233. /**
  234. Tests if the protection of a member is public.
  235. */
  236. template isPublicMember(T, string M)
  237. {
  238. import std.algorithm, std.typetuple : TypeTuple;
  239.  
  240. static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false;
  241. else {
  242. alias MEM = TypeTuple!(__traits(getMember, T, M));
  243. enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
  244. }
  245. }
  246.  
  247. unittest {
  248. class C {
  249. int a;
  250. export int b;
  251. protected int c;
  252. private int d;
  253. package int e;
  254. void f() {}
  255. static void g() {}
  256. private void h() {}
  257. private static void i() {}
  258. }
  259.  
  260. static assert (isPublicMember!(C, "a"));
  261. static assert (isPublicMember!(C, "b"));
  262. static assert (!isPublicMember!(C, "c"));
  263. static assert (!isPublicMember!(C, "d"));
  264. static assert (!isPublicMember!(C, "e"));
  265. static assert (isPublicMember!(C, "f"));
  266. static assert (isPublicMember!(C, "g"));
  267. static assert (!isPublicMember!(C, "h"));
  268. static assert (!isPublicMember!(C, "i"));
  269.  
  270. struct S {
  271. int a;
  272. export int b;
  273. private int d;
  274. package int e;
  275. }
  276. static assert (isPublicMember!(S, "a"));
  277. static assert (isPublicMember!(S, "b"));
  278. static assert (!isPublicMember!(S, "d"));
  279. static assert (!isPublicMember!(S, "e"));
  280.  
  281. S s;
  282. s.a = 21;
  283. assert(s.a == 21);
  284. }
  285.  
  286. /**
  287. Tests if a member requires $(D this) to be used.
  288. */
  289. template isNonStaticMember(T, string M)
  290. {
  291. import std.typetuple;
  292. import std.traits;
  293.  
  294. alias MF = TypeTuple!(__traits(getMember, T, M));
  295. static if (M.length == 0) {
  296. enum isNonStaticMember = false;
  297. } else static if (anySatisfy!(isSomeFunction, MF)) {
  298. enum isNonStaticMember = !__traits(isStaticFunction, MF);
  299. } else {
  300. enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
  301. }
  302. }
  303.  
  304. unittest { // normal fields
  305. struct S {
  306. int a;
  307. static int b;
  308. enum c = 42;
  309. void f();
  310. static void g();
  311. ref int h() { return a; }
  312. static ref int i() { return b; }
  313. }
  314. static assert(isNonStaticMember!(S, "a"));
  315. static assert(!isNonStaticMember!(S, "b"));
  316. static assert(!isNonStaticMember!(S, "c"));
  317. static assert(isNonStaticMember!(S, "f"));
  318. static assert(!isNonStaticMember!(S, "g"));
  319. static assert(isNonStaticMember!(S, "h"));
  320. static assert(!isNonStaticMember!(S, "i"));
  321. }
  322.  
  323. unittest { // tuple fields
  324. struct S(T...) {
  325. T a;
  326. static T b;
  327. }
  328.  
  329. alias T = S!(int, float);
  330. auto p = T.b;
  331. static assert(isNonStaticMember!(T, "a"));
  332. static assert(!isNonStaticMember!(T, "b"));
  333.  
  334. alias U = S!();
  335. static assert(!isNonStaticMember!(U, "a"));
  336. static assert(!isNonStaticMember!(U, "b"));
  337. }
  338.  
  339.  
  340. /**
  341. Tests if a Group of types is implicitly convertible to a Group of target types.
  342. */
  343. bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)()
  344. if (isGroup!TYPES && isGroup!TARGET_TYPES)
  345. {
  346. static assert(TYPES.expand.length == TARGET_TYPES.expand.length);
  347. foreach (i, V; TYPES.expand)
  348. if (!is(V : TARGET_TYPES.expand[i]))
  349. return false;
  350. return true;
  351. }
  352.  
  353. /// Test if the type $(D DG) is a correct delegate for an opApply where the
  354. /// key/index is of type $(D TKEY) and the value of type $(D TVALUE).
  355. template isOpApplyDg(DG, TKEY, TVALUE) {
  356. import std.traits;
  357. static if (is(DG == delegate) && is(ReturnType!DG : int)) {
  358. private alias PTT = ParameterTypeTuple!(DG);
  359. private alias PSCT = ParameterStorageClassTuple!(DG);
  360. private alias STC = ParameterStorageClass;
  361. // Just a value
  362. static if (PTT.length == 1) {
  363. enum isOpApplyDg = (is(PTT[0] == TVALUE) && PSCT[0] == STC.ref_);
  364. } else static if (PTT.length == 2) {
  365. enum isOpApplyDg = (is(PTT[0] == TKEY) && PSCT[0] == STC.ref_)
  366. && (is(PTT[1] == TKEY) && PSCT[1] == STC.ref_);
  367. } else
  368. enum isOpApplyDg = false;
  369. } else {
  370. enum isOpApplyDg = false;
  371. }
  372. }
  373.  
  374. /**
  375. TypeTuple which does not auto-expand.
  376. Useful when you need
  377. to multiple several type tuples as different template argument
  378. list parameters, without merging those.
  379. */
  380. template Group(T...)
  381. {
  382. alias expand = T;
  383. }
  384.  
  385. ///
  386. unittest
  387. {
  388. alias group = Group!(int, double, string);
  389. static assert (!is(typeof(group.length)));
  390. static assert (group.expand.length == 3);
  391. static assert (is(group.expand[1] == double));
  392. }
  393.  
  394. /**
  395. */
  396. template isGroup(T...)
  397. {
  398. static if (T.length != 1) enum isGroup = false;
  399. else enum isGroup =
  400. !is(T[0]) && is(typeof(T[0]) == void) // does not evaluate to something
  401. && is(typeof(T[0].expand.length) : size_t) // expands to something with length
  402. && !is(typeof(&(T[0].expand))); // expands to not addressable
  403. }
  404.  
  405. version (unittest) // NOTE: GDC complains about template definitions in unittest blocks
  406. {
  407. import std.typetuple;
  408. alias group = Group!(int, double, string);
  409. alias group2 = Group!();
  410. template Fake(T...)
  411. {
  412. int[] expand;
  413. }
  414. alias fake = Fake!(int, double, string);
  415.  
  416. alias fake2 = TypeTuple!(int, double, string);
  417.  
  418. static assert (isGroup!group);
  419. static assert (isGroup!group2);
  420. static assert (!isGroup!fake);
  421. static assert (!isGroup!fake2);
  422. }
  423.  
  424. /* Copied from Phobos as it is private there.
  425. */
  426. private template isSame(ab...)
  427. if (ab.length == 2)
  428. {
  429. static if (is(ab[0]) && is(ab[1]))
  430. {
  431. enum isSame = is(ab[0] == ab[1]);
  432. }
  433. else static if (!is(ab[0]) &&
  434. !is(ab[1]) &&
  435. is(typeof(ab[0] == ab[1]) == bool) &&
  436. (ab[0] == ab[1]))
  437. {
  438. static if (!__traits(compiles, &ab[0]) ||
  439. !__traits(compiles, &ab[1]))
  440. enum isSame = (ab[0] == ab[1]);
  441. else
  442. enum isSame = __traits(isSame, ab[0], ab[1]);
  443. }
  444. else
  445. {
  446. enum isSame = __traits(isSame, ab[0], ab[1]);
  447. }
  448. }
  449.  
  450. /**
  451. Compares two groups for element identity
  452. Params:
  453. Group1, Group2 = any instances of `Group`
  454.  
  455. Returns:
  456. `true` if each element of Group1 is identical to
  457. the one of Group2 at the same index
  458. */
  459. template Compare(alias Group1, alias Group2)
  460. if (isGroup!Group1 && isGroup!Group2)
  461. {
  462. private bool implementation()
  463. {
  464. static if (Group1.expand.length == Group2.expand.length) {
  465. foreach (index, element; Group1.expand)
  466. {
  467. static if (!isSame!(Group1.expand[index], Group2.expand[index])) {
  468. return false;
  469. }
  470. }
  471. return true;
  472. }
  473. else {
  474. return false;
  475. }
  476. }
  477.  
  478. enum Compare = implementation();
  479. }
  480.  
  481. ///
  482. unittest
  483. {
  484. alias one = Group!(int, double);
  485. alias two = Group!(int, double);
  486. alias three = Group!(double, int);
  487. static assert (Compare!(one, two));
  488. static assert (!Compare!(one, three));
  489. }
  490.  
  491. /**
  492. Small convenience wrapper to find and extract certain UDA from given type.
  493. Will stop on first element which is of required type.
  494.  
  495. Params:
  496. UDA = type or template to search for in UDA list
  497. Symbol = symbol to query for UDA's
  498. allow_types = if set to `false` considers attached `UDA` types an error
  499. (only accepts instances/values)
  500.  
  501. Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
  502. `found` is boolean flag for having a valid find. `index` is integer index in
  503. attribute list this UDA was found at.
  504. */
  505. template findFirstUDA(alias UDA, alias Symbol, bool allow_types = false) if (!is(UDA))
  506. {
  507. enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
  508. }
  509.  
  510. /// Ditto
  511. template findFirstUDA(UDA, alias Symbol, bool allow_types = false)
  512. {
  513. enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
  514. }
  515.  
  516. private struct UdaSearchResult(alias UDA)
  517. {
  518. alias value = UDA;
  519. bool found = false;
  520. long index = -1;
  521. }
  522.  
  523. /**
  524. Small convenience wrapper to find and extract certain UDA from given type.
  525. Will start at the given index and stop on the next element which is of required type.
  526.  
  527. Params:
  528. UDA = type or template to search for in UDA list
  529. Symbol = symbol to query for UDA's
  530. idx = 0-based index to start at. Should be positive, and under the total number of attributes.
  531. allow_types = if set to `false` considers attached `UDA` types an error
  532. (only accepts instances/values)
  533.  
  534. Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
  535. `found` is boolean flag for having a valid find. `index` is integer index in
  536. attribute list this UDA was found at.
  537. */
  538. template findNextUDA(alias UDA, alias Symbol, long idx, bool allow_types = false) if (!is(UDA))
  539. {
  540. import std.traits : isInstanceOf;
  541. import std.typetuple : TypeTuple;
  542.  
  543. private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
  544.  
  545. static assert(idx >= 0, "Index given to findNextUDA can't be negative");
  546. static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
  547.  
  548. public template extract(size_t index, list...)
  549. {
  550. static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
  551. else {
  552. static if (is(list[0])) {
  553. static if (is(UDA) && is(list[0] == UDA) || !is(UDA) && isInstanceOf!(UDA, list[0])) {
  554. static assert (allow_types, "findNextUDA is designed to look up values, not types");
  555. enum extract = UdaSearchResult!(list[0])(true, index);
  556. } else enum extract = extract!(index + 1, list[1..$]);
  557. } else {
  558. static if (is(UDA) && is(typeof(list[0]) == UDA) || !is(UDA) && isInstanceOf!(UDA, typeof(list[0]))) {
  559. import vibe.internal.meta.traits : isPropertyGetter;
  560. static if (isPropertyGetter!(list[0])) {
  561. enum value = list[0];
  562. enum extract = UdaSearchResult!(value)(true, index);
  563. } else enum extract = UdaSearchResult!(list[0])(true, index);
  564. } else enum extract = extract!(index + 1, list[1..$]);
  565. }
  566. }
  567. }
  568.  
  569. enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
  570. }
  571. /// ditto
  572. template findNextUDA(UDA, alias Symbol, long idx, bool allow_types = false)
  573. {
  574. import std.traits : isInstanceOf;
  575. import std.typetuple : TypeTuple;
  576.  
  577. private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
  578.  
  579. static assert(idx >= 0, "Index given to findNextUDA can't be negative");
  580. static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
  581.  
  582. public template extract(size_t index, list...)
  583. {
  584. static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
  585. else {
  586. static if (is(list[0])) {
  587. static if (is(list[0] == UDA)) {
  588. static assert (allow_types, "findNextUDA is designed to look up values, not types");
  589. enum extract = UdaSearchResult!(list[0])(true, index);
  590. } else enum extract = extract!(index + 1, list[1..$]);
  591. } else {
  592. static if (is(typeof(list[0]) == UDA)) {
  593. static if (isPropertyGetter!(list[0])) {
  594. enum value = list[0];
  595. enum extract = UdaSearchResult!(value)(true, index);
  596. } else enum extract = UdaSearchResult!(list[0])(true, index);
  597. } else enum extract = extract!(index + 1, list[1..$]);
  598. }
  599. }
  600. }
  601.  
  602. enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
  603. }
  604.  
  605.  
  606. ///
  607. unittest
  608. {
  609. struct Attribute { int x; }
  610.  
  611. @("something", Attribute(42), Attribute(41))
  612. void symbol();
  613.  
  614. enum result0 = findNextUDA!(string, symbol, 0);
  615. static assert (result0.found);
  616. static assert (result0.index == 0);
  617. static assert (result0.value == "something");
  618.  
  619. enum result1 = findNextUDA!(Attribute, symbol, 0);
  620. static assert (result1.found);
  621. static assert (result1.index == 1);
  622. static assert (result1.value == Attribute(42));
  623.  
  624. enum result2 = findNextUDA!(int, symbol, 0);
  625. static assert (!result2.found);
  626.  
  627. enum result3 = findNextUDA!(Attribute, symbol, result1.index + 1);
  628. static assert (result3.found);
  629. static assert (result3.index == 2);
  630. static assert (result3.value == Attribute(41));
  631. }
  632.  
  633. unittest
  634. {
  635. struct Attribute { int x; }
  636.  
  637. @(Attribute) void symbol();
  638.  
  639. static assert (!is(findNextUDA!(Attribute, symbol, 0)));
  640.  
  641. enum result0 = findNextUDA!(Attribute, symbol, 0, true);
  642. static assert (result0.found);
  643. static assert (result0.index == 0);
  644. static assert (is(result0.value == Attribute));
  645. }
  646.  
  647. unittest
  648. {
  649. struct Attribute { int x; }
  650. enum Dummy;
  651.  
  652. @property static Attribute getter()
  653. {
  654. return Attribute(42);
  655. }
  656.  
  657. @Dummy @getter void symbol();
  658.  
  659. enum result0 = findNextUDA!(Attribute, symbol, 0);
  660. static assert (result0.found);
  661. static assert (result0.index == 1);
  662. static assert (result0.value == Attribute(42));
  663. }
  664.  
  665. /// Eager version of findNextUDA that represent all instances of UDA in a Tuple.
  666. /// If one of the attribute is a type instead of an instance, compilation will fail.
  667. template UDATuple(alias UDA, alias Sym) {
  668. import std.typetuple : TypeTuple;
  669.  
  670. private template extract(size_t maxSize, Founds...)
  671. {
  672. private alias LastFound = Founds[$ - 1];
  673. // No more to find
  674. static if (!LastFound.found)
  675. enum extract = Founds[0 .. $ - 1];
  676. else {
  677. // For ease of use, this is a Tuple of UDA, not a tuple of UdaSearchResult!(...)
  678. private alias Result = TypeTuple!(Founds[0 .. $ - 1], LastFound.value);
  679. // We're at the last parameter
  680. static if (LastFound.index == maxSize)
  681. enum extract = Result;
  682. else
  683. enum extract = extract!(maxSize, Result, findNextUDA!(UDA, Sym, LastFound.index + 1));
  684. }
  685. }
  686.  
  687. private enum maxIndex = TypeTuple!(__traits(getAttributes, Sym)).length;
  688. enum UDATuple = extract!(maxIndex, findNextUDA!(UDA, Sym, 0));
  689. }
  690.  
  691. unittest
  692. {
  693. import std.typetuple : TypeTuple;
  694.  
  695. struct Attribute { int x; }
  696. enum Dummy;
  697.  
  698. @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute(84)) void symbol() {}
  699. @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute) void wrong() {}
  700.  
  701. alias Cmp = TypeTuple!(Attribute(21), Attribute(42), Attribute(84));
  702. static assert(Cmp == UDATuple!(Attribute, symbol));
  703. static assert(!is(UDATuple!(Attribute, wrong)));
  704. }
  705.  
  706. /// Avoid repeating the same error message again and again.
  707. /// ----
  708. /// if (!__ctfe)
  709. /// assert(0, onlyAsUda!func);
  710. /// ----
  711. template onlyAsUda(string from /*= __FUNCTION__*/)
  712. {
  713. // With default param, DMD think expression is void, even when writing 'enum string onlyAsUda = ...'
  714. enum onlyAsUda = from~" must only be used as an attribute - not called as a runtime function.";
  715. }