Change __auto_type behavior with qualifiers to match GCC behavior
authorAaron Ballman <aaron@aaronballman.com>
Wed, 23 Mar 2022 17:24:53 +0000 (13:24 -0400)
committerAaron Ballman <aaron@aaronballman.com>
Wed, 23 Mar 2022 17:25:31 +0000 (13:25 -0400)
Currently, Clang handles some qualifiers correctly for __auto_type, but
it does not handle the restrict or _Atomic qualifiers in the same way
that GCC does. This patch handles those qualifiers so that they attach
to the deduced type the same as const and volatile already do.

This fixes https://github.com/llvm/llvm-project/issues/53652

clang/docs/ReleaseNotes.rst
clang/include/clang/AST/Type.h
clang/lib/AST/ASTContext.cpp
clang/lib/Sema/SemaExpr.cpp
clang/lib/Sema/SemaType.cpp
clang/test/Sema/auto-type.c

index ebbed4b..83d5dbd 100644 (file)
@@ -73,6 +73,11 @@ Bug Fixes
   Now fixed by setting identifiers for them.
   This fixes `Issue 28475 (PR28101) <https://github.com/llvm/llvm-project/issues/28475>`_.
 
+- Now allow the `restrict` and `_Atomic` qualifiers to be used in conjunction
+  with `__auto_type` to match the behavior in GCC. This fixes
+  `Issue 53652 <https://github.com/llvm/llvm-project/issues/53652>`_.
+
+
 Improvements to Clang's diagnostics
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - ``-Wliteral-range`` will warn on floating-point equality comparisons with
index 448dd2f..bc66ffb 100644 (file)
@@ -5083,6 +5083,10 @@ public:
     return getKeyword() == AutoTypeKeyword::DecltypeAuto;
   }
 
+  bool isGNUAutoType() const {
+    return getKeyword() == AutoTypeKeyword::GNUAutoType;
+  }
+
   AutoTypeKeyword getKeyword() const {
     return (AutoTypeKeyword)AutoTypeBits.Keyword;
   }
index 3ba9f40..77b9edc 100644 (file)
@@ -10285,7 +10285,16 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS,
       if (RHS->isObjCIdType() && LHS->isBlockPointerType())
         return RHS;
     }
-
+    // Allow __auto_type to match anything; it merges to the type with more
+    // information.
+    if (const auto *AT = LHS->getAs<AutoType>()) {
+      if (AT->isGNUAutoType())
+        return RHS;
+    }
+    if (const auto *AT = RHS->getAs<AutoType>()) {
+      if (AT->isGNUAutoType())
+        return LHS;
+    }
     return {};
   }
 
index 23f7625..9ad2797 100644 (file)
@@ -9399,6 +9399,15 @@ Sema::CheckAssignmentConstraints(QualType LHSType, ExprResult &RHS,
     return Compatible;
   }
 
