On 10/22/2018 04:08 PM, Jason Merrill wrote:
On 10/13/18 8:19 PM, Martin Sebor wrote:
+  oper = cp_parser_type_id (parser);
+  parser->in_type_id_in_expr_p = saved_in_type_id_in_expr_p;
+
+  if (cp_parser_parse_definitely (parser))
+    {
+      /* If all went well, set OPER to the type.  */
+      cp_decl_specifier_seq decl_specs;
+
+      /* Build a trivial decl-specifier-seq.  */
+      clear_decl_specs (&decl_specs);
+      decl_specs.type = oper;
+
+      /* Call grokdeclarator to figure out what type this is.  */
+      oper = grokdeclarator (NULL,
+                 &decl_specs,
+                 TYPENAME,
+                 /*initialized=*/0,
+                 /*attrlist=*/NULL);
+    }

Doesn't grokdeclarator here give you back the same type you already had
from cp_parser_type_id?  The latter already calls grokdeclarator.

I don't know why cp_parser_sizeof_operand does this, either.  Try
removing it from both places?

You're right, the call in cp_parser_has_attribute_expression
was unnecessary.  cp_parser_sizeof_operand still needs it.


+  /* Consume the comma if it's there.  */
+  if (!cp_parser_require (parser, CPP_COMMA, RT_COMMA))
+    {
+      parens.require_close (parser);

I think you want cp_parser_skip_to_closing_parenthesis for error
recovery, rather than require_close.

Thanks, the error messages look slightly better that way (there
are fewer of them), although still not as good as in C or other
compilers in some cases.


+  if (tree attr = cp_parser_gnu_attribute_list (parser,
/*exactly_one=*/true))
+    {
+      if (oper != error_mark_node)
+    {
+      /* Fold constant expressions used in attributes first.  */
+      cp_check_const_attributes (attr);
+
+      /* Finally, see if OPER has been declared with ATTR.  */
+      ret = has_attribute (atloc, oper, attr, default_conversion);
+    }
+    }
+  else
+    {
+      error_at (atloc, "expected identifier");
+      cp_parser_skip_to_closing_parenthesis (parser, true, false,
false);
+    }
+
+  parens.require_close (parser);

I think the require_close should be in the valid case, since *skip*
already consumes a closing paren.

Ah, I need to make it consume the paren by passing true as the last
argument.  With that it works.


+is valuated.  The @var{type-or-expression} argument is subject to the
same

evaluated

Thanks for the review.

Attached is an updated patch with the fixes above.

Martin

gcc/c/ChangeLog:

	* c-parser.c (c_parser_has_attribute_expression): New function.
	(c_parser_attribute): New function.
	(c_parser_attributes): Move code into c_parser_attribute.
	(c_parser_unary_expression): Handle RID_HAS_ATTRIBUTE_EXPRESSION.

gcc/c-family/ChangeLog:

	* c-attribs.c (type_for_vector_size): New function.
	(type_valid_for_vector_size): Same.
	(handle_vector_size_attribute): Move code to the functions above
	and call them.
	(validate_attribute, has_attribute): New functions.
	* c-common.h (has_attribute): Declare.
	(rid): Add RID_HAS_ATTRIBUTE_EXPRESSION.
	* c-common.c (c_common_resword): Same.

gcc/cp/ChangeLog:

	* cp-tree.h (cp_check_const_attributes): Declare.
	* decl2.c (cp_check_const_attributes): Declare extern.
	* parser.c (cp_parser_has_attribute_expression): New function.
	(cp_parser_unary_expression): Handle RID_HAS_ATTRIBUTE_EXPRESSION.
	(cp_parser_gnu_attribute_list): Add argument.

gcc/ChangeLog:

	* doc/extend.texi (Other Builtins): Add __builtin_has_attribute.

gcc/testsuite/ChangeLog:

	* c-c++-common/builtin-has-attribute-2.c: New test.
	* c-c++-common/builtin-has-attribute-3.c: New test.
	* c-c++-common/builtin-has-attribute-4.c: New test.
	* c-c++-common/builtin-has-attribute.c: New test.
	* gcc.dg/builtin-has-attribute.c: New test.
	* gcc/testsuite/gcc.target/i386/builtin-has-attribute.c: New test.

diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 3a88766..c0a1bb5 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -3128,34 +3128,11 @@ handle_deprecated_attribute (tree *node, tree name,
   return NULL_TREE;
 }
 
-/* Handle a "vector_size" attribute; arguments as in
-   struct attribute_spec.handler.  */
-
+/* Return the "base" type from TYPE that is suitable to apply attribute
+   vector_size to by stripping arrays, function types, etc.  */
 static tree
-handle_vector_size_attribute (tree *node, tree name, tree args,
-			      int ARG_UNUSED (flags),
-			      bool *no_add_attrs)
+type_for_vector_size (tree type)
 {
-  unsigned HOST_WIDE_INT vecsize, nunits;
-  machine_mode orig_mode;
-  tree type = *node, new_type, size;
-
-  *no_add_attrs = true;
-
-  size = TREE_VALUE (args);
-  if (size && TREE_CODE (size) != IDENTIFIER_NODE
-      && TREE_CODE (size) != FUNCTION_DECL)
-    size = default_conversion (size);
-
-  if (!tree_fits_uhwi_p (size))
-    {
-      warning (OPT_Wattributes, "%qE attribute ignored", name);
-      return NULL_TREE;
-    }
-
-  /* Get the vector size (in bytes).  */
-  vecsize = tree_to_uhwi (size);
-
   /* We need to provide for vector pointers, vector arrays, and
      functions returning vectors.  For example:
 
@@ -3171,8 +3148,25 @@ handle_vector_size_attribute (tree *node, tree name, tree args,
 	 || TREE_CODE (type) == OFFSET_TYPE)
     type = TREE_TYPE (type);
 
+  return type;
+}
+
+/* Given TYPE, return the base type to which the vector_size attribute
+   ATNAME with ARGS, when non-null, can be applied, if one exists.
+   On success and when both ARGS and PTRNUNITS are non-null, set
+   *PTRNUNINTS to the number of vector units.  When PTRNUNITS is not
+   null, issue a warning when the attribute argument is not constant
+   and an error if there is no such type.  Otherwise issue a warning
+   in the latter case and return null.  */
+
+static tree
+type_valid_for_vector_size (tree type, tree atname, tree args,
+			    unsigned HOST_WIDE_INT *ptrnunits)
+{
+  bool error_p = ptrnunits != NULL;
+
   /* Get the mode of the type being modified.  */
-  orig_mode = TYPE_MODE (type);
+  machine_mode orig_mode = TYPE_MODE (type);
 
   if ((!INTEGRAL_TYPE_P (type)
        && !SCALAR_FLOAT_TYPE_P (type)
@@ -3183,14 +3177,38 @@ handle_vector_size_attribute (tree *node, tree name, tree args,
       || !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
       || TREE_CODE (type) == BOOLEAN_TYPE)
     {
-      error ("invalid vector type for attribute %qE", name);
+      if (error_p)
+	error ("invalid vector type for attribute %qE", atname);
+      else
+	warning (OPT_Wattributes, "invalid vector type for attribute %qE",
+		 atname);
       return NULL_TREE;
     }
 
+  /* When no argument has been provided this is just a request to validate
+     the type above.  Return TYPE to indicate success.  */
+  if (!args)
+    return type;
+
+  tree size = TREE_VALUE (args);
+  if (size && TREE_CODE (size) != IDENTIFIER_NODE
+      && TREE_CODE (size) != FUNCTION_DECL)
+    size = default_conversion (size);
+
+  if (!tree_fits_uhwi_p (size))
+    {
+      /* FIXME: make the error message more informative.  */
+      if (error_p)
+	warning (OPT_Wattributes, "%qE attribute ignored", atname);
+      return NULL_TREE;
+    }
+
+  unsigned HOST_WIDE_INT vecsize = tree_to_uhwi (size);
   if (vecsize % tree_to_uhwi (TYPE_SIZE_UNIT (type)))
     {
-      error ("vector size not an integral multiple of component size");
-      return NULL;
+      if (error_p)
+	error ("vector size not an integral multiple of component size");
+      return NULL_TREE;
     }
 
   if (vecsize == 0)
@@ -3200,14 +3218,44 @@ handle_vector_size_attribute (tree *node, tree name, tree args,
     }
 
   /* Calculate how many units fit in the vector.  */
-  nunits = vecsize / tree_to_uhwi (TYPE_SIZE_UNIT (type));
+  unsigned HOST_WIDE_INT nunits = vecsize / tree_to_uhwi (TYPE_SIZE_UNIT (type));
   if (nunits & (nunits - 1))
     {
-      error ("number of components of the vector not a power of two");
+      if (error_p)
+	error ("number of components of the vector not a power of two");
+      else
+	warning (OPT_Wattributes,
+		 "number of components of the vector not a power of two");
       return NULL_TREE;
     }
 
-  new_type = build_vector_type (type, nunits);
+  if (ptrnunits)
+    *ptrnunits = nunits;
+
+  return type;
+}
+
+/* Handle a "vector_size" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_vector_size_attribute (tree *node, tree name, tree args,
+			      int ARG_UNUSED (flags),
+			      bool *no_add_attrs)
+{
+  *no_add_attrs = true;
+
+  /* Determine the "base" type to apply the attribute to.  */
+  tree type = type_for_vector_size (*node);
+
+  /* Get the vector size (in bytes) and let the function compute
+     the number of vector units.  */
+  unsigned HOST_WIDE_INT nunits;
+  type = type_valid_for_vector_size (type, name, args, &nunits);
+  if (!type)
+    return NULL_TREE;
+
+  tree new_type = build_vector_type (type, nunits);
 
   /* Build back pointers if needed.  */
   *node = lang_hooks.types.reconstruct_complex_type (*node, new_type);
@@ -3678,3 +3726,324 @@ handle_patchable_function_entry_attribute (tree *, tree, tree, int, bool *)
   /* Nothing to be done here.  */
   return NULL_TREE;
 }
+
+/* Attempt to partially validate a single attribute ATTR as if
+   it were to be applied to an entity OPER.  */
+
+static bool
+validate_attribute (location_t atloc, tree oper, tree attr)
+{
+  /* Determine whether the name of the attribute is valid
+     and fail with an error if not.  */
+  tree atname = get_attribute_name (attr);
+  if (!lookup_attribute_spec (atname))
+    {
+      if (atloc != UNKNOWN_LOCATION)
+	error_at (atloc, "unknown attribute %qE", atname);
+      return false;
+    }
+
+  tree args = TREE_VALUE (attr);
+  if (!args)
+    return true;
+
+  /* FIXME: Do some validation.  */
+  const char *atstr = IDENTIFIER_POINTER (atname);
+  if (!strcmp (atstr, "format"))
+    return true;
+
+  /* Only when attribute arguments have been provided try to validate
+     the whole thing.  decl_attributes doesn't return an indication of
+     success or failure so proceed regardless.  */
+  const char tmpname[] = "__builtin_has_attribute_tmp.";
+  tree tmpid = get_identifier (tmpname);
+  tree tmpdecl;
+  if (!strcmp (atstr, "vector_size"))
+    {
+      tree type = TYPE_P (oper) ? oper : TREE_TYPE (oper);
+      /* Check for function type here since type_for_vector_size
+	 strips it while looking for a function's return type.  */
+      if (FUNC_OR_METHOD_TYPE_P (type))
+	{
+	  warning_at (atloc, OPT_Wattributes,
+		      "invalid operand type %qT for %qs", type, atstr);
+	  return false;
+	}
+
+      type = type_for_vector_size (type);
+      if (VECTOR_TYPE_P (type))
+	type = TREE_TYPE (type);
+      /* Avoid trying to apply attribute vector_size to OPER since
+	 it's overly restrictive.  Simply make sure it has the right
+	 type.  */
+      return type_valid_for_vector_size (type, atname, args, NULL);
+    }
+
+  if (TYPE_P (oper))
+    tmpdecl = build_decl (atloc, TYPE_DECL, tmpid, oper);
+  else
+    tmpdecl = build_decl (atloc, TREE_CODE (oper), tmpid, TREE_TYPE (oper));
+
+  /* Temporarily clear CURRENT_FUNCTION_DECL to make decl_attributes
+     believe the DECL declared above is at file scope.  (See bug 87526.)  */
+  tree save_curfunc = current_function_decl;
+  current_function_decl = NULL_TREE;
+  if (DECL_P (tmpdecl))
+    {
+      if (DECL_P (oper))
+	/* An alias cannot be a defintion so declare the symbol extern.  */
+	DECL_EXTERNAL (tmpdecl) = true;
+      /* Attribute visibility only applies to symbols visible from other
+	 translation units so make it "public."   */
+      TREE_PUBLIC (tmpdecl) = TREE_PUBLIC (oper);
+    }
+  decl_attributes (&tmpdecl, attr, 0);
+  current_function_decl = save_curfunc;
+
+  /* FIXME: Change decl_attributes to indicate success or failure (and
+     parameterize it to avoid failing with errors).  */
+  return true;
+}
+
+/* Return true if the DECL, EXPR, or TYPE t has been declared with
+   attribute ATTR.  For DECL, consider also its type.  For EXPR,
+   consider just its type.  */
+
+bool
+has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
+{
+  if (!attr || !t || t == error_mark_node)
+    return false;
+
+  if (!validate_attribute (atloc, t, attr))
+    return false;
+
+  tree type = NULL_TREE;
+  tree expr = NULL_TREE;
+  if (TYPE_P (t))
+    type = t;
+  else
+    {
+      do
+	{
+	  /* Determine the array element/member declaration from
+	     an ARRAY/COMPONENT_REF.  */
+	  STRIP_NOPS (t);
+	  tree_code code = TREE_CODE (t);
+	  if (code == ARRAY_REF)
+	    t = TREE_OPERAND (t, 0);
+	  else if (code == COMPONENT_REF)
+	    t = TREE_OPERAND (t, 1);
+	  else
+	    break;
+	} while (true);
+      expr = t;
+    }
+
+  /* Set to true when an attribute is found in the referenced entity
+     that matches the specified attribute.  */
+  bool found_match = false;
+
+  tree atname = get_attribute_name (attr);
+  const char *namestr = IDENTIFIER_POINTER (atname);
+
+   /* Iterate once for a type and twice for a function or variable
+     declaration: once for the DECL and the second time for its
+     TYPE.  */
+  for (bool done = false; !found_match && !done; )
+    {
+      tree atlist;
+      if (type)
+	{
+	  if (type == error_mark_node)
+	    {
+	      /* This could be a label.  FIXME: add support for labels.  */
+	      warning_at (atloc, OPT_Wattributes,
+			  (TYPE_P (t)
+			   ? G_("%qs attribute not supported for %qT "
+				"in %<__builtin_has_attribute%>")
+			   : G_("%qs attribute not supported for %qE "
+				"in %<__builtin_has_attribute%>")),
+			  namestr, t);
+	      return false;
+	    }
+
+	  /* Clear EXPR to prevent considering it again below.  */
+	  atlist = TYPE_ATTRIBUTES (type);
+	  expr = NULL_TREE;
+	  done = true;
+	}
+      else if (DECL_P (expr))
+	{
+	  /* Set TYPE to the DECL's type to process it on the next
+	     iteration.  */
+	  atlist = DECL_ATTRIBUTES (expr);
+	  type = TREE_TYPE (expr);
+	}
+      else
+	{
+	  atlist = TYPE_ATTRIBUTES (TREE_TYPE (expr));
+	  done = true;
+	}
+
+     /* True when an attribute with the sought name (though not necessarily
+	 with the sought attributes) has been found on the attribute chain.  */
+      bool found_attr = false;
+
+      /* For attribute aligned ignore the attribute list and consider
+	 the tree node itself instead.  */
+      if (type && !strcmp ("aligned", namestr))
+	atlist = NULL_TREE;
+
+      /* When clear, the first mismatched attribute argument results
+	 in failure.  Otherwise, the first matched attribute argument
+	 results in success.  */
+      bool attr_nonnull = !strcmp ("nonnull", namestr);
+      bool ignore_mismatches = attr_nonnull;
+
+      /* Iterate over the instances of the sought attribute on the DECL or
+	 TYPE (there may be multiple instances with different arguments).  */
+      for (; (atlist = lookup_attribute (namestr, atlist));
+	   found_attr = true, atlist = TREE_CHAIN (atlist))
+	{
+	  /* If there are no arguments to match the result is true except
+	     for nonnull where the attribute with no arguments must match.  */
+	  if (!TREE_VALUE (attr))
+	    return attr_nonnull ? !TREE_VALUE (atlist) : true;
+
+	  /* Attribute nonnull with no arguments subsumes all values of
+	     the attribute.  FIXME: This is overly broad since it only
+	     applies to pointer arguments, but querying non-pointer
+	     arguments is diagnosed.  */
+	  if (!TREE_VALUE (atlist) && attr_nonnull)
+	    return true;
+
+	  /* Iterate over the DECL or TYPE attribute argument's values.  */
+	  for (tree val = TREE_VALUE (atlist); val; val = TREE_CHAIN (val))
+	    {
+	      /* Iterate over the arguments in the sought attribute comparing
+		 their values to those specified for the DECL or TYPE.  */
+	      for (tree arg = TREE_VALUE (attr); arg; arg = TREE_CHAIN (arg))
+		{
+		  tree v1 = TREE_VALUE (val);
+		  tree v2 = TREE_VALUE (arg);
+		  if (v1 == v2)
+		    return true;
+
+		  if (!v1 || !v2)
+		    break;
+
+		  if (TREE_CODE (v1) == IDENTIFIER_NODE
+		      || TREE_CODE (v2) == IDENTIFIER_NODE)
+		    /* Two identifiers are the same if their values are
+		       equal (that's handled above).  Otherwise ther are
+		       either not the same or oneis not an identifier.  */
+		    return false;
+
+		  /* Convert to make them equality-comparable.  */
+		  v1 = convert (v1);
+		  v2 = convert (v2);
+
+		  /* A positive value indicates equality, negative means
+		     "don't know."  */
+		  if (simple_cst_equal (v1, v2) == 1)
+		    return true;
+
+		  if (!ignore_mismatches)
+		    break;
+		}
+	    }
+	}
+
+      if (!found_attr)
+	{
+	  /* Some attributes are encoded directly in the tree node.  */
+	  if (!strcmp ("aligned", namestr))
+	    {
+	      if (tree arg = TREE_VALUE (attr))
+		{
+		  arg = convert (TREE_VALUE (arg));
+		  if (expr && DECL_P (expr)
+		      && DECL_USER_ALIGN (expr)
+		      && tree_fits_uhwi_p (arg))
+		    found_match = DECL_ALIGN_UNIT (expr) == tree_to_uhwi (arg);
+		  else if (type && TYPE_USER_ALIGN (type))
+		    found_match = TYPE_ALIGN_UNIT (type) == tree_to_uhwi (arg);
+		}
+	      else if (expr && DECL_P (expr))
+		found_match = DECL_USER_ALIGN (expr);
+	      else if (type)
+		found_match = TYPE_USER_ALIGN (type);
+	    }
+	  else if (!strcmp ("const", namestr))
+	    {
+	      if (expr && DECL_P (expr))
+		found_match = TREE_READONLY (expr);
+	    }
+	  else if (!strcmp ("const", namestr))
+	    {
+	      if (expr && DECL_P (expr))
+		found_match = DECL_PURE_P (expr);
+	    }
+	  else if (!strcmp ("deprecated", namestr))
+	    {
+	      found_match = TREE_DEPRECATED (expr ? expr : type);
+	      if (found_match)
+		return true;
+	    }
+	  else if (!strcmp ("vector_size", namestr))
+	    {
+	      if (!type)
+		continue;
+
+	      /* Determine the base type from arrays, pointers, and such.
+		 Fail if the base type is not a vector.  */
+	      type = type_for_vector_size (type);
+	      if (!VECTOR_TYPE_P (type))
+		return false;
+
+	      if (tree arg = TREE_VALUE (attr))
+		{
+		  /* Compare the vector size argument for equality.  */
+		  arg = convert (TREE_VALUE (arg));
+		  return tree_int_cst_equal (arg, TYPE_SIZE_UNIT (type)) == 1;
+		}
+	      else
+		return true;
+	    }
+	  else if (!strcmp ("warn_if_not_aligned", namestr))
+	    {
+	      if (tree arg = TREE_VALUE (attr))
+		{
+		  arg = convert (TREE_VALUE (arg));
+		  if (expr && DECL_P (expr))
+		    found_match = (DECL_WARN_IF_NOT_ALIGN (expr)
+				   == tree_to_uhwi (arg) * BITS_PER_UNIT);
+		  else if (type)
+		    found_match = (TYPE_WARN_IF_NOT_ALIGN (type)
+				   == tree_to_uhwi (arg) * BITS_PER_UNIT);
+		}
+	      else if (expr && DECL_P (expr))
+		found_match = DECL_WARN_IF_NOT_ALIGN (expr);
+	      else if (type)
+		found_match = TYPE_WARN_IF_NOT_ALIGN (type);
+	    }
+	  else if (!strcmp ("transparent_union", namestr))
+	    {
+	      if (type)
+		found_match = TYPE_TRANSPARENT_AGGR (type) != 0;
+	    }
+	  else if (!strcmp ("mode", namestr))
+	    {
+	      /* Finally issue a warning for attributes that cannot
+		 be supported in this context.  Attribute mode is not
+		 added to a symbol and cannot be determined from it.  */
+	      warning_at (atloc, OPT_Wattributes,
+			  "%qs attribute not supported in "
+			  "%<__builtin_has_attribute%>", namestr);
+	      break;
+	    }
+	}
+    }
+  return found_match;
+}
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 10a8bc2..7d139be 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -372,6 +372,7 @@ const struct c_common_resword c_common_reswords[] =
     RID_BUILTIN_CALL_WITH_STATIC_CHAIN, D_CONLY },
   { "__builtin_choose_expr", RID_CHOOSE_EXPR, D_CONLY },
   { "__builtin_complex", RID_BUILTIN_COMPLEX, D_CONLY },
+  { "__builtin_has_attribute", RID_BUILTIN_HAS_ATTRIBUTE, 0 },
   { "__builtin_launder", RID_BUILTIN_LAUNDER, D_CXXONLY },
   { "__builtin_shuffle", RID_BUILTIN_SHUFFLE, 0 },
   { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 9e86876..6447ecb 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -103,6 +103,7 @@ enum rid
   RID_EXTENSION, RID_IMAGPART, RID_REALPART, RID_LABEL,      RID_CHOOSE_EXPR,
   RID_TYPES_COMPATIBLE_P,      RID_BUILTIN_COMPLEX,	     RID_BUILTIN_SHUFFLE,
   RID_BUILTIN_TGMATH,
+  RID_BUILTIN_HAS_ATTRIBUTE,
   RID_DFLOAT32, RID_DFLOAT64, RID_DFLOAT128,
 
   /* TS 18661-3 keywords, in the same sequence as the TI_* values.  */
@@ -1333,6 +1334,7 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   enum cpp_ttype token_type,
 						   location_t prev_token_loc);
 extern tree braced_list_to_string (tree, tree);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
 
 #if CHECKING_P
 namespace selftest {
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 1f173fc..090eef2 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -1439,6 +1439,8 @@ static vec<tree, va_gc> *c_parser_expr_list (c_parser *, bool, bool,
 					     vec<tree, va_gc> **, location_t *,
 					     tree *, vec<location_t> *,
 					     unsigned int * = NULL);
+static struct c_expr c_parser_has_attribute_expression (c_parser *);
+
 static void c_parser_oacc_declare (c_parser *);
 static void c_parser_oacc_enter_exit_data (c_parser *, bool);
 static void c_parser_oacc_update (c_parser *);
@@ -4290,7 +4292,126 @@ c_parser_attribute_any_word (c_parser *parser)
    type), a reserved word storage class specifier, type specifier or
    type qualifier.  ??? This still leaves out most reserved keywords
    (following the old parser), shouldn't we include them, and why not
-   allow identifiers declared as types to start the arguments?  */
+   allow identifiers declared as types to start the arguments?
+   When EXPECT_COMMA is true, expect the attribute to be preceded
+   by a comma and fail if it isn't.
+   When EMPTY_OK is true, allow and consume any number of consecutive
+   commas with no attributes in between.  */
+
+static tree
+c_parser_attribute (c_parser *parser, tree attrs,
+		    bool expect_comma = false, bool empty_ok = true)
+{
+  bool comma_first = c_parser_next_token_is (parser, CPP_COMMA);
+  if (!comma_first
+      && !c_parser_next_token_is (parser, CPP_NAME)
+      && !c_parser_next_token_is (parser, CPP_KEYWORD))
+    return NULL_TREE;
+
+  while (c_parser_next_token_is (parser, CPP_COMMA))
+    {
+      c_parser_consume_token (parser);
+      if (!empty_ok)
+	return attrs;
+    }
+
+  tree attr_name = c_parser_attribute_any_word (parser);
+  if (attr_name == NULL_TREE)
+    return NULL_TREE;
+
+  attr_name = canonicalize_attr_name (attr_name);
+  c_parser_consume_token (parser);
+
+  tree attr;
+  if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN))
+    {
+      if (expect_comma && !comma_first)
+	{
+	  /* A comma is missing between the last attribute on the chain
+	     and this one.  */
+	  c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
+				     "expected %<)%>");
+	  return error_mark_node;
+	}
+      attr = build_tree_list (attr_name, NULL_TREE);
+      /* Add this attribute to the list.  */
+      attrs = chainon (attrs, attr);
+      return attrs;
+    }
+  c_parser_consume_token (parser);
+
+  vec<tree, va_gc> *expr_list;
+  tree attr_args;
+  /* Parse the attribute contents.  If they start with an
+     identifier which is followed by a comma or close
+     parenthesis, then the arguments start with that
+     identifier; otherwise they are an expression list.
+     In objective-c the identifier may be a classname.  */
+  if (c_parser_next_token_is (parser, CPP_NAME)
+      && (c_parser_peek_token (parser)->id_kind == C_ID_ID
+	  || (c_dialect_objc ()
+	      && c_parser_peek_token (parser)->id_kind
+	      == C_ID_CLASSNAME))
+      && ((c_parser_peek_2nd_token (parser)->type == CPP_COMMA)
+	  || (c_parser_peek_2nd_token (parser)->type
+	      == CPP_CLOSE_PAREN))
+      && (attribute_takes_identifier_p (attr_name)
+	  || (c_dialect_objc ()
+	      && c_parser_peek_token (parser)->id_kind
+	      == C_ID_CLASSNAME)))
+    {
+      tree arg1 = c_parser_peek_token (parser)->value;
+      c_parser_consume_token (parser);
+      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	attr_args = build_tree_list (NULL_TREE, arg1);
+      else
+	{
+	  tree tree_list;
+	  c_parser_consume_token (parser);
+	  expr_list = c_parser_expr_list (parser, false, true,
+					  NULL, NULL, NULL, NULL);
+	  tree_list = build_tree_list_vec (expr_list);
+	  attr_args = tree_cons (NULL_TREE, arg1, tree_list);
+	  release_tree_vector (expr_list);
+	}
+    }
+  else
+    {
+      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	attr_args = NULL_TREE;
+      else
+	{
+	  expr_list = c_parser_expr_list (parser, false, true,
+					  NULL, NULL, NULL, NULL);
+	  attr_args = build_tree_list_vec (expr_list);
+	  release_tree_vector (expr_list);
+	}
+    }
+
+  attr = build_tree_list (attr_name, attr_args);
+  if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+    c_parser_consume_token (parser);
+  else
+    {
+      parser->lex_untranslated_string = false;
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
+				 "expected %<)%>");
+      return error_mark_node;
+    }
+
+  if (expect_comma && !comma_first)
+    {
+      /* A comma is missing between the last attribute on the chain
+	 and this one.  */
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
+				 "expected %<)%>");
+      return error_mark_node;
+    }
+
+  /* Add this attribute to the list.  */
+  attrs = chainon (attrs, attr);
+  return attrs;
+}
 
 static tree
 c_parser_attributes (c_parser *parser)
