master
h 362 lines 13.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 ArgMap.h
8
9 Abstract:
10
11 Declaration of ArgMap, the container for parsed command-line arguments and their validated
12 (converted) value cache. Split out from ArgumentTypes.h, which holds only the argument enums
13 and type mappings ArgMap is built on.
14
15 --*/
16 #pragma once
17 #include "ArgumentTypes.h"
18 #include "EnumVariantMap.h"
19 #include <any>
20 #include <map>
21 #include <set>
22 #include <type_traits>
23 #include <vector>
24 #include <utility>
25
26 namespace wsl::windows::wslc::argument {
27
28 struct ArgMap;
29
30 namespace details {
31 struct RawArgMapAccess;
32
33 template <ArgType E, bool IsFlag = std::is_same_v<typename ArgDataMapping<E>::value_t, bool>>
34 struct ArgValueTraits;
35
36 template <ArgType E>
37 struct ArgValueTraits<E, true>
38 {
39 using value_t = typename ArgDataMapping<E>::value_t;
40 static constexpr bool Converted = false;
41 };
42
43 template <ArgType E>
44 struct ArgValueTraits<E, false>
45 {
46 using converted_t = typename ArgConvertedTypeMapping<E>::value_t;
47 using value_t = std::conditional_t<std::is_same_v<converted_t, NoConversion>, typename ArgDataMapping<E>::value_t, converted_t>;
48 static constexpr bool Converted = !std::is_same_v<converted_t, NoConversion>;
49 };
50 } // namespace details
51
52 // Validates one argument on demand against its current raw values. Defined in ArgumentValidation.cpp
53 // so this header stays decoupled from the converter/domain headers.
54 void EnsureArgumentValidated(ArgMap& map, ArgType type);
55
56 // Map-action callback (defined after ArgMap, as it calls a member): operations that can mutate raw
57 // values update that ArgType's validation state.
58 inline void ArgMapInvalidateValidatedCache(const void* map, ArgType type, EnumBasedVariantMapAction action);
59
60 // This is the main ArgType map used for storing parsed arguments.
61 struct ArgMap : private wsl::windows::wslc::EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping, &ArgMapInvalidateValidatedCache>
62 {
63 private:
64 using Base = wsl::windows::wslc::EnumBasedVariantMap<ArgType, wsl::windows::wslc::argument::details::ArgDataMapping, &ArgMapInvalidateValidatedCache>;
65
66 friend struct details::RawArgMapAccess;
67
68 // Raw reads are implementation details used by validation and the typed accessors below.
69 // Callers consume arguments through GetValue/GetAllValues so reads validate and freeze them.
70 using Base::Get;
71 using Base::GetAll;
72
73 public:
74 ArgMap() = default;
75 ArgMap(const ArgMap&) = default;
76 ArgMap(ArgMap&&) = default;
77 ArgMap& operator=(const ArgMap&) = delete;
78 ArgMap& operator=(ArgMap&&) = delete;
79
80 using Base::Add;
81 using Base::Contains;
82 using Base::Count;
83 using Base::GetCount;
84 using Base::GetKeys;
85 using Base::IsMatchingType;
86 using Base::Remove;
87
88 template <ArgType E>
89 using value_t = typename details::ArgValueTraits<E>::value_t;
90
91 // Validated-value cache. Argument validation converts raw strings into typed values and caches
92 // them here so execution reuses them without re-parsing. The store is type-erased (std::any keyed
93 // by ArgType) to keep this base header free of the domain headers that define the converted types;
94 // access is by a compile-time ArgType whose value type is derived from the argument's ConvertedType
95 // (ArgumentConvertedTypes.h), so a wrong-type access is a compile error. A multimap preserves order
96 // and multiplicity for arguments that allow multiple values.
97 template <ArgType E>
98 void AddValidated(typename details::ArgConvertedTypeMapping<E>::value_t value)
99 {
100 using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
101 static_assert(
102 !std::is_same_v<value_t, details::NoConversion>,
103 "This argument has no converted type (NoConversion); it cannot be cached. "
104 "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
105
106 ThrowIfImmutable(E, "add converted validation data");
107 m_validated.emplace(E, std::any{std::move(value)});
108 }
109
110 bool ContainsValidated(ArgType type) const
111 {
112 return m_validated.find(type) != m_validated.end();
113 }
114
115 size_t CountValidated(ArgType type) const
116 {
117 return m_validated.count(type);
118 }
119
120 bool IsValidated(ArgType type) const
121 {
122 return m_validatedTypes.count(type) != 0;
123 }
124
125 // Drops `type`'s memoized validation state (converted cache and validated record) so it never
126 // outlives the raw data.
127 void InvalidateValidated(ArgType type)
128 {
129 ThrowIfImmutable(type, "invalidate cached validation data");
130 ClearValidated(type);
131 }
132
133 // Records `type` as validated for its current raw values so reads skip re-validation.
134 void MarkValidated(ArgType type)
135 {
136 if (IsValidated(type))
137 {
138 return;
139 }
140
141 ThrowIfImmutable(type, "mark the argument as validated");
142 m_validatedTypes.insert(type);
143 }
144
145 void HandleMapMutation(ArgType type, EnumBasedVariantMapAction action)
146 {
147 WI_ASSERT(action == EnumBasedVariantMapAction::Add || action == EnumBasedVariantMapAction::GetMutable || action == EnumBasedVariantMapAction::Remove);
148
149 const char* operation = nullptr;
150 switch (action)
151 {
152 case EnumBasedVariantMapAction::Add:
153 operation = "add a raw argument value";
154 break;
155
156 case EnumBasedVariantMapAction::GetMutable:
157 operation = "get mutable access to a raw argument value";
158 break;
159
160 case EnumBasedVariantMapAction::Remove:
161 operation = "remove the raw argument values";
162 break;
163
164 default:
165 WI_ASSERT(false);
166 return;
167 }
168
169 ThrowIfImmutable(type, operation);
170 ClearValidated(type);
171 }
172
173 // Reads an argument in one call: the cached converted value if the argument declares a
174 // ConvertedType, otherwise the raw parsed value. An absent argument resolves to defaultValue,
175 // which defaults to the value type's default constructor. The first resolved default is retained
176 // so later reads return the same effective value. Caller-provided defaults are already typed and
177 // do not populate the raw map or change Contains(). A successful read makes the argument immutable.
178 template <ArgType E>
179 const value_t<E>& GetValue(value_t<E> defaultValue = {})
180 {
181 if (const auto* resolvedDefault = GetResolvedDefault<E>())
182 {
183 return *resolvedDefault;
184 }
185
186 if (!Contains(E))
187 {
188 auto [itr, inserted] = m_resolvedDefaults.emplace(E, std::any{std::move(defaultValue)});
189 WI_ASSERT(inserted);
190 MarkImmutable(E);
191
192 const auto* value = std::any_cast<value_t<E>>(&itr->second);
193 WI_ASSERT_MSG(value != nullptr, "resolved default holds the wrong type for this argument");
194 return *value;
195 }
196
197 if constexpr (!details::ArgValueTraits<E>::Converted)
198 {
199 // Validate-only arguments have no converted cache but can still fail validation, so run
200 // it on demand before returning the raw value (covers values added post-validation).
201 EnsureValidated(E);
202 const auto& value = std::as_const(*this).template Get<E>();
203 MarkImmutable(E);
204 return value;
205 }
206 else
207 {
208 const auto& value = GetValidated<E>();
209 MarkImmutable(E);
210 return value;
211 }
212 }
213
214 // Like GetValue, but returns every value for an argument that may appear multiple times (ArgMap
215 // is a multimap), in insertion order. An absent argument returns an empty vector.
216 template <ArgType E>
217 auto GetAllValues()
218 {
219 static_assert(details::ArgDataMapping<E>::c_kind != Kind::Flag, "GetAllValues is not valid for Kind::Flag arguments.");
220
221 if constexpr (!details::ArgValueTraits<E>::Converted)
222 {
223 // See GetValue: ensure validate-only arguments are checked on demand too.
224 EnsureValidated(E);
225 auto values = GetAll<E>();
226 MarkImmutable(E);
227 return values;
228 }
229 else
230 {
231 auto values = GetAllValidated<E>();
232 MarkImmutable(E);
233 return values;
234 }
235 }
236
237 private:
238 // Validates `type` against its current raw values unless already recorded as validated. The
239 // record is set by a completed validation and cleared by the map-action callback on any raw
240 // Add/Remove, so an argument added or overwritten after the up-front pass is validated on
241 // demand, and its errors reported, exactly like a command-line value.
242 void EnsureValidated(ArgType type)
243 {
244 if (IsValidated(type))
245 {
246 return;
247 }
248
249 EnsureArgumentValidated(*this, type);
250 }
251
252 // Branch helper for GetValue's converted path. Private so callers go through GetValue.
253 template <ArgType E>
254 const typename details::ArgConvertedTypeMapping<E>::value_t& GetValidated()
255 {
256 using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
257 static_assert(
258 !std::is_same_v<value_t, details::NoConversion>,
259 "This argument has no converted type (NoConversion); it cannot be read from the cache. "
260 "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
261
262 // Validate on demand if `E` is not recorded as validated (added or overwritten after the
263 // up-front pass), so the value read here is converted and its errors reported as usual.
264 EnsureValidated(E);
265
266 auto itr = m_validated.find(E);
267 THROW_HR_IF_MSG(E_NOT_SET, itr == m_validated.end(), "GetValidated(%d): argument not validated", static_cast<int>(E));
268
269 // any_cast cannot fail: entries under key E are only ever written by AddValidated<E>, which
270 // stores exactly value_t. A null result is an internal invariant violation, not a runtime case.
271 const value_t* value = std::any_cast<value_t>(&itr->second);
272 WI_ASSERT_MSG(value != nullptr, "validated cache holds the wrong type for this argument");
273
274 return *value;
275 }
276
277 // Branch helper for GetAllValues's converted path. Private so callers go through GetAllValues.
278 template <ArgType E>
279 std::vector<typename details::ArgConvertedTypeMapping<E>::value_t> GetAllValidated()
280 {
281 using value_t = typename details::ArgConvertedTypeMapping<E>::value_t;
282 static_assert(
283 !std::is_same_v<value_t, details::NoConversion>,
284 "This argument has no converted type (NoConversion); it cannot be read from the cache. "
285 "Declare its ConvertedType in ArgumentDefinitions.h to enable caching.");
286
287 // See GetValidated: validate on demand if `E`'s validated record was cleared post-validation.
288 EnsureValidated(E);
289
290 std::vector<value_t> results;
291 auto range = m_validated.equal_range(E);
292 for (auto it = range.first; it != range.second; ++it)
293 {
294 // See GetValidated: any_cast cannot fail for a correctly populated cache.
295 const value_t* value = std::any_cast<value_t>(&it->second);
296 WI_ASSERT_MSG(value != nullptr, "validated cache holds the wrong type for this argument");
297 results.push_back(*value);
298 }
299
300 return results;
301 }
302
303 template <ArgType E>
304 const value_t<E>* GetResolvedDefault() const
305 {
306 const auto itr = m_resolvedDefaults.find(E);
307 if (itr == m_resolvedDefaults.end())
308 {
309 return nullptr;
310 }
311
312 const auto* value = std::any_cast<value_t<E>>(&itr->second);
313 WI_ASSERT_MSG(value != nullptr, "resolved default holds the wrong type for this argument");
314 return value;
315 }
316
317 void MarkImmutable(ArgType type)
318 {
319 m_immutableTypes.insert(type);
320 }
321
322 void ClearValidated(ArgType type)
323 {
324 m_validated.erase(type);
325 m_validatedTypes.erase(type);
326 }
327
328 void ThrowIfImmutable(ArgType type, const char* operation) const
329 {
330 THROW_HR_IF_MSG(
331 E_ILLEGAL_METHOD_CALL,
332 m_immutableTypes.count(type) != 0,
333 "ArgMap argument %d is immutable because its effective value was already read by GetValue/GetAllValues; attempted to "
334 "%hs",
335 static_cast<int>(type),
336 operation);
337 }
338
339 std::multimap<ArgType, std::any> m_validated;
340 std::map<ArgType, std::any> m_resolvedDefaults;
341
342 // ArgTypes validated against their current raw values. Distinct from m_validated (only converted
343 // arguments populate that), so validate-only arguments are covered too. Cleared per type by
344 // InvalidateValidated on a raw Add/Remove.
345 std::set<ArgType> m_validatedTypes;
346
347 // A successful GetValue/GetAllValues makes that ArgType's raw and validated data immutable.
348 std::set<ArgType> m_immutableTypes;
349 };
350
351 // Only operations that can mutate raw values affect validation state; const reads are ignored.
352 // Recovering the non-const ArgMap from the callback's type-erased pointer is valid because these
353 // actions originate from non-const base operations. The base subobject is at offset 0 of ArgMap.
354 inline void ArgMapInvalidateValidatedCache(const void* map, ArgType type, EnumBasedVariantMapAction action)
355 {
356 if (action == EnumBasedVariantMapAction::Add || action == EnumBasedVariantMapAction::GetMutable || action == EnumBasedVariantMapAction::Remove)
357 {
358 const_cast<ArgMap*>(static_cast<const ArgMap*>(map))->HandleMapMutation(type, action);
359 }
360 }
361
362 } // namespace wsl::windows::wslc::argument