Newer
Older
dub_jkp / source / dub / internal / configy / FieldRef.d
@WebFreak001 WebFreak001 on 4 Feb 2023 6 KB fix typo(s)
  1. /*******************************************************************************
  2.  
  3. Implement a template to keep track of a field references
  4.  
  5. Passing field references by `alias` template parameter creates many problem,
  6. and is extremely cumbersome to work with. Instead, we pass an instance of
  7. a `FieldRef` around, which also contains structured information.
  8.  
  9. Copyright:
  10. Copyright (c) 2019-2022 BOSAGORA Foundation
  11. All rights reserved.
  12.  
  13. License:
  14. MIT License. See LICENSE for details.
  15.  
  16. *******************************************************************************/
  17.  
  18. module dub.internal.configy.FieldRef;
  19.  
  20. // Renamed imports as the names exposed by `FieldRef` shadow the imported ones.
  21. import dub.internal.configy.Attributes : CAName = Name, CAOptional = Optional, SetInfo;
  22.  
  23. import std.meta;
  24. import std.traits;
  25.  
  26. /*******************************************************************************
  27.  
  28. A reference to a field in a `struct`
  29.  
  30. The compiler sometimes rejects passing fields by `alias`, or complains about
  31. missing `this` (meaning it tries to evaluate the value). Sometimes, it also
  32. discards the UDAs.
  33.  
  34. To prevent this from happening, we always pass around a `FieldRef`,
  35. which wraps the parent struct type (`T`), the name of the field
  36. as `FieldName`, and other information.
  37.  
  38. To avoid any issue, eponymous usage is also avoided, hence the reference
  39. needs to be accessed using `Ref`.
  40.  
  41. *******************************************************************************/
  42.  
  43. package template FieldRef (alias T, string name, bool forceOptional = false)
  44. {
  45. /// The reference to the field
  46. public alias Ref = __traits(getMember, T, name);
  47.  
  48. /// Type of the field
  49. public alias Type = typeof(Ref);
  50.  
  51. /// The name of the field in the struct itself
  52. public alias FieldName = name;
  53.  
  54. /// The name used in the configuration field (taking `@Name` into account)
  55. static if (hasUDA!(Ref, CAName))
  56. {
  57. static assert (getUDAs!(Ref, CAName).length == 1,
  58. "Field `" ~ fullyQualifiedName!(Ref) ~
  59. "` cannot have more than one `Name` attribute");
  60.  
  61. public immutable Name = getUDAs!(Ref, CAName)[0].name;
  62.  
  63. public immutable Pattern = getUDAs!(Ref, CAName)[0].startsWith;
  64. }
  65. else
  66. {
  67. public immutable Name = FieldName;
  68. public immutable Pattern = false;
  69. }
  70.  
  71. /// Default value of the field (may or may not be `Type.init`)
  72. public enum Default = __traits(getMember, T.init, name);
  73.  
  74. /// Evaluates to `true` if this field is to be considered optional
  75. /// (does not need to be present in the YAML document)
  76. public enum Optional = forceOptional ||
  77. hasUDA!(Ref, CAOptional) ||
  78. is(immutable(Type) == immutable(bool)) ||
  79. is(Type : SetInfo!FT, FT) ||
  80. (Default != Type.init);
  81. }
  82.  
  83. unittest
  84. {
  85. import dub.internal.configy.Attributes : Name;
  86.  
  87. static struct Config1
  88. {
  89. int integer2 = 42;
  90. @Name("notStr2")
  91. @(42) string str2;
  92. }
  93.  
  94. static struct Config2
  95. {
  96. Config1 c1dup = { 42, "Hello World" };
  97. string message = "Something";
  98. }
  99.  
  100. static struct Config3
  101. {
  102. Config1 c1;
  103. int integer;
  104. string str;
  105. Config2 c2 = { c1dup: { integer2: 69 } };
  106. }
  107.  
  108. static assert(is(FieldRef!(Config3, "c2").Type == Config2));
  109. static assert(FieldRef!(Config3, "c2").Default != Config2.init);
  110. static assert(FieldRef!(Config2, "message").Default == Config2.init.message);
  111. alias NFR1 = FieldRef!(Config3, "c2");
  112. alias NFR2 = FieldRef!(NFR1.Ref, "c1dup");
  113. alias NFR3 = FieldRef!(NFR2.Ref, "integer2");
  114. alias NFR4 = FieldRef!(NFR2.Ref, "str2");
  115. static assert(hasUDA!(NFR4.Ref, int));
  116.  
  117. static assert(FieldRefTuple!(Config3)[1].Name == "integer");
  118. static assert(FieldRefTuple!(FieldRefTuple!(Config3)[0].Type)[1].Name == "notStr2");
  119. }
  120.  
  121. /// A pseudo `FieldRef` used for structs which are not fields (top-level)
  122. package template StructFieldRef (ST, string DefaultName = null)
  123. {
  124. ///
  125. public enum Ref = ST.init;
  126.  
  127. ///
  128. public alias Type = ST;
  129.  
  130. ///
  131. public enum Default = ST.init;
  132.  
  133. ///
  134. public enum Optional = false;
  135.  
  136. /// Some places reference their parent's Name / FieldName
  137. public enum Name = DefaultName;
  138. /// Ditto
  139. public enum FieldName = DefaultName;
  140. }
  141.  
  142. /// A pseudo `FieldRef` for nested types (e.g. arrays / associative arrays)
  143. package template NestedFieldRef (ElemT, alias FR)
  144. {
  145. ///
  146. public enum Ref = ElemT.init;
  147. ///
  148. public alias Type = ElemT;
  149. ///
  150. public enum Name = FR.Name;
  151. ///
  152. public enum FieldName = FR.FieldName;
  153. /// Element or keys are never optional
  154. public enum Optional = false;
  155.  
  156. }
  157.  
  158. /// Get a tuple of `FieldRef` from a `struct`
  159. package template FieldRefTuple (T)
  160. {
  161. static assert(is(T == struct),
  162. "Argument " ~ T.stringof ~ " to `FieldRefTuple` should be a `struct`");
  163.  
  164. ///
  165. static if (__traits(getAliasThis, T).length == 0)
  166. public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
  167. else
  168. {
  169. /// Tuple of strings of aliased fields
  170. /// As of DMD v2.100.0, only a single alias this is supported in D.
  171. private immutable AliasedFieldNames = __traits(getAliasThis, T);
  172. static assert(AliasedFieldNames.length == 1, "Multiple `alias this` are not supported");
  173.  
  174. // Ignore alias to functions (if it's a property we can't do anything)
  175. static if (isSomeFunction!(__traits(getMember, T, AliasedFieldNames)))
  176. public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
  177. else
  178. {
  179. /// "Base" field names minus aliased ones
  180. private immutable BaseFields = Erase!(AliasedFieldNames, FieldNameTuple!T);
  181. static assert(BaseFields.length == FieldNameTuple!(T).length - 1);
  182.  
  183. public alias FieldRefTuple = AliasSeq!(
  184. staticMap!(Pred, BaseFields),
  185. FieldRefTuple!(typeof(__traits(getMember, T, AliasedFieldNames))));
  186. }
  187. }
  188.  
  189. private alias Pred (string name) = FieldRef!(T, name);
  190. }
  191.  
  192. /// Returns: An alias sequence of field names, taking UDAs (`@Name` et al) into account
  193. package alias FieldsName (T) = staticMap!(FieldRefToName, FieldRefTuple!T);
  194.  
  195. /// Helper template for `staticMap` used for strict mode
  196. private enum FieldRefToName (alias FR) = FR.Name;
  197.  
  198. /// Dub extension
  199. package enum IsPattern (alias FR) = FR.Pattern;
  200. /// Dub extension
  201. package alias Patterns (T) = staticMap!(FieldRefToName, Filter!(IsPattern, FieldRefTuple!T));