@@ -4315,97 +4436,19 @@ c_parser_attributes (c_parser *parser)
 	  c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
 	  return attrs;
 	}
-      /* Parse the attribute list.  */
-      while (c_parser_next_token_is (parser, CPP_COMMA)
-	     || c_parser_next_token_is (parser, CPP_NAME)
-	     || c_parser_next_token_is (parser, CPP_KEYWORD))
+      /* Parse the attribute list.  Require a comma between successive
+	 (possibly empty) attributes.  */
+      for (bool expect_comma = false; ; expect_comma = true)
 	{
-	  tree attr, attr_name, attr_args;
-	  vec<tree, va_gc> *expr_list;
-	  if (c_parser_next_token_is (parser, CPP_COMMA))
-	    {
-	      c_parser_consume_token (parser);
-	      continue;
-	    }
-
-	  attr_name = c_parser_attribute_any_word (parser);
-	  if (attr_name == NULL)
+	  /* Parse a single attribute.  */
+	  tree attr = c_parser_attribute (parser, attrs, expect_comma);
+	  if (attr == error_mark_node)
+	    return attrs;
+	  if (!attr)
 	    break;
-	  attr_name = canonicalize_attr_name (attr_name);
-	  c_parser_consume_token (parser);
-	  if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN))
-	    {
-	      attr = build_tree_list (attr_name, NULL_TREE);
-	      /* Add this attribute to the list.  */
-	      attrs = chainon (attrs, attr);
-	      /* If the next token isn't a comma, we're done.  */
-	      if (!c_parser_next_token_is (parser, CPP_COMMA))
-		break;
-	      continue;
-	    }
-	  c_parser_consume_token (parser);
-	  /* Parse the attribute contents.  If they start with an
-	     identifier which is followed by a comma or close
-	     parenthesis, then the arguments start with that
-	     identifier; otherwise they are an expression list.  
-	     In objective-c the identifier may be a classname.  */
-	  if (c_parser_next_token_is (parser, CPP_NAME)
-	      && (c_parser_peek_token (parser)->id_kind == C_ID_ID
-		  || (c_dialect_objc ()
-		      && c_parser_peek_token (parser)->id_kind
-			 == C_ID_CLASSNAME))
-	      && ((c_parser_peek_2nd_token (parser)->type == CPP_COMMA)
-		  || (c_parser_peek_2nd_token (parser)->type
-		      == CPP_CLOSE_PAREN))
-	      && (attribute_takes_identifier_p (attr_name)
-		  || (c_dialect_objc ()
-		      && c_parser_peek_token (parser)->id_kind
-			 == C_ID_CLASSNAME)))
-	    {
-	      tree arg1 = c_parser_peek_token (parser)->value;
-	      c_parser_consume_token (parser);
-	      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
-		attr_args = build_tree_list (NULL_TREE, arg1);
-	      else
-		{
-		  tree tree_list;
-		  c_parser_consume_token (parser);
-		  expr_list = c_parser_expr_list (parser, false, true,
-						  NULL, NULL, NULL, NULL);
-		  tree_list = build_tree_list_vec (expr_list);
-		  attr_args = tree_cons (NULL_TREE, arg1, tree_list);
-		  release_tree_vector (expr_list);
-		}
-	    }
-	  else
-	    {
-	      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
-		attr_args = NULL_TREE;
-	      else
-		{
-		  expr_list = c_parser_expr_list (parser, false, true,
-						  NULL, NULL, NULL, NULL);
-		  attr_args = build_tree_list_vec (expr_list);
-		  release_tree_vector (expr_list);
-		}
-	    }
+	  attrs = attr;
+      }
 
