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 configy.FieldRef;
19 
20 // Renamed imports as the names exposed by `FieldRef` shadow the imported ones.
21 import 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 informations.
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 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));