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));