diff mbox

PATCH RFA: Support Plan 9 extensions in gcc

Message ID mcr62xryakz.fsf@google.com
State New
Headers show

Commit Message

Ian Lance Taylor Sept. 27, 2010, 6:17 a.m. UTC
Back in http://gcc.gnu.org/ml/gcc-patches/2009-04/msg00727.html I
proposed adding an option -fplan9-extensions to gcc.  This option would
provide two extensions which exist in the Plan 9 C compiler.


The first extension is if a structure has an anonymous field, a pointer
to that structure can be automatically converted to apointer to the
anonymous field in an assignment or a function call.  For example:

struct s1 { int a; };
struct s2 { int b; struct s1; };
extern void f1 (struct s1 *);
void f2 (struct s2 *p) { f1 (p); }

Here struct s2 has an anonymous field of type struct s1.  When calling
f1 from f2, the pointer to s2 is converted into a pointer to s1.  Note
that the anonymous field is not the first field in s2, so this actually
adjusts the pointer in order to make the call.


The second extension is that if a pointer has an anonymous field, and
the type of the anonymous field is a typedef for a struct or a union,
then code can refer to the entire anonymous field using the typedef
name.  For example, this is valid:

typedef struct { int a; } s3;
struct s4 { s3; };
s3 f1 (struct s4 *p) { return p->s3; }

Note the use of the typedef name s3 as the name of a field in s4.


At the time I could not explain what this code was for.  Now I can say
that it is for compiling the supporting code in the Go library.  This
code is written to rely on these extensions; the Go language has similar
features.  These parts of the Go library are shared with the other Go
compiler, so maintaining a common code base is desirable.  In order for
gcc to compile this code, this extension is needed.


There were two main objections raised last time.  The first was a
question about ambiguity.  The Plan 9 compiler does not permit these
extensions to be used when they would be ambiguous.  This gcc patch acts
the same way.  The second was the more general objection to adding
extensions to gcc.  I believe that this one is reasonably limited and
has an existing use case which will be part of gcc itself.

Joseph Myers asked specifically about standardization; I very much doubt
the Plan 9 folks care one way or another about whether this
functionality is added to the standard.  However, it seems to me
unlikely that any contradictory functionality would be added.


Bootstrapped and tested on x86_64-unknown-linux-gnu.  OK for mainline?

Ian


gcc/ChangeLog:

2010-09-26  Ian Lance Taylor  <iant@google.com>

	* c-typeck.c (lookup_field): If -fplan9-extensions, permit
	referring to a field using a typedef name.
	(find_anonymous_field_with_type): New static function.
	(convert_to_anonymous_field): New static function.
	(convert_for_assignment): If -fplan9-extensions, permit converting
	pointer to struct to pointer to anonymous field.
	* c-decl.c (grokfield): If -fplan9-extensions, permit anonymous
	fields.
	* doc/invoke.texi (Option Summary): Mention -fplan9-extensions.
	(C Dialect Options): Document -fplan9-extensions.
	* doc/extend.texi (Unnamed Fields): Document -fplan9-extensions.

gcc/c-family/ChangeLog:

2010-09-26  Ian Lance Taylor  <iant@google.com>

	* c.opt (-plan9-extensions): New option.

gcc/testsuite/ChangeLog:

2010-09-26  Ian Lance Taylor  <iant@google.com>

	* gcc.dg/anon-struct-11.c: New test.
	* gcc.dg/anon-struct-12.c: New test.
	* gcc.dg/anon-struct-13.c: New test.

Comments

Joseph Myers Sept. 28, 2010, 9:14 p.m. UTC | #1
On Sun, 26 Sep 2010, Ian Lance Taylor wrote:

> Joseph Myers asked specifically about standardization; I very much doubt
> the Plan 9 folks care one way or another about whether this
> functionality is added to the standard.  However, it seems to me
> unlikely that any contradictory functionality would be added.

