@@ -80,7 +80,7 @@ namespace cppwinrt
80
80
// by the caller and callee. The exception to this rule is property setters where the callee may simply store a
81
81
// reference to the collection. The collection thus becomes async in the sense that it is expected to remain
82
82
// valid beyond the duration of the call.
83
-
83
+
84
84
if (is_put_overload (m_method))
85
85
{
86
86
return true ;
@@ -148,10 +148,54 @@ namespace cppwinrt
148
148
return static_cast <bool >(get_attribute (row, type_namespace, type_name));
149
149
}
150
150
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
+
151
195
template <typename T>
152
196
auto get_attribute_value (CustomAttribute const & attribute, uint32_t const arg)
153
197
{
154
- return std::get <T>(std::get<ElemSig>( attribute.Value ().FixedArgs ()[arg]. value ). value );
198
+ return get_attribute_value <T>(attribute.Value ().FixedArgs ()[arg]);
155
199
}
156
200
157
201
static auto get_abi_name (MethodDef const & method)
@@ -283,33 +327,200 @@ namespace cppwinrt
283
327
return bases;
284
328
}
285
329
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)
287
381
{
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
+ }
289
400
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;
290
411
for (auto && attribute : type.CustomAttribute ())
291
412
{
292
- auto name = attribute.TypeNamespaceAndName ();
413
+ auto [ns, name] = attribute.TypeNamespaceAndName ();
414
+ if (ns != " Windows.Foundation.Metadata" )
415
+ {
416
+ continue ;
417
+ }
293
418
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" )
295
476
{
296
477
continue ;
297
478
}
298
479
299
- if (name. second == " ContractVersionAttribute" )
480
+ if (name == " ContractVersionAttribute" )
300
481
{
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) ;
303
484
}
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 ());
304
498
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 ))
306
503
{
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 ());
308
507
break ;
309
508
}
310
509
}
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
+ }
311
522
312
- return { HIWORD (version), LOWORD (version) } ;
523
+ return result ;
313
524
}
314
525
315
526
struct interface_info
@@ -321,7 +532,11 @@ namespace cppwinrt
321
532
bool base{};
322
533
bool exclusive{};
323
534
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{};
325
540
std::vector<std::vector<std::string>> generic_param_stack{};
326
541
};
327
542
@@ -421,7 +636,6 @@ namespace cppwinrt
421
636
}
422
637
423
638
info.exclusive = has_attribute (info.type , " Windows.Foundation.Metadata" , " ExclusiveToAttribute" );
424
- info.version = get_version (info.type );
425
639
get_interfaces_impl (w, result, info.defaulted , info.overridable , base, info.generic_param_stack , info.type .InterfaceImpl ());
426
640
insert_or_assign (result, name, std::move (info));
427
641
}
@@ -443,10 +657,32 @@ namespace cppwinrt
443
657
return result;
444
658
}
445
659
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)
447
663
{
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
+ }
450
686
451
687
std::partial_sort (result.begin (), result.begin () + count, result.end (), [](auto && left_pair, auto && right_pair)
452
688
{
@@ -482,9 +718,9 @@ namespace cppwinrt
482
718
return left_enabled;
483
719
}
484
720
485
- if (left.version != right.version )
721
+ if (left.relative_version != right.relative_version )
486
722
{
487
- return left.version < right.version ;
723
+ return left.relative_version < right.relative_version ;
488
724
}
489
725
490
726
return left_pair.first < right_pair.first ;
0 commit comments