From 5e0bd4ff42da8513db286c5c701fa9d90257ce24 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Wed, 6 Sep 2023 22:58:42 -0400 Subject: [PATCH] Reword qualifiers and support _Nonnull attributes Get qualifiers in the right places and change how they are printed to be more clear. The qualifiers on the Type are for the innermost (non-pointer) type. The pointer qualifiers are for the pointers. --- subdoc/gen_tests/struct-complex/S.html | 16 +- subdoc/gen_tests/subdoc-test-style.css | 4 - .../gen_tests/templates/TemplateMethods.html | 10 +- .../n-FunctionParams.html | 22 +-- subdoc/lib/gen/generate_function.cc | 18 ++- subdoc/lib/gen/generate_record.cc | 9 +- subdoc/lib/type.cc | 117 +++++++++----- subdoc/lib/type.h | 125 ++++++++++++++- subdoc/tests/type_unittest.cc | 148 ++++++++++++------ 9 files changed, 339 insertions(+), 130 deletions(-) diff --git a/subdoc/gen_tests/struct-complex/S.html b/subdoc/gen_tests/struct-complex/S.html index fe8af09fa..50abddedf 100644 --- a/subdoc/gen_tests/struct-complex/S.html +++ b/subdoc/gen_tests/struct-complex/S.html @@ -118,14 +118,14 @@
- +
static bool static_bool_member

Comment headline static_bool_member

- +

Comment headline static_type_member

@@ -140,7 +140,7 @@
-
staticautostatic_bool_method() -> bool
+
static auto static_bool_method() -> bool
@@ -149,7 +149,7 @@
- +
static auto static_type_method() -> OtherType
@@ -165,14 +165,14 @@
-
autoint_method() volatile -> void
+
auto int_method() volatile -> void
- +
@@ -181,7 +181,7 @@
-
autovoid_method() const& -> void
+
auto void_method() const& -> void
@@ -203,7 +203,7 @@
- +

Comment headline type_field

