Skip to content

Commit 728b132

Browse files
authored
[SYCL] Specific error messages for invalid properties on non pointer types (#11843)
This change adds specific error messages for invalid properties that are specified on non pointer types. This change is directly in the header annotated_arg.hpp and these checks are done before the `check_property_list` check which previously accounted for this but had a generic error message. This change adds static asserts so that the error message specifies the invalid property and that it is invalid due to a non pointer type for the underlying data type. For example, if the user tries to declare the following variable: ```cpp annotated_arg<int, decltype(properties{buffer_location<0>})> a; ``` The following error message is outputted: ``` error: static assertion failed due to requirement '!has_buffer_location': Property buffer_location cannot be specified for annotated_arg<T> when T is a non pointer type. 288 | static_assert(!has_buffer_location, | ^~~~~~~~~~~~~~~~~~~~ ``` Prior to this change, the following generic error message was printed with multiple notes: ``` /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/common_annotated_properties/properties.hpp:59:17: error: static assertion failed due to requirement 'is_valid_property_for_given_type': Property is invalid for the given type. 59 | static_assert(is_valid_property_for_given_type, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:307:7: note: in instantiation of template class 'sycl::ext::oneapi::experimental::check_property_list<int, sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>' requested here 307 | check_property_list<T, Props...>::value; | ^ /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:308:17: note: in instantiation of static data member 'sycl::ext::oneapi::experimental::annotated_arg<int, sycl::ext::oneapi::experimental::properties<std::tuple<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>>>::contains_valid_properties' requested here 308 | static_assert(contains_valid_properties, | ^ temp.cpp:15:66: note: in instantiation of template class 'sycl::ext::oneapi::experimental::annotated_arg<int, sycl::ext::oneapi::experimental::properties<std::tuple<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>>>' requested here 15 | annotated_arg<int, decltype(properties{buffer_location<0>})> a; | ^ ``` ### Alternate Methods PR #11748 adds the same checks but in the `fpga_annotated_properties.hpp` file as a separate check. This PR has the checks directly in the `annotated_arg.hpp` header file so that there are fewer notes printed below the error message. Here is a comparison of how the error message is outputted when the checks are in different files: With error checks in properties header file and using a macro (PR #11748): ``` /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/intel/experimental/fpga_annotated_properties.hpp:396:3: error: static assertion failed due to requirement '!has_buffer_location': Property buffer_location cannot be specified for annotated_arg<T> when T is a non pointer type. 396 | CHECK_INVALID_PROPERTY(buffer_location, list) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/intel/experimental/fpga_annotated_properties.hpp:389:17: note: expanded from macro 'CHECK_INVALID_PROPERTY' 389 | static_assert(!has_##property, \ | ^~~~~~~~~~~~~~~ /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:202:25: note: in instantiation of template class 'sycl::ext::oneapi::experimental::detail::checkPropertiesForNonPointerType<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>' requested here 202 | static_assert(detail::checkPropertiesForNonPointerType<Props...>::value); | ^ temp.cpp:15:66: note: in instantiation of template class 'sycl::ext::oneapi::experimental::annotated_arg<int, sycl::ext::oneapi::experimental::properties<std::tuple<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>>>' requested here 15 | annotated_arg<int, decltype(properties{buffer_location<0>})> a; | ^ ``` With error check in annotated_arg header and using a macro: ``` /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:292:3: error: static assertion failed due to requirement '!has_buffer_location': Property buffer_location cannot be specified for annotated_arg<T> when T is a non pointer type. 292 | CHECK_INVALID_PROPERTY(buffer_location, property_tuple) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:23:17: note: expanded from macro 'CHECK_INVALID_PROPERTY' 23 | static_assert(!has_##property, \ | ^~~~~~~~~~~~~~~ temp.cpp:15:66: note: in instantiation of template class 'sycl::ext::oneapi::experimental::annotated_arg<int, sycl::ext::oneapi::experimental::properties<std::tuple<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>>>' requested here 15 | annotated_arg<int, decltype(properties{buffer_location<0>})> a; | ^ ``` ### This Change With error check in annotated_arg header and not using a macro (This PR): ``` /p/psg/swip/w/yugteshs/sycl_include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:288:17: error: static assertion failed due to requirement '!has_buffer_location': Property buffer_location cannot be specified for annotated_arg<T> when T is a non pointer type. 288 | static_assert(!has_buffer_location, | ^~~~~~~~~~~~~~~~~~~~ temp.cpp:15:66: note: in instantiation of template class 'sycl::ext::oneapi::experimental::annotated_arg<int, sycl::ext::oneapi::experimental::properties<std::tuple<sycl::ext::oneapi::experimental::property_value<sycl::ext::intel::experimental::buffer_location_key, std::integral_constant<int, 0>>>>>' requested here 15 | annotated_arg<int, decltype(properties{buffer_location<0>})> a; | ^ ``` This version only prints one note and therefore it is more clear for the user.
1 parent c0291b2 commit 728b132

File tree

5 files changed

+170
-74
lines changed

5 files changed

+170
-74
lines changed

sycl/include/sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp

Lines changed: 105 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <sycl/detail/defines.hpp>
1212
#include <sycl/ext/intel/experimental/fpga_annotated_properties.hpp>
13+
#include <sycl/ext/oneapi/experimental/annotated_ptr/annotated_ptr_properties.hpp>
1314
#include <sycl/ext/oneapi/experimental/common_annotated_properties/properties.hpp>
1415
#include <sycl/ext/oneapi/properties/properties.hpp>
1516

@@ -84,26 +85,6 @@ __SYCL_TYPE(annotated_arg) annotated_arg<T *, detail::properties_t<Props...>> {
8485
#endif
8586

8687
public:
87-
static constexpr bool is_valid_property_list =
88-
is_property_list<property_list_t>::value;
89-
static_assert(is_valid_property_list, "Property list is invalid.");
90-
static constexpr bool contains_valid_properties =
91-
check_property_list<T *, Props...>::value;
92-
static_assert(contains_valid_properties,
93-
"The property list contains invalid property.");
94-
// check the set if FPGA specificed properties are used
95-
static constexpr bool hasValidFPGAProperties =
96-
detail::checkValidFPGAPropertySet<Props...>::value;
97-
static_assert(hasValidFPGAProperties,
98-
"FPGA Interface properties (i.e. awidth, dwidth, etc.)"
99-
"can only be set with BufferLocation together.");
100-
// check if conduit and register_map properties are specified together
101-
static constexpr bool hasConduitAndRegisterMapProperties =
102-
detail::checkHasConduitAndRegisterMap<Props...>::value;
103-
static_assert(hasConduitAndRegisterMapProperties,
104-
"The properties conduit and register_map cannot be "
105-
"specified at the same time.");
106-
10788
annotated_arg() noexcept = default;
10889
annotated_arg(const annotated_arg &) = default;
10990
annotated_arg &operator=(annotated_arg &) = default;
@@ -191,6 +172,34 @@ __SYCL_TYPE(annotated_arg) annotated_arg<T *, detail::properties_t<Props...>> {
191172
template <typename PropertyT> static constexpr auto get_property() {
192173
return property_list_t::template get_property<PropertyT>();
193174
}
175+
176+
// *************************************************************************
177+
// All static error checking is added here instead of placing inside neat
178+
// functions to minimize the number lines printed out when an assert
179+
// is triggered.
180+
// static constexprs are used to ensure that the triggered assert prints
181+
// a message that is very readable. Without these, the assert will
182+
// print out long templated names
183+
// *************************************************************************
184+
static constexpr bool is_valid_property_list =
185+
is_property_list<property_list_t>::value;
186+
static_assert(is_valid_property_list, "Property list is invalid.");
187+
static constexpr bool contains_valid_properties =
188+
check_property_list<T *, Props...>::value;
189+
static_assert(contains_valid_properties,
190+
"The property list contains invalid property.");
191+
// check the set if FPGA specificed properties are used
192+
static constexpr bool hasValidFPGAProperties =
193+
detail::checkValidFPGAPropertySet<Props...>::value;
194+
static_assert(hasValidFPGAProperties,
195+
"FPGA Interface properties (i.e. awidth, dwidth, etc.) "
196+
"can only be set with BufferLocation together.");
197+
// check if conduit and register_map properties are specified together
198+
static constexpr bool hasConduitAndRegisterMapProperties =
199+
detail::checkHasConduitAndRegisterMap<Props...>::value;
200+
static_assert(hasConduitAndRegisterMapProperties,
201+
"The properties conduit and register_map cannot be "
202+
"specified at the same time.");
194203
};
195204

196205
// Partial specialization for non-pointer type
@@ -212,28 +221,6 @@ __SYCL_TYPE(annotated_arg) annotated_arg<T, detail::properties_t<Props...>> {
212221
#endif
213222

214223
public:
215-
static constexpr bool is_device_copyable = is_device_copyable_v<T>;
216-
static_assert(is_device_copyable, "Type T must be device copyable.");
217-
static constexpr bool is_valid_property_list =
218-
is_property_list<property_list_t>::value;
219-
static_assert(is_valid_property_list, "Property list is invalid.");
220-
static constexpr bool contains_valid_properties =
221-
check_property_list<T, Props...>::value;
222-
static_assert(contains_valid_properties,
223-
"The property list contains invalid property.");
224-
// check the set if FPGA specificed properties are used
225-
static constexpr bool hasValidFPGAProperties =
226-
detail::checkValidFPGAPropertySet<Props...>::value;
227-
static_assert(hasValidFPGAProperties,
228-
"FPGA Interface properties (i.e. awidth, dwidth, etc.)"
229-
"can only be set with BufferLocation together.");
230-
// check if conduit and register_map properties are specified together
231-
static constexpr bool hasConduitAndRegisterMapProperties =
232-
detail::checkHasConduitAndRegisterMap<Props...>::value;
233-
static_assert(hasConduitAndRegisterMapProperties,
234-
"The properties conduit and register_map cannot be "
235-
"specified at the same time.");
236-
237224
annotated_arg() noexcept = default;
238225
annotated_arg(const annotated_arg &) = default;
239226
annotated_arg &operator=(annotated_arg &) = default;
@@ -383,6 +370,82 @@ __SYCL_TYPE(annotated_arg) annotated_arg<T, detail::properties_t<Props...>> {
383370
R operator<<(const annotated_arg<T2, PropertyList2> &other) const {
384371
return obj << other.obj;
385372
}
373+
374+
// *************************************************************************
375+
// All static error checking is added here instead of placing inside neat
376+
// functions to minimize the number lines printed out when an assert
377+
// is triggered.
378+
// static constexprs are used to ensure that the triggered assert prints
379+
// a message that is very readable. Without these, the assert will
380+
// print out long templated names
381+
// *************************************************************************
382+
static constexpr bool is_device_copyable = is_device_copyable_v<T>;
383+
static_assert(is_device_copyable, "Type T must be device copyable.");
384+
385+
// check if invalid properties are specified for non pointer type
386+
static constexpr bool has_buffer_location =
387+
has_property<buffer_location_key>();
388+
static_assert(!has_buffer_location,
389+
"Property buffer_location cannot be specified for "
390+
"annotated_arg<T> when T is a non pointer type.");
391+
392+
static constexpr bool has_awidth = has_property<awidth_key>();
393+
static_assert(!has_awidth, "Property awidth cannot be specified for "
394+
"annotated_arg<T> when T is a non pointer type.");
395+
396+
static constexpr bool has_dwidth = has_property<dwidth_key>();
397+
static_assert(!has_dwidth, "Property dwidth cannot be specified for "
398+
"annotated_arg<T> when T is a non pointer type.");
399+
400+
static constexpr bool has_latency = has_property<latency_key>();
401+
static_assert(!has_latency, "Property latency cannot be specified for "
402+
"annotated_arg<T> when T is a non pointer type.");
403+
404+
static constexpr bool has_read_write_mode =
405+
has_property<read_write_mode_key>();
406+
static_assert(!has_read_write_mode,
407+
"Property read_write_mode cannot be specified for "
408+
"annotated_arg<T> when T is a non pointer type.");
409+
410+
static constexpr bool has_maxburst = has_property<maxburst_key>();
411+
static_assert(!has_maxburst,
412+
"Property maxburst cannot be specified for "
413+
"annotated_arg<T> when T is a non pointer type.");
414+
415+
static constexpr bool has_wait_request = has_property<wait_request_key>();
416+
static_assert(!has_wait_request,
417+
"Property wait_request cannot be specified for "
418+
"annotated_arg<T> when T is a non pointer type.");
419+
420+
static constexpr bool has_alignment = has_property<alignment_key>();
421+
static_assert(!has_alignment,
422+
"Property alignment cannot be specified for "
423+
"annotated_arg<T> when T is a non pointer type.");
424+
425+
static constexpr bool has_usm_kind = has_property<usm_kind_key>();
426+
static_assert(!has_usm_kind,
427+
"Property usm_kind cannot be specified for "
428+
"annotated_arg<T> when T is a non pointer type.");
429+
430+
static constexpr bool is_valid_property_list =
431+
is_property_list<property_list_t>::value;
432+
static_assert(is_valid_property_list, "Property list is invalid.");
433+
static constexpr bool contains_valid_properties =
434+
check_property_list<T, Props...>::value;
435+
static_assert(contains_valid_properties,
436+
"The property list contains invalid property.");
437+
// check the set if FPGA specificed properties are used
438+
static constexpr bool hasValidFPGAProperties =
439+
detail::checkValidFPGAPropertySet<Props...>::value;
440+
static_assert(hasValidFPGAProperties,
441+
"FPGA Interface properties (i.e. awidth, dwidth, etc.) "
442+
"can only be set with BufferLocation together.");
443+
// check if conduit and register_map properties are specified together
444+
static constexpr bool hasConduitAndRegisterMapProperties =
445+
detail::checkHasConduitAndRegisterMap<Props...>::value;
446+
static_assert(hasConduitAndRegisterMapProperties,
447+
"The properties conduit and register_map cannot be "
448+
"specified at the same time.");
386449
};
387450

388451
template <typename T, typename PropertyList, typename T2,

sycl/include/sycl/ext/oneapi/experimental/annotated_ptr/annotated_ptr.hpp

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -219,26 +219,6 @@ __SYCL_TYPE(annotated_ptr) annotated_ptr<T, detail::properties_t<Props...>> {
219219
#endif
220220

221221
public:
222-
static constexpr bool is_valid_property_list =
223-
is_property_list<property_list_t>::value;
224-
static_assert(is_valid_property_list, "Property list is invalid.");
225-
static constexpr bool contains_valid_properties =
226-
check_property_list<T *, Props...>::value;
227-
static_assert(contains_valid_properties,
228-
"The property list contains invalid property.");
229-
// check the set if FPGA specificed properties are used
230-
static constexpr bool hasValidFPGAProperties =
231-
detail::checkValidFPGAPropertySet<Props...>::value;
232-
static_assert(hasValidFPGAProperties,
233-
"FPGA Interface properties (i.e. awidth, dwidth, etc.)"
234-
"can only be set with BufferLocation together.");
235-
// check if conduit and register_map properties are specified together
236-
static constexpr bool hasConduitAndRegisterMapProperties =
237-
detail::checkHasConduitAndRegisterMap<Props...>::value;
238-
static_assert(hasConduitAndRegisterMapProperties,
239-
"The properties conduit and register_map cannot be "
240-
"specified at the same time.");
241-
242222
annotated_ptr() noexcept = default;
243223
annotated_ptr(const annotated_ptr &) = default;
244224
annotated_ptr &operator=(const annotated_ptr &) = default;
@@ -360,6 +340,34 @@ __SYCL_TYPE(annotated_ptr) annotated_ptr<T, detail::properties_t<Props...>> {
360340
template <typename PropertyT> static constexpr auto get_property() {
361341
return property_list_t::template get_property<PropertyT>();
362342
}
343+
344+
// *************************************************************************
345+
// All static error checking is added here instead of placing inside neat
346+
// functions to minimize the number lines printed out when an assert
347+
// is triggered.
348+
// static constexprs are used to ensure that the triggered assert prints
349+
// a message that is very readable. Without these, the assert will
350+
// print out long templated names
351+
// *************************************************************************
352+
static constexpr bool is_valid_property_list =
353+
is_property_list<property_list_t>::value;
354+
static_assert(is_valid_property_list, "Property list is invalid.");
355+
static constexpr bool contains_valid_properties =
356+
check_property_list<T *, Props...>::value;
357+
static_assert(contains_valid_properties,
358+
"The property list contains invalid property.");
359+
// check the set if FPGA specificed properties are used
360+
static constexpr bool hasValidFPGAProperties =
361+
detail::checkValidFPGAPropertySet<Props...>::value;
362+
static_assert(hasValidFPGAProperties,
363+
"FPGA Interface properties (i.e. awidth, dwidth, etc.) "
364+
"can only be set with BufferLocation together.");
365+
// check if conduit and register_map properties are specified together
366+
static constexpr bool hasConduitAndRegisterMapProperties =
367+
detail::checkHasConduitAndRegisterMap<Props...>::value;
368+
static_assert(hasConduitAndRegisterMapProperties,
369+
"The properties conduit and register_map cannot be "
370+
"specified at the same time.");
363371
};
364372

365373
} // namespace experimental

sycl/include/sycl/ext/oneapi/experimental/annotated_ptr/annotated_ptr_properties.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#pragma once
1111

12+
#include <sycl/ext/oneapi/experimental/common_annotated_properties/properties.hpp>
1213
#include <sycl/ext/oneapi/properties/properties.hpp> // for properties_t
1314
#include <sycl/usm/usm_enums.hpp>
1415

sycl/test/extensions/annotated_arg/annotated_arg_negative.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clangxx -fsycl -fsycl-targets=%sycl_triple -fsyntax-only -Xclang -verify -Xclang -verify-ignore-unexpected=note,warning %s
1+
// RUN: %clangxx -fsycl -ferror-limit=0 -fsycl-targets=%sycl_triple -fsyntax-only -Xclang -verify -Xclang -verify-ignore-unexpected=note,warning %s
22

33
#include "sycl/sycl.hpp"
44
#include <sycl/ext/intel/fpga_extensions.hpp>
@@ -17,7 +17,42 @@ void check_conduit_and_register_map_properties() {
1717
annotated_ptr<int, decltype(properties{conduit, register_map})> c;
1818
}
1919

20+
void check_invalid_properties_on_non_pointer_types() {
21+
// check buffer location property specified on non pointer type
22+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property buffer_location cannot be specified for annotated_arg<T> when T is a non pointer type.}}
23+
annotated_arg<int, decltype(properties{buffer_location<0>})> a;
24+
25+
// check awidth property specified on non pointer type
26+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property awidth cannot be specified for annotated_arg<T> when T is a non pointer type.}}
27+
annotated_arg<int, decltype(properties{awidth<32>})> b;
28+
29+
// check dwidth property specified on non pointer type
30+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property dwidth cannot be specified for annotated_arg<T> when T is a non pointer type.}}
31+
annotated_arg<int, decltype(properties{dwidth<32>})> c;
32+
33+
// check latency property specified on non pointer type
34+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property latency cannot be specified for annotated_arg<T> when T is a non pointer type.}}
35+
annotated_arg<int, decltype(properties{latency<1>})> d;
36+
37+
// check read_write_mode property specified on non pointer type
38+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property read_write_mode cannot be specified for annotated_arg<T> when T is a non pointer type.}}
39+
annotated_arg<int, decltype(properties{read_write_mode_readwrite})> e;
40+
41+
// check maxburst property specified on non pointer type
42+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property maxburst cannot be specified for annotated_arg<T> when T is a non pointer type.}}
43+
annotated_arg<int, decltype(properties{maxburst<1>})> f;
44+
45+
// check wait_request property specified on non pointer type
46+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property wait_request cannot be specified for annotated_arg<T> when T is a non pointer type.}}
47+
annotated_arg<int, decltype(properties{wait_request_requested})> g;
48+
49+
// check alignment property specified on non pointer type
50+
// expected-error@sycl/ext/oneapi/experimental/annotated_arg/annotated_arg.hpp:* {{Property alignment cannot be specified for annotated_arg<T> when T is a non pointer type.}}
51+
annotated_arg<int, decltype(properties{alignment<256>})> h;
52+
}
53+
2054
int main() {
55+
check_invalid_properties_on_non_pointer_types();
2156
check_conduit_and_register_map_properties();
2257
return 0;
2358
}

sycl/test/extensions/annotated_arg/annotated_arg_properties.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,6 @@ template <typename T> void checkIsPropertyOf() {
4646
is_property_value_of<decltype(wait_request_requested), T>::value);
4747
}
4848

49-
// Checks is_property_key_of and is_property_value_of are false for non-pointer
50-
// type T.
51-
template <typename T> void checkIsValidPropertyOfNonPtr() {
52-
static_assert(
53-
is_valid_property<T, decltype(wait_request_not_requested)>::value ==
54-
false);
55-
static_assert(is_valid_property<T, decltype(latency<8>)>::value == false);
56-
}
57-
5849
int main() {
5950
static_assert(is_property_key<register_map_key>::value);
6051
static_assert(is_property_key<buffer_location_key>::value);
@@ -90,7 +81,5 @@ int main() {
9081
static_assert(AnnotatedArg4.get_property<read_write_mode_key>() ==
9182
read_write_mode_read);
9283

93-
// Check if a property is valid for a given type
94-
checkIsValidPropertyOfNonPtr<A>();
9584
return 0;
9685
}

0 commit comments

Comments
 (0)