+  // If the LHS has an __auto_type, there are no additional type constraints
+  // to be worried about.
+  if (const auto *AT = dyn_cast<AutoType>(LHSType)) {
+    if (AT->isGNUAutoType()) {
+      Kind = CK_NoOp;
+      return Compatible;
+    }
+  }
+
   // If we have an atomic type, try a non-atomic assignment, then just add an
   // atomic qualification step.
   if (const AtomicType *AtomicTy = dyn_cast<AtomicType>(LHSType)) {
index ad38738..419ccbb 100644 (file)
@@ -1880,6 +1880,14 @@ static std::string getPrintableNameForEntity(DeclarationName Entity) {
   return "type name";
 }
 
+static bool isDependentOrGNUAutoType(QualType T) {
+  if (T->isDependentType())
+    return true;
+
+  const auto *AT = dyn_cast<AutoType>(T);
+  return AT && AT->isGNUAutoType();
+}
+
 QualType Sema::BuildQualifiedType(QualType T, SourceLocation Loc,
                                   Qualifiers Qs, const DeclSpec *DS) {
   if (T.isNull())
@@ -1913,7 +1921,10 @@ QualType Sema::BuildQualifiedType(QualType T, SourceLocation Loc,
         DiagID = diag::err_typecheck_invalid_restrict_invalid_pointee;
         ProblemTy = EltTy;
       }
-    } else if (!T->isDependentType()) {
+    } else if (!isDependentOrGNUAutoType(T)) {
+      // For an __auto_type variable, we may not have seen the initializer yet
+      // and so have no idea whether the underlying type is a pointer type or
+      // not.
       DiagID = diag::err_typecheck_invalid_restrict_not_pointer;
       ProblemTy = T;
     }
@@ -9101,7 +9112,7 @@ QualType Sema::BuildUnaryTransformType(QualType BaseType,
 }
 
 QualType Sema::BuildAtomicType(QualType T, SourceLocation Loc) {
-  if (!T->isDependentType()) {
+  if (!isDependentOrGNUAutoType(T)) {
     // FIXME: It isn't entirely clear whether incomplete atomic types
     // are allowed or not; for simplicity, ban them for the moment.
     if (RequireCompleteType(Loc, T, diag::err_atomic_specifier_bad_type, 0))
index 65f53f4..7faa8a6 100644 (file)
@@ -24,3 +24,57 @@ int i() {
 int k(l)
 __auto_type l; // expected-error {{'__auto_type' not allowed in K&R-style function parameter}}
 {}
+
+void Issue53652(void) {
+  // Ensure that qualifiers all work the same way as GCC.
+  const __auto_type cat = a;
+  const __auto_type pcat = &a;
+  volatile __auto_type vat = a;
+  volatile __auto_type pvat = &a;
+  restrict __auto_type rat = &a;
+  _Atomic __auto_type aat1 = a;
+  _Atomic __auto_type paat = &a;
+
+  // GCC does not accept this either, for the same reason.
+  _Atomic(__auto_type) aat2 = a; // expected-error {{'__auto_type' not allowed here}} \
+                                 // expected-warning {{type specifier missing, defaults to 'int'}}
+
+  // Ensure the types are what we expect them to be, regardless of order we
+  // pass the types.
+  _Static_assert(__builtin_types_compatible_p(__typeof(cat), const int), "");
+  _Static_assert(__builtin_types_compatible_p(const int, __typeof(cat)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(pcat), int *const), "");
+  _Static_assert(__builtin_types_compatible_p(int *const, __typeof(pcat)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(vat), volatile int), "");
+  _Static_assert(__builtin_types_compatible_p(volatile int, __typeof(vat)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(pvat), int *volatile), "");
+  _Static_assert(__builtin_types_compatible_p(int *volatile, __typeof(pvat)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(rat), int *restrict), "");
+  _Static_assert(__builtin_types_compatible_p(int *restrict, __typeof(rat)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(aat1), _Atomic int), "");
+  _Static_assert(__builtin_types_compatible_p(_Atomic int, __typeof(aat1)), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof(paat), _Atomic(int *)), "");
+  _Static_assert(__builtin_types_compatible_p(_Atomic(int *), __typeof(paat)), "");
+
+  // Ensure the types also work in generic selection expressions. Remember, the
+  // type of the expression argument to _Generic is treated as-if it undergoes
+  // lvalue to rvalue conversion, which drops qualifiers. We're making sure the
+  // use of __auto_type doesn't impact that.
+  (void)_Generic(cat, int : 0);
+  (void)_Generic(pcat, int * : 0);
+  (void)_Generic(vat, int : 0);
+  (void)_Generic(pvat, int * : 0);
+  (void)_Generic(rat, int * : 0);
+  (void)_Generic(aat1, int : 0);
+  (void)_Generic(paat, int * : 0);
+
+  // Ensure that trying to merge two different __auto_type types does not
+  // decide that they are both the same type when they're actually different,
+  // and that we reject when the types are the same.
+  __auto_type i = 12;
+  __auto_type f = 1.2f;
+  (void)_Generic(a, __typeof__(i) : 0, __typeof__(f) : 1);
+  (void)_Generic(a,
+                 __typeof__(i) : 0,   // expected-note {{compatible type 'typeof (i)' (aka 'int') specified here}}
+                 __typeof__(a) : 1);  // expected-error {{type 'typeof (a)' (aka 'int') in generic association compatible with previously specified type 'typeof (i)' (aka 'int')}}
+}