diff mbox

Record nested types

Message ID 20171031190308.18094-1-keiths@redhat.com
State New
Headers show

Commit Message

Keith Seitz Oct. 31, 2017, 7:03 p.m. UTC
On 10/27/2017 05:00 AM, Pedro Alves wrote:
>> 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

Fixed.

>> @@ -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.

Bah. Those are from another patch. I thought I'd removed all those bits, but I apparently did not. Expect to see those at some future date.

>>    /* 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?

Sure. I've added type_can_define_types (const struct die_info *) to do this.

>> @@ -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)?

I would, of course, say, "Probably not," if I could verify it. INIT_CPLUS_SPECIFIC *is* called unconditionally from read_structure_type, but that is a little more innocent than ALLOCATE_CPLUS_TYPE. I see a lot of calls fenceposted by language != language_ada, though, and I see no harm in adding that here. [And I have done so.]

I don't know Ada very well, but I think that the typedef case is not fenceposted only because the language doesn't support the concept (today at least). Of course, AFAICT, Ada doesn't support nested type definitions, either.

>> 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?

The same could be said of TYPE_TYPEDEF_*, but at least as far as Java would go -- the only other language we support with which I have any real familiarity -- that could certainly be supported.

>> +++ 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?

Preprocessor? I used the logging feature of the test script to generate the struct. [When the log is enabled, it sends the entire struct (up to the nested type limit) to gdb.log.]

If you would like this done some other way, just let me know.

>> +
>> +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"

Bah. Fixed. How did that not generate an error? [Oh, I see. Defensive programming -- "info exists" is used later.]

>> --- 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... :-) )

Sure. Although Tcl does not support anonymous namespaces, I've wrapped the next_line procedure inside a new cp_support_internal namespace.

>> +# 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:

I've rewritten this whole thing a little more clearly based on your suggestions.

> 
>   # 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 ""
> 
> ?

Yeah, with the submitted patch as-is, empty string elements cannot be pushed onto stacks/queues. [Well, they can, but without using the empty proc, one cannot tell the difference. I've therefore changed both stack and queue to error when an empty stack/queue is popped.

>> @@ -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.

I've followed suit with how other commands handle the "unlimited" keyword, updating documentation/help strings as necessary, e.g.,

(gdb) help set print type nested-type-limit 
Set the number of recursive nested type definitions to print ("unlimited" or -1 to show all).

Revised patch below (including commit log).

Keith

--- Patch v2

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
modification 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.
	(type_can_define_types): New function.
	Update assertion to use type_can_define_types.
	Permit NULL for a field's name.
	(process_structure_scope): Handle child DIEs of types that can
	define 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                   |  13 +
 gdb/dwarf2read.c                      |  98 +++--
 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      | 364 +++++++++++++++---
 gdb/testsuite/lib/data-structures.exp | 164 +++++++++
 gdb/typeprint.c                       |  71 +++-
 gdb/typeprint.h                       |   3 +
 11 files changed, 1616 insertions(+), 86 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

Pedro Alves Nov. 28, 2017, 2:39 p.m. UTC | #1
Hi Keith,

Sorry for the delay...

This version is OK with super minor nits below.

On 10/31/2017 07:03 PM, Keith Seitz wrote:
> On 10/27/2017 05:00 AM, Pedro Alves wrote:

> 
>>> @@ -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)?
> 
> I would, of course, say, "Probably not," if I could verify it. INIT_CPLUS_SPECIFIC *is* called unconditionally from read_structure_type, but that is a little more innocent than ALLOCATE_CPLUS_TYPE. I see a lot of calls fenceposted by language != language_ada, though, and I see no harm in adding that here. [And I have done so.]
> 
> I don't know Ada very well, but I think that the typedef case is not fenceposted only because the language doesn't support the concept (today at least). Of course, AFAICT, Ada doesn't support nested type definitions, either.

OK.  The think that made me question it is that main_type.type_specific is
a union, and thus unconditionally setting it to cplus_specific looks
wrong if other languages expect to put/find a different active union
member there.  E.g., for set_die_type has:

static struct type *
set_die_type (struct die_info *die, struct type *type, struct dwarf2_cu *cu)
{
  struct dwarf2_per_cu_offset_and_type **slot, ofs;
  struct objfile *objfile = cu->objfile;
  struct attribute *attr;
  struct dynamic_prop prop;

  /* For Ada types, make sure that the gnat-specific data is always
     initialized (if not already set).  There are a few types where
     we should not be doing so, because the type-specific area is
     already used to hold some other piece of info (eg: TYPE_CODE_FLT
     where the type-specific area is used to store the floatformat).
     But this is not a problem, because the gnat-specific information
     is actually not needed for these types.  */
  if (need_gnat_info (cu)
      && TYPE_CODE (type) != TYPE_CODE_FUNC
      && TYPE_CODE (type) != TYPE_CODE_FLT
      && TYPE_CODE (type) != TYPE_CODE_METHODPTR
      && TYPE_CODE (type) != TYPE_CODE_MEMBERPTR
      && TYPE_CODE (type) != TYPE_CODE_METHOD
      && !HAVE_GNAT_AUX_INFO (type))
    INIT_GNAT_SPECIFIC (type);