diff --git a/subdoc/gen_tests/subdoc-test-style.css b/subdoc/gen_tests/subdoc-test-style.css index d6687887f..38fe6dd19 100644 --- a/subdoc/gen_tests/subdoc-test-style.css +++ b/subdoc/gen_tests/subdoc-test-style.css @@ -223,10 +223,6 @@ main { } -.overload .function-signature span { - margin-right: 0.4em; /* Put back the space after other elements */ -} - .description.short { margin: 0; line-height: 1.2; diff --git a/subdoc/gen_tests/templates/TemplateMethods.html b/subdoc/gen_tests/templates/TemplateMethods.html index 6b13da1fe..336748113 100644 --- a/subdoc/gen_tests/templates/TemplateMethods.html +++ b/subdoc/gen_tests/templates/TemplateMethods.html @@ -117,7 +117,7 @@
-
template <class U>
staticU member
+
template <class U>
static U member
@@ -144,21 +144,21 @@
-
template <class var:auto>
autoconcept_param(Concept auto var) -> void
+
template <class var:auto>
auto concept_param(Concept auto var) -> void
-
autoconcept_return() -> Concept<S> auto
+
auto concept_return() -> Concept<S> auto
-
template <class U>
autolocal_template_params(T t, U u) -> U
+
template <class U>
auto local_template_params(T t, U u) -> U
requires @@ -175,7 +175,7 @@
-
autotemplate_params(T t) -> T
+
auto template_params(T t) -> T
requires diff --git a/subdoc/gen_tests/typenames-across-paths/n-FunctionParams.html b/subdoc/gen_tests/typenames-across-paths/n-FunctionParams.html index 548d03da9..d317ac6e4 100644 --- a/subdoc/gen_tests/typenames-across-paths/n-FunctionParams.html +++ b/subdoc/gen_tests/typenames-across-paths/n-FunctionParams.html @@ -116,7 +116,7 @@
-
staticautoconst_pointer(S const* s) -> S const*
+
static auto const_pointer(const S* s) -> const S*
@@ -125,7 +125,7 @@
-
staticautoconst_pointer_const(S const* const s) -> S const*
+
static auto const_pointer_const(const S *const s) -> const S*
@@ -134,7 +134,7 @@
-
staticautoconst_ref(S const& s) -> S const&
+
static auto const_ref(const S& s) -> const S&
@@ -143,7 +143,7 @@
-
staticautoconst_ref_pointer(S const* const& s) -> S const* const&
+
static auto const_ref_pointer(const S *const& s) -> const S *const&
@@ -152,7 +152,7 @@
-
staticautoconst_rvalue_ref(S const&& s) -> S const&&
+
static auto const_rvalue_ref(const S&& s) -> const S&&
@@ -161,7 +161,7 @@
-
staticautomulti_pointer(S const* volatile* const s) -> S const* volatile*
+
static auto multi_pointer(const S *volatile *const s) -> const S *volatile *
@@ -170,7 +170,7 @@
-
staticautomut_ref(S& s) -> S&
+
static auto mut_ref(S& s) -> S&
@@ -179,7 +179,7 @@
-
staticautomut_ref_pointer(S const*& s) -> S const*&
+
static auto mut_ref_pointer(const S*& s) -> const S*&
@@ -188,7 +188,7 @@
-
staticautopointer(S* s) -> S*
+
static auto pointer(S* s) -> S*
@@ -197,7 +197,7 @@
-
staticautopointer_const(S* const s) -> S*
+
static auto pointer_const(S *const s) -> S*
@@ -206,7 +206,7 @@
-
staticautorvalue_ref(S&& s) -> S&&
+
static auto rvalue_ref(S&& s) -> S&&
diff --git a/subdoc/lib/gen/generate_function.cc b/subdoc/lib/gen/generate_function.cc index 6fbf1cec9..1f80db83e 100644 --- a/subdoc/lib/gen/generate_function.cc +++ b/subdoc/lib/gen/generate_function.cc @@ -127,14 +127,20 @@ void generate_overload_set(HtmlWriter::OpenDiv& div, template_div.write_text(">"); } if (is_static) { - auto static_span = signature_div.open_span(HtmlWriter::SingleLine); - static_span.add_class("static"); - static_span.write_text("static"); + { + auto static_span = signature_div.open_span(HtmlWriter::SingleLine); + static_span.add_class("static"); + static_span.write_text("static"); + } + signature_div.write_text(" "); } if (has_return) { - auto auto_span = signature_div.open_span(HtmlWriter::SingleLine); - auto_span.add_class("function-auto"); - auto_span.write_text("auto"); + { + auto auto_span = signature_div.open_span(HtmlWriter::SingleLine); + auto_span.add_class("function-auto"); + auto_span.write_text("auto"); + } + signature_div.write_text(" "); } } { diff --git a/subdoc/lib/gen/generate_record.cc b/subdoc/lib/gen/generate_record.cc index 114e5c35e..3eba6f3c8 100644 --- a/subdoc/lib/gen/generate_record.cc +++ b/subdoc/lib/gen/generate_record.cc @@ -232,9 +232,12 @@ sus::Result generate_record_fields( template_div.write_text(">"); } if (static_fields) { - auto static_span = name_div.open_span(HtmlWriter::SingleLine); - static_span.add_class("static"); - static_span.write_text("static"); + { + auto static_span = name_div.open_span(HtmlWriter::SingleLine); + static_span.add_class("static"); + static_span.write_text("static"); + } + name_div.write_text(" "); } generate_type(name_div, fe.type, [&](HtmlWriter::OpenDiv& div) { auto anchor = div.open_a(); diff --git a/subdoc/lib/type.cc b/subdoc/lib/type.cc index 9661295fc..5dbebecdb 100644 --- a/subdoc/lib/type.cc +++ b/subdoc/lib/type.cc @@ -28,10 +28,29 @@ namespace subdoc { namespace { Qualifier qualifier_from_qualtype(clang::QualType q) noexcept { - return Qualifier{ - .is_const = q.isLocalConstQualified(), - .is_volatile = q.isLocalVolatileQualified(), - }; + Nullness null = Nullness::Unknown; + if (auto* attr_type = clang::dyn_cast(&*q)) { + if (auto try_null = attr_type->getImmediateNullability(); + try_null.has_value()) { + switch (try_null.value()) { + case clang::NullabilityKind::NonNull: + null = Nullness::Disallowed; + break; + case clang::NullabilityKind::Nullable: null = Nullness::Allowed; break; + case clang::NullabilityKind::NullableResult: + null = Nullness::Allowed; + break; + case clang::NullabilityKind::Unspecified: break; + } + } + + // `AttributedType` does not have qualifiers, the type inside does. + q = attr_type->getEquivalentType(); + } + return Qualifier() + .set_const(q.isLocalConstQualified()) + .set_volatile(q.isLocalVolatileQualified()) + .set_nullness(null); } std::string name_of_type(clang::QualType q) noexcept { @@ -176,6 +195,26 @@ TypeOrValue build_template_param( sus::unreachable(); }; +clang::QualType unwrap_skipped_types(clang::QualType q) noexcept { + // Arrays may already be "DecayedType", but we can get the original type from + // it. + if (auto* dtype = clang::dyn_cast(&*q)) + q = dtype->getOriginalType(); + + // A `using A = B` is an elaborated type that names a typedef A, so unpack + // the ElaboratedType. Template specializations can be inside an + // ElaboratedType, so this comes first. + while (auto* elab = clang::dyn_cast(&*q)) + q = elab->getNamedType(); + + // `AttributedType` have an attribute applied, and should be unwrapped to get + // to the underlying type. + while (auto* attr = clang::dyn_cast(&*q)) + q = attr->getEquivalentType(); + + return q; +} + Type build_local_type_internal( clang::QualType qualtype, llvm::ArrayRef template_params_from_context, @@ -194,7 +233,10 @@ Type build_local_type_internal( qualtype->isLValueReferenceType() ? Refs::LValueRef : (qualtype->isRValueReferenceType() ? Refs::RValueRef : Refs::None); - qualtype = qualtype.getNonReferenceType(); + // Grab the qualifiers on the outer type, if it's a pointer this is what we + // want. But for an array we will need to replace these. + Qualifier qualifier = qualifier_from_qualtype(qualtype.getNonReferenceType()); + qualtype = unwrap_skipped_types(qualtype.getNonReferenceType()); sus::Vec nested_names; if (auto* dep = clang::dyn_cast(&*qualtype)) { @@ -218,16 +260,6 @@ Type build_local_type_internal( } } - // Arrays may already be "DecayedType", but we can get the original type from - // it. - if (auto* dtype = clang::dyn_cast(&*qualtype)) { - qualtype = dtype->getOriginalType(); - } - - // Grab the qualifiers on the outer type, if it's a pointer this is what we - // want. But for an array we will need to replace these. - Qualifier qualifier = qualifier_from_qualtype(qualtype); - sus::Vec array_dims; while (qualtype->isArrayType()) { // The type inside a pack can not be an array. @@ -237,7 +269,8 @@ Type build_local_type_internal( qualtype = qualtype.IgnoreParens(); sus::check(clang::isa(&*qualtype)); - const clang::Type* const type = &*qualtype; + const clang::ArrayType* const type = + clang::cast(&*qualtype); if (auto* constarr = clang::dyn_cast(type)) { array_dims.push( llvm_int_without_sign_to_string(constarr->getSize(), false)); @@ -252,25 +285,20 @@ Type build_local_type_internal( sus::unreachable(); // This is a C thing, not C++. } - qualtype = clang::cast(&*qualtype)->getElementType(); // For arrays the root qualifiers come from the element type. - qualifier = qualifier_from_qualtype(qualtype); + qualifier = qualifier_from_qualtype(type->getElementType()); + qualtype = unwrap_skipped_types(type->getElementType()); } // The array can be an array of pointers, so we look for pointers after // unwrapping the array. sus::Vec pointers; while (qualtype->isPointerType()) { - qualtype = qualtype->getPointeeType(); - pointers.push(qualifier_from_qualtype(qualtype)); + pointers.push(qualifier); + qualifier = qualifier_from_qualtype(qualtype->getPointeeType()); + qualtype = unwrap_skipped_types(qualtype->getPointeeType()); } - // A `using A = B` is an elaborated type that names a typedef A, so unpack - // the ElaboratedType. Template specializations can be inside an - // ElaboratedType, so this comes first. - while (auto* elab = clang::dyn_cast(&*qualtype)) - qualtype = elab->getNamedType(); - // TODO: Drop the from_range() on the uses of iter_args: // https://github.com/chromium/subspace/issues/348 auto iter_args = @@ -505,6 +533,15 @@ void type_to_string_internal( sus::fn::FnMutRef& const_qualifier_fn, sus::fn::FnMutRef& volatile_qualifier_fn, sus::Option> var_name_fn) noexcept { + if (type.qualifier.is_const) { + const_qualifier_fn(); + text_fn(" "); + } + if (type.qualifier.is_volatile) { + volatile_qualifier_fn(); + text_fn(" "); + } + for (const TypeOrValue& tv : type.nested_names) { switch (tv.choice) { case TypeOrValueTag::Type: { @@ -576,26 +613,28 @@ void type_to_string_internal( if (type.category == TypeCategory::Concept) text_fn(" auto"); + bool wrote_quals = false; for (Qualifier q : type.pointers) { + bool has_quals = q.is_const || q.is_volatile; + // If there are quals on either side of the `*` put a space to the left + // of the `*. + // + // wrote_quals gives: *const[space here]* + // has_quals gives: *[space here]*const + if (wrote_quals || has_quals) text_fn(" "); + text_fn("*"); + wrote_quals = false; + if (q.is_const) { - text_fn(" "); + if (wrote_quals) text_fn(" "); + wrote_quals = true; const_qualifier_fn(); } if (q.is_volatile) { - text_fn(" "); + if (wrote_quals) text_fn(" "); + wrote_quals = true; volatile_qualifier_fn(); } - text_fn("*"); - } - - // TODO: Option to put the const/volatile qualifiers before the type name? - if (type.qualifier.is_const) { - text_fn(" "); - const_qualifier_fn(); - } - if (type.qualifier.is_volatile) { - text_fn(" "); - volatile_qualifier_fn(); } if (type.array_dims.is_empty()) { diff --git a/subdoc/lib/type.h b/subdoc/lib/type.h index c3117ed1c..3431ebc23 100644 --- a/subdoc/lib/type.h +++ b/subdoc/lib/type.h @@ -24,14 +24,125 @@ namespace subdoc { struct TypeOrValue; +enum class Nullness { + Allowed, + Disallowed, + Unknown, +}; + struct Qualifier { - bool is_const; - bool is_volatile; + /// Creates `Qualifier` with neither const nor volatile set, and with + /// nullness set to `Nullness::Unknown`. + constexpr Qualifier() = default; + /// Creates `Qualifier` with const set. + constexpr static Qualifier with_const() noexcept { + return Qualifier().set_const(true); + } + /// Creates `Qualifier` with volatile set. + constexpr static Qualifier with_volatile() noexcept { + return Qualifier().set_volatile(true); + } + /// Creates `Qualifier` with both const and volatile set. + constexpr static Qualifier with_cv() noexcept { + return Qualifier().set_const(true).set_volatile(true); + } + + /// Creates a new `Qualifier` from this with const set to `c`. + constexpr Qualifier set_const(bool c) noexcept { + Qualifier q = *this; + q.is_const = c; + return q; + } + /// Creates a new `Qualifier` from this with volatile set to `v`. + constexpr Qualifier set_volatile(bool v) noexcept { + Qualifier q = *this; + q.is_volatile = v; + return q; + } + /// Creates a new `Qualifier` from this with nullness set to `n`. + constexpr Qualifier set_nullness(Nullness n) noexcept { + Qualifier q = *this; + q.nullness = n; + return q; + } + + bool is_const = false; + bool is_volatile = false; + Nullness nullness = Nullness::Unknown; friend bool operator==(const Qualifier&, const Qualifier&) = default; }; static_assert(sus::ops::Eq); +} // namespace subdoc + +template <> +struct fmt::formatter { + template + constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + template + constexpr auto format(const subdoc::Nullness& t, FormatContext& ctx) const { + using enum subdoc::Nullness; + auto out = ctx.out(); + switch (t) { + case Allowed: out = fmt::format_to(out, "Allowed"); break; + case Disallowed: out = fmt::format_to(out, "Disallowed"); break; + case Unknown: out = fmt::format_to(out, "Unknown"); break; + } + ctx.advance_to(out); + return out; + } +}; + +template <> +struct fmt::formatter { + template + constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + + template + constexpr auto format(const subdoc::Qualifier& t, FormatContext& ctx) const { + auto out = ctx.out(); + out = fmt::format_to(out, "Qualifier("); + ctx.advance_to(out); + u32 count; + if (t.is_const) { + out = fmt::format_to(out, "c"); + ctx.advance_to(out); + count += 1u; + } + if (t.is_volatile) { + if (count > 0u) { + out = fmt::format_to(out, ", "); + ctx.advance_to(out); + } + out = fmt::format_to(out, "v"); + ctx.advance_to(out); + count += 1u; + } + if (t.nullness != subdoc::Nullness::Unknown) { + if (count > 0u) { + out = fmt::format_to(out, ", "); + ctx.advance_to(out); + } + if (t.nullness == subdoc::Nullness::Allowed) { + out = fmt::format_to(out, "nullable"); + ctx.advance_to(out); + } else { + out = fmt::format_to(out, "nonnull"); + ctx.advance_to(out); + } + } + out = fmt::format_to(out, ")"); + ctx.advance_to(out); + return out; + } +}; + +namespace subdoc { enum class Refs { LValueRef, None, @@ -60,15 +171,19 @@ struct Type { std::string name; /// For types of the form `A::B::C` the `nested_name` would hold `B` and `C`. sus::Vec nested_names; - /// Refs can only appear on the outermost type. + /// References can only be applied to the outermost type. While most of the + /// `Type` structure refers to the innermost type (the deepest pointee, a + /// non-pointer), this refers to the outermost type (the first pointer in + /// `int***`). Refs refs; /// Const-volatile qualifiers for the outermost type. Qualifier qualifier; /// The qualifiers of each level of pointer indirection. Empty if the type is /// not a pointer. The order is reversed from the order that they are applied, - /// to optimize for display. + /// to optimize for display. The qualifiers for the inner most type are stored + /// on the `Type`. /// - /// `T *const<1st *const<2nd *const<3rd`. + /// `const T *const<1st *const<2nd *const<3rd`. /// sus::Vec pointers; /// The dimension of each level of an array, if any. An empty string diff --git a/subdoc/tests/type_unittest.cc b/subdoc/tests/type_unittest.cc index 8ba9c727a..bc5ce0129 100644 --- a/subdoc/tests/type_unittest.cc +++ b/subdoc/tests/type_unittest.cc @@ -22,6 +22,7 @@ namespace { +using subdoc::Nullness; using subdoc::Qualifier; std::string make_string(std::string_view var_name, const subdoc::Type& type) { @@ -134,7 +135,7 @@ TEST_F(SubDocTypeTest, Const) { EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(make_string("foo", t), "!bool! const foo"); + EXPECT_EQ(make_string("foo", t), "const !bool! foo"); }); } @@ -153,7 +154,7 @@ TEST_F(SubDocTypeTest, Volatile) { EXPECT_EQ(t.qualifier.is_volatile, true); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(make_string("foo", t), "!bool! volatile foo"); + EXPECT_EQ(make_string("foo", t), "volatile !bool! foo"); }); } @@ -172,7 +173,7 @@ TEST_F(SubDocTypeTest, ConstVolatile) { EXPECT_EQ(t.qualifier.is_volatile, true); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(make_string("foo", t), "!bool! const volatile foo"); + EXPECT_EQ(make_string("foo", t), "const volatile !bool! foo"); }); } @@ -191,7 +192,7 @@ TEST_F(SubDocTypeTest, ConstRef) { EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(make_string("foo", t), "!int! const& foo"); + EXPECT_EQ(make_string("foo", t), "const !int!& foo"); }); } @@ -229,7 +230,7 @@ TEST_F(SubDocTypeTest, ConstRRef) { EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::RValueRef); - EXPECT_EQ(make_string("foo", t), "!int! const&& foo"); + EXPECT_EQ(make_string("foo", t), "const !int!&& foo"); }); } @@ -266,7 +267,7 @@ TEST_F(SubDocTypeTest, Pointer) { EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(t.pointers, sus::vec(Qualifier(false, false))); + EXPECT_EQ(t.pointers, sus::vec(Qualifier())); EXPECT_EQ(make_string("foo", t), "!int!* foo"); }); @@ -286,7 +287,7 @@ TEST_F(SubDocTypeTest, RefToPointer) { EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(t.pointers, sus::vec(Qualifier(false, false))); + EXPECT_EQ(t.pointers, sus::vec(Qualifier())); EXPECT_EQ(make_string("foo", t), "!int!*& foo"); }); @@ -303,18 +304,18 @@ TEST_F(SubDocTypeTest, ConstRefToPointer) { EXPECT_EQ(t.category, subdoc::TypeCategory::Type); EXPECT_EQ(t.name, "int"); - EXPECT_EQ(t.qualifier.is_const, true); + EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(t.pointers, sus::vec(Qualifier(false, false))); + EXPECT_EQ(t.pointers, sus::vec(Qualifier::with_const())); - EXPECT_EQ(make_string("foo", t), "!int!* const& foo"); + EXPECT_EQ(make_string("foo", t), "!int! *const& foo"); }); } TEST_F(SubDocTypeTest, ConstRefToPointerToConst) { const char test[] = R"( - void f(int const* const&); + void f(int const *const &); )"; run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("f", cx); @@ -326,15 +327,15 @@ TEST_F(SubDocTypeTest, ConstRefToPointerToConst) { EXPECT_EQ(t.qualifier.is_const, true); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(t.pointers, sus::vec(Qualifier(true, false))); + EXPECT_EQ(t.pointers, sus::vec(Qualifier::with_const())); - EXPECT_EQ(make_string("foo", t), "!int! const* const& foo"); + EXPECT_EQ(make_string("foo", t), "const !int! *const& foo"); }); } TEST_F(SubDocTypeTest, PointerQualifiers) { const char test[] = R"( - void f(int const* * const volatile* * volatile*); + void f(int const * *const volatile * *volatile *); )"; run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("f", cx); @@ -343,16 +344,15 @@ TEST_F(SubDocTypeTest, PointerQualifiers) { EXPECT_EQ(t.category, subdoc::TypeCategory::Type); EXPECT_EQ(t.name, "int"); - EXPECT_EQ(t.qualifier.is_const, false); + EXPECT_EQ(t.qualifier.is_const, true); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::None); EXPECT_EQ(t.pointers, - sus::vec(Qualifier(true, false), Qualifier(false, false), - Qualifier(true, true), Qualifier(false, false), - Qualifier(false, true))); + sus::vec(Qualifier(), Qualifier::with_cv(), Qualifier(), + Qualifier::with_volatile(), Qualifier())); EXPECT_EQ(make_string("foo", t), - "!int! const** const volatile** volatile* foo"); + "const !int!* *const volatile * *volatile * foo"); }); } @@ -394,7 +394,7 @@ TEST_F(SubDocTypeTest, QualifiedArray) { EXPECT_EQ(t.array_dims, sus::vec("5")); EXPECT_EQ(t.pointers, sus::vec()); - EXPECT_EQ(make_string("foo", t), "!int! const foo[5]"); + EXPECT_EQ(make_string("foo", t), "const !int! foo[5]"); }); } @@ -501,7 +501,7 @@ TEST_F(SubDocTypeTest, SizedArrayRef) { EXPECT_EQ(t.array_dims, sus::vec("3")); EXPECT_EQ(t.pointers, sus::vec()); - EXPECT_EQ(make_string("foo", t), "!int! const (&foo)[3]"); + EXPECT_EQ(make_string("foo", t), "const !int! (&foo)[3]"); }); } @@ -523,7 +523,7 @@ TEST_F(SubDocTypeTest, SizedArrayRvalueRef) { EXPECT_EQ(t.array_dims, sus::vec("3")); EXPECT_EQ(t.pointers, sus::vec()); - EXPECT_EQ(make_string("foo", t), "!int! volatile (&&foo)[3]"); + EXPECT_EQ(make_string("foo", t), "volatile !int! (&&foo)[3]"); }); } @@ -545,7 +545,7 @@ TEST_F(SubDocTypeTest, NamespaceReference) { EXPECT_EQ(t.record_path, sus::vec()); EXPECT_EQ(t.namespace_path, sus::Vec("a", "b", "c")); - EXPECT_EQ(make_string("foo", t), "!S! const& foo"); + EXPECT_EQ(make_string("foo", t), "const !S!& foo"); }); } @@ -624,7 +624,7 @@ TEST_F(SubDocTypeTest, AutoRef) { EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(make_string("foo", t), "auto const& foo"); + EXPECT_EQ(make_string("foo", t), "const auto& foo"); }); } @@ -642,7 +642,7 @@ TEST_F(SubDocTypeTest, AutoPointer) { EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(t.pointers, sus::vec(Qualifier(false, false))); + EXPECT_EQ(t.pointers, sus::vec(Qualifier())); EXPECT_EQ(make_string("foo", t), "auto* foo"); }); @@ -682,7 +682,7 @@ TEST_F(SubDocTypeTest, Concept) { EXPECT_EQ(t2.namespace_path, sus::vec("a", "b")); EXPECT_EQ(make_string("foo", t), "!C! auto foo"); - EXPECT_EQ(make_string("foo", t2), "!C! auto const& foo"); + EXPECT_EQ(make_string("foo", t2), "const !C! auto& foo"); }); } @@ -764,7 +764,7 @@ TEST_F(SubDocTypeTest, ConceptWithDependentTypeParam) { const char test[] = R"( namespace a::b { template concept C = true; } template - void f(a::b::C auto); + void f(a::b::C auto); )"; run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("f", cx); @@ -782,11 +782,13 @@ TEST_F(SubDocTypeTest, ConceptWithDependentTypeParam) { EXPECT_EQ(p1.category, subdoc::TypeCategory::TemplateVariable); EXPECT_EQ(p1.name, "T"); EXPECT_EQ(p1.is_pack, false); - EXPECT_EQ(p1.qualifier.is_const, true); + EXPECT_EQ(p1.qualifier.is_const, false); + EXPECT_EQ(p1.qualifier.is_volatile, true); EXPECT_EQ(p1.refs, subdoc::Refs::RValueRef); - EXPECT_EQ(p1.pointers, sus::Vec(subdoc::Qualifier(false, true))); + EXPECT_EQ(p1.pointers, + sus::Vec(subdoc::Qualifier::with_const())); - EXPECT_EQ(make_string("foo", t), "!C! auto foo"); + EXPECT_EQ(make_string("foo", t), "!C! auto foo"); }); } @@ -794,7 +796,7 @@ TEST_F(SubDocTypeTest, ConceptWithTypeParam) { const char test[] = R"( namespace a::b { template concept C = true; } namespace c::d { struct E {}; } - void f(a::b::C auto); + void f(a::b::C auto); )"; run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("f", cx); @@ -812,11 +814,13 @@ TEST_F(SubDocTypeTest, ConceptWithTypeParam) { EXPECT_EQ(p1.category, subdoc::TypeCategory::Type); EXPECT_EQ(p1.name, "E"); EXPECT_EQ(p1.is_pack, false); - EXPECT_EQ(p1.qualifier.is_const, true); + EXPECT_EQ(p1.qualifier.is_const, false); + EXPECT_EQ(p1.qualifier.is_volatile, true); EXPECT_EQ(p1.refs, subdoc::Refs::RValueRef); - EXPECT_EQ(p1.pointers, sus::Vec(subdoc::Qualifier(false, true))); + EXPECT_EQ(p1.pointers, + sus::Vec(subdoc::Qualifier::with_const())); - EXPECT_EQ(make_string("foo", t), "!C! auto foo"); + EXPECT_EQ(make_string("foo", t), "!C! auto foo"); }); } @@ -958,7 +962,7 @@ TEST_F(SubDocTypeTest, DependentTypeQualified) { EXPECT_EQ(p1.qualifier.is_const, true); EXPECT_EQ(p1.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(make_string("foo", t), "!S! foo"); + EXPECT_EQ(make_string("foo", t), "!S! foo"); }); } @@ -975,16 +979,19 @@ TEST_F(SubDocTypeTest, DependentTypePointer) { EXPECT_EQ(t.category, subdoc::TypeCategory::Type); EXPECT_EQ(t.name, "S"); + ASSERT_EQ(t.qualifier.is_const, false); + ASSERT_EQ(t.qualifier.is_volatile, false); ASSERT_EQ(t.template_params.len(), 1u); const subdoc::Type& p1 = t.template_params[0u].choice.as(); EXPECT_EQ(p1.category, subdoc::TypeCategory::TemplateVariable); EXPECT_EQ(p1.name, "T"); EXPECT_EQ(p1.qualifier.is_const, false); + ASSERT_EQ(p1.qualifier.is_volatile, true); EXPECT_EQ(p1.refs, subdoc::Refs::None); - EXPECT_EQ(p1.pointers, sus::vec(subdoc::Qualifier(false, true))); + EXPECT_EQ(p1.pointers, sus::vec(subdoc::Qualifier())); - EXPECT_EQ(make_string("foo", t), "!S! foo"); + EXPECT_EQ(make_string("foo", t), "!S! foo"); }); } @@ -1207,7 +1214,7 @@ TEST_F(SubDocTypeTest, AutoReturnPointer) { EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::None); - EXPECT_EQ(t.pointers, sus::vec(subdoc::Qualifier(false, false))); + EXPECT_EQ(t.pointers, sus::vec(subdoc::Qualifier())); EXPECT_EQ(make_string("foo", t), "auto* foo"); }); @@ -1460,7 +1467,7 @@ TEST_F(SubDocTypeTest, UsingType) { EXPECT_EQ(t.refs, subdoc::Refs::LValueRef); EXPECT_EQ(t.namespace_path, sus::vec("c", "d")); - EXPECT_EQ(make_string("foo", t), "!S! const& foo"); + EXPECT_EQ(make_string("foo", t), "const !S!& foo"); }); } @@ -1669,7 +1676,7 @@ TEST_F(SubDocTypeTest, PartialSpecializationMethodInjectedClassName) { EXPECT_EQ(p21.qualifier.is_volatile, false); EXPECT_EQ(p21.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(make_string("foo", t), "!F!>& foo"); + EXPECT_EQ(make_string("foo", t), "!F!>& foo"); }); } @@ -1712,7 +1719,7 @@ TEST_F(SubDocTypeTest, PartialSpecializationMethodInNestedTemplateClass) { EXPECT_EQ(p21.qualifier.is_volatile, false); EXPECT_EQ(p21.refs, subdoc::Refs::LValueRef); - EXPECT_EQ(make_string("foo", t), "!F!>& foo"); + EXPECT_EQ(make_string("foo", t), "!F!>& foo"); }); run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("g", cx); @@ -1744,7 +1751,7 @@ TEST_F(SubDocTypeTest, namespace a::b { template struct F {}; } namespace c::d { template struct S {}; } template - struct a::b::F> { + struct a::b::F> { static void f(F&); }; )"; @@ -1770,26 +1777,28 @@ TEST_F(SubDocTypeTest, .choice.as(); EXPECT_EQ(p11.return_type.category, subdoc::TypeCategory::TemplateVariable); EXPECT_EQ(p11.return_type.name, "A"); + EXPECT_EQ(p11.return_type.qualifier.is_const, false); + EXPECT_EQ(p11.return_type.qualifier.is_volatile, false); EXPECT_EQ(p11.return_type.refs, subdoc::Refs::None); const subdoc::Type& parm1 = p11.param_types[0u]; EXPECT_EQ(parm1.category, subdoc::TypeCategory::TemplateVariable); EXPECT_EQ(parm1.name, "T"); - EXPECT_EQ(parm1.qualifier.is_const, true); + EXPECT_EQ(parm1.qualifier.is_const, false); EXPECT_EQ(parm1.qualifier.is_volatile, false); EXPECT_EQ(parm1.refs, subdoc::Refs::RValueRef); EXPECT_EQ(parm1.pointers, - sus::vec(Qualifier(false, false), Qualifier(false, true))); + sus::vec(Qualifier::with_volatile(), Qualifier::with_const())); EXPECT_EQ(parm1.is_pack, true); EXPECT_EQ(make_string("foo", t), - "!F!>& foo"); + "!F!>& foo"); }); } TEST_F(SubDocTypeTest, VariadicConcept) { const char test[] = R"( namespace a::b { template concept C = true; } - static void f(a::b::C auto * volatile* const&&...); + static void f(a::b::C auto *volatile *const&&...); )"; run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { sus::Option qual = find_function_parm("f", cx); @@ -1798,15 +1807,15 @@ TEST_F(SubDocTypeTest, VariadicConcept) { EXPECT_EQ(t.category, subdoc::TypeCategory::Concept); EXPECT_EQ(t.name, "C"); - EXPECT_EQ(t.qualifier.is_const, true); + EXPECT_EQ(t.qualifier.is_const, false); EXPECT_EQ(t.qualifier.is_volatile, false); EXPECT_EQ(t.refs, subdoc::Refs::RValueRef); EXPECT_EQ(t.namespace_path, sus::vec("a", "b")); EXPECT_EQ(t.pointers, - sus::vec(Qualifier(false, false), Qualifier(false, true))); + sus::vec(Qualifier::with_volatile(), Qualifier::with_const())); EXPECT_EQ(t.is_pack, true); - EXPECT_EQ(make_string("foo", t), "!C! auto* volatile* const&&... foo"); + EXPECT_EQ(make_string("foo", t), "!C! auto *volatile *const&&... foo"); }); } @@ -1837,4 +1846,45 @@ TEST_F(SubDocTypeTest, DependentNameType) { }); } +TEST_F(SubDocTypeTest, NullAttributeTemplate) { + const char test[] = R"( + template + void f(_Nonnull T i); + )"; + run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { + sus::Option qual = find_function_parm("f", cx); + subdoc::Type t = + subdoc::build_local_type(*qual, cx.getSourceManager(), preprocessor); + + EXPECT_EQ(t.category, subdoc::TypeCategory::TemplateVariable); + EXPECT_EQ(t.name, "T"); + EXPECT_EQ(t.qualifier.is_const, false); + EXPECT_EQ(t.qualifier.is_volatile, false); + EXPECT_EQ(t.qualifier.nullness, Nullness::Disallowed); + + EXPECT_EQ(make_string("foo", t), "T foo"); + }); +} + +TEST_F(SubDocTypeTest, NullAttributePointer) { + const char test[] = R"( + template + void f(const int *const _Nullable *_Nonnull i); + )"; + run_test(test, [](clang::ASTContext& cx, clang::Preprocessor& preprocessor) { + sus::Option qual = find_function_parm("f", cx); + subdoc::Type t = + subdoc::build_local_type(*qual, cx.getSourceManager(), preprocessor); + + EXPECT_EQ(t.category, subdoc::TypeCategory::Type); + EXPECT_EQ(t.name, "int"); + EXPECT_EQ(t.qualifier, Qualifier::with_const()); + EXPECT_EQ(t.pointers, + sus::vec(Qualifier::with_const().set_nullness(Nullness::Allowed), + Qualifier().set_nullness(Nullness::Disallowed))); + + EXPECT_EQ(make_string("foo", t), "const !int! *const * foo"); + }); +} + } // namespace