It is also the case that we now know what form of anonymous structures and 
unions is in C1X and I implemented support for that in GCC.

However, one case of ambiguity that this patch does not detect appears to 
conflict with the standard semantics.

typedef struct { int a; } s1;
struct s2 { s1; int s1; };
int f(struct s2 *p) { return p->s1; }

In C1X, p->s1 is an unambiguous reference to the int field.  With 
-fplan9-extensions, there is ambiguity; this is not detected as such, but 
instead you get an error:

t1.c:3:23: error: incompatible types when returning type 's1' but 'int' was expected

Logically this option should perhaps cause such a case to be detected with 
an error for duplicate fields (and so be documented as causing some code 
to be rejected that is otherwise accepted).

The duplicate field errors for anonymous structures and unions post-date 
your previous patch.  I don't actually see how the other cases of 
ambiguity that your patch tries to detect can arise without an error being 
given earlier for duplicate field names - they all involve multiple fields 
with the same anonymous structure or union type, which would mean all the 
fields within that type are duplicated.  If you ignore that issue, I don't 
think the ambiguity detection will behave as intended in all cases.

> +/* Return whether STRUCT_TYPE has an anonymous field with type TYPE.
> +   If there are more than one such field, this returns NULL and sets

(false, not NULL)

> +   *AMBIGUOUS_P.  This is used to implement -fplan9-extensions.  */
> +
> +static bool
> +find_anonymous_field_with_type (tree struct_type, tree type,
> +				bool *ambiguous_p)
> +{
> +  tree field;
> +  bool found;
> +
> +  gcc_assert (TREE_CODE (struct_type) == RECORD_TYPE
> +	      || TREE_CODE (struct_type) == UNION_TYPE);
> +  found = false;
> +  for (field = TYPE_FIELDS (struct_type);
> +       field != NULL_TREE;
> +       field = TREE_CHAIN (field))
> +    {
> +      if (DECL_NAME (field) == NULL
> +	  && comptypes (type, TYPE_MAIN_VARIANT (TREE_TYPE (field))))
> +	{
> +	  if (found)
> +	    {
> +	      *ambiguous_p = true;
> +	      return false;
> +	    }
> +	  found = true;
> +	}
> +      else if (DECL_NAME (field) == NULL
> +	       && (TREE_CODE (TREE_TYPE (field)) == RECORD_TYPE
> +		   || TREE_CODE (TREE_TYPE (field)) == UNION_TYPE)
> +	       && find_anonymous_field_with_type (TREE_TYPE (field), type,
> +						  ambiguous_p))
> +	{
> +	  if (found)
> +	    {
> +	      *ambiguous_p = true;
> +	      return false;
> +	    }
> +	  found = true;
> +	}
> +    }
> +  return found;
> +}

The inner call to find_anonymous_field_with_type could detect ambiguity 
and return false, with *ambiguous_p set to true.  The loop inside the 
outer find_anonymous_field_with_type would then continue and might find 
yet another field with the given type, so returning true but with 
*ambiguous_p set to true as well.  This at least isn't in accordance with 
the documented semantics of this function; I think you want something 
similar to the

> +      else if (*ambiguous_p)
> +	return NULL_TREE;

in convert_to_anonymous_field.
diff mbox

Patch

Index: doc/extend.texi
===================================================================
--- doc/extend.texi	(revision 164498)
+++ doc/extend.texi	(working copy)
@@ -12985,6 +12985,34 @@  also be a definition with a tag such as 
 @samp{struct foo;}, or a reference to a @code{typedef} name for a
 previously defined structure or union type with a tag.
 
+@opindex fplan9-extensions
+The option @option{-fplan9-extensions} enables
+@option{-fms-extensions} as well as two other extensions.  First, a
+pointer to a structure is automatically converted to a pointer to an
+anonymous field for assignments and function calls.  For example:
+
+@smallexample
+struct s1 @{ int a; @};
+struct s2 @{ struct s1; @};
+extern void f1 (struct s1 *);
+void f2 (struct s2 *p) @{ f1 (p); @}
+@end smallexample
+
+In the call to @code{f1} inside @code{f2}, the pointer @code{p} is
+converted into a pointer to the anonymous field.
+
+Second, when the type of an anonymous field is a @code{typedef} for a
+@code{struct} or @code{union}, code may refer to the field using the
+name of the @code{typedef}.
+
+@smallexample
+typedef struct @{ int a; @} s1;
+struct s2 @{ s1; @};
+s1 f1 (struct s2 *p) @{ return p->s1; @}
+@end smallexample
+
+These usages are only permitted when they are not ambiguous.
+
 @node Thread-Local
 @section Thread-Local Storage
 @cindex Thread-Local Storage
Index: doc/invoke.texi
===================================================================
--- doc/invoke.texi	(revision 164498)
+++ doc/invoke.texi	(working copy)
@@ -172,7 +172,7 @@  in the following sections.
 @gccoptlist{-ansi  -std=@var{standard}  -fgnu89-inline @gol
 -aux-info @var{filename} @gol
 -fno-asm  -fno-builtin  -fno-builtin-@var{function} @gol
--fhosted  -ffreestanding -fopenmp -fms-extensions @gol
+-fhosted  -ffreestanding -fopenmp -fms-extensions -fplan9-extensions @gol
 -trigraphs  -no-integrated-cpp  -traditional  -traditional-cpp @gol
 -fallow-single-precision  -fcond-mismatch -flax-vector-conversions @gol
 -fsigned-bitfields  -fsigned-char @gol
@@ -1685,6 +1685,16 @@  Some cases of unnamed fields in structur
 accepted with this option.  @xref{Unnamed Fields,,Unnamed struct/union
 fields within structs/unions}, for details.
 
+@item -fplan9-extensions
+Accept some non-standard constructs used in Plan 9 code.
+
+This enables @option{-fms-extensions}, permits passing pointers to
+structures with anonymous fields to functions which expect pointers to
+elements of the type of the field, and permits referring to anonymous
+fields declared using a typedef.  @xref{Unnamed Fields,,Unnamed
+struct/union fields within structs/unions}, for details.  This is only
+supported for C, not C++.
+
 @item -trigraphs
 @opindex trigraphs
 Support ISO C trigraphs.  The @option{-ansi} option (and @option{-std}
Index: c-family/c.opt
===================================================================
--- c-family/c.opt	(revision 164498)
+++ c-family/c.opt	(working copy)
@@ -749,6 +749,10 @@  fpermissive
 C++ ObjC++
 Downgrade conformance errors to warnings
 
+fplan9-extensions
+C ObjC Var(flag_plan9_extensions)
+Enable Plan 9 language extensions
+
 fpreprocessed
 C ObjC C++ ObjC++
 Treat the input file as already preprocessed
Index: c-decl.c
===================================================================
--- c-decl.c	(revision 164498)
+++ c-decl.c	(working copy)
@@ -6610,15 +6610,15 @@  grokfield (location_t loc,
 	 is the anonymous union extension.  Similarly for struct.
 
 	 If this is something of the form "struct foo;", then
-	   If MS extensions are enabled, this is handled as an
-	     anonymous struct.
+	   If MS or Plan 9 extensions are enabled, this is handled as
+	     an anonymous struct.
 	   Otherwise this is a forward declaration of a structure tag.
 
 	 If this is something of the form "foo;" and foo is a TYPE_DECL, then
 	   If foo names a structure or union without a tag, then this
 	     is an anonymous struct (this is permitted by C1X).
-	   If MS extensions are enabled and foo names a structure, then
-	     again this is an anonymous struct.
+	   If MS or Plan 9 extensions are enabled and foo names a
+	     structure, then again this is an anonymous struct.
 	   Otherwise this is an error.
 
 	 Oh what a horrid tangled web we weave.  I wonder if MS consciously
@@ -6632,7 +6632,7 @@  grokfield (location_t loc,
 
       if (type_ok)
 	{
-	  if (flag_ms_extensions)
+	  if (flag_ms_extensions || flag_plan9_extensions)
 	    ok = true;
 	  else if (TYPE_NAME (TYPE_MAIN_VARIANT (type)) == NULL)
 	    ok = true;
Index: c-typeck.c
===================================================================
--- c-typeck.c	(revision 164498)
+++ c-typeck.c	(working copy)
@@ -2044,6 +2044,17 @@  lookup_field (tree type, tree component)
 
 		      if (anon)
 			return tree_cons (NULL_TREE, field, anon);
+
+		      /* The Plan 9 compiler permits referring
+			 directly to an anonymous struct/union field
+			 using a typedef name.  */
+		      if (flag_plan9_extensions
+			  && TYPE_NAME (TREE_TYPE (field)) != NULL_TREE
+			  && (TREE_CODE (TYPE_NAME (TREE_TYPE (field)))
+			      == TYPE_DECL)
+			  && (DECL_NAME (TYPE_NAME (TREE_TYPE (field)))
+			      == component))
+			break;
 		    }
 		}
 
@@ -2080,6 +2091,16 @@  lookup_field (tree type, tree component)
 
 	      if (anon)
 		return tree_cons (NULL_TREE, field, anon);
+
+	      /* The Plan 9 compiler permits referring directly to an
+		 anonymous struct/union field using a typedef
+		 name.  */
+	      if (flag_plan9_extensions
+		  && TYPE_NAME (TREE_TYPE (field)) != NULL_TREE
+		  && TREE_CODE (TYPE_NAME (TREE_TYPE (field))) == TYPE_DECL
+		  && (DECL_NAME (TYPE_NAME (TREE_TYPE (field)))
+		      == component))
+		break;
 	    }
 
 	  if (DECL_NAME (field) == component)
@@ -4948,6 +4969,127 @@  build_modify_expr (location_t location, 
   return result;
 }
 
+/* Return whether STRUCT_TYPE has an anonymous field with type TYPE.
+   If there are more than one such field, this returns NULL and sets
+   *AMBIGUOUS_P.  This is used to implement -fplan9-extensions.  */
+
+static bool
+find_anonymous_field_with_type (tree struct_type, tree type,
+				bool *ambiguous_p)
+{
+  tree field;
+  bool found;
+
+  gcc_assert (TREE_CODE (struct_type) == RECORD_TYPE
+	      || TREE_CODE (struct_type) == UNION_TYPE);
+  found = false;
+  for (field = TYPE_FIELDS (struct_type);
+       field != NULL_TREE;
+       field = TREE_CHAIN (field))
+    {
+      if (DECL_NAME (field) == NULL
+	  && comptypes (type, TYPE_MAIN_VARIANT (TREE_TYPE (field))))
+	{
+	  if (found)
+	    {
+	      *ambiguous_p = true;
+	      return false;
+	    }
+	  found = true;
+	}
+      else if (DECL_NAME (field) == NULL
+	       && (TREE_CODE (TREE_TYPE (field)) == RECORD_TYPE
+		   || TREE_CODE (TREE_TYPE (field)) == UNION_TYPE)
+	       && find_anonymous_field_with_type (TREE_TYPE (field), type,
+						  ambiguous_p))
+	{
+	  if (found)
+	    {
+	      *ambiguous_p = true;
+	      return false;
+	    }
+	  found = true;
+	}
+    }
+  return found;
+}
+
+/* RHS is an expression whose type is pointer to struct.  If there is
+   an anonymous field in RHS with type TYPE, then return a pointer to
+   that field in RHS.  This implements an extension to C first found
+   in the Plan 9 C compiler.  This is used with -fplan9-extensions.
+   This returns NULL if no conversion could be found.  If an ambiguous
+   conversion is found, this returns NULL and sets *AMBIGUOUS_P to
+   true.  */
+
+static tree
+convert_to_anonymous_field (location_t location, tree type, tree rhs,
+			    bool *ambiguous_p)
+{
+  tree rhs_struct_type, lhs_main_type;
+  tree field, found_field;
+  bool found_sub_field;
+  tree ret;
+
+  gcc_assert (POINTER_TYPE_P (TREE_TYPE (rhs)));
+  rhs_struct_type = TREE_TYPE (TREE_TYPE (rhs));
+  gcc_assert (TREE_CODE (rhs_struct_type) == RECORD_TYPE
+	      || TREE_CODE (rhs_struct_type) == UNION_TYPE);
+
+  gcc_assert (POINTER_TYPE_P (type));
+  lhs_main_type = TYPE_MAIN_VARIANT (TREE_TYPE (type));
+
+  found_field = NULL_TREE;
+  found_sub_field = false;
+  for (field = TYPE_FIELDS (rhs_struct_type);
+       field != NULL_TREE;
+       field = TREE_CHAIN (field))
+    {
+      if (DECL_NAME (field) != NULL_TREE
+	  || (TREE_CODE (TREE_TYPE (field)) != RECORD_TYPE
+	      && TREE_CODE (TREE_TYPE (field)) != UNION_TYPE))
+	continue;
+      if (comptypes (lhs_main_type, TYPE_MAIN_VARIANT (TREE_TYPE (field))))
+	{
+	  if (found_field != NULL_TREE)
+	    {
+	      *ambiguous_p = true;
+	      return NULL_TREE;
+	    }
+	  found_field = field;
+	}
+      else if (find_anonymous_field_with_type (TREE_TYPE (field),
+					       lhs_main_type, ambiguous_p))
+	{
+	  if (found_field != NULL_TREE)
+	    {
+	      *ambiguous_p = true;
+	      return NULL_TREE;
+	    }
+	  found_field = field;
+	  found_sub_field = true;
+	}
+      else if (*ambiguous_p)
+	return NULL_TREE;
+    }
+
+  if (found_field == NULL_TREE)
+    return NULL_TREE;
+
+  ret = fold_build3_loc (location, COMPONENT_REF, TREE_TYPE (found_field),
+			 build_fold_indirect_ref (rhs), found_field,
+			 NULL_TREE);
+  ret = build_fold_addr_expr_loc (location, ret);
+
+  if (found_sub_field)
+    {
+      ret = convert_to_anonymous_field (location, type, ret, ambiguous_p);
+      gcc_assert (ret != NULL_TREE);
+    }
+
+  return ret;
+}
+
 /* Convert value RHS to type TYPE as preparation for an assignment to
    an lvalue of type TYPE.  If ORIGTYPE is not NULL_TREE, it is the
    original type of RHS; this differs from TREE_TYPE (RHS) for enum
@@ -5307,6 +5449,7 @@  convert_for_assignment (location_t locat
       tree ttr = TREE_TYPE (rhstype);
       tree mvl = ttl;
       tree mvr = ttr;
+      bool ambiguous_p = false;
       bool is_opaque_pointer;
       int target_cmp = 0;   /* Cache comp_target_types () result.  */
       addr_space_t asl;
@@ -5319,6 +5462,26 @@  convert_for_assignment (location_t locat
       /* Opaque pointers are treated like void pointers.  */
       is_opaque_pointer = vector_targets_convertible_p (ttl, ttr);
 
+      /* The Plan 9 compiler permits a pointer to a struct to be
+	 automatically converted into a pointer to an anonymous field
+	 within the struct.  */
+      if (flag_plan9_extensions
+	  && (TREE_CODE (mvl) == RECORD_TYPE || TREE_CODE(mvl) == UNION_TYPE)
+	  && (TREE_CODE (mvr) == RECORD_TYPE || TREE_CODE(mvr) == UNION_TYPE)
+	  && mvl != mvr)
+	{
+	  tree new_rhs = convert_to_anonymous_field (location, type, rhs,
+						     &ambiguous_p);
+	  if (new_rhs != NULL_TREE)
+	    {
+	      rhs = new_rhs;
+	      rhstype = TREE_TYPE (rhs);
+	      coder = TREE_CODE (rhstype);
+	      ttr = TREE_TYPE (rhstype);
+	      mvr = TYPE_MAIN_VARIANT (ttr);
+	    }
+	}
+
       /* C++ does not allow the implicit conversion void* -> T*.  However,
 	 for the purpose of reducing the number of false positives, we
 	 tolerate the special case of
@@ -5492,6 +5655,9 @@  convert_for_assignment (location_t locat
 				  "pointer type"),
 			       G_("return from incompatible pointer type"));
 
+      if (ambiguous_p)
+	inform (location, "conversion to type %qT is ambiguous", type);
+
       return convert (type, rhs);
     }
   else if (codel == POINTER_TYPE && coder == ARRAY_TYPE)
Index: testsuite/gcc.dg/anon-struct-11.c
===================================================================
--- testsuite/gcc.dg/anon-struct-11.c	(revision 0)
+++ testsuite/gcc.dg/anon-struct-11.c	(revision 0)
@@ -0,0 +1,111 @@ 
+/* { dg-do compile } */
+
+/* No special options--in particular, turn off the default
+   -pedantic-errors option.  */
+/* { dg-options "" } */
+
+/* When not using -fplan9-extensions, we don't support automatic
+   conversion of pointer types, and we don't support referring to a
+   typedef name directly.  */
+
+extern void exit (int);
+extern void abort (void);
+
+struct A { char a; };
+
+struct B {
+  char b;
+  struct A;		/* { dg-warning "does not declare anything" } */
+  char c;
+};
+
+void
+f1 (struct A *p)	/* { dg-message "expected" } */
+{
+  p->a = 1;
+}
+
+void
+test1 (void)
+{
+  struct B b;
+  struct A *p;
+
+  b.b = 2;
+  b.c = 3;
+  f1 (&b);		/* { dg-warning "incompatible pointer type" } */
+  if (b.a != 1)		/* { dg-error "no member" } */
+    abort ();
+  if (b.b != 2 || b.c != 3)
+    abort ();
+  p = &b;		/* { dg-warning "incompatible pointer type" } */
+  if (p->a != 1)
+    abort ();
+}
+
+typedef struct { char d; } D;
+
+struct E {
+  char b;
+  struct F { char f; };	/* { dg-warning "does not declare anything" } */
+  char c;
+  union {
+    D;
+  };
+  char e;
+};
+
+void
+f2 (struct F *p)	/* { dg-message "expected" } */
+{
+  p->f = 6;
+}
+
+void
+f3 (D *p)		/* { dg-message "expected" } */
+{
+  p->d = 4;
+}
+
+void
+f4 (D d)
+{
+}
+
+void
+test2 (void)
+{
+  struct E e;
+  struct F *pf;
+  D *pd;
+  D d;
+
+  e.b = 2;
+  e.c = 3;
+  e.e = 5;
+  f2 (&e);		/* { dg-warning "incompatible pointer type" } */
+  f3 (&e);		/* { dg-warning "incompatible pointer type" } */
+  if (e.d != 4)
+    abort ();
+  if (e.f != 6)		/* { dg-error "no member" } */
+    abort ();
+  if (e.b != 2 || e.c != 3 || e.e != 5)
+    abort ();
+  pf = &e;		/* { dg-warning "incompatible pointer type" } */
+  if (pf->f != 6)
+    abort ();
+  pd = &e;		/* { dg-warning "incompatible pointer type" } */
+  if (pd->d != 4)
+    abort ();
+  d = e.D;		/* { dg-error "no member" } */
+  f3 (&e.D);		/* { dg-error "no member" } */
+  f4 (e.D);		/* { dg-error "no member" } */
+}
+
+int
+main ()
+{
+  test1 ();
+  test2 ();
+  exit (0);
+}
Index: testsuite/gcc.dg/anon-struct-12.c
===================================================================
--- testsuite/gcc.dg/anon-struct-12.c	(revision 0)
+++ testsuite/gcc.dg/anon-struct-12.c	(revision 0)
@@ -0,0 +1,108 @@ 
+/* { dg-do run } */
+/* { dg-options "-fplan9-extensions" } */
+
+/* When using -fplan9-extensions, we support automatic conversion of
+   pointer types, and we support referring to a typedef name
+   directly.  */
+
+extern void exit (int);
+extern void abort (void);
+
+struct A { char a; };
+
+struct B {
+  char b;
+  struct A;
+  char c;
+};
+
+void
+f1 (struct A *p)
+{
+  p->a = 1;
+}
+
+void
+test1 (void)
+{
+  struct B b;
+  struct A *p;
+
+  b.b = 2;
+  b.c = 3;
+  f1 (&b);
+  if (b.a != 1)
+    abort ();
+  if (b.b != 2 || b.c != 3)
+    abort ();
+  p = &b;
+  if (p->a != 1)
+    abort ();
+}
+
+typedef struct { char d; } D;
+
+struct E {
+  char b;
+  struct F { char f; };
+  char c;
+  union {
+    D;
+  };
+  char e;
+};
+
+void
+f2 (struct F *p)
+{
+  p->f = 6;
+}
+
+void
+f3 (D *p)
+{
+  p->d = 4;
+}
+
+void
+f4 (D d)
+{
+}
+
+void
+test2 (void)
+{
+  struct E e;
+  struct F *pf;
+  D *pd;
+  D d;
+
+  e.b = 2;
+  e.c = 3;
+  e.e = 5;
+  f2 (&e);
+  f3 (&e);
+  if (e.d != 4)
+    abort ();
+  if (e.f != 6)
+    abort ();
+  if (e.b != 2 || e.c != 3 || e.e != 5)
+    abort ();
+  pf = &e;
+  if (pf->f != 6)
+    abort ();
+  pd = &e;
+  if (pd->d != 4)
+    abort ();
+  d = e.D;
+  f3 (&e.D);
+  f4 (e.D);
+}
+
+int
+main ()
+{
+  test1 ();
+  test2 ();
+  exit (0);
+}
Index: testsuite/gcc.dg/anon-struct-13.c
===================================================================
--- testsuite/gcc.dg/anon-struct-13.c	(revision 0)
+++ testsuite/gcc.dg/anon-struct-13.c	(revision 0)
@@ -0,0 +1,64 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fplan9-extensions" } */
+
+/* Test for ambiguity when using the Plan 9 extensions.  */
+
+struct A {
+  char a;		/* { dg-error "duplicate member" } */
+};
+
+struct B
+{
+  struct A;
+  struct A;
+};
+
+char
+f1 (struct B *p)
+{
+  return p->a;		/* { dg-error "no member" } */
+}
+
+void
+f2 (struct A *p)	/* { dg-message "expected" } */
+{
+}
+
+void
+f3 (struct B *p)
+{
+  f2 (p);		/* { dg-warning "incompatible pointer type" } */
+}
+
+struct C
+{
+  struct A;
+  struct B;
+};
+
+char
+f4 (struct C *p)
+{
+  return p->a;		/* { dg-error "no member" } */
+}
+
+void
+f5 (struct C *p)
+{
+  f2 (p);		/* { dg-warning "incompatible pointer type" } */
+}
+
+struct A
+f6 (struct B *p)
+{
+  return p->A;		/* { dg-error "no member" } */
+}
+
+struct A
+f7 (struct C *p)
+{
+  return p->A;		/* { dg-error "no member" } */
+}
+
+/* { dg-message "ambiguous" "" { target "*-*-*" } 30 } */
+/* { dg-message "ambiguous" "" { target "*-*-*" } 48 } */