-	  attr = build_tree_list (attr_name, attr_args);
-	  if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
-	    c_parser_consume_token (parser);
-	  else
-	    {
-	      parser->lex_untranslated_string = false;
-	      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
-					 "expected %<)%>");
-	      return attrs;
-	    }
-	  /* Add this attribute to the list.  */
-	  attrs = chainon (attrs, attr);
-	  /* If the next token isn't a comma, we're done.  */
-	  if (!c_parser_next_token_is (parser, CPP_COMMA))
-	    break;
-	}
       /* Look for the two `)' tokens.  */
       if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
 	c_parser_consume_token (parser);
@@ -7240,6 +7283,8 @@ c_parser_unary_expression (c_parser *parser)
 	  return c_parser_sizeof_expression (parser);
 	case RID_ALIGNOF:
 	  return c_parser_alignof_expression (parser);
+	case RID_BUILTIN_HAS_ATTRIBUTE:
+	  return c_parser_has_attribute_expression (parser);
 	case RID_EXTENSION:
 	  c_parser_consume_token (parser);
 	  ext = disable_extension_diagnostics ();
@@ -7439,6 +7484,123 @@ c_parser_alignof_expression (c_parser *parser)
     }
 }
 
+/* Parse the __builtin_has_attribute ([expr|type], attribute-spec)
+   expression.  */
+
+static struct c_expr
+c_parser_has_attribute_expression (c_parser *parser)
+{
+  gcc_assert (c_parser_next_token_is_keyword (parser,
+					      RID_BUILTIN_HAS_ATTRIBUTE));
+  c_parser_consume_token (parser);
+
+  c_inhibit_evaluation_warnings++;
+
+  matching_parens parens;
+  if (!parens.require_open (parser))
+    {
+      c_inhibit_evaluation_warnings--;
+      in_typeof--;
+
+      struct c_expr result;
+      result.set_error ();
+      result.original_code = ERROR_MARK;
+      result.original_type = NULL;
+      return result;
+    }
+
+  /* Treat the type argument the same way as in typeof for the purposes
+     of warnings.  FIXME: Generalize this so the warning refers to
+     __builtin_has_attribute rather than typeof.  */
+  in_typeof++;
+
+  /* The first operand: one of DECL, EXPR, or TYPE.  */
+  tree oper = NULL_TREE;
+  if (c_parser_next_tokens_start_typename (parser, cla_prefer_id))
+    {
+      struct c_type_name *tname = c_parser_type_name (parser);
+      in_typeof--;
+      if (tname)
+	{
+	  oper = groktypename (tname, NULL, NULL);
+	  pop_maybe_used (variably_modified_type_p (oper, NULL_TREE));
+	}
+    }
+  else
+    {
+      struct c_expr cexpr = c_parser_expr_no_commas (parser, NULL);
+      c_inhibit_evaluation_warnings--;
+      in_typeof--;
+      if (cexpr.value != error_mark_node)
+	{
+	  mark_exp_read (cexpr.value);
+	  oper = cexpr.value;
+	  tree etype = TREE_TYPE (oper);
+	  bool was_vm = variably_modified_type_p (etype, NULL_TREE);
+	  /* This is returned with the type so that when the type is
+	     evaluated, this can be evaluated.  */
+	  if (was_vm)
+	    oper = c_fully_fold (oper, false, NULL);
+	  pop_maybe_used (was_vm);
+	}
+    }
+
+  struct c_expr result;
+  result.original_code = ERROR_MARK;
+  result.original_type = NULL;
+
+  if (!c_parser_require (parser, CPP_COMMA, "expected %<,%>"))
+    {
+      /* Consume the closing parenthesis if that's the next token
+	 in the likely case the built-in was invoked with fewer
+	 than two arguments.  */
+      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	c_parser_consume_token (parser);
+      c_inhibit_evaluation_warnings--;
+      result.set_error ();
+      return result;
+    }
+
+  parser->lex_untranslated_string = true;
+
+  location_t atloc = c_parser_peek_token (parser)->location;
+  /* Parse a single attribute.  Require no leading comma and do not
+     allow empty attributes.  */
+  tree attr = c_parser_attribute (parser, NULL_TREE, false, false);
+
+  parser->lex_untranslated_string = false;
+
+  if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+    c_parser_consume_token (parser);
+  else
+    {
+      c_parser_error (parser, "expected identifier");
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
+
+      result.set_error ();
+      return result;
+    }
+
+  if (!attr)
+    {
+      error_at (atloc, "expected identifier");
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
+				 "expected %<)%>");
+      result.set_error ();
+      return result;
+    }
+
+  result.original_code = INTEGER_CST;
+  result.original_type = boolean_type_node;
+
+  if (has_attribute (atloc, oper, attr, default_conversion))
+    result.value = boolean_true_node;
+  else
+    result.value =  boolean_false_node;
+
+  return result;
+}
+
 /* Helper function to read arguments of builtins which are interfaces
    for the middle-end nodes like COMPLEX_EXPR, VEC_PERM_EXPR and
    others.  The name of the builtin is passed using BNAME parameter.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index efbdad8..8e8af94 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6428,6 +6428,7 @@ extern int parm_index                           (tree);
 extern tree vtv_start_verification_constructor_init_function (void);
 extern tree vtv_finish_verification_constructor_init_function (tree);
 extern bool cp_omp_mappable_type		(tree);
+extern void cp_check_const_attributes (tree);
 
 /* in error.c */
 extern const char *type_as_string		(tree, int);
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index a5ad0ee..baf303f 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -1378,7 +1378,7 @@ cp_reconstruct_complex_type (tree type, tree bottom)
 /* Replaces any constexpr expression that may be into the attributes
    arguments with their reduced value.  */
 
