Record nested types

Message ID 20171020182648.5093-1-keiths@redhat.com
State New, archived
Headers

Commit Message

Keith Seitz Oct. 20, 2017, 6:26 p.m. UTC
  GDB currently does not track types defined in classes.  Consider:

class A
{
  public:

  class B
  {
    public:
      class C { };
  };
};

(gdb) ptype A
type = class A {
   <no data fields>
}

This patch changes this behavior so that GDB records these nested types
and displays them to the user when he has set the (new) "print type"
option "nested-type-limit."

Example:

(gdb) set print type nested-type-limit 1
(gdb) ptype A
type = class A {
    <no data fields>
    class A::B {
        <no data fields>
    };
}
(gdb) set print type nested-type-limit 2
type = class A {
    <no data fields>
    class A::B {
        <no data fields>
        class A::B::C {
            <no data fields>
        };
    };
}

By default, the code maintains the status quo, that is, it will not print
any nested type definitions at all.

Testing is carried out via cp_ptype_class which required quite a bit of
modificaiton to permit recursive calling (for the nested types).  This
was most easily facilitated by turning the ptype command output into a
queue.  Upshot: the test suite now has stack and queue data structures that
may be used by test writers.

gdb/ChangeLog

	* NEWS (New commands): Mention set/show print type nested-type-limit.
	* c-typeprint.c (c_type_print_base): Print out nested types.
	* dwarf2read.c (struct typedef_field_list): Rename to ...
	(struct decl_field_list): ... this.  Change all uses.
	(struct field_info) <nested_types_list, nested_types_list_count>:
	New fields.
	(add_partial_symbol): Look for nested type definitions in C++, too.
	(dwarf2_add_typedef): Rename to ...
	(dwarf2_add_type_defn): ... this.
	Update assertion to allow classes, structures, unions, and enums.
	Permit NULL for a field's name.
	(process_structure_scope): Handle child DIEs of class, structure,
	union, and enum types.
	Copy the list of nested types into the type struct.
	* gdbtypes.h (struct typedef_field): Rename to ...
	(struct decl_field): ... this.  Change all uses.
	[is_protected, is_private]: New fields.
	(struct cplus_struct_type) <nested_types, nested_types_count>: New
	fields.
	(TYPE_NESTED_TYPES_ARRAY, TYPE_NESTED_TYPES_FIELD)
	(TYPE_NESTED_TYPES_FIELD_NAME, TYPE_NESTED_TYPES_FIELD_TYPE)
	(TYPE_NESTED_TYPES_COUNT, TYPE_NESTED_TYPES_FIELD_PUBLIC)
	(TYPE_NESTED_TYPES_FIELD_PROTECTED)
	(TYPE_NESTED_TYPES_FIELD_PRIVATE): New macros.
	* typeprint.c (type_print_raw_options, default_ptype_flags): Add
	default value for print_nested_type_limit.
	(print_nested_type_limit): New static variable.
	(set_print_type_nested_types, show_print_type_nested_types): New
	functions.
	(_initialize_typeprint): Register new commands for set/show
	`print-nested-type-limit'.
	* typeprint.h (struct type_print_options) [print_nested_type_limit]:
	New field.

gdb/testsuite/ChangeLog

	* gdb.cp/nested-types.cc: New file.
	* gdb.cp/nested-types.exp: New file.
	* lib/cp-support.exp: Load data-structures.exp library.
	(debug_cp_test_ptype_class): New global.
	(cp_ptype_class_verbose, next_line): New procedures.
	(cp_test_ptype_class): Add and document new parameter `recursive_qid'.
	Add and document new return value.
	Switch the list of lines to a queue.
	Add support for new `type' key for nested type definitions.
	Add debugging/troubleshooting messages.
	* lib/data-structures.exp: New file.

gdb/doc/ChangeLog

	* gdb.texinfo (Symbols): Document "set print type nested-type-limit"
	and "show print type nested-type-limit".
---
 gdb/NEWS                              |   4 +
 gdb/c-typeprint.c                     |  22 +-
 gdb/doc/gdb.texinfo                   |  11 +
 gdb/dwarf2read.c                      |  94 +++--
 gdb/gdbtypes.h                        |  28 +-
 gdb/testsuite/gdb.cp/nested-types.cc  | 668 ++++++++++++++++++++++++++++++++++
 gdb/testsuite/gdb.cp/nested-types.exp | 267 ++++++++++++++
 gdb/testsuite/lib/cp-support.exp      | 359 +++++++++++++++---
 gdb/testsuite/lib/data-structures.exp | 160 ++++++++
 gdb/typeprint.c                       |  71 +++-
 gdb/typeprint.h                       |   3 +
 11 files changed, 1598 insertions(+), 89 deletions(-)
 create mode 100644 gdb/testsuite/gdb.cp/nested-types.cc
 create mode 100644 gdb/testsuite/gdb.cp/nested-types.exp
 create mode 100644 gdb/testsuite/lib/data-structures.exp
  

Comments

Eli Zaretskii Oct. 20, 2017, 6:45 p.m. UTC | #1
> From: Keith Seitz <keiths@redhat.com>
> Date: Fri, 20 Oct 2017 11:26:48 -0700
> 
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -94,6 +94,10 @@ maint info selftests
>  starti
>    Start the debugged program stopping at the first instruction.
>  
> +set|show print type nested-type-limit
> +  Set and show the number of recursively defined nested types that the
> +  type printer will show.

Not sure why you used "recursive" here, I think it just makes the
intent more mysterious.  How about this wording instead:

  Set and show the limit of the nesting level for nested types that
  the type printer will show.

> +@kindex set print type nested-type-limit
> +@item set print type nested-type-limit @var{limit}
> +Set the recursive display limit of nested types that the type printer will
> +show.  A @var{limit} of -1 is treated as unlimited.  By default, the type
> +printer will not show any nested types defined in classes.

Same here.

Thanks.
  
Keith Seitz Oct. 20, 2017, 6:47 p.m. UTC | #2
On 10/20/2017 11:45 AM, Eli Zaretskii wrote:
>> From: Keith Seitz <keiths@redhat.com>
>> Date: Fri, 20 Oct 2017 11:26:48 -0700
>>
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -94,6 +94,10 @@ maint info selftests
>>  starti
>>    Start the debugged program stopping at the first instruction.
>>  
>> +set|show print type nested-type-limit
>> +  Set and show the number of recursively defined nested types that the
>> +  type printer will show.
> 
> Not sure why you used "recursive" here, I think it just makes the
> intent more mysterious.  How about this wording instead:
> 
>   Set and show the limit of the nesting level for nested types that
>   the type printer will show.

That's much better!

>> +@kindex set print type nested-type-limit
>> +@item set print type nested-type-limit @var{limit}
>> +Set the recursive display limit of nested types that the type printer will
>> +show.  A @var{limit} of -1 is treated as unlimited.  By default, the type
>> +printer will not show any nested types defined in classes.
> 
> Same here.

I'll make both changes.

Thank you!

Keith
  
Pedro Alves Oct. 27, 2017, noon UTC | #3
Hi Keith,

On 10/20/2017 07:26 PM, Keith Seitz wrote:
> GDB currently does not track types defined in classes.  Consider:
> 
> class A
> {
>   public:
> 
>   class B
>   {
>     public:
>       class C { };
>   };
> };
> 
> (gdb) ptype A
> type = class A {
>    <no data fields>
> }
> 
> This patch changes this behavior so that GDB records these nested types
> and displays them to the user when he has set the (new) "print type"
> option "nested-type-limit."

Nice!

This looks mostly good to me.  A few comments/questions below.

> By default, the code maintains the status quo, that is, it will not print
> any nested type definitions at all.

IWBN to add a new flag to ptype that allows overriding this.

> Testing is carried out via cp_ptype_class which required quite a bit of
> modificaiton to permit recursive calling (for the nested types).  This

modificaiton -> modification

> was most easily facilitated by turning the ptype command output into a
> queue.  Upshot: the test suite now has stack and queue data structures that
> may be used by test writers.
> 

Super.

> @@ -7139,7 +7144,8 @@ add_partial_symbol (struct partial_die_info *pdi, struct dwarf2_cu *cu)
>      {
>      case DW_TAG_subprogram:
>        addr = gdbarch_adjust_dwarf2_addr (gdbarch, pdi->lowpc + baseaddr);
> -      if (pdi->is_external || cu->language == language_ada)
> +      if (pdi->is_external || cu->language == language_ada
> +	  || cu->language == language_cplus)

Why were this ...

>  	{
>            /* brobecker/2007-12-26: Normally, only "external" DIEs are part
>               of the global scope.  But in Ada, we want to be able to access
> @@ -7392,7 +7398,7 @@ add_partial_subprogram (struct partial_die_info *pdi,
>    if (! pdi->has_children)
>      return;
>  
> -  if (cu->language == language_ada)
> +  if (cu->language == language_ada || cu->language == language_cplus)

... and this ...


>      {
>        pdi = pdi->die_child;
>        while (pdi != NULL)
> @@ -12635,7 +12641,7 @@ dwarf2_get_subprogram_pc_bounds (struct die_info *die,
>  
>    /* If the language does not allow nested subprograms (either inside
>       subprograms or lexical blocks), we're done.  */
> -  if (cu->language != language_ada)
> +  if (cu->language != language_ada && cu->language != language_cplus)

... and this changes necessary?  The new nested-types.exp test
passes cleanly without them.


>    /* Allocate a new field list entry and link it in.  */
> -  new_field = XCNEW (struct typedef_field_list);
> +  new_field = XCNEW (struct decl_field_list);
>    make_cleanup (xfree, new_field);
>  
> -  gdb_assert (die->tag == DW_TAG_typedef);
> +  gdb_assert (die->tag == DW_TAG_typedef
> +	      || die->tag == DW_TAG_class_type
> +	      || die->tag == DW_TAG_structure_type
> +	      || die->tag == DW_TAG_union_type
> +	      || die->tag == DW_TAG_enumeration_type);
>  

> -	  else if (child_die->tag == DW_TAG_typedef)
> -	    dwarf2_add_typedef (&fi, child_die, cu);
> +	  else if (child_die->tag == DW_TAG_typedef
> +		   || child_die->tag == DW_TAG_class_type
> +		   || child_die->tag == DW_TAG_structure_type
> +		   || child_die->tag == DW_TAG_union_type
> +		   || child_die->tag == DW_TAG_enumeration_type)
> +	    dwarf2_add_type_defn (&fi, child_die, cu);

Feel free to not do this, but, would it make sense to factor these
out to a function like:

bool is_something (tag)
{
  switch (tag)
    {
    case DW_TAG_typedef:
    case DW_TAG_class_type:
    case DW_TAG_structure_type:
    case DW_TAG_union_type:
    case DW_TAG_enumeration_type:
       return true;
    default:
       return false;
   }
}

and then use is_something in both places?

> @@ -13887,6 +13907,30 @@ process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
>  	    }
>  	}
>  
> +      /* Copy fi.nested_types_list linked list elements content into the
> +	 allocated array TYPE_NESTED_TYPES_ARRAY (type).  */
> +      if (fi.nested_types_list != NULL)
> +	{
> +	  int i = fi.nested_types_list_count;
> +
> +	  ALLOCATE_CPLUS_STRUCT_TYPE (type);

Is it OK to call ALLOCATE_CPLUS_STRUCT_TYPE for Ada symbols (for example)?

> +	  TYPE_NESTED_TYPES_ARRAY (type)
> +	    = ((struct decl_field *)
> +	       TYPE_ALLOC (type, sizeof (struct decl_field) * i));
> +	  TYPE_NESTED_TYPES_COUNT (type) = i;
> +
> +	  /* Reverse the list order to keep the debug info elements order.  */
> +	  while (--i >= 0)
> +	    {
> +	      struct decl_field *dest, *src;
> +
> +	      dest = &TYPE_NESTED_TYPES_FIELD (type, i);
> +	      src = &fi.nested_types_list->field;
> +	      fi.nested_types_list = fi.nested_types_list->next;
> +	      *dest = *src;
> +	    }
> +	}
> +
>        do_cleanups (back_to);
>      }
>  
> diff --git a/gdb/gdbtypes.h b/gdb/gdbtypes.h
> index 5c1aecd211..d01c59a88f 100644

> @@ -1424,6 +1431,23 @@ extern void set_type_vptr_basetype (struct type *, struct type *);
>  #define TYPE_TYPEDEF_FIELD_PRIVATE(thistype, n)        \
>    TYPE_TYPEDEF_FIELD (thistype, n).is_private
>  
> +#define TYPE_NESTED_TYPES_ARRAY(thistype)	\
> +  TYPE_CPLUS_SPECIFIC (thistype)->nested_types
> +#define TYPE_NESTED_TYPES_FIELD(thistype, n) \
> +  TYPE_CPLUS_SPECIFIC (thistype)->nested_types[n]
> +#define TYPE_NESTED_TYPES_FIELD_NAME(thistype, n) \
> +  TYPE_NESTED_TYPES_FIELD (thistype, n).name
> +#define TYPE_NESTED_TYPES_FIELD_TYPE(thistype, n) \
> +  TYPE_NESTED_TYPES_FIELD (thistype, n).type
> +#define TYPE_NESTED_TYPES_COUNT(thistype) \
> +  TYPE_CPLUS_SPECIFIC (thistype)->nested_types_count

These always map to TYPE_CPLUS_SPECIFIC stuff, but their
names don't imply C++.  Is this OK for other languages?

> +++ b/gdb/testsuite/gdb.cp/nested-types.cc
> @@ -0,0 +1,668 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2017 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Tests for nested type definitions.  */
> +
> +struct S10
> +{

W00t, lots of repetition.  OOC, did you consider using the preprocessor
to build the types?

> +
> +proc build_node {id} {
> +    global nodes
> +
> +    # For any node, FIELDS is always the types i(N), e(N), u(N)
> +    # CHILDREN is a list of nodes called [E(N), U(N)] S(N+1)
> +    #
> +    # The root (10) also has S(N+11), S(N+21), S(N+31), S(N+41)
> +
> +    set nodes($id,fields) [list "int i$id" "E$id e$id" "U$id u$id"]
> +    set ndoes($id,children) {}

Typo: "ndoes" -> "nodes"

> --- a/gdb/testsuite/lib/cp-support.exp
> +++ b/gdb/testsuite/lib/cp-support.exp

> -# Test ptype of a class.
> +# A convenience procedure for outputting debug info for cp_test_ptype_class
> +# to the log.  Set the global variable "debug_cp_test_ptype_class"
> +# to enable logging (to help with debugging failures).
> +
> +proc cp_ptype_class_verbose {msg} {
> +    global debug_cp_test_ptype_class
> +
> +    if {$debug_cp_test_ptype_class} {
> +	verbose -log $msg
> +    }
> +}
> +
> +# A convenience procedure to return the next element of the queue.
> +
> +proc next_line {qid} {

Should we pick a name that is a bit less generic, to avoid
potentially causing conflict/overrides in other parts of the
hardness / tests?  ( I know, namespaces... :-) )

> +# To create a stack, call ::Stack::new, recording the returned object ID
> +# for future calls to manipulate the stack object.
> +#
> +# Example:
> +#
> +# set sid1 [::Stack::new]
> +# set sid2 [::Stack::new]
> +# stack push $sid1 a
> +# stack push $sid1 b
> +# stack empty $sid1;  # returns false
> +# stack empty $sid2;  # returns true
> +# stack pop $sid1;    # returns "b"
> +# stack pop $sid2;    # returns ""

I'd suggest adding a line for a second pop on sid1 to
make it dead obvious that this really behaves like a LIFO.
And then maybe do the last pop that returns "" on the
same stack:

  # stack pop $sid1;    # returns "b"
  # stack pop $sid1;    # returns "a"
  # stack pop $sid1;    # returns ""

Though off hand I'd assume that poping from an empty
stack would be an error instead.  But perhaps that's more
common in TCL?  Or does it not work to push an empty
element, like:

  # stack push $sid1 ""

?

> +# A namespace/commands to support a queue.
> +#
> +# To create a queue, call ::Queue::new, recording the returned stack ID

"stack ID" -> "queue ID" ?  (The reuse of stack elements seems like
implementation detail.)

> +# for future calls to manipulate the queue object.
> +#
> +# Example:
> +#
> +# set qid1 [::Queue::new]
> +# set qid2 [::Queue::new]
> +# queue push $qid1 a
> +# queue push $qid1 b
> +# queue empty $qid1;  # returns false
> +# queue empty $qid2;  # returns true
> +# queue pop $qid1;    # returns "a"
> +# queue pop $qid2;    # returns ""
> +# queue delete $qid1
> +# queue delete $qid2

Same comments, really.

> @@ -755,6 +792,16 @@ Show printing of typedefs defined in classes."), NULL,
>  			   set_print_type_typedefs,
>  			   show_print_type_typedefs,
>  			   &setprinttypelist, &showprinttypelist);
> +
> +  add_setshow_zuinteger_unlimited_cmd ("nested-type-limit", no_class,
> +				       &print_nested_type_limit,
> +				       _("\
> +Set the number of recursive nested type definitions to print \
> +(-1 to show all)."), _("\

Please mention the "unlimited" literal.  Something
like ("unlimited" or -1 to show all), or even just 
("unlimited" to show all).  Likewise in the manual.
See f81d112039fa.

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index fbf5591781..076b02ba9b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -94,6 +94,10 @@  maint info selftests
 starti
   Start the debugged program stopping at the first instruction.
 
+set|show print type nested-type-limit
+  Set and show the number of recursively defined nested types that the
+  type printer will show.
+
 * TUI Single-Key mode now supports two new shortcut keys: `i' for stepi and
   `o' for nexti.
 
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 22fdaa5beb..983b7308a3 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -1334,11 +1334,31 @@  c_type_print_base (struct type *type, struct ui_file *stream,
 		}
 	    }
 
+	  /* Print out nested types.  */
+	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	      && semi_local_flags.print_nested_type_limit != 0)
+	    {
+	      if (semi_local_flags.print_nested_type_limit > 0)
+		--semi_local_flags.print_nested_type_limit;
+
+	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+		fprintf_filtered (stream, "\n");
+
+	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+		{
+		  print_spaces_filtered (level + 4, stream);
+		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+				"", stream, show, level + 4, &semi_local_flags);
+		  fprintf_filtered (stream, ";\n");
+		}
+	    }
+
 	  /* Print typedefs defined in this class.  */
 
 	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
 	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
 		fprintf_filtered (stream, "\n");
 
 	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index bfeb7a9a35..9729caa5cd 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -16942,6 +16942,17 @@  cause @value{GDBN} to omit the methods.
 This command shows the current setting of method display when printing
 classes.
 
+@kindex set print type nested-type-limit
+@item set print type nested-type-limit @var{limit}
+Set the recursive display limit of nested types that the type printer will
+show.  A @var{limit} of -1 is treated as unlimited.  By default, the type
+printer will not show any nested types defined in classes.
+
+@kindex show print type nested-type-limit
+@item show print type nested-type-limit
+This command shows the current display limit of nested types when
+printing classes.
+
 @kindex set print type typedefs
 @item set print type typedefs
 @itemx set print type typedefs on
diff --git a/gdb/dwarf2read.c b/gdb/dwarf2read.c
index 686fa3f3d3..984f53ce2c 100644
--- a/gdb/dwarf2read.c
+++ b/gdb/dwarf2read.c
@@ -1461,10 +1461,10 @@  struct fnfieldlist
   struct nextfnfield *head;
 };
 
-struct typedef_field_list
+struct decl_field_list
 {
-  struct typedef_field field;
-  struct typedef_field_list *next;
+  struct decl_field field;
+  struct decl_field_list *next;
 };
 
 /* The routines that read and process dies for a C struct or C++ class
@@ -1494,8 +1494,13 @@  struct field_info
 
     /* typedefs defined inside this class.  TYPEDEF_FIELD_LIST contains head of
        a NULL terminated list of TYPEDEF_FIELD_LIST_COUNT elements.  */
-    struct typedef_field_list *typedef_field_list;
+    struct decl_field_list *typedef_field_list;
     unsigned typedef_field_list_count;
+
+    /* Nested types defined by this class and the number of elements in this
+       list.  */
+    struct decl_field_list *nested_types_list;
+    unsigned nested_types_list_count;
   };
 
 /* One item on the queue of compilation units to read in full symbols
@@ -7139,7 +7144,8 @@  add_partial_symbol (struct partial_die_info *pdi, struct dwarf2_cu *cu)
     {
     case DW_TAG_subprogram:
       addr = gdbarch_adjust_dwarf2_addr (gdbarch, pdi->lowpc + baseaddr);
-      if (pdi->is_external || cu->language == language_ada)
+      if (pdi->is_external || cu->language == language_ada
+	  || cu->language == language_cplus)
 	{
           /* brobecker/2007-12-26: Normally, only "external" DIEs are part
              of the global scope.  But in Ada, we want to be able to access
@@ -7392,7 +7398,7 @@  add_partial_subprogram (struct partial_die_info *pdi,
   if (! pdi->has_children)
     return;
 
-  if (cu->language == language_ada)
+  if (cu->language == language_ada || cu->language == language_cplus)
     {
       pdi = pdi->die_child;
       while (pdi != NULL)
@@ -12635,7 +12641,7 @@  dwarf2_get_subprogram_pc_bounds (struct die_info *die,
 
   /* If the language does not allow nested subprograms (either inside
      subprograms or lexical blocks), we're done.  */
-  if (cu->language != language_ada)
+  if (cu->language != language_ada && cu->language != language_cplus)
     return;
 
   /* Check all the children of the given DIE.  If it contains nested
@@ -13088,28 +13094,29 @@  dwarf2_add_field (struct field_info *fip, struct die_info *die,
     }
 }
 
-/* Add a typedef defined in the scope of the FIP's class.  */
+/* Add a type definition defined in the scope of the FIP's class.  */
 
 static void
-dwarf2_add_typedef (struct field_info *fip, struct die_info *die,
-		    struct dwarf2_cu *cu)
+dwarf2_add_type_defn (struct field_info *fip, struct die_info *die,
+		      struct dwarf2_cu *cu)
 {
-  struct typedef_field_list *new_field;
-  struct typedef_field *fp;
+  struct decl_field_list *new_field;
+  struct decl_field *fp;
 
   /* Allocate a new field list entry and link it in.  */
-  new_field = XCNEW (struct typedef_field_list);
+  new_field = XCNEW (struct decl_field_list);
   make_cleanup (xfree, new_field);
 
-  gdb_assert (die->tag == DW_TAG_typedef);
+  gdb_assert (die->tag == DW_TAG_typedef
+	      || die->tag == DW_TAG_class_type
+	      || die->tag == DW_TAG_structure_type
+	      || die->tag == DW_TAG_union_type
+	      || die->tag == DW_TAG_enumeration_type);
 
   fp = &new_field->field;
 
-  /* Get name of field.  */
+  /* Get name of field.  NULL is okay here, meaning an anonymous type.  */
   fp->name = dwarf2_name (die, cu);
-  if (fp->name == NULL)
-    return;
-
   fp->type = read_type_die (die, cu);
 
   /* Save accessibility.  */
@@ -13135,9 +13142,18 @@  dwarf2_add_typedef (struct field_info *fip, struct die_info *die,
 		 _("Unhandled DW_AT_accessibility value (%x)"), accessibility);
     }
 
-  new_field->next = fip->typedef_field_list;
-  fip->typedef_field_list = new_field;
-  fip->typedef_field_list_count++;
+  if (die->tag == DW_TAG_typedef)
+    {
+      new_field->next = fip->typedef_field_list;
+      fip->typedef_field_list = new_field;
+      fip->typedef_field_list_count++;
+    }
+  else
+    {
+      new_field->next = fip->nested_types_list;
+      fip->nested_types_list = new_field;
+      fip->nested_types_list_count++;
+    }
 }
 
 /* Create the vector of fields, and attach it to the type.  */
@@ -13761,8 +13777,12 @@  process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
 	      /* C++ base class field.  */
 	      dwarf2_add_field (&fi, child_die, cu);
 	    }
-	  else if (child_die->tag == DW_TAG_typedef)
-	    dwarf2_add_typedef (&fi, child_die, cu);
+	  else if (child_die->tag == DW_TAG_typedef
+		   || child_die->tag == DW_TAG_class_type
+		   || child_die->tag == DW_TAG_structure_type
+		   || child_die->tag == DW_TAG_union_type
+		   || child_die->tag == DW_TAG_enumeration_type)
+	    dwarf2_add_type_defn (&fi, child_die, cu);
 	  else if (child_die->tag == DW_TAG_template_type_param
 		   || child_die->tag == DW_TAG_template_value_param)
 	    {
@@ -13871,14 +13891,14 @@  process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
 
 	  ALLOCATE_CPLUS_STRUCT_TYPE (type);
 	  TYPE_TYPEDEF_FIELD_ARRAY (type)
-	    = ((struct typedef_field *)
+	    = ((struct decl_field *)
 	       TYPE_ALLOC (type, sizeof (TYPE_TYPEDEF_FIELD (type, 0)) * i));
 	  TYPE_TYPEDEF_FIELD_COUNT (type) = i;
 
 	  /* Reverse the list order to keep the debug info elements order.  */
 	  while (--i >= 0)
 	    {
-	      struct typedef_field *dest, *src;
+	      struct decl_field *dest, *src;
 
 	      dest = &TYPE_TYPEDEF_FIELD (type, i);
 	      src = &fi.typedef_field_list->field;
@@ -13887,6 +13907,30 @@  process_structure_scope (struct die_info *die, struct dwarf2_cu *cu)
 	    }
 	}
 
+      /* Copy fi.nested_types_list linked list elements content into the
+	 allocated array TYPE_NESTED_TYPES_ARRAY (type).  */
+      if (fi.nested_types_list != NULL)
+	{
+	  int i = fi.nested_types_list_count;
+
+	  ALLOCATE_CPLUS_STRUCT_TYPE (type);
+	  TYPE_NESTED_TYPES_ARRAY (type)
+	    = ((struct decl_field *)
+	       TYPE_ALLOC (type, sizeof (struct decl_field) * i));
+	  TYPE_NESTED_TYPES_COUNT (type) = i;
+
+	  /* Reverse the list order to keep the debug info elements order.  */
+	  while (--i >= 0)
+	    {
+	      struct decl_field *dest, *src;
+
+	      dest = &TYPE_NESTED_TYPES_FIELD (type, i);
+	      src = &fi.nested_types_list->field;
+	      fi.nested_types_list = fi.nested_types_list->next;
+	      *dest = *src;
+	    }
+	}
+
       do_cleanups (back_to);
     }
 
diff --git a/gdb/gdbtypes.h b/gdb/gdbtypes.h
index 5c1aecd211..d01c59a88f 100644
--- a/gdb/gdbtypes.h
+++ b/gdb/gdbtypes.h
@@ -863,7 +863,7 @@  struct fn_field
 
 };
 
-struct typedef_field
+struct decl_field
 {
   /* * Unqualified name to be prefixed by owning class qualified
      name.  */
@@ -978,10 +978,17 @@  struct cplus_struct_type
     /* * typedefs defined inside this class.  typedef_field points to
        an array of typedef_field_count elements.  */
 
-    struct typedef_field *typedef_field;
+    struct decl_field *typedef_field;
 
     unsigned typedef_field_count;
 
+    /* * The nested types defined by this type.  nested_types points to
+       an array of nested_types_count elements.  */
+
+    struct decl_field *nested_types;
+
+    unsigned nested_types_count;
+
     /* * The template arguments.  This is an array with
        N_TEMPLATE_ARGUMENTS elements.  This is NULL for non-template
        classes.  */
@@ -1424,6 +1431,23 @@  extern void set_type_vptr_basetype (struct type *, struct type *);
 #define TYPE_TYPEDEF_FIELD_PRIVATE(thistype, n)        \
   TYPE_TYPEDEF_FIELD (thistype, n).is_private
 
+#define TYPE_NESTED_TYPES_ARRAY(thistype)	\
+  TYPE_CPLUS_SPECIFIC (thistype)->nested_types
+#define TYPE_NESTED_TYPES_FIELD(thistype, n) \
+  TYPE_CPLUS_SPECIFIC (thistype)->nested_types[n]
+#define TYPE_NESTED_TYPES_FIELD_NAME(thistype, n) \
+  TYPE_NESTED_TYPES_FIELD (thistype, n).name
+#define TYPE_NESTED_TYPES_FIELD_TYPE(thistype, n) \
+  TYPE_NESTED_TYPES_FIELD (thistype, n).type
+#define TYPE_NESTED_TYPES_COUNT(thistype) \
+  TYPE_CPLUS_SPECIFIC (thistype)->nested_types_count
+#define TYPE_NESTED_TYPES_FIELD_PUBLIC(thistype, n)	\
+  TYPE_NESTED_TYPES_FIELD (thistype, n).is_public
+#define TYPE_NESTED_TYPES_FIELD_PROTECTED(thistype, n) \
+  TYPE_NESTED_TYPES_FIELD (thistype, n).is_protected
+#define TYPE_NESTED_TYPES_FIELD_PRIVATE(thistype, n)	\
+  TYPE_NESTED_TYPES_FIELD (thistype, n).is_private
+
 #define TYPE_IS_OPAQUE(thistype) \
   (((TYPE_CODE (thistype) == TYPE_CODE_STRUCT) \
     || (TYPE_CODE (thistype) == TYPE_CODE_UNION)) \
diff --git a/gdb/testsuite/gdb.cp/nested-types.cc b/gdb/testsuite/gdb.cp/nested-types.cc
new file mode 100644
index 0000000000..10ef6485c2
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/nested-types.cc
@@ -0,0 +1,668 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Tests for nested type definitions.  */
+
+struct S10
+{
+  enum E10 { A10, B10, C10 };
+  union U10
+  {
+    int a;
+    char c;
+  };
+  int i10;
+  E10 e10;
+  U10 u10;
+
+  struct S11
+  {
+    enum E11 { A11, B11, C11 };
+    union U11
+    {
+      int a;
+      char c;
+    };
+    int i11;
+    E11 e11;
+    U11 u11;
+
+    struct S12
+    {
+      enum E12 { A12, B12, C12 };
+      union U12
+      {
+	int a;
+	char c;
+      };
+      int i12;
+      E12 e12;
+      U12 u12;
+
+      struct S13
+      {
+	enum E13 { A13, B13, C13 };
+	union U13
+	{
+	  int a;
+	  char c;
+	};
+	int i13;
+	E13 e13;
+	U13 u13;
+
+	struct S14
+	{
+	  enum E14 { A14, B14, C14 };
+	  union U14
+	  {
+	    int a;
+	    char c;
+	  };
+	  int i14;
+	  E14 e14;
+	  U14 u14;
+
+	  struct S15
+	  {
+	    enum E15 { A15, B15, C15 };
+	    union U15
+	    {
+	      int a;
+	      char c;
+	    };
+	    int i15;
+	    E15 e15;
+	    U15 u15;
+
+	    struct S16
+	    {
+	      enum E16 { A16, B16, C16 };
+	      union U16
+	      {
+		int a;
+		char c;
+	      };
+	      int i16;
+	      E16 e16;
+	      U16 u16;
+
+	      struct S17
+	      {
+		enum E17 { A17, B17, C17 };
+		union U17
+		{
+		  int a;
+		  char c;
+		};
+		int i17;
+		E17 e17;
+		U17 u17;
+
+		struct S18
+		{
+		  enum E18 { A18, B18, C18 };
+		  union U18
+		  {
+		    int a;
+		    char c;
+		  };
+		  int i18;
+		  E18 e18;
+		  U18 u18;
+
+		  struct S19
+		  {
+		    enum E19 { A19, B19, C19 };
+		    union U19
+		    {
+		      int a;
+		      char c;
+		    };
+		    int i19;
+		    E19 e19;
+		    U19 u19;
+		  };
+		};
+	      };
+	    };
+	  };
+	};
+      };
+    };
+  };
+
+  struct S21
+  {
+    enum E21 { A21, B21, C21 };
+    union U21
+    {
+      int a;
+      char c;
+    };
+    int i21;
+    E21 e21;
+    U21 u21;
+
+    struct S22
+    {
+      enum E22 { A22, B22, C22 };
+      union U22
+      {
+	int a;
+	char c;
+      };
+      int i22;
+      E22 e22;
+      U22 u22;
+
+      struct S23
+      {
+	enum E23 { A23, B23, C23 };
+	union U23
+	{
+	  int a;
+	  char c;
+	};
+	int i23;
+	E23 e23;
+	U23 u23;
+
+	struct S24
+	{
+	  enum E24 { A24, B24, C24 };
+	  union U24
+	  {
+	    int a;
+	    char c;
+	  };
+	  int i24;
+	  E24 e24;
+	  U24 u24;
+
+	  struct S25
+	  {
+	    enum E25 { A25, B25, C25 };
+	    union U25
+	    {
+	      int a;
+	      char c;
+	    };
+	    int i25;
+	    E25 e25;
+	    U25 u25;
+
+	    struct S26
+	    {
+	      enum E26 { A26, B26, C26 };
+	      union U26
+	      {
+		int a;
+		char c;
+	      };
+	      int i26;
+	      E26 e26;
+	      U26 u26;
+
+	      struct S27
+	      {
+		enum E27 { A27, B27, C27 };
+		union U27
+		{
+		  int a;
+		  char c;
+		};
+		int i27;
+		E27 e27;
+		U27 u27;
+
+		struct S28
+		{
+		  enum E28 { A28, B28, C28 };
+		  union U28
+		  {
+		    int a;
+		    char c;
+		  };
+		  int i28;
+		  E28 e28;
+		  U28 u28;
+
+		  struct S29
+		  {
+		    enum E29 { A29, B29, C29 };
+		    union U29
+		    {
+		      int a;
+		      char c;
+		    };
+		    int i29;
+		    E29 e29;
+		    U29 u29;
+		  };
+		};
+	      };
+	    };
+	  };
+	};
+      };
+    };
+  };
+
+  struct S31
+  {
+    enum E31 { A31, B31, C31 };
+    union U31
+    {
+      int a;
+      char c;
+    };
+    int i31;
+    E31 e31;
+    U31 u31;
+
+    struct S32
+    {
+      enum E32 { A32, B32, C32 };
+      union U32
+      {
+	int a;
+	char c;
+      };
+      int i32;
+      E32 e32;
+      U32 u32;
+
+      struct S33
+      {
+	enum E33 { A33, B33, C33 };
+	union U33
+	{
+	  int a;
+	  char c;
+	};
+	int i33;
+	E33 e33;
+	U33 u33;
+
+	struct S34
+	{
+	  enum E34 { A34, B34, C34 };
+	  union U34
+	  {
+	    int a;
+	    char c;
+	  };
+	  int i34;
+	  E34 e34;
+	  U34 u34;
+
+	  struct S35
+	  {
+	    enum E35 { A35, B35, C35 };
+	    union U35
+	    {
+	      int a;
+	      char c;
+	    };
+	    int i35;
+	    E35 e35;
+	    U35 u35;
+
+	    struct S36
+	    {
+	      enum E36 { A36, B36, C36 };
+	      union U36
+	      {
+		int a;
+		char c;
+	      };
+	      int i36;
+	      E36 e36;
+	      U36 u36;
+
+	      struct S37
+	      {
+		enum E37 { A37, B37, C37 };
+		union U37
+		{
+		  int a;
+		  char c;
+		};
+		int i37;
+		E37 e37;
+		U37 u37;
+
+		struct S38
+		{
+		  enum E38 { A38, B38, C38 };
+		  union U38
+		  {
+		    int a;
+		    char c;
+		  };
+		  int i38;
+		  E38 e38;
+		  U38 u38;
+
+		  struct S39
+		  {
+		    enum E39 { A39, B39, C39 };
+		    union U39
+		    {
+		      int a;
+		      char c;
+		    };
+		    int i39;
+		    E39 e39;
+		    U39 u39;
+		  };
+		};
+	      };
+	    };
+	  };
+	};
+      };
+    };
+  };
+
+  struct S41
+  {
+    enum E41 { A41, B41, C41 };
+    union U41
+    {
+      int a;
+      char c;
+    };
+    int i41;
+    E41 e41;
+    U41 u41;
+
+    struct S42
+    {
+      enum E42 { A42, B42, C42 };
+      union U42
+      {
+	int a;
+	char c;
+      };
+      int i42;
+      E42 e42;
+      U42 u42;
+
+      struct S43
+      {
+	enum E43 { A43, B43, C43 };
+	union U43
+	{
+	  int a;
+	  char c;
+	};
+	int i43;
+	E43 e43;
+	U43 u43;
+
+	struct S44
+	{
+	  enum E44 { A44, B44, C44 };
+	  union U44
+	  {
+	    int a;
+	    char c;
+	  };
+	  int i44;
+	  E44 e44;
+	  U44 u44;
+
+	  struct S45
+	  {
+	    enum E45 { A45, B45, C45 };
+	    union U45
+	    {
+	      int a;
+	      char c;
+	    };
+	    int i45;
+	    E45 e45;
+	    U45 u45;
+
+	    struct S46
+	    {
+	      enum E46 { A46, B46, C46 };
+	      union U46
+	      {
+		int a;
+		char c;
+	      };
+	      int i46;
+	      E46 e46;
+	      U46 u46;
+
+	      struct S47
+	      {
+		enum E47 { A47, B47, C47 };
+		union U47
+		{
+		  int a;
+		  char c;
+		};
+		int i47;
+		E47 e47;
+		U47 u47;
+
+		struct S48
+		{
+		  enum E48 { A48, B48, C48 };
+		  union U48
+		  {
+		    int a;
+		    char c;
+		  };
+		  int i48;
+		  E48 e48;
+		  U48 u48;
+
+		  struct S49
+		  {
+		    enum E49 { A49, B49, C49 };
+		    union U49
+		    {
+		      int a;
+		      char c;
+		    };
+		    int i49;
+		    E49 e49;
+		    U49 u49;
+		  };
+		};
+	      };
+	    };
+	  };
+	};
+      };
+    };
+  };
+
+  struct S51
+  {
+    enum E51 { A51, B51, C51 };
+    union U51
+    {
+      int a;
+      char c;
+    };
+    int i51;
+    E51 e51;
+    U51 u51;
+
+    struct S52
+    {
+      enum E52 { A52, B52, C52 };
+      union U52
+      {
+	int a;
+	char c;
+      };
+      int i52;
+      E52 e52;
+      U52 u52;
+
+      struct S53
+      {
+	enum E53 { A53, B53, C53 };
+	union U53
+	{
+	  int a;
+	  char c;
+	};
+	int i53;
+	E53 e53;
+	U53 u53;
+
+	struct S54
+	{
+	  enum E54 { A54, B54, C54 };
+	  union U54
+	  {
+	    int a;
+	    char c;
+	  };
+	  int i54;
+	  E54 e54;
+	  U54 u54;
+
+	  struct S55
+	  {
+	    enum E55 { A55, B55, C55 };
+	    union U55
+	    {
+	      int a;
+	      char c;
+	    };
+	    int i55;
+	    E55 e55;
+	    U55 u55;
+
+	    struct S56
+	    {
+	      enum E56 { A56, B56, C56 };
+	      union U56
+	      {
+		int a;
+		char c;
+	      };
+	      int i56;
+	      E56 e56;
+	      U56 u56;
+
+	      struct S57
+	      {
+		enum E57 { A57, B57, C57 };
+		union U57
+		{
+		  int a;
+		  char c;
+		};
+		int i57;
+		E57 e57;
+		U57 u57;
+
+		struct S58
+		{
+		  enum E58 { A58, B58, C58 };
+		  union U58
+		  {
+		    int a;
+		    char c;
+		  };
+		  int i58;
+		  E58 e58;
+		  U58 u58;
+
+		  struct S59
+		  {
+		    enum E59 { A59, B59, C59 };
+		    union U59
+		    {
+		      int a;
+		      char c;
+		    };
+		    int i59;
+		    E59 e59;
+		    U59 u59;
+		  };
+		};
+	      };
+	    };
+	  };
+	};
+      };
+    };
+  };
+};
+
+int
+main ()
+{
+  S10 s10;
+  S10::S11 s11;
+  S10::S11::S12 s12;
+  S10::S11::S12::S13 s13;
+  S10::S11::S12::S13::S14 s14;
+  S10::S11::S12::S13::S14::S15 s15;
+  S10::S11::S12::S13::S14::S15::S16 s16;
+  S10::S11::S12::S13::S14::S15::S16::S17 s17;
+  S10::S11::S12::S13::S14::S15::S16::S17::S18 s18;
+  S10::S11::S12::S13::S14::S15::S16::S17::S18::S19 s19;
+  S10::S21 s21;
+  S10::S21::S22 s22;
+  S10::S21::S22::S23 s23;
+  S10::S21::S22::S23::S24 s24;
+  S10::S21::S22::S23::S24::S25 s25;
+  S10::S21::S22::S23::S24::S25::S26 s26;
+  S10::S21::S22::S23::S24::S25::S26::S27 s27;
+  S10::S21::S22::S23::S24::S25::S26::S27::S28 s28;
+  S10::S21::S22::S23::S24::S25::S26::S27::S28::S29 s29;
+  S10::S31 s31;
+  S10::S31::S32 s32;
+  S10::S31::S32::S33 s33;
+  S10::S31::S32::S33::S34 s34;
+  S10::S31::S32::S33::S34::S35 s35;
+  S10::S31::S32::S33::S34::S35::S36 s36;
+  S10::S31::S32::S33::S34::S35::S36::S37 s37;
+  S10::S31::S32::S33::S34::S35::S36::S37::S38 s38;
+  S10::S31::S32::S33::S34::S35::S36::S37::S38::S39 s39;
+  S10::S41 s41;
+  S10::S41::S42 s42;
+  S10::S41::S42::S43 s43;
+  S10::S41::S42::S43::S44 s44;
+  S10::S41::S42::S43::S44::S45 s45;
+  S10::S41::S42::S43::S44::S45::S46 s46;
+  S10::S41::S42::S43::S44::S45::S46::S47 s47;
+  S10::S41::S42::S43::S44::S45::S46::S47::S48 s48;
+  S10::S41::S42::S43::S44::S45::S46::S47::S48::S49 s49;
+  S10::S51 s51;
+  S10::S51::S52 s52;
+  S10::S51::S52::S53 s53;
+  S10::S51::S52::S53::S54 s54;
+  S10::S51::S52::S53::S54::S55 s55;
+  S10::S51::S52::S53::S54::S55::S56 s56;
+  S10::S51::S52::S53::S54::S55::S56::S57 s57;
+  S10::S51::S52::S53::S54::S55::S56::S57::S58 s58;
+  S10::S51::S52::S53::S54::S55::S56::S57::S58::S59 s59;
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.cp/nested-types.exp b/gdb/testsuite/gdb.cp/nested-types.exp
new file mode 100644
index 0000000000..a96493ff51
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/nested-types.exp
@@ -0,0 +1,267 @@ 
+# Copyright 2017 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test nested class definitions with the type printer.
+#
+# This test works by constructing a tree to represent "struct S10" in
+# the corresponding source file.  It then walks the nodes of this tree
+# to construct input suitable for passing to cp_test_ptype_class.
+
+if {[skip_cplus_tests]} { continue }
+
+load_lib "cp-support.exp"
+
+standard_testfile .cc
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 {debug c++}]} {
+    return -1
+}
+
+# Build the node given by ID (a number representing the struct S[ID] in
+# the source file).
+#
+# For each node, stored as ::nodes(ID,ARG), where ARG is
+#
+# fields   - list of fields [no children]
+# children - list of types [children]
+
+proc build_node {id} {
+    global nodes
+
+    # For any node, FIELDS is always the types i(N), e(N), u(N)
+    # CHILDREN is a list of nodes called [E(N), U(N)] S(N+1)
+    #
+    # The root (10) also has S(N+11), S(N+21), S(N+31), S(N+41)
+
+    set nodes($id,fields) [list "int i$id" "E$id e$id" "U$id u$id"]
+    set ndoes($id,children) {}
+    if {$id == 10} {
+	set limit 5
+    } else {
+	set limit 1
+    }
+    for {set i 0} {$i < $limit} {incr i} {
+	set n [expr {1 + $id + $i * 10}]
+
+	# We don't build nodes which are multiples of 10
+	# (the source only uses that at the root struct).
+	# We also don't create nodes not in the source file
+	# (id >= 60).
+	if {[expr {$n % 10}] != 0 && $n < 60} {
+	    lappend nodes($id,children) $n
+	}
+    }
+}
+
+# A helper procedure to indent the log output by LVL.  This is used for
+# debugging the tree, if ever necessary.
+
+proc indent {lvl} {
+    for {set i 0} {$i < $lvl} {incr i} {
+	send_log "  "
+    }
+}
+
+# For the given CHILD name and PARENT_LIST, return the fully qualified
+# name of the child type.
+
+proc qual_name {child parent_list} {
+    if {[string range $child 0 2] != "int" && [llength $parent_list]} {
+	return "[join $parent_list ::]::$child"
+    } else {
+	return "$child"
+    }
+}
+
+# Output to the log and/or create the result list for the fields of node ID.
+
+proc make_fields {result_var id parent_list indent_lvl log} {
+    upvar $result_var result
+    global nodes
+
+    foreach type $nodes($id,fields) {
+	set s "[qual_name $type $parent_list];"
+	if {$log} {
+	    indent $indent_lvl
+	    send_log "$s\n"
+	}
+	lappend result [list "field" "public" "$s"]
+    }
+}
+
+# Output to the log and/or create the result list for the union type in
+# node ID.
+
+proc make_union {result_var id parent_list indent_lvl log} {
+    upvar $result_var result
+
+    set s "[qual_name U$id $parent_list]"
+    set a "int a;"
+    set c "char c;"
+    lappend result [list "type" "public" "union" $s [list $a $c]]
+    if {$log} {
+	indent $indent_lvl
+	send_log "union $s \{\n"
+	indent [expr {$indent_lvl + 1}]
+	send_log "$a\n"
+	indent [expr {$indent_lvl + 1}]
+	send_log "$c\n"
+	indent $indent_lvl
+	send_log "\};\n"
+    }
+}
+
+# Output to the log and/or create the result list for the enum type in
+# node ID.
+
+proc make_enum {result_var id parent_list indent_lvl log} {
+    upvar $result_var result
+
+    set s "[qual_name E$id $parent_list]"
+    set a "[qual_name A$id $parent_list]"
+    set b "[qual_name B$id $parent_list]"
+    set c "[qual_name C$id $parent_list]"
+    lappend result [list "type" "public" "enum" $s [list $a $b $c]]
+
+    if {$log} {
+	indent $indent_lvl
+	send_log "enum $s \{$a, $b, $c\};\n"
+    }
+}
+
+# Output to the log and/or create the result list for the node given by ID.
+#
+# LIMIT describes the number of nested types to output (corresponding to
+# the "set print type nested-type-limit" command).
+# PARENT_LIST is the list of parent nodes already seen.
+# INDENT_LVL is the indentation level (used when LOG is true).
+
+proc node_result {result_var id limit parent_list indent_lvl log} {
+    upvar $result_var result
+
+    # Start a new type list.
+    set my_name "S$id"
+    set s "[qual_name $my_name $parent_list]"
+    set my_result [list "type" "public" "struct" $s]
+
+    if {$log} {
+	indent $indent_lvl
+	send_log "struct $s \{\n"
+    }
+
+    # Add this node to the parent list so that its name appears in
+    # qualified names.
+    lappend parent_list "$my_name"
+
+    # Output field list to a local children list.
+    set children_list {}
+    make_fields children_list $id $parent_list [expr {$indent_lvl + 1}] $log
+
+    # Output type definitions to the local children list.
+    # The first number of ID gives us the depth of the node.
+    if {[string index $id 1] < $limit || $limit < 0} {
+	if {$log} {
+	    send_log "\n"
+	}
+	make_enum children_list $id $parent_list [expr {$indent_lvl + 1}] $log
+	make_union children_list $id $parent_list [expr {$indent_lvl + 1}] $log
+    }
+
+    # Output the children to the local children list.
+    global nodes
+    if {[info exists nodes($id,children)]} {
+	foreach c $nodes($id,children) {
+	    if {[string index $c 1] <= $limit || $limit < 0} {
+		node_result children_list $c $limit $parent_list \
+		    [expr {$indent_lvl + 1}] $log
+	    }
+	}
+    }
+
+    # Add this node's children to its result and add its result to
+    # its parent's results.
+    lappend my_result $children_list
+    lappend result $my_result
+
+    if {$log} {
+	indent $indent_lvl
+	send_log "\};\n"
+    }
+}
+
+# Test nested type definitions.  LIMIT specifies how many nested levels
+# of definitions to test.  If LOG is true, output the tree to the log in
+# a human-readable format mimicing the source code.
+
+proc test_nested_limit {limit log} {
+    set result {}
+
+    # Set the number of nested definitions to print.
+    gdb_test_no_output "set print type nested-type-limit $limit"
+
+    # Check the output of "show type print nested-type-limit"
+    if {$limit < 0} {
+	set lstr "unlimited"
+    } else {
+	set lstr $limit
+    }
+    gdb_test "show print type nested-type-limit" \
+	"Will print $lstr nested types defined in a class" \
+	"show print type nested-type-limit ($limit)"
+
+    # Generate the result list and test it.
+    if {$log} {
+	send_log "Tree to $limit levels:\n"
+    }
+    node_result result 10 $limit {} 0 $log
+
+    # The only output we check for is the contents of the struct, ignoring
+    # the leading "type = struct S10 {" and trailing "}" of the outermost node.
+    set result [lindex $result 0]
+    lassign $result type access key name children
+    cp_test_ptype_class $name "ptype $name (limit = $limit)" $key \
+	$name $children
+}
+
+# Build a tree of nodes describing the structures in the source file.
+
+# An array holding all the nodes
+array set nodes {}
+build_node 10
+for {set i 1} {$i < 6} {incr i} {
+    for {set j 1} {$j < 10} {incr j} {
+	build_node $i$j
+    }
+}
+
+# Check relevant commands.
+
+# By default, we do not print nested type definitions.
+gdb_test "show print type nested-type-limit" \
+    "Will not print nested types defined in a class" \
+    "show default print type nested-type-limit"
+
+# -1 means we print all nested types
+test_nested_limit -1 false
+
+# Test the output of "show print type nested-type-limit" and
+# ptype on the test source.
+
+for {set i 1} {$i < 9} {incr i} {
+    test_nested_limit $i false
+}
+
+unset -nocomplain nodes result
diff --git a/gdb/testsuite/lib/cp-support.exp b/gdb/testsuite/lib/cp-support.exp
index 5291921673..40a7088477 100644
--- a/gdb/testsuite/lib/cp-support.exp
+++ b/gdb/testsuite/lib/cp-support.exp
@@ -15,6 +15,15 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+load_lib "data-structures.exp"
+
+# Controls whether detailed logging for cp_test_ptype_class is enabled.
+# By default, it is not.  Enable it to assist with troubleshooting
+# failed cp_test_ptype_class tests.  [Users can simply add the statement
+# "set debug_cp_ptype_test_class true" after this file is loaded.]
+
+set ::debug_cp_test_ptype_class false
+
 # Auxiliary function to check for known problems.
 #
 # EXPECTED_STRING is the string expected by the test.
@@ -38,7 +47,37 @@  proc cp_check_errata { expected_string actual_string errata_table } {
     }
 }
 
-# Test ptype of a class.
+# A convenience procedure for outputting debug info for cp_test_ptype_class
+# to the log.  Set the global variable "debug_cp_test_ptype_class"
+# to enable logging (to help with debugging failures).
+
+proc cp_ptype_class_verbose {msg} {
+    global debug_cp_test_ptype_class
+
+    if {$debug_cp_test_ptype_class} {
+	verbose -log $msg
+    }
+}
+
+# A convenience procedure to return the next element of the queue.
+
+proc next_line {qid} {
+    set elem {}
+
+    while {$elem == "" && ![queue empty $qid]} {
+	# We make cp_test_ptype_class trim whitespace
+	set elem [queue pop $qid]
+    }
+
+    if {$elem == ""} {
+	cp_ptype_class_verbose "next line element: no more lines"
+    } else {
+	cp_ptype_class_verbose "next line element: \"$elem\""
+    }
+    return $elem
+}
+
+# Test ptype of a class.  Return `true' if the test passes, false otherwise.
 #
 # Different C++ compilers produce different output.  To accommodate all
 # the variations listed below, I read the output of "ptype" and process
@@ -87,6 +126,20 @@  proc cp_check_errata { expected_string actual_string errata_table } {
 #      the class has a typedef with the given access type and the
 #      given declaration.
 #
+#   { type "access" "key" "name" children }
+#
+#      The class has a nested type definition with the given ACCESS.
+#      KEY is the keyword of the nested type ("enum", "union", "struct",
+#         "class").
+#      NAME is the (tag) name of the type.
+#      CHILDREN is a list of the type's children.  For struct and union keys,
+#        this is simply the same type of list that is normally passed to
+#        this procedure.  For enums the list of children should be the
+#        defined enumerators.  For unions it is a list of declarations.
+#        NOTE: The enum key will add a regexp to handle optional storage
+#        class specifiers (": unsigned int", e.g.).  The caller need not
+#        specify this.
+#
 # If you test the same class declaration more than once, you can specify
 # IN_CLASS_TABLE as "ibid".  "ibid" means: look for a previous class
 # table that had the same IN_KEY and IN_TAG, and re-use that table.
@@ -102,6 +155,11 @@  proc cp_check_errata { expected_string actual_string errata_table } {
 # 
 # IN_PTYPE_ARG are arguments to pass to ptype.  The default is "/r".
 #
+# RECURSIVE_QID is used internally to call this procedure recursively
+# when, e.g., testing nested type definitions.  The "ptype" command will
+# not be sent to GDB and the lines in the queue given by this argument will
+# be used instead.
+#
 # gdb can vary the output of ptype in several ways:
 #
 # . CLASS/STRUCT
@@ -178,16 +236,20 @@  proc cp_check_errata { expected_string actual_string errata_table } {
 #
 # -- chastain 2004-08-07
 
-proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_tail "" } { in_errata_table { } } { in_ptype_arg /r } } {
+proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table
+			   { in_tail "" } { in_errata_table { } }
+			   { in_ptype_arg /r } { recursive_qid 0 } } {
     global gdb_prompt
     set wsopt "\[\r\n\t \]*"
 
-    # The test name defaults to the command, but without the
-    # arguments, for historical reasons.
+    if {$recursive_qid == 0} {
+	# The test name defaults to the command, but without the
+	# arguments, for historical reasons.
 
-    if { "$in_testname" == "" } then { set in_testname "ptype $in_exp" }
+	if { "$in_testname" == "" } then { set in_testname "ptype $in_exp" }
 
-    set in_command "ptype${in_ptype_arg} $in_exp"
+	set in_command "ptype${in_ptype_arg} $in_exp"
+    }
 
     # Save class tables in a history array for reuse.
 
@@ -195,7 +257,7 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     if { $in_class_table == "ibid" } then {
 	if { ! [info exists cp_class_table_history("$in_key,$in_tag") ] } then {
 	    fail "$in_testname // bad ibid"
-	    return
+	    return false
 	}
 	set in_class_table $cp_class_table_history("$in_key,$in_tag")
     } else {
@@ -209,6 +271,9 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     set list_fields  { }
     set list_methods { }
     set list_typedefs { }
+    set list_types    { }
+    set list_enums    { }
+    set list_unions   { }
 
     foreach class_line $in_class_table {
 	switch [lindex $class_line 0] {
@@ -217,7 +282,11 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	    "field"  { lappend list_fields  [lrange $class_line 1 2] }
 	    "method" { lappend list_methods [lrange $class_line 1 2] }
 	    "typedef" { lappend list_typedefs [lrange $class_line 1 2] }
-	    default  { fail "$in_testname // bad line in class table: $class_line"; return; }
+	    "type"    { lappend list_types [lrange $class_line 1 4] }
+	    default  {
+		fail "$in_testname // bad line in class table: $class_line"
+		return false
+	    }
 	}
     }
 
@@ -225,24 +294,56 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     # These are: { count ccess-type regular-expression }.
 
     set list_synth { }
-    lappend list_synth [list 0 "public" "$in_tag & operator=\\($in_tag const ?&\\);"]
-    lappend list_synth [list 0 "public" "$in_tag\\((int,|) ?$in_tag const ?&\\);"]
-    lappend list_synth [list 0 "public" "$in_tag\\((int|void|)\\);"]
-
-    # Actually do the ptype.
-
-    set parse_okay 0
-    gdb_test_multiple "$in_command" "$in_testname // parse failed" {
-	-re "type = (struct|class)${wsopt}(\[^ \t\]*)${wsopt}(\\\[with .*\\\]${wsopt})?((:\[^\{\]*)?)${wsopt}\{(.*)\}${wsopt}(\[^\r\n\]*)\[\r\n\]+$gdb_prompt $" {
-	    set parse_okay          1
-	    set actual_key          $expect_out(1,string)
-	    set actual_tag          $expect_out(2,string)
-	    set actual_base_string  $expect_out(4,string)
-	    set actual_body         $expect_out(6,string)
-	    set actual_tail         $expect_out(7,string)
+    lappend list_synth [list 0 "public" \
+			    "$in_tag & operator=\\($in_tag const ?&\\);"]
+    lappend list_synth [list 0 "public" \
+			    "$in_tag\\((int,|) ?$in_tag const ?&\\);"]
+    lappend list_synth [list 0 "public" \
+			    "$in_tag\\((int|void|)\\);"]
+
+    # Partial regexp for parsing the struct/class header.
+    set regexp_header "(struct|class)${wsopt}(\[^ \t\]*)${wsopt}"
+    append regexp_header "(\\\[with .*\\\]${wsopt})?((:\[^\{\]*)?)${wsopt}\{"
+    if {$recursive_qid == 0} {
+	# Actually do the ptype.
+
+	# For processing the output of ptype, we must get to the prompt.
+	set the_regexp "type = ${regexp_header}"
+	append the_regexp "(.*)\}${wsopt}(\[^\r\n\]*)\[\r\n\]+$gdb_prompt $"
+	set parse_okay 0
+	gdb_test_multiple "$in_command" "$in_testname // parse failed" {
+	    -re $the_regexp {
+		set parse_okay          1
+		set actual_key          $expect_out(1,string)
+		set actual_tag          $expect_out(2,string)
+		set actual_base_string  $expect_out(4,string)
+		set actual_body         $expect_out(6,string)
+		set actual_tail         $expect_out(7,string)
+	    }
+	}
+    } else {
+	# The struct/class header by the first element in the line queue.
+	# "Parse" that instead of the output of ptype.
+	set header [next_line $recursive_qid]
+	set parse_okay [regexp $regexp_header $header dummy actual_key \
+			    actual_tag dummy actual_base_string]
+
+	if {$parse_okay} {
+	    cp_ptype_class_verbose \
+		"Parsing nested type definition (parse_okay=$parse_okay):"
+	    cp_ptype_class_verbose \
+		"\tactual_key=$actual_key, actual_tag=$actual_tag"
+	    cp_ptype_class_verbose "\tactual_base_string=$actual_base_string"
 	}
+
+	# Cannot have a tail with a nested type definition.
+	set actual_tail ""
+    }
+
+    if { ! $parse_okay } {
+	cp_ptype_class_verbose "*** parse failed ***"
+	return false
     }
-    if { ! $parse_okay } then { return }
 
     # Check the actual key.  It would be nice to require that it match
     # the input key, but gdb does not support that.  For now, accept any
@@ -256,7 +357,7 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	    cp_check_errata "class"  "$actual_key" $in_errata_table
 	    cp_check_errata "struct" "$actual_key" $in_errata_table
 	    fail "$in_testname // wrong key: $actual_key"
-	    return
+	    return false
 	}
     }
 
@@ -265,7 +366,7 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     if { "$actual_tag" != "$in_tag" } then {
 	cp_check_errata "$in_tag" "$actual_tag" $in_errata_table
 	fail "$in_testname // wrong tag: $actual_tag"
-	return
+	return false
     }
 
     # Check the actual bases.
@@ -281,11 +382,11 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 
     if { [llength $list_actual_bases] < [llength $list_bases] } then {
 	fail "$in_testname // too few bases"
-	return
+	return false
     }
     if { [llength $list_actual_bases] > [llength $list_bases] } then {
 	fail "$in_testname // too many bases"
-	return
+	return false
     }
 
     # Check each base.
@@ -296,7 +397,7 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	if { "$actual_base" != "$base" } then {
 	    cp_check_errata "$base" "$actual_base" $in_errata_table
 	    fail "$in_testname // wrong base: $actual_base"
-	    return
+	    return false
 	}
 	set list_bases [lreplace $list_bases 0 0]
     }
@@ -306,11 +407,26 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     set last_was_access 0
     set vbase_match 0
 
-    foreach actual_line [split $actual_body "\r\n"] {
+    if {$recursive_qid == 0} {
+	# Use a queue to hold the lines that will be checked.
+	# This will allow processing below to remove lines from the input
+	# more easily.
+	set line_queue [::Queue::new]
+	foreach l [split $actual_body "\r\n"] {
+	    set l [string trim $l]
+	    if {$l != ""} {
+		queue push $line_queue $l
+	    }
+	}
+    } else {
+	set line_queue $recursive_qid
+    }
 
-	# Chomp the line.
+    while {![queue empty $line_queue]} {
 
-	set actual_line [string trim $actual_line]
+	# Get the next line.
+
+	set actual_line [next_line $line_queue]
 	if { "$actual_line" == "" } then { continue }
 
 	# Access specifiers.
@@ -319,7 +435,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	    set access "$s1"
 	    if { $last_was_access } then {
 		fail "$in_testname // redundant access specifier"
-		return
+		queue delete $line_queue
+		return false
 	    }
 	    set last_was_access 1
 	    continue
@@ -335,7 +452,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 		if { "$access" != "private" } then {
 		    cp_check_errata "private" "$access" $in_errata_table
 		    fail "$in_testname // wrong access specifier for virtual base: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 		set list_vbases [lreplace $list_vbases 0 0]
 		set vbase_match 1
@@ -348,11 +466,18 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	if { [llength $list_fields] > 0 } then {
 	    set field_access [lindex [lindex $list_fields 0] 0]
 	    set field_decl   [lindex [lindex $list_fields 0] 1]
+	    if {$recursive_qid > 0} {
+		cp_ptype_class_verbose "\tactual_line=$actual_line"
+		cp_ptype_class_verbose "\tfield_access=$field_access"
+		cp_ptype_class_verbose "\tfield_decl=$field_decl"
+		cp_ptype_class_verbose "\taccess=$access"
+	    }
 	    if { "$actual_line" == "$field_decl" } then {
 		if { "$access" != "$field_access" } then {
 		    cp_check_errata "$field_access" "$access" $in_errata_table
 		    fail "$in_testname // wrong access specifier for field: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 		set list_fields [lreplace $list_fields 0 0]
 		continue
@@ -361,7 +486,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	    # Data fields must appear before synths and methods.
 	    cp_check_errata "$field_decl" "$actual_line" $in_errata_table
 	    fail "$in_testname // unrecognized line type 1: $actual_line"
-	    return
+	    queue delete $line_queue
+	    return false
 	}
 
 	# Method function.
@@ -373,7 +499,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 		if { "$access" != "$method_access" } then {
 		    cp_check_errata "$method_access" "$access" $in_errata_table
 		    fail "$in_testname // wrong access specifier for method: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 		set list_methods [lreplace $list_methods 0 0]
 		continue
@@ -385,7 +512,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 		if { "$access" != "$method_access" } then {
 		    cp_check_errata "$method_access" "$access" $in_errata_table
 		    fail "$in_testname // wrong access specifier for method: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 		set list_methods [lreplace $list_methods 0 0]
 		continue
@@ -401,13 +529,129 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 		if {![string equal $access $typedef_access]} {
 		    cp_check_errata $typedef_access $access $in_errata_table
 		    fail "$in_testname // wrong access specifier for typedef: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 		set list_typedefs [lreplace $list_typedefs 0 0]
 		continue
 	    }
 	}
 
+	# Nested type definitions
+
+	if {[llength $list_types] > 0} {
+	    cp_ptype_class_verbose "Nested type definition: "
+	    lassign [lindex $list_types 0] nested_access nested_key \
+		nested_name nested_children
+	    set msg "nested_access=$nested_access, nested_key=$nested_key, "
+	    append msg "nested_name=$nested_name, "
+	    append msg "[llength $nested_children] children"
+	    cp_ptype_class_verbose $msg
+
+	    if {![string equal $access $nested_access]} {
+		cp_check_errata $nested_access $access $in_errata_table
+		set txt "$in_testname // wrong access specifier for "
+		append txt "nested type: $access"
+		fail $txt
+		queue delete $line_queue
+		return false
+	    }
+
+	    switch $nested_key {
+		enum {
+		    set expected_result \
+			"enum $nested_name (: (unsigned )?int)? \{"
+		    foreach c $nested_children {
+			append expected_result "$c, "
+		    }
+		    set expected_result \
+			[string trimright $expected_result { ,}]
+		    append expected_result "\};"
+		    cp_ptype_class_verbose \
+			"Expecting enum result: $expected_result"
+		    if {![regexp -- $expected_result $actual_line]} {
+			set txt "$in_testname // wrong nested type enum"
+			append txt " definition: $actual_linejj"
+			fail $txt
+			queue delete $line_queue
+			return false
+		    }
+		    cp_ptype_class_verbose "passed enum $nested_name"
+		}
+
+		union {
+		    set expected_result "union $nested_name \{"
+		    cp_ptype_class_verbose \
+			"Expecting union result: $expected_result"
+		    if {![string equal $expected_result $actual_line]} {
+			set txt "$in_testname // wrong nested type union"
+			append txt " definition: $actual_line"
+			fail $txt
+			queue delete $line_queue
+			return false
+		    }
+
+		    # This will be followed by lines for each member of the
+		    # union.
+		    cp_ptype_class_verbose "matched union name"
+		    foreach m $nested_children {
+			set actual_line [next_line $line_queue]
+			cp_ptype_class_verbose "Expecting union member: $m"
+			if {![string equal $m $actual_line]} {
+			    set txt "$in_testname // unexpected union member: "
+			    append txt $m
+			    fail $txt
+			    queue delete $line_queue
+			    return false
+			}
+			cp_ptype_class_verbose "matched union child \"$m\""
+		    }
+
+		    # Nested union types always end with a trailing curly brace.
+		    set actual_line [next_line $line_queue]
+		    if {![string equal $actual_line "\};"]} {
+			fail "$in_testname // missing closing curly brace"
+			queue delete $line_queue
+			return false
+		    }
+		    cp_ptype_class_verbose "passed union $nested_name"
+		}
+
+		struct -
+		class {
+		    cp_ptype_class_verbose \
+			"Expecting [llength $nested_children] children"
+		    foreach c $nested_children {
+			cp_ptype_class_verbose "\t$c"
+		    }
+		    # Start by pushing the current line back into the queue
+		    # so that the recursive call can parse the class/struct
+		    # header.
+		    queue unpush $line_queue $actual_line
+		    cp_ptype_class_verbose \
+			"Recursing for type $nested_key $nested_name"
+		    if {![cp_test_ptype_class $in_exp $in_testname $nested_key \
+			      $nested_name $nested_children $in_tail \
+			      $in_errata_table $in_ptype_arg $line_queue]} {
+			# The recursive call has already called `fail' and
+			# released the line queue.
+			return false
+		    }
+		    cp_ptype_class_verbose \
+			"passed nested type $nested_key $nested_name"
+		}
+
+		default {
+		    fail "$in_testname // invalid nested type key: $nested_key"
+		    queue delete $line_queue
+		    return false
+		}
+	    }
+
+	    set list_types [lreplace $list_types 0 0]
+	    continue
+	}
+
 	# Synthetic operators.  These are optional and can be mixed in
 	# with the methods in any order, but duplicates are wrong.
 	#
@@ -427,7 +671,8 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 		if { "$access" != "$synth_access" } then {
 		    cp_check_errata "$synth_access" "$access" $in_errata_table
 		    fail "$in_testname // wrong access specifier for synthetic operator: $access"
-		    return
+		    queue delete $line_queue
+		    return false
 		}
 
 		if { $synth_count > 0 } then {
@@ -449,6 +694,12 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	}
 	if { $synth_match } then { continue }
 
+	# If checking a nested type/recursively and we see a closing curly
+	# brace, we're done.
+	if {$recursive_qid != 0 && [string equal $actual_line "\};"]} {
+	    break
+	}
+
 	# Unrecognized line.
 
 	if { [llength $list_methods] > 0 } then {
@@ -457,7 +708,13 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
 	}
 
 	fail "$in_testname // unrecognized line type 2: $actual_line"
-	return
+	queue delete $line_queue
+	return false
+    }
+
+    # Done with the line queue.
+    if {$recursive_qid == 0} {
+	queue delete $line_queue
     }
 
     # Check for missing elements.
@@ -465,23 +722,23 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     if { $vbase_match } then {
 	if { [llength $list_vbases] > 0 } then {
 	    fail "$in_testname // missing virtual base pointers"
-	    return
+	    return false
 	}
     }
 
     if { [llength $list_fields] > 0 } then {
 	fail "$in_testname // missing fields"
-	return
+	return false
     }
 
     if { [llength $list_methods] > 0 } then {
 	fail "$in_testname // missing methods"
-	return
+	return false
     }
 
     if {[llength $list_typedefs] > 0} {
 	fail "$in_testname // missing typedefs"
-	return
+	return false
     }
 
     # Check the tail.
@@ -490,11 +747,15 @@  proc cp_test_ptype_class { in_exp in_testname in_key in_tag in_class_table { in_
     if { "$actual_tail" != "$in_tail" } then {
 	cp_check_errata "$in_tail" "$actual_tail" $in_errata_table
 	fail "$in_testname // wrong tail: $actual_tail"
-	return
+	return false
     }
 
-    # It all worked!
+    # It all worked, but don't call `pass' if we've been called
+    # recursively.
+
+    if {$recursive_qid == 0} {
+	pass "$in_testname"
+    }
 
-    pass "$in_testname"
-    return
+    return true
 }
diff --git a/gdb/testsuite/lib/data-structures.exp b/gdb/testsuite/lib/data-structures.exp
new file mode 100644
index 0000000000..a44978be3a
--- /dev/null
+++ b/gdb/testsuite/lib/data-structures.exp
@@ -0,0 +1,160 @@ 
+# Copyright 2017 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file implements some simple data structures in Tcl.
+
+# A namespace/commands to support a stack.
+#
+# To create a stack, call ::Stack::new, recording the returned object ID
+# for future calls to manipulate the stack object.
+#
+# Example:
+#
+# set sid1 [::Stack::new]
+# set sid2 [::Stack::new]
+# stack push $sid1 a
+# stack push $sid1 b
+# stack empty $sid1;  # returns false
+# stack empty $sid2;  # returns true
+# stack pop $sid1;    # returns "b"
+# stack pop $sid2;    # returns ""
+# stack delete $sid1
+# stack delete $sid2
+
+namespace eval ::Stack {
+    # A counter used to create object IDs
+    variable num_ 0
+
+    # An array holding all object lists, indexed by object ID.
+    variable data_
+
+    # Create a new stack object, returning its object ID.
+    proc new {} {
+	variable num_
+	variable data_
+
+	set oid [incr num_]
+	set data_($oid) [list]
+	return $oid
+    }
+
+    # Delete the given stack ID.
+    proc delete {oid} {
+	variable data_
+
+	error_if $oid
+	unset data_($oid)
+    }
+
+    # Returns whether the given stack is empty.
+    proc empty {oid} {
+	variable data_
+
+	error_if $oid
+	return [expr {[llength $data_($oid)] == 0}]
+    }
+
+    # Push ELEM onto the stack given by OID.
+    proc push {oid elem} {
+	variable data_
+
+	error_if $oid
+	lappend data_($oid) $elem
+    }
+
+    # Return and pop the top element on OID.
+    proc pop {oid} {
+	variable data_
+
+	error_if $oid
+	set elem [lindex $data_($oid) end]
+	set data_($oid) [lreplace $data_($oid) end end]
+	return $elem
+    }
+
+    # Returns the depth of a given ID.
+    proc length {oid} {
+	variable data_
+
+	error_if $oid
+	return [llength $data_($oid)]
+    }
+
+    # Error handler for invalid object IDs.
+    proc error_if {oid} {
+	variable data_
+
+	if {![info exists data_($oid)]} {
+	    ::error "object ID $oid does not exist"
+	}
+    }
+
+    # Export procs to be used.
+    namespace export empty push pop new delete length error_if
+
+    # Create an ensemble command to use instead of requiring users
+    # to type namespace proc names.
+    namespace ensemble create -command ::stack
+}
+
+# A namespace/commands to support a queue.
+#
+# To create a queue, call ::Queue::new, recording the returned stack ID
+# for future calls to manipulate the queue object.
+#
+# Example:
+#
+# set qid1 [::Queue::new]
+# set qid2 [::Queue::new]
+# queue push $qid1 a
+# queue push $qid1 b
+# queue empty $qid1;  # returns false
+# queue empty $qid2;  # returns true
+# queue pop $qid1;    # returns "a"
+# queue pop $qid2;    # returns ""
+# queue delete $qid1
+# queue delete $qid2
+
+namespace eval ::Queue {
+
+    # Remove and return the oldest element in the queue given by OID.
+    proc pop {oid} {
+	variable ::Stack::data_
+
+	error_if $oid
+	set elem [lindex $data_($oid) 0]
+	set data_($oid) [lreplace $data_($oid) 0 0]
+	return $elem
+    }
+
+    # "Unpush" ELEM back to the head of the queue given by QID.
+    proc unpush {oid elem} {
+	variable ::Stack::data_
+
+	error_if $oid
+	set data_($oid) [linsert $data_($oid) 0 $elem]
+    }
+
+    # Re-use some common routines from the Stack implementation.
+    namespace import ::Stack::create ::Stack::new ::Stack::empty \
+	::Stack::delete ::Stack::push ::Stack::length ::Stack::error_if
+
+    # Export procs to be used.
+    namespace export new empty push pop new delete length error_if unpush
+
+    # Create an ensemble command to use instead of requiring users
+    # to type namespace proc names.
+    namespace ensemble create -command ::queue
+}
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 441cbb8742..ef236fef02 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -48,6 +48,7 @@  const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -60,6 +61,7 @@  static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -85,7 +87,7 @@  struct typedef_hash_table
 static hashval_t
 hash_typedef_field (const void *p)
 {
-  const struct typedef_field *tf = (const struct typedef_field *) p;
+  const struct decl_field *tf = (const struct decl_field *) p;
   struct type *t = check_typedef (tf->type);
 
   return htab_hash_string (TYPE_SAFE_NAME (t));
@@ -96,8 +98,8 @@  hash_typedef_field (const void *p)
 static int
 eq_typedef_field (const void *a, const void *b)
 {
-  const struct typedef_field *tfa = (const struct typedef_field *) a;
-  const struct typedef_field *tfb = (const struct typedef_field *) b;
+  const struct decl_field *tfa = (const struct decl_field *) a;
+  const struct decl_field *tfb = (const struct decl_field *) b;
 
   return types_equal (tfa->type, tfb->type);
 }
@@ -115,7 +117,7 @@  recursively_update_typedef_hash (struct typedef_hash_table *table,
 
   for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (t); ++i)
     {
-      struct typedef_field *tdef = &TYPE_TYPEDEF_FIELD (t, i);
+      struct decl_field *tdef = &TYPE_TYPEDEF_FIELD (t, i);
       void **slot;
 
       slot = htab_find_slot (table->table, tdef, INSERT);
@@ -143,14 +145,14 @@  add_template_parameters (struct typedef_hash_table *table, struct type *t)
 
   for (i = 0; i < TYPE_N_TEMPLATE_ARGUMENTS (t); ++i)
     {
-      struct typedef_field *tf;
+      struct decl_field *tf;
       void **slot;
 
       /* We only want type-valued template parameters in the hash.  */
       if (SYMBOL_CLASS (TYPE_TEMPLATE_ARGUMENT (t, i)) != LOC_TYPEDEF)
 	continue;
 
-      tf = XOBNEW (&table->storage, struct typedef_field);
+      tf = XOBNEW (&table->storage, struct decl_field);
       tf->name = SYMBOL_LINKAGE_NAME (TYPE_TEMPLATE_ARGUMENT (t, i));
       tf->type = SYMBOL_TYPE (TYPE_TEMPLATE_ARGUMENT (t, i));
 
@@ -268,7 +270,7 @@  find_global_typedef (const struct type_print_options *flags,
 {
   char *applied;
   void **slot;
-  struct typedef_field tf, *new_tf;
+  struct decl_field tf, *new_tf;
 
   if (flags->global_typedefs == NULL)
     return NULL;
@@ -279,13 +281,13 @@  find_global_typedef (const struct type_print_options *flags,
   slot = htab_find_slot (flags->global_typedefs->table, &tf, INSERT);
   if (*slot != NULL)
     {
-      new_tf = (struct typedef_field *) *slot;
+      new_tf = (struct decl_field *) *slot;
       return new_tf->name;
     }
 
   /* Put an entry into the hash table now, in case
      apply_ext_lang_type_printers recurses.  */
-  new_tf = XOBNEW (&flags->global_typedefs->storage, struct typedef_field);
+  new_tf = XOBNEW (&flags->global_typedefs->storage, struct decl_field);
   new_tf->name = NULL;
   new_tf->type = t;
 
@@ -314,12 +316,12 @@  find_typedef_in_hash (const struct type_print_options *flags, struct type *t)
 {
   if (flags->local_typedefs != NULL)
     {
-      struct typedef_field tf, *found;
+      struct decl_field tf, *found;
 
       tf.name = NULL;
       tf.type = t;
-      found = (struct typedef_field *) htab_find (flags->local_typedefs->table,
-						  &tf);
+      found = (struct decl_field *) htab_find (flags->local_typedefs->table,
+					       &tf);
 
       if (found != NULL)
 	return found->name;
@@ -707,6 +709,41 @@  show_print_type_typedefs (struct ui_file *file, int from_tty,
 		    value);
 }
 
+/* Limit on the number of nested type definitions to print or -1 to print
+   all nested type definitions in a class.  By default, we do not print
+   nested definitions.  */
+
+static int print_nested_type_limit = 0;
+
+/* Set how many nested type definitions should be printed by the type
+   printer.  */
+
+static void
+set_print_type_nested_types (char *args, int from_tty,
+			     struct cmd_list_element *c)
+{
+  default_ptype_flags.print_nested_type_limit = print_nested_type_limit;
+}
+
+/* Show how many nested type definitions the type printer will print.  */
+
+static void
+show_print_type_nested_types  (struct ui_file *file, int from_tty,
+			       struct cmd_list_element *c, const char *value)
+{
+  if (*value == '0')
+    {
+      fprintf_filtered (file,
+			_("Will not print nested types defined in a class\n"));
+    }
+  else
+    {
+      fprintf_filtered (file,
+			_("Will print %s nested types defined in a class\n"),
+			value);
+    }
+}
+
 void
 _initialize_typeprint (void)
 {
@@ -755,6 +792,16 @@  Show printing of typedefs defined in classes."), NULL,
 			   set_print_type_typedefs,
 			   show_print_type_typedefs,
 			   &setprinttypelist, &showprinttypelist);
+
+  add_setshow_zuinteger_unlimited_cmd ("nested-type-limit", no_class,
+				       &print_nested_type_limit,
+				       _("\
+Set the number of recursive nested type definitions to print \
+(-1 to show all)."), _("\
+Show the number of recursive nested type definitions to print."), NULL,
+				       set_print_type_nested_types,
+				       show_print_type_nested_types,
+				       &setprinttypelist, &showprinttypelist);
 }
 
 /* Print <not allocated> status to stream STREAM.  */
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a458aa4e2f..a2058b0120 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,6 +35,9 @@  struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* The number of nested type definitions to print.  -1 == all.  */
+  int print_nested_type_limit;
+
   /* If not NULL, a local typedef hash table used when printing a
      type.  */
   struct typedef_hash_table *local_typedefs;