@@ -80,7 +80,7 @@ namespace cppwinrt
8080 // by the caller and callee. The exception to this rule is property setters where the callee may simply store a
8181 // reference to the collection. The collection thus becomes async in the sense that it is expected to remain
8282 // valid beyond the duration of the call.
83-
83+
8484 if (is_put_overload (m_method))
8585 {
8686 return true ;
@@ -148,10 +148,54 @@ namespace cppwinrt
148148 return static_cast <bool >(get_attribute (row, type_namespace, type_name));
149149 }
150150
151+ namespace impl
152+ {
153+ template <typename T, typename ... Types>
154+ struct variant_index ;
155+
156+ template <typename T, typename First, typename ... Types>
157+ struct variant_index <T, First, Types...>
158+ {
159+ static constexpr bool found = std::is_same_v<T, First>;
160+ static constexpr std::size_t value = std::conditional_t <found,
161+ std::integral_constant<std::size_t , 0 >,
162+ variant_index<T, Types...>>::value + (found ? 0 : 1 );
163+ };
164+ }
165+
166+ template <typename Variant, typename T>
167+ struct variant_index ;
168+
169+ template <typename ... Types, typename T>
170+ struct variant_index <std::variant<Types...>, T> : impl::variant_index<T, Types...>
171+ {
172+ };
173+
174+ template <typename Variant, typename T>
175+ constexpr std::size_t variant_index_v = variant_index<Variant, T>::value;
176+
177+ template <typename T>
178+ auto get_integer_attribute (FixedArgSig const & signature)
179+ {
180+ auto variant = std::get<ElemSig>(signature.value ).value ;
181+ switch (variant.index ())
182+ {
183+ case variant_index_v<decltype (variant), std::make_unsigned_t <T>>: return static_cast <T>(std::get<std::make_unsigned_t <T>>(variant));
184+ case variant_index_v<decltype (variant), std::make_signed_t <T>>: return static_cast <T>(std::get<std::make_signed_t <T>>(variant));
185+ default : return std::get<T>(variant); // Likely throws, but that's intentional
186+ }
187+ }
188+
189+ template <typename T>
190+ auto get_attribute_value (FixedArgSig const & signature)
191+ {
192+ return std::get<T>(std::get<ElemSig>(signature.value ).value );
193+ }
194+
151195 template <typename T>
152196 auto get_attribute_value (CustomAttribute const & attribute, uint32_t const arg)
153197 {
154- return std::get <T>(std::get<ElemSig>( attribute.Value ().FixedArgs ()[arg]. value ). value );
198+ return get_attribute_value <T>(attribute.Value ().FixedArgs ()[arg]);
155199 }
156200
157201 static auto get_abi_name (MethodDef const & method)
@@ -283,33 +327,200 @@ namespace cppwinrt
283327 return bases;
284328 }
285329
286- static std::pair<uint16_t , uint16_t > get_version (TypeDef const & type)
330+ struct contract_version
331+ {
332+ std::string_view name;
333+ uint32_t version;
334+ };
335+
336+ struct previous_contract
337+ {
338+ std::string_view contract_from;
339+ std::string_view contract_to;
340+ uint32_t version_low;
341+ uint32_t version_high;
342+ };
343+
344+ struct contract_history
345+ {
346+ contract_version current_contract;
347+
348+ // Sorted such that the first entry is the first contract the type was introduced in
349+ std::vector<previous_contract> previous_contracts;
350+ };
351+
352+ static contract_version decode_contract_version_attribute (CustomAttribute const & attribute)
353+ {
354+ // ContractVersionAttribute has three constructors, but only two we care about here:
355+ // .ctor(string contract, uint32 version)
356+ // .ctor(System.Type contract, uint32 version)
357+ auto signature = attribute.Value ();
358+ auto & args = signature.FixedArgs ();
359+ assert (args.size () == 2 );
360+
361+ contract_version result{};
362+ result.version = get_integer_attribute<uint32_t >(args[1 ]);
363+ call (std::get<ElemSig>(args[0 ].value ).value ,
364+ [&](ElemSig::SystemType t)
365+ {
366+ result.name = t.name ;
367+ },
368+ [&](std::string_view name)
369+ {
370+ result.name = name;
371+ },
372+ [](auto &&)
373+ {
374+ assert (false );
375+ });
376+
377+ return result;
378+ }
379+
380+ static previous_contract decode_previous_contract_attribute (CustomAttribute const & attribute)
287381 {
288- uint32_t version{};
382+ // PreviousContractVersionAttribute has two constructors:
383+ // .ctor(string fromContract, uint32 versionLow, uint32 versionHigh)
384+ // .ctor(string fromContract, uint32 versionLow, uint32 versionHigh, string contractTo)
385+ auto signature = attribute.Value ();
386+ auto & args = signature.FixedArgs ();
387+ assert (args.size () >= 3 );
388+
389+ previous_contract result{};
390+ result.contract_from = get_attribute_value<std::string_view>(args[0 ]);
391+ result.version_low = get_integer_attribute<uint32_t >(args[1 ]);
392+ result.version_high = get_integer_attribute<uint32_t >(args[2 ]);
393+ if (args.size () == 4 )
394+ {
395+ result.contract_to = get_attribute_value<std::string_view>(args[3 ]);
396+ }
397+
398+ return result;
399+ }
289400
401+ static contract_version get_initial_contract_version (TypeDef const & type)
402+ {
403+ // Most types don't have previous contracts, so optimize for that scenario to avoid unnecessary allocations
404+ contract_version current_contract{};
405+
406+ // The initial contract, assuming the type has moved contracts, is the only contract name that doesn't appear as
407+ // a "to contract" argument to a PreviousContractVersionAttribute. Note that this assumes that a type does not
408+ // "return" to a prior contract, however this is a restriction enforced by midlrt
409+ std::vector<contract_version> previous_contracts;
410+ std::vector<std::string_view> to_contracts;
290411 for (auto && attribute : type.CustomAttribute ())
291412 {
292- auto name = attribute.TypeNamespaceAndName ();
413+ auto [ns, name] = attribute.TypeNamespaceAndName ();
414+ if (ns != " Windows.Foundation.Metadata" )
415+ {
416+ continue ;
417+ }
293418
294- if (name.first != " Windows.Foundation.Metadata" )
419+ if (name == " ContractVersionAttribute" )
420+ {
421+ assert (current_contract.name .empty ());
422+ current_contract = decode_contract_version_attribute (attribute);
423+ }
424+ else if (name == " PreviousContractVersionAttribute" )
425+ {
426+ auto prev = decode_previous_contract_attribute (attribute);
427+
428+ // If this contract was the target of an earlier contract change, we know this isn't the initial one
429+ if (std::find (to_contracts.begin (), to_contracts.end (), prev.contract_from ) == to_contracts.end ())
430+ {
431+ previous_contracts.push_back (contract_version{ prev.contract_from , prev.version_low });
432+ }
433+
434+ if (!prev.contract_to .empty ())
435+ {
436+ auto itr = std::find_if (previous_contracts.begin (), previous_contracts.end (), [&](auto const & ver)
437+ {
438+ return ver.name == prev.contract_to ;
439+ });
440+ if (itr != previous_contracts.end ())
441+ {
442+ *itr = previous_contracts.back ();
443+ previous_contracts.pop_back ();
444+ }
445+
446+ to_contracts.push_back (prev.contract_to );
447+ }
448+ }
449+ else if (name == " VersionAttribute" )
450+ {
451+ // Prefer contract versioning, if present. Otherwise, use an empty contract name to indicate that this
452+ // is not a contract version
453+ if (current_contract.name .empty ())
454+ {
455+ current_contract.version = get_attribute_value<uint32_t >(attribute, 0 );
456+ }
457+ }
458+ }
459+
460+ if (!previous_contracts.empty ())
461+ {
462+ assert (previous_contracts.size () == 1 );
463+ return previous_contracts[0 ];
464+ }
465+
466+ return current_contract;
467+ }
468+
469+ static contract_history get_contract_history (TypeDef const & type)
470+ {
471+ contract_history result{};
472+ for (auto && attribute : type.CustomAttribute ())
473+ {
474+ auto [ns, name] = attribute.TypeNamespaceAndName ();
475+ if (ns != " Windows.Foundation.Metadata" )
295476 {
296477 continue ;
297478 }
298479
299- if (name. second == " ContractVersionAttribute" )
480+ if (name == " ContractVersionAttribute" )
300481 {
301- version = get_attribute_value< uint32_t >(attribute, 1 );
302- break ;
482+ assert (result. current_contract . name . empty () );
483+ result. current_contract = decode_contract_version_attribute (attribute) ;
303484 }
485+ else if (name == " PreviousContractVersionAttribute" )
486+ {
487+ result.previous_contracts .push_back (decode_previous_contract_attribute (attribute));
488+ }
489+ // We could report the version that the type was introduced if the type is not contract versioned, however
490+ // that information is not useful to us anywhere, so just skip it
491+ }
492+
493+ if (result.previous_contracts .empty ())
494+ {
495+ return result;
496+ }
497+ assert (!result.current_contract .name .empty ());
304498
305- if (name.second == " VersionAttribute" )
499+ // There's no guarantee that the contract history will be sorted in metadata (in fact it's unlikely to be)
500+ for (auto & prev : result.previous_contracts )
501+ {
502+ if (prev.contract_to .empty () || (prev.contract_to == result.current_contract .name ))
306503 {
307- version = get_attribute_value<uint32_t >(attribute, 0 );
504+ // No 'to' contract indicates that this was the last contract before the current one
505+ prev.contract_to = result.current_contract .name ;
506+ std::swap (prev, result.previous_contracts .back ());
308507 break ;
309508 }
310509 }
510+ assert (result.previous_contracts .back ().contract_to == result.current_contract .name );
511+
512+ for (size_t size = result.previous_contracts .size () - 1 ; size; --size)
513+ {
514+ auto & last = result.previous_contracts [size];
515+ auto itr = std::find_if (result.previous_contracts .begin (), result.previous_contracts .begin () + size, [&](auto const & prev)
516+ {
517+ return prev.contract_to == last.contract_from ;
518+ });
519+ assert (itr != result.previous_contracts .end ());
520+ std::swap (*itr, result.previous_contracts [size - 1 ]);
521+ }
311522
312- return { HIWORD (version), LOWORD (version) } ;
523+ return result ;
313524 }
314525
315526 struct interface_info
@@ -321,7 +532,11 @@ namespace cppwinrt
321532 bool base{};
322533 bool exclusive{};
323534 bool fastabi{};
324- std::pair<uint16_t , uint16_t > version{};
535+ // A pair of (relativeContract, version) where 'relativeContract' is the contract the interface was introduced
536+ // in relative to the contract history of the class. E.g. if a class goes from contract 'A' to 'B' to 'C',
537+ // 'relativeContract' would be '0' for an interface introduced in contract 'A', '1' for an interface introduced
538+ // in contract 'B', etc. This is only set/valid for 'fastabi' interfaces
539+ std::pair<uint32_t , uint32_t > relative_version{};
325540 std::vector<std::vector<std::string>> generic_param_stack{};
326541 };
327542
@@ -421,7 +636,6 @@ namespace cppwinrt
421636 }
422637
423638 info.exclusive = has_attribute (info.type , " Windows.Foundation.Metadata" , " ExclusiveToAttribute" );
424- info.version = get_version (info.type );
425639 get_interfaces_impl (w, result, info.defaulted , info.overridable , base, info.generic_param_stack , info.type .InterfaceImpl ());
426640 insert_or_assign (result, name, std::move (info));
427641 }
@@ -443,10 +657,32 @@ namespace cppwinrt
443657 return result;
444658 }
445659
446- auto count = std::count_if (result.begin (), result.end (), [](auto && pair)
660+ auto history = get_contract_history (type);
661+ size_t count = 0 ;
662+ for (auto & pair : result)
447663 {
448- return pair.second .exclusive && !pair.second .base && !pair.second .overridable ;
449- });
664+ if (pair.second .exclusive && !pair.second .base && !pair.second .overridable )
665+ {
666+ ++count;
667+
668+ auto introduced = get_initial_contract_version (pair.second .type );
669+ pair.second .relative_version .second = introduced.version ;
670+
671+ auto itr = std::find_if (history.previous_contracts .begin (), history.previous_contracts .end (), [&](previous_contract const & prev)
672+ {
673+ return prev.contract_from == introduced.name ;
674+ });
675+ if (itr != history.previous_contracts .end ())
676+ {
677+ pair.second .relative_version .first = static_cast <uint32_t >(itr - history.previous_contracts .begin ());
678+ }
679+ else
680+ {
681+ assert (history.current_contract .name == introduced.name );
682+ pair.second .relative_version .first = static_cast <uint32_t >(history.previous_contracts .size ());
683+ }
684+ }
685+ }
450686
451687 std::partial_sort (result.begin (), result.begin () + count, result.end (), [](auto && left_pair, auto && right_pair)
452688 {
@@ -482,9 +718,9 @@ namespace cppwinrt
482718 return left_enabled;
483719 }
484720
485- if (left.version != right.version )
721+ if (left.relative_version != right.relative_version )
486722 {
487- return left.version < right.version ;
723+ return left.relative_version < right.relative_version ;
488724 }
489725
490726 return left_pair.first < right_pair.first ;
0 commit comments