-static void
+void
 cp_check_const_attributes (tree attributes)
 {
   if (attributes == error_mark_node)
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 6696f17..f07df78 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -2049,6 +2049,8 @@ static cp_expr cp_parser_unary_expression
   (cp_parser *, cp_id_kind * = NULL, bool = false, bool = false, bool = false);
 static enum tree_code cp_parser_unary_operator
   (cp_token *);
+static tree cp_parser_has_attribute_expression
+  (cp_parser *);
 static tree cp_parser_new_expression
   (cp_parser *);
 static vec<tree, va_gc> *cp_parser_new_placement
@@ -2380,7 +2382,7 @@ static tree cp_parser_attributes_opt
 static tree cp_parser_gnu_attributes_opt
   (cp_parser *);
 static tree cp_parser_gnu_attribute_list
-  (cp_parser *);
+  (cp_parser *, bool = false);
 static tree cp_parser_std_attribute
   (cp_parser *, tree);
 static tree cp_parser_std_attribute_spec
@@ -8065,6 +8067,9 @@ cp_parser_unary_expression (cp_parser *parser, cp_id_kind * pidk,
 	    return ret_expr;
 	  }
 
+	case RID_BUILTIN_HAS_ATTRIBUTE:
+	  return cp_parser_has_attribute_expression (parser);
+
 	case RID_NEW:
 	  return cp_parser_new_expression (parser);
 
@@ -8362,6 +8367,129 @@ cp_parser_unary_operator (cp_token* token)
     }
 }
 
