- /*******************************************************************************
-
- Implement a template to keep track of a field references
-
- Passing field references by `alias` template parameter creates many problem,
- and is extremely cumbersome to work with. Instead, we pass an instance of
- a `FieldRef` around, which also contains structured information.
-
- Copyright:
- Copyright (c) 2019-2022 BOSAGORA Foundation
- All rights reserved.
-
- License:
- MIT License. See LICENSE for details.
-
- *******************************************************************************/
-
- module dub.internal.configy.FieldRef;
-
- // Renamed imports as the names exposed by `FieldRef` shadow the imported ones.
- import dub.internal.configy.Attributes : CAName = Name, CAOptional = Optional, SetInfo;
-
- import std.meta;
- import std.traits;
-
- /*******************************************************************************
-
- A reference to a field in a `struct`
-
- The compiler sometimes rejects passing fields by `alias`, or complains about
- missing `this` (meaning it tries to evaluate the value). Sometimes, it also
- discards the UDAs.
-
- To prevent this from happening, we always pass around a `FieldRef`,
- which wraps the parent struct type (`T`), the name of the field
- as `FieldName`, and other information.
-
- To avoid any issue, eponymous usage is also avoided, hence the reference
- needs to be accessed using `Ref`.
-
- *******************************************************************************/
-
- package template FieldRef (alias T, string name, bool forceOptional = false)
- {
- /// The reference to the field
- public alias Ref = __traits(getMember, T, name);
-
- /// Type of the field
- public alias Type = typeof(Ref);
-
- /// The name of the field in the struct itself
- public alias FieldName = name;
-
- /// The name used in the configuration field (taking `@Name` into account)
- static if (hasUDA!(Ref, CAName))
- {
- static assert (getUDAs!(Ref, CAName).length == 1,
- "Field `" ~ fullyQualifiedName!(Ref) ~
- "` cannot have more than one `Name` attribute");
-
- public immutable Name = getUDAs!(Ref, CAName)[0].name;
-
- public immutable Pattern = getUDAs!(Ref, CAName)[0].startsWith;
- }
- else
- {
- public immutable Name = FieldName;
- public immutable Pattern = false;
- }
-
- /// Default value of the field (may or may not be `Type.init`)
- public enum Default = __traits(getMember, T.init, name);
-
- /// Evaluates to `true` if this field is to be considered optional
- /// (does not need to be present in the YAML document)
- public enum Optional = forceOptional ||
- hasUDA!(Ref, CAOptional) ||
- is(immutable(Type) == immutable(bool)) ||
- is(Type : SetInfo!FT, FT) ||
- (Default != Type.init);
- }
-
- unittest
- {
- import dub.internal.configy.Attributes : Name;
-
- static struct Config1
- {
- int integer2 = 42;
- @Name("notStr2")
- @(42) string str2;
- }
-
- static struct Config2
- {
- Config1 c1dup = { 42, "Hello World" };
- string message = "Something";
- }
-
- static struct Config3
- {
- Config1 c1;
- int integer;
- string str;
- Config2 c2 = { c1dup: { integer2: 69 } };
- }
-
- static assert(is(FieldRef!(Config3, "c2").Type == Config2));
- static assert(FieldRef!(Config3, "c2").Default != Config2.init);
- static assert(FieldRef!(Config2, "message").Default == Config2.init.message);
- alias NFR1 = FieldRef!(Config3, "c2");
- alias NFR2 = FieldRef!(NFR1.Ref, "c1dup");
- alias NFR3 = FieldRef!(NFR2.Ref, "integer2");
- alias NFR4 = FieldRef!(NFR2.Ref, "str2");
- static assert(hasUDA!(NFR4.Ref, int));
-
- static assert(FieldRefTuple!(Config3)[1].Name == "integer");
- static assert(FieldRefTuple!(FieldRefTuple!(Config3)[0].Type)[1].Name == "notStr2");
- }
-
- /// A pseudo `FieldRef` used for structs which are not fields (top-level)
- package template StructFieldRef (ST, string DefaultName = null)
- {
- ///
- public enum Ref = ST.init;
-
- ///
- public alias Type = ST;
-
- ///
- public enum Default = ST.init;
-
- ///
- public enum Optional = false;
-
- /// Some places reference their parent's Name / FieldName
- public enum Name = DefaultName;
- /// Ditto
- public enum FieldName = DefaultName;
- }
-
- /// A pseudo `FieldRef` for nested types (e.g. arrays / associative arrays)
- package template NestedFieldRef (ElemT, alias FR)
- {
- ///
- public enum Ref = ElemT.init;
- ///
- public alias Type = ElemT;
- ///
- public enum Name = FR.Name;
- ///
- public enum FieldName = FR.FieldName;
- /// Element or keys are never optional
- public enum Optional = false;
-
- }
-
- /// Get a tuple of `FieldRef` from a `struct`
- package template FieldRefTuple (T)
- {
- static assert(is(T == struct),
- "Argument " ~ T.stringof ~ " to `FieldRefTuple` should be a `struct`");
-
- ///
- static if (__traits(getAliasThis, T).length == 0)
- public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
- else
- {
- /// Tuple of strings of aliased fields
- /// As of DMD v2.100.0, only a single alias this is supported in D.
- private immutable AliasedFieldNames = __traits(getAliasThis, T);
- static assert(AliasedFieldNames.length == 1, "Multiple `alias this` are not supported");
-
- // Ignore alias to functions (if it's a property we can't do anything)
- static if (isSomeFunction!(__traits(getMember, T, AliasedFieldNames)))
- public alias FieldRefTuple = staticMap!(Pred, FieldNameTuple!T);
- else
- {
- /// "Base" field names minus aliased ones
- private immutable BaseFields = Erase!(AliasedFieldNames, FieldNameTuple!T);
- static assert(BaseFields.length == FieldNameTuple!(T).length - 1);
-
- public alias FieldRefTuple = AliasSeq!(
- staticMap!(Pred, BaseFields),
- FieldRefTuple!(typeof(__traits(getMember, T, AliasedFieldNames))));
- }
- }
-
- private alias Pred (string name) = FieldRef!(T, name);
- }
-
- /// Returns: An alias sequence of field names, taking UDAs (`@Name` et al) into account
- package alias FieldsName (T) = staticMap!(FieldRefToName, FieldRefTuple!T);
-
- /// Helper template for `staticMap` used for strict mode
- private enum FieldRefToName (alias FR) = FR.Name;
-
- /// Dub extension
- package enum IsPattern (alias FR) = FR.Pattern;
- /// Dub extension
- package alias Patterns (T) = staticMap!(FieldRefToName, Filter!(IsPattern, FieldRefTuple!T));