Looking at:

enum type_specific_kind
{
  TYPE_SPECIFIC_NONE,
  TYPE_SPECIFIC_CPLUS_STUFF,
  TYPE_SPECIFIC_GNAT_STUFF,
  TYPE_SPECIFIC_FLOATFORMAT,
  /* Note: This is used by TYPE_CODE_FUNC and TYPE_CODE_METHOD.  */
  TYPE_SPECIFIC_FUNC,
  TYPE_SPECIFIC_SELF_TYPE
};


I guess Ada uses TYPE_SPECIFIC_GNAT_STUFF, and every other
language uses TYPE_SPECIFIC_CPLUS_STUFF.

> 
>>> 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?
> 
> The same could be said of TYPE_TYPEDEF_*, but at least as far as Java would go -- the only other language we support with which I have any real familiarity -- that could certainly be supported.

Alright.  Given that and the above, this sounds OK.

>>> +++ b/gdb/testsuite/gdb.cp/nested-types.cc
>>> @@ -0,0 +1,668 @@
>>> +/* This testcase is part of GDB, the GNU debugger.

...

>>> +/* Tests for nested type definitions.  */
>>> +
>>> +struct S10
>>> +{
>>
>> W00t, lots of repetition.  OOC, did you consider using the preprocessor
>> to build the types?
> 
> Preprocessor? I used the logging feature of the test script to generate the struct. [When the log is enabled, it sends the entire struct (up to the nested type limit) to gdb.log.]

Ah, that's even better.  Could you add a comment at the top of
the .cc file, so that the next person that needs to change/extend
the file doesn't try to do it manually?

> 
> If you would like this done some other way, just let me know.

Nope, that's fine; I had assumed you had typed the whole thing!  :-P

> I've rewritten this whole thing a little more clearly based on your suggestions.
> 
>>
>>   # 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 ""
>>
>> ?
> 
> Yeah, with the submitted patch as-is, empty string elements cannot be pushed onto stacks/queues. [Well, they can, but without using the empty proc, one cannot tell the difference. I've therefore changed both stack and queue to error when an empty stack/queue is popped.

Thanks.

One thing that crossed my mind is that it would be really nice
to have unit tests for these tcl data structures we come up with.
E.g., add a new gdb.whatever/testsuite-selftests.exp or something
like that, that runs just as any other GDB testcase, but that instead
of spawning GDB, it exercises the new APIs, and calls pass/fail
accordingly.  That'd serve both to unsure test coverage of the whole API,
and also as kind of API documentation, as folks that want to know how
to use the API would find easy/isolated examples there.

Anyway, that's a IWBN; it can be done separately and I don't want
to delay this patch further.

> 
>>> @@ -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.
> 
> I've followed suit with how other commands handle the "unlimited" keyword, updating documentation/help strings as necessary, e.g.,
> 
> (gdb) help set print type nested-type-limit 
> Set the number of recursive nested type definitions to print ("unlimited" or -1 to show all).
> 
> Revised patch below (including commit log).

Thanks.

>  
> -/* Add a typedef defined in the scope of the FIP's class.  */
> +/* Can the type given by DIE define another type?  */
> +
> +static bool
> +type_can_define_types (const struct die_info *die)
> +{
> +  switch (die->tag)
> +    {
> +    case DW_TAG_typedef:
> +    case DW_TAG_class_type:
> +    case  DW_TAG_structure_type:

Spurious double space above.

> +    case DW_TAG_union_type:
> +    case DW_TAG_enumeration_type:
> +      return true;
> +
> +    default:
> +      return false;
> +    }
> +}

...

> +# A namespace/commands to support a queue.
> +#
> +# To create a queue, call ::Queue::new, recording the returned queue ID
> +# for future calls to manipulate the queue object.
> +#
> +# Example:
> +#
> +# set qid [::Queue::new]
> +# queue push $qid a
> +# queue push $qid b
> +# queue empty $qid;  # returns false
> +# queue pop $qid;    # returns "b"
> +# queue pop $qid;    # returns "a"

I assume the first pop returns "a" instead.  :-P

> +# queue pop $qid;    # errors with "queue is empty"
> +# queue delete $qid
> +

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index fbf5591781..47d9c92994 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 limit of nesting level for 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..0a9b888f7e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -16942,6 +16942,19 @@  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}
+@itemx set print type nested-type-limit unlimited
+Set the limit of displayed nested types that the type printer will
+show.  A @var{limit} of @code{unlimited} or @code{-1} will show all
+nested definitions.  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..8c53e06e2d 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
@@ -13088,28 +13093,44 @@  dwarf2_add_field (struct field_info *fip, struct die_info *die,
     }
 }
 
-/* Add a typedef defined in the scope of the FIP's class.  */
+/* Can the type given by DIE define another type?  */
+
+static bool
+type_can_define_types (const struct die_info *die)
+{
+  switch (die->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;
+    }
+}
+
+/* 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 (type_can_define_types (die));
 
   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 +13156,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 +13791,8 @@  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 (type_can_define_types (child_die))
+	    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 +13901,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 +13917,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 && cu->language != language_ada)
+	{
+	  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..2b540e9907
--- /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 nodes($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..261b77ee6a 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,41 @@  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 namespace to wrap internal procedures.
+
+namespace eval ::cp_support_internal {
+
+    # 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 +130,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 +159,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 +240,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 +261,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 +275,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 +286,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 +298,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 [cp_support_internal::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 +361,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 +370,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 +386,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 +401,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 +411,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
+    }
+
+    while {![queue empty $line_queue]} {
 
-	# Chomp the line.
+	# Get the next line.
 
-	set actual_line [string trim $actual_line]
+	set actual_line [cp_support_internal::next_line $line_queue]
 	if { "$actual_line" == "" } then { continue }
 
 	# Access specifiers.
@@ -319,7 +439,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 +456,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 +470,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 +490,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 +503,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 +516,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 +533,130 @@  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 \
+			    [cp_support_internal::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 [cp_support_internal::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 +676,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 +699,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 +713,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 +727,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 +752,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..ba389402fa
--- /dev/null
+++ b/gdb/testsuite/lib/data-structures.exp
@@ -0,0 +1,164 @@ 
+# 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 sid [::Stack::new]
+# stack push $sid a
+# stack push $sid b
+# stack empty $sid;  # returns false
+# stack pop $sid;    # returns "b"
+# stack pop $sid;    # returns "a"
+# stack pop $sid;    # errors with "stack is empty"
+# stack delete $sid1
+
+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.  It is an error to pop
+    # an empty stack.
+    proc pop {oid} {
+	variable data_
+
+	error_if $oid
+	if {[llength $data_($oid)] == 0} {
+	    ::error "stack is empty"
+	}
+	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 queue ID
+# for future calls to manipulate the queue object.
+#
+# Example:
+#
+# set qid [::Queue::new]
+# queue push $qid a
+# queue push $qid b
+# queue empty $qid;  # returns false
+# queue pop $qid;    # returns "b"
+# queue pop $qid;    # returns "a"
+# queue pop $qid;    # errors with "queue is empty"
+# queue delete $qid
+
+namespace eval ::Queue {
+
+    # Remove and return the oldest element in the queue given by OID.
+    # It is an error to pop an empty queue.
+    proc pop {oid} {
+	variable ::Stack::data_
+
+	error_if $oid
+	if {[llength $data_($oid)] == 0} {
+	    error "queue is empty"
+	}
+	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..4e8159cb2a 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 \
+(\"unlimited\" or -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;