+/* Parse a __builtin_has_attribute([expr|type], attribute-spec) expression.
+   Returns a representation of the expression.  */
+
+static tree
+cp_parser_has_attribute_expression (cp_parser *parser)
+{
+  location_t start_loc = cp_lexer_peek_token (parser->lexer)->location;
+
+  /* Consume the __builtin_has_attribute token.  */
+  cp_lexer_consume_token (parser->lexer);
+
+  matching_parens parens;
+  if (!parens.require_open (parser))
+    return error_mark_node;
+
+  /* Types cannot be defined in a `sizeof' expression.  Save away the
+     old message.  */
+  const char *saved_message = parser->type_definition_forbidden_message;
+  /* And create the new one.  */
+  const int kwd = RID_BUILTIN_HAS_ATTRIBUTE;
+  char *tmp = concat ("types may not be defined in %<",
+		      IDENTIFIER_POINTER (ridpointers[kwd]),
+		      "%> expressions", NULL);
+  parser->type_definition_forbidden_message = tmp;
+
+  /* The restrictions on constant-expressions do not apply inside
+     sizeof expressions.  */
+  bool saved_integral_constant_expression_p
+    = parser->integral_constant_expression_p;
+  bool saved_non_integral_constant_expression_p
+    = parser->non_integral_constant_expression_p;
+  parser->integral_constant_expression_p = false;
+
+  /* Do not actually evaluate the expression.  */
+  ++cp_unevaluated_operand;
+  ++c_inhibit_evaluation_warnings;
+
+  tree oper = NULL_TREE;
+
+  /* We can't be sure yet whether we're looking at a type-id or an
+     expression.  */
+  cp_parser_parse_tentatively (parser);
+
+  bool saved_in_type_id_in_expr_p = parser->in_type_id_in_expr_p;
+  parser->in_type_id_in_expr_p = true;
+  /* Look for the type-id.  */
+  oper = cp_parser_type_id (parser);
+  parser->in_type_id_in_expr_p = saved_in_type_id_in_expr_p;
+
+  if (cp_parser_parse_definitely (parser))
+    {
+      /* If all went well, set OPER to the type.  */
+      cp_decl_specifier_seq decl_specs;
+
+      /* Build a trivial decl-specifier-seq.  */
+      clear_decl_specs (&decl_specs);
+      decl_specs.type = oper;
+    }
+
+  /* If the type-id production did not work out, then we must be
+     looking at the unary-expression production.  */
+  if (!oper || oper == error_mark_node)
+    oper = cp_parser_unary_expression (parser);
+
+  /* Go back to evaluating expressions.  */
+  --cp_unevaluated_operand;
+  --c_inhibit_evaluation_warnings;
+
+  /* Free the message we created.  */
+  free (tmp);
+  /* And restore the old one.  */
+  parser->type_definition_forbidden_message = saved_message;
+  parser->integral_constant_expression_p
+    = saved_integral_constant_expression_p;
+  parser->non_integral_constant_expression_p
+    = saved_non_integral_constant_expression_p;
+
+  /* Consume the comma if it's there.  */
+  if (!cp_parser_require (parser, CPP_COMMA, RT_COMMA))
+    {
+      cp_parser_skip_to_closing_parenthesis (parser, false, false,
+					     /*consume_paren=*/true);
+      return error_mark_node;
+    }
+
+  /* Parse the attribute specification.  */
+  bool ret = false;
+  location_t atloc = cp_lexer_peek_token (parser->lexer)->location;
+  if (tree attr = cp_parser_gnu_attribute_list (parser, /*exactly_one=*/true))
+    {
+      if (oper != error_mark_node)
+	{
+	  /* Fold constant expressions used in attributes first.  */
+	  cp_check_const_attributes (attr);
+
+	  /* Finally, see if OPER has been declared with ATTR.  */
+	  ret = has_attribute (atloc, oper, attr, default_conversion);
+	}
+
+      parens.require_close (parser);
+    }
+  else
+    {
+      error_at (atloc, "expected identifier");
+      cp_parser_skip_to_closing_parenthesis (parser, true, false, true);
+    }
+
+  /* Construct a location e.g. :
+     __builtin_has_attribute (oper, attr)
+     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+     with start == caret at the start of the built-in token,
+     and with the endpoint at the final closing paren.  */
+  location_t finish_loc
+    = cp_lexer_previous_token (parser->lexer)->location;
+  location_t compound_loc
+    = make_location (start_loc, start_loc, finish_loc);
+
+  cp_expr ret_expr (ret ? boolean_true_node : boolean_false_node);
+  ret_expr.set_location (compound_loc);
+  ret_expr = ret_expr.maybe_add_location_wrapper ();
+  return ret_expr;
+}
+
 /* Parse a new-expression.
 
    new-expression:
@@ -25154,7 +25282,7 @@ cp_parser_gnu_attributes_opt (cp_parser* parser)
    the arguments, if any.  */
 
 static tree
