Newer
Older
dub_jkp / source / dub / internal / configy / FieldRef.d
@Mathias Lang Mathias Lang on 5 Feb 2024 7 KB Update configy to the latest HEAD
  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. static if (forceOptional || hasUDA!(Ref, CAOptional))
  77. public enum Optional = true;
  78. // Booleans are always optional
  79. else static if (is(immutable(Type) == immutable(bool)))
  80. public enum Optional = true;
  81. // A mandatory SetInfo would not make sense
  82. else static if (is(Type : SetInfo!FT, FT))
  83. public enum Optional = true;
  84. // Use `is` to avoid calling `opEquals` which might not be CTFEable,
  85. // except for static arrays as that triggers a deprecation warning.
  86. else static if (is(Type : E[k], E, size_t k))
  87. public enum Optional = (Default[] !is Type.init[]);
  88. else
  89. public enum Optional = (Default !is Type.init);
  90. }
  91.  
  92. unittest
  93. {
  94. import dub.internal.configy.Attributes : Name;
  95.  
  96. static struct Config1
  97. {
  98. int integer2 = 42;
  99. @Name("notStr2")
  100. @(42) string str2;
  101. }
  102.  
  103. static struct Config2
  104. {
  105. Config1 c1dup = { 42, "Hello World" };
  106. string message = "Something";
  107. }
  108.  
  109. static struct Config3
  110. {
  111. Config1 c1;
  112. int integer;
  113. string str;
  114. Config2 c2 = { c1dup: { integer2: 69 } };
  115. }
  116.  
  117. static assert(is(FieldRef!(Config3, "c2").Type == Config2));
  118. static assert(FieldRef!(Config3, "c2").Default != Config2.init);
  119. static assert(FieldRef!(Config2, "message").Default == Config2.init.message);
  120. alias NFR1 = FieldRef!(Config3, "c2");
  121. alias NFR2 = FieldRef!(NFR1.Ref, "c1dup");
  122. alias NFR3 = FieldRef!(NFR2.Ref, "integer2");
  123. alias NFR4 = FieldRef!(NFR2.Ref, "str2");
  124. static assert(hasUDA!(NFR4.Ref, int));
  125.  
  126. static assert(FieldRefTuple!(Config3)[1].Name == "integer");
  127. static assert(FieldRefTuple!(FieldRefTuple!(Config3)[0].Type)[1].Name == "notStr2");
  128. }
  129.  
  130. /**
  131. * A pseudo `FieldRef` used for structs which are not fields (top-level)
  132. *
  133. * Params:
  134. * ST = Type for which this pseudo-FieldRef is
  135. * DefaultName = A name to give to this FieldRef, default to `null`,
  136. * but required to prevent forward references in `parseAs`.
  137. */
  138. package template StructFieldRef (ST, string DefaultName = null)
  139. {
  140. ///
  141. public enum Ref = ST.init;
  142.  
  143. ///
  144. public alias Type = ST;
  145.  
  146. ///
  147. public enum Default = ST.init;
  148.  
  149. ///
  150. public enum Optional = false;
  151.  
  152. /// Some places reference their parent's Name / FieldName
  153. public enum Name = DefaultName;
  154. /// Ditto
  155. public enum FieldName = DefaultName;
  156. }
  157.  
  158. /// A pseudo `FieldRef` for nested types (e.g. arrays / associative arrays)
  159. package template NestedFieldRef (ElemT, alias FR)
  160. {
  161. ///
  162. public enum Ref = ElemT.init;
  163. ///
  164. public alias Type = ElemT;
  165. ///
  166. public enum Name = FR.Name;
  167. ///
  168. public enum FieldName = FR.FieldName;
  169. /// Element or keys are never optional
  170. public enum Optional = false;
  171.  
  172. }
  173.  
  174. /// Get a tuple of `FieldRef` from a `struct`
  175. package template FieldRefTuple (T)
  176. {
  177. static assert(is(T == struct),
  178. "Argument " ~ T.stringof ~ " to `FieldRefTuple` should be a `struct`");
  179.  
  180. ///
  181. static if (__traits(getAliasThis, T).length == 0)
  182. public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
  183. else
  184. {
  185. /// Tuple of strings of aliased fields
  186. /// As of DMD v2.100.0, only a single alias this is supported in D.
  187. private immutable AliasedFieldNames = __traits(getAliasThis, T);
  188. static assert(AliasedFieldNames.length == 1, "Multiple `alias this` are not supported");
  189.  
  190. // Ignore alias to functions (if it's a property we can't do anything)
  191. static if (isSomeFunction!(__traits(getMember, T, AliasedFieldNames)))
  192. public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
  193. else
  194. {
  195. /// "Base" field names minus aliased ones
  196. private immutable BaseFields = Erase!(AliasedFieldNames, FieldNameTuple!T);
  197. static assert(BaseFields.length == FieldNameTuple!(T).length - 1);
  198.  
  199. public alias FieldRefTuple = AliasSeq!(
  200. staticMap!(Pred, BaseFields),
  201. FieldRefTuple!(typeof(__traits(getMember, T, AliasedFieldNames))));
  202. }
  203. }
  204.  
  205. private alias Pred (string name) = FieldRef!(T, name);
  206. }
  207.  
  208. /// Returns: An alias sequence of field names, taking UDAs (`@Name` et al) into account
  209. package alias FieldsName (T) = staticMap!(FieldRefToName, FieldRefTuple!T);
  210.  
  211. /// Helper template for `staticMap` used for strict mode
  212. private enum FieldRefToName (alias FR) = FR.Name;
  213.  
  214. /// Dub extension
  215. package enum IsPattern (alias FR) = FR.Pattern;
  216. /// Dub extension
  217. package alias Patterns (T) = staticMap!(FieldRefToName, Filter!(IsPattern, FieldRefTuple!T));