-cp_parser_gnu_attribute_list (cp_parser* parser)
+cp_parser_gnu_attribute_list (cp_parser* parser, bool exactly_one /* = false */)
 {
   tree attribute_list = NULL_TREE;
   bool save_translate_strings_p = parser->translate_strings_p;
@@ -25221,9 +25349,9 @@ cp_parser_gnu_attribute_list (cp_parser* parser)
 
 	  token = cp_lexer_peek_token (parser->lexer);
 	}
-      /* Now, look for more attributes.  If the next token isn't a
-	 `,', we're done.  */
-      if (token->type != CPP_COMMA)
+      /* Unless EXACTLY_ONE is set look for more attributes.
+	 If the next token isn't a `,', we're done.  */
+      if (exactly_one || token->type != CPP_COMMA)
 	break;
 
       /* Consume the comma and keep going.  */
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 8ffb0cd..05eb0ac 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -11069,6 +11069,7 @@ is called and the @var{flag} argument passed to it.
 @findex __builtin_call_with_static_chain
 @findex __builtin_extend_pointer
 @findex __builtin_fpclassify
+@findex __builtin_has_attribute
 @findex __builtin_isfinite
 @findex __builtin_isnormal
 @findex __builtin_isgreater
@@ -11726,6 +11727,45 @@ check its compatibility with @var{size}.
 
 @end deftypefn
 
+@deftypefn {Built-in Function} bool __builtin_has_attribute (@var{type-or-expression}, @var{attribute})
+The @code{__builtin_has_attribute} function evaluates to an integer constant
+expression equal to @code{true} if the symbol or type referenced by
+the @var{type-or-expression} argument has been declared with
+the @var{attribute} referenced by the second argument.  Neither argument
+is evaluated.  The @var{type-or-expression} argument is subject to the same
+restrictions as the argument to @code{typeof} (@pxref{Typeof}).  The
+@var{attribute} argument is an attribute name optionally followed by
+arguments.  Both forms of attribute names---with and without double
+leading and trailing underscores---are recognized.  See @xref{Attribute Syntax}
+for details.  When no attribute arguments are specified for an attribute
+that expects one or more arguments the function returns @code{true} if
+@var{type-or-expression} has been declared with the attribute regardless
+of the attribute argument values.  Arguments provided for an attribute
+that expects some are validated and matched up to the provided number.
+The function returns true if all provided arguments match.  For example,
+the first call to the function below evaluates to @code{true} because
+@code{x} is declared with the @code{aligned} attribute but the second
+call evaluates to @code{false} because @code{x} is declared
+@code{aligned (8)} and not @code{aligned (4)}.
+
+@smallexample
+__attribute__ ((aligned (8))) int x;
+_Static_assert (__builtin_has_attribute (x, aligned), "aligned");
+_Static_assert (!__builtin_has_attribute (x, aligned (4)), "aligned (4)");
+@end smallexample
+
+Due to a limitation the @code{__builtin_has_attribute} function returns
+@code{false} for the @code{mode} attribute even if the type or variable
+referenced by the @var{type-or-expression} argument was declared with one.
+The function is also not supported with labels, and in C with enumerators.
+
+Note that unlike the @code{__has_attribute} preprocessor operator which
+is suitable for use in @code{#if} preprocessing directives
+@code{__builtin_has_attribute} is an intrinsic function that is not
+recognized in such contexts.
+
+@end deftypefn
+
 @deftypefn {Built-in Function} @var{type} __builtin_speculation_safe_value (@var{type} val, @var{type} failval)
 
 This built-in function can be used to help mitigate against unsafe

Reply via email to