This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

PATCH to improve handling of enumeration tags in templates



There were numerous oddities in the way enumerations were handled in
templates.  These resulted in bogus warnings on:

  template <int I>
  int f() { enum E { a = I };
            E e = a }

Where we complained at instantiation time.

  bug1.C:3: warning: conversion from `int' to `enum f<7>()::E'
 
Although I did not test this hypothesis, I suspect this bug could have
caused incorrect overload resolution, etc.

More seriously, the error messages on:

  template <class T>
  struct vector {};

  template<class T>
  void fn(T)
  {
    enum tern { H, L, X, U };

    vector<tern> ternvec; // ERROR - composed from a local type
  }

were:

  bug1.C:9: type/value mismatch at argument 1 in template parameter
            list for `template <class T> vector<T>'
  bug1.C:9: sorry, not implemented: `type_decl' not supported by dump_expr
  bug1.C:9:   expected a type, got `{error}'

rather than the more helpful:

  bug1.C:9: type `fn<int>(int)::tern' composed from a local type is
            not a valid template-argument   

Most seriously:

  template <int I>
  int f()
  {
    enum E { a = I };
  
    struct S {
      int g() {
        E e;
        e = a;
        return (int) e;
      };
    };
  
    S s;
  
    return s.g();
  }
  
  
  int main()
  {
    if (f<7>() != 7)
      return 1;
    if (f<-3>() != -3)
      return 1;
    return 0;
  }

result in an ICE, as did similar code with a class type in place of
`E'.

The attached patch fixes all of these problems.

-- 
Mark Mitchell 			mark@markmitchell.com
Mark Mitchell Consulting	http://www.markmitchell.com

1998-08-06  Mark Mitchell  <mark@markmitchell.com>

	* cp-tree.h (ENUM_TEMPLATE_INFO): New macro.
	(TYPE_TEMPLATE_INFO): Likewise.
	(SET_TYPE_TEMPLATE_INFO): Likewise.
	(ENUM_TI_TEMPLATE): Likewise.
	(ENUM_TI_ARGS): Likewise.
	(lookup_nested_type_by_name): Remove.
	* decl.c (maybe_process_template_type_declaration): Handle enums.
	(start_enum): Don't check for primary-template enum declarations
	here. 
	(finish_enum): Clean up, document.  Make sure template enum
	constants get the correct type.
	(build_enumerator): Copy initializers for template enumerations,
	too. 
	(grok_enum_decls): Document.
	* lex.c (do_identifier): Document use of LOOKUP_EXPR a bit
	better.  Build LOOKUP_EXPRs for local variables, even if they are
	TREE_PERMANENT.
	* pt.c (tsubst_enum): Remove field_chain parameter.
	(template_class_depth): Include the depth of surrounding function
	contexts.
	(push_template_decl): Check for primary-template enum declarations
	here.  Deal with enumeration templates.
	(lookup_template_class): Likewise.
	(for_each_template_parm): Likewise.
	(instantiate_class_template): Don't call tsubst_enum directly,
	call tsubst instead, to instantiate enums.  Deal with all
	field_chain issues here, not in tsubst_enum.
	(lookup_nested_type_by_name): Remove.
	(tsubst_aggr_type): Revise handling of enumeration types.
	(tsubst): Likewise.
	(tsubst_copy): Likewise.
	(tsubst_expr): Call tsubst, not tsubst_enum for TAG_DEFNs.
	
Index: testsuite/g++.old-deja/g++.pt/crash19.C
===================================================================
RCS file: crash19.C
diff -N crash19.C
*** /dev/null	Mon Dec 31 20:00:00 1979
--- crash19.C	Thu Aug  6 16:22:50 1998
***************
*** 0 ****
--- 1,19 ----
+ // Build don't link:
+ 
+ template <int I>
+ void f()
+ {
+   class C { public: int c; };
+ 
+   struct S {
+     void g() {
+       C e;
+       e.c = 3;
+     };
+   };
+ 
+   S s;
+   s.g();
+ }
+ 
+ template void f<7>();
Index: testsuite/g++.old-deja/g++.pt/enum6.C
===================================================================
RCS file: enum6.C
diff -N enum6.C
*** /dev/null	Mon Dec 31 20:00:00 1979
--- enum6.C	Thu Aug  6 16:22:50 1998
***************
*** 0 ****
--- 1,14 ----
+ // Build don't link:
+ 
+ template <class T>
+ struct vector {};
+ 
+ template<class T>
+ void fn(T)
+ {
+   enum tern { H, L, X, U };
+ 
+   vector<tern> ternvec; // ERROR - composed from a local type
+ }
+ 
+ template void fn(int);
Index: testsuite/g++.old-deja/g++.pt/enum7.C
===================================================================
RCS file: enum7.C
diff -N enum7.C
*** /dev/null	Mon Dec 31 20:00:00 1979
--- enum7.C	Thu Aug  6 16:22:50 1998
***************
*** 0 ****
--- 1,27 ----
+ template <int I>
+ int f()
+ {
+   enum E { a = I };
+ 
+   struct S {
+     int g() {
+       E e;
+       e = a;
+       return (int) e;
+     };
+   };
+ 
+   S s;
+ 
+   return s.g();
+ }
+ 
+ 
+ int main()
+ {
+   if (f<7>() != 7)
+     return 1;
+   if (f<-3>() != -3)
+     return 1;
+   return 0;
+ }
Index: cp/cp-tree.h
===================================================================
RCS file: /egcs/carton/cvsfiles/egcs/gcc/cp/cp-tree.h,v
retrieving revision 1.107
diff -c -p -r1.107 cp-tree.h
*** cp-tree.h	1998/08/03 22:11:19	1.107
--- cp-tree.h	1998/08/06 23:23:07
*************** struct lang_decl
*** 1245,1259 ****
--- 1245,1282 ----
  
  /* For a VAR_DECL or FUNCTION_DECL: template-specific information.  */
  #define DECL_TEMPLATE_INFO(NODE) (DECL_LANG_SPECIFIC(NODE)->decl_flags.template_info)
+ 
+ /* Template information for a RECORD_TYPE or UNION_TYPE.  */
  #define CLASSTYPE_TEMPLATE_INFO(NODE) (TYPE_LANG_SPECIFIC(NODE)->template_info)
+ 
+ /* Template information for an ENUMERAL_TYPE.  Although an enumeration may
+    not be a primary template, it may be declared within the scope of a
+    primary template and the enumeration constants may depend on
+    non-type template parameters.  */
+ #define ENUM_TEMPLATE_INFO(NODE) (TYPE_BINFO (NODE))
+ 
+ /* Template information for an ENUMERAL_, RECORD_, or UNION_TYPE.  */
+ #define TYPE_TEMPLATE_INFO(NODE)					\
+   (TREE_CODE (NODE) == ENUMERAL_TYPE 					\
+    ? ENUM_TEMPLATE_INFO (NODE) : CLASSTYPE_TEMPLATE_INFO (NODE))
+ 
+ /* Set the template information for an ENUMERAL_, RECORD_, or
+    UNION_TYPE to VAL.  */
+ #define SET_TYPE_TEMPLATE_INFO(NODE, VAL) 	\
+   (TREE_CODE (NODE) == ENUMERAL_TYPE 		\
+    ? (ENUM_TEMPLATE_INFO (NODE) = VAL) 		\
+    : (CLASSTYPE_TEMPLATE_INFO (NODE) = VAL))
+ 
  #define TI_TEMPLATE(NODE) (TREE_PURPOSE (NODE))
  #define TI_ARGS(NODE) (TREE_VALUE (NODE))
  #define TI_SPEC_INFO(NODE) (TREE_CHAIN (NODE))
  #define TI_PENDING_TEMPLATE_FLAG(NODE) TREE_LANG_FLAG_1 (NODE)
+ 
  /* TI_PENDING_SPECIALIZATION_FLAG on a template-info node indicates
     that the template is a specialization of a member template, but
     that we don't yet know which one.  */
  #define TI_PENDING_SPECIALIZATION_FLAG(NODE) TREE_LANG_FLAG_1 (NODE)
+ 
  /* The TEMPLATE_DECL instantiated or specialized by NODE.  This
     TEMPLATE_DECL will be the immediate parent, not the most general
     template.  For example, in:
*************** struct lang_decl
*** 1273,1278 ****
--- 1296,1302 ----
     the DECL_TI_TEMPLATE will be a LOOKUP_EXPR for `f' and the
     DECL_TI_ARGS will be {int}.  */ 
  #define DECL_TI_TEMPLATE(NODE)      TI_TEMPLATE (DECL_TEMPLATE_INFO (NODE))
+ 
  /* The template arguments used to obtain this decl from the most
     general form of DECL_TI_TEMPLATE.  For the example given for
     DECL_TI_TEMPLATE, the DECL_TI_ARGS will be {int, double}.  These
*************** struct lang_decl
*** 1282,1287 ****
--- 1306,1324 ----
  #define CLASSTYPE_TI_TEMPLATE(NODE) TI_TEMPLATE (CLASSTYPE_TEMPLATE_INFO (NODE))
  #define CLASSTYPE_TI_ARGS(NODE)     TI_ARGS (CLASSTYPE_TEMPLATE_INFO (NODE))
  #define CLASSTYPE_TI_SPEC_INFO(NODE) TI_SPEC_INFO (CLASSTYPE_TEMPLATE_INFO (NODE))
+ #define ENUM_TI_TEMPLATE(NODE) 			\
+   TI_TEMPLATE (ENUM_TEMPLATE_INFO (NODE))
+ #define ENUM_TI_ARGS(NODE)			\
+   TI_ARGS (ENUM_TEMPLATE_INFO (NODE))
+ 
+ /* Like DECL_TI_TEMPLATE, but for an ENUMERAL_, RECORD_, or UNION_TYPE.  */
+ #define TYPE_TI_TEMPLATE(NODE)			\
+   (TI_TEMPLATE (TYPE_TEMPLATE_INFO (NODE)))
+ 
+ /* Like DECL_TI_ARGS, , but for an ENUMERAL_, RECORD_, or UNION_TYPE.  */
+ #define TYPE_TI_ARGS(NODE)			\
+   (TI_ARGS (TYPE_TEMPLATE_INFO (NODE)))
+ 
  #define INNERMOST_TEMPLATE_PARMS(NODE)  TREE_VALUE(NODE)
  
  #define TEMPLATE_PARMS_FOR_INLINE(NODE) TREE_LANG_FLAG_1 (NODE)
*************** extern void mark_class_instantiated		PRO
*** 2783,2789 ****
  extern void do_decl_instantiation		PROTO((tree, tree, tree));
  extern void do_type_instantiation		PROTO((tree, tree));
  extern tree instantiate_decl			PROTO((tree));
- extern tree lookup_nested_type_by_name		PROTO((tree, tree));
  extern tree do_poplevel				PROTO((void));
  extern tree get_bindings			PROTO((tree, tree, tree));
  /* CONT ... */
--- 2820,2825 ----
Index: cp/decl.c
===================================================================
RCS file: /egcs/carton/cvsfiles/egcs/gcc/cp/decl.c,v
retrieving revision 1.175
diff -c -p -r1.175 decl.c
*** decl.c	1998/08/04 11:46:35	1.175
--- decl.c	1998/08/06 23:23:48
*************** maybe_process_template_type_declaration 
*** 2253,2278 ****
      {
        maybe_check_template_type (type);
  
!       if (IS_AGGR_TYPE (type)
! 	  && (/* If !GLOBALIZE then we are looking at a definition.
! 		 It may not be a primary template.  (For example, in:
  		  
! 		 template <class T>
! 		 struct S1 { class S2 {}; }
  		  
! 		 we have to push_template_decl for S2.)  */
! 	      (processing_template_decl && !globalize)
! 	      /* If we are declaring a friend template class, we will
! 		 have GLOBALIZE set, since something like:
! 
! 		 template <class T>
! 		 struct S1 {
! 		   template <class U>
! 		   friend class S2; 
! 		 };
  
! 		 declares S2 to be at global scope.  */
! 	      || PROCESSING_REAL_TEMPLATE_DECL_P ()))
  	{
  	  /* This may change after the call to
  	     push_template_decl_real, but we want the original value.  */
--- 2253,2281 ----
      {
        maybe_check_template_type (type);
  
!       my_friendly_assert (IS_AGGR_TYPE (type) 
! 			  || TREE_CODE (type) == ENUMERAL_TYPE, 0);
! 			  
! 			  
!       if (/* If !GLOBALIZE then we are looking at a definition.
! 	     It may not be a primary template.  (For example, in:
  		  
! 	       template <class T>
! 	       struct S1 { class S2 {}; }
  		  
! 	     we have to push_template_decl for S2.)  */
! 	  (processing_template_decl && !globalize)
! 	  /* If we are declaring a friend template class, we will
! 	     have GLOBALIZE set, since something like:
! 
! 	       template <class T>
! 	       struct S1 {
! 		 template <class U>
! 		 friend class S2; 
! 	       };
  
! 	     declares S2 to be at global scope.  */
! 	  || PROCESSING_REAL_TEMPLATE_DECL_P ())
  	{
  	  /* This may change after the call to
  	     push_template_decl_real, but we want the original value.  */
*************** maybe_process_template_type_declaration 
*** 2286,2292 ****
  	     declaration of the member class into the class scope.  In the
  	     friend case, push_template_decl will already have put the
  	     friend into global scope, if appropriate.  */
! 	  if (!globalize && b->pseudo_global
  	      && b->level_chain->parm_flag == 2)
  	    {
  	      pushdecl_with_scope (CLASSTYPE_TI_TEMPLATE (type),
--- 2289,2296 ----
  	     declaration of the member class into the class scope.  In the
  	     friend case, push_template_decl will already have put the
  	     friend into global scope, if appropriate.  */
! 	  if (TREE_CODE (type) != ENUMERAL_TYPE
! 	      && !globalize && b->pseudo_global
  	      && b->level_chain->parm_flag == 2)
  	    {
  	      pushdecl_with_scope (CLASSTYPE_TI_TEMPLATE (type),
*************** start_enum (name)
*** 11753,11761 ****
        pushtag (name, enumtype, 0);
      }
  
-   if (b->pseudo_global)
-     cp_error ("template declaration of `%#T'", enumtype);
- 
    if (current_class_type)
      TREE_ADDRESSABLE (b->tags) = 1;
  
--- 11767,11772 ----
*************** finish_enum (enumtype, values)
*** 11783,11812 ****
  
    if (values)
      {
!       register tree pair;
!       register tree value = DECL_INITIAL (TREE_VALUE (values));
  
!       if (! processing_template_decl)
! 	{
! 	  /* Speed up the main loop by performing some precalculations */
! 	  TREE_TYPE (TREE_VALUE (values)) = enumtype;
! 	  TREE_TYPE (value) = enumtype;
! 	  minnode = maxnode = value;
! 	}
!       TREE_VALUE (values) = value;
!       
!       for (pair = TREE_CHAIN (values); pair; pair = TREE_CHAIN (pair))
  	{
! 	  value = DECL_INITIAL (TREE_VALUE (pair));
! 	  if (! processing_template_decl)
! 	    {
! 	      TREE_TYPE (TREE_VALUE (pair)) = enumtype;
  	      TREE_TYPE (value) = enumtype;
! 	      if (tree_int_cst_lt (maxnode, value))
  		maxnode = value;
  	      else if (tree_int_cst_lt (value, minnode))
  		minnode = value;
  	    }
  	  TREE_VALUE (pair) = value;
  	}
      }
--- 11794,11839 ----
  
    if (values)
      {
!       tree pair;
  
!       for (pair = values; pair; pair = TREE_CHAIN (pair))
  	{
! 	  tree decl;
! 	  tree value;
! 
! 	  /* The TREE_VALUE is a CONST_DECL for this enumeration
! 	     constant.  */
! 	  decl = TREE_VALUE (pair);
! 
! 	  /* The type of the CONST_DECL is the type of the enumeration,
! 	     not an INTEGER_TYPE.  */
! 	  TREE_TYPE (decl) = enumtype;
! 
! 	  /* The DECL_INITIAL will be NULL if we are processing a
! 	     template declaration and this enumeration constant had no
! 	     explicit initializer.  */
! 	  value = DECL_INITIAL (decl);
! 	  if (value)
! 	    {
! 	      /* Set the TREE_TYPE for the VALUE as well.  When
! 		 processing a template, however, we might have a
! 		 TEMPLATE_PARM_INDEX, and we should not change the
! 		 type of such a thing.  */
! 	      if (TREE_CODE (value) == TEMPLATE_PARM_INDEX)
! 		DECL_INITIAL (decl) = value 
! 		  = build1 (NOP_EXPR, enumtype, value);
  	      TREE_TYPE (value) = enumtype;
! 
! 	      if (!minnode)
! 		minnode = maxnode = value;
! 	      else if (tree_int_cst_lt (maxnode, value))
  		maxnode = value;
  	      else if (tree_int_cst_lt (value, minnode))
  		minnode = value;
  	    }
+ 
+ 	  /* In the list we're building up, we want the enumeration
+ 	     values, not the CONST_DECLs.  */
  	  TREE_VALUE (pair) = value;
  	}
      }
*************** build_enumerator (name, value)
*** 11922,11937 ****
       /* Remove no-op casts from the value.  */
       if (value)
         STRIP_TYPE_NOPS (value);
- 
-      /* We have to always copy here; not all INTEGER_CSTs are unshared,
- 	and there's no wedding ring. Look at size_int()...*/
-      value = copy_node (value);
  #if 0
       /* To fix MAX_VAL enum consts. (bkoz)  */
       TREE_TYPE (value) = integer_type_node;
  #endif
     }
  
    /* C++ associates enums with global, function, or class declarations.  */
  
    decl = current_scope ();
--- 11949,11965 ----
       /* Remove no-op casts from the value.  */
       if (value)
         STRIP_TYPE_NOPS (value);
  #if 0
       /* To fix MAX_VAL enum consts. (bkoz)  */
       TREE_TYPE (value) = integer_type_node;
  #endif
     }
  
+  /* We have to always copy here; not all INTEGER_CSTs are unshared,
+     and there's no wedding ring. Look at size_int()...*/
+  if (value != NULL_TREE)
+    value = copy_node (value);
+ 
    /* C++ associates enums with global, function, or class declarations.  */
  
    decl = current_scope ();
*************** build_enumerator (name, value)
*** 11969,11974 ****
--- 11997,12019 ----
    result = saveable_tree_cons (name, decl, NULL_TREE);
    return result;
  }
+ 
+ /* Called after we have finished the declaration of an enumeration
+    type, and, perhaps, some objects whose type involves the
+    enumeration type.  DECL, if non-NULL, is the declaration of the
+    first such object.  
+ 
+    If CURRENT_LOCAL_ENUM is NULL, the DECL is returned. 
+ 
+    If CURRENT_LOCAL_ENUM is non-NULL, it should be the CONST_DECL for
+    the last enumeration constant of an enumeration type that is a
+    member of a class.  The enumeration constants are already chained
+    together through their TREE_CHAIN fields.  This function sets the
+    TREE_CHAIN of the last enumeration constant to DECL.  The
+    CONST_DECL for the last enumeration constant is returned.  
+ 
+    CURRENT_LOCAL_ENUM will always be NULL when this function 
+    returns.  */
  
  tree
  grok_enum_decls (decl)
Index: cp/lex.c
===================================================================
RCS file: /egcs/carton/cvsfiles/egcs/gcc/cp/lex.c,v
retrieving revision 1.60
diff -c -p -r1.60 lex.c
*** lex.c	1998/08/02 21:37:17	1.60
--- lex.c	1998/08/06 23:24:14
*************** do_identifier (token, parsing, args)
*** 2988,2996 ****
--- 2988,3013 ----
    else
      id = hack_identifier (id, token);
  
+   /* We must look up dependent names when the template is
+      instantiated, not while parsing it.  For now, we don't
+      distinguish between dependent and independent names.  So, for
+      example, we look up all overloaded functions at
+      instantiation-time, even though in some cases we should just use
+      the DECL we have here.  We also use LOOKUP_EXPRs to find things
+      like local variables, rather than created TEMPLATE_DECLs for the
+      local variables and then finding matching instantiations.  */
    if (current_template_parms
        && (is_overloaded_fn (id) 
+ 	  /* If it's not going to be around at instantiation time, we
+ 	     look it up then.  This is a hack, and should go when we
+ 	     really get dependent/independent name lookup right.  */
  	  || !TREE_PERMANENT (id)
+ 	  /* Some local VAR_DECLs (such as those for local variables
+ 	     in member functions of local classes) are built on the
+ 	     permanent obstack.  */
+ 	  || (TREE_CODE (id) == VAR_DECL 
+ 	      && CP_DECL_CONTEXT (id)
+ 	      && TREE_CODE (CP_DECL_CONTEXT (id)) == FUNCTION_DECL)
  	  || TREE_CODE (id) == PARM_DECL
  	  || TREE_CODE (id) == USING_DECL))
      id = build_min_nt (LOOKUP_EXPR, token);
Index: cp/pt.c
===================================================================
RCS file: /egcs/carton/cvsfiles/egcs/gcc/cp/pt.c,v
retrieving revision 1.173
diff -c -p -r1.173 pt.c
*** pt.c	1998/08/04 11:46:39	1.173
--- pt.c	1998/08/06 23:25:09
*************** static tree tsubst_expr_values PROTO((tr
*** 85,91 ****
  static int list_eq PROTO((tree, tree));
  static tree get_class_bindings PROTO((tree, tree, tree));
  static tree coerce_template_parms PROTO((tree, tree, tree, int, int));
! static tree tsubst_enum	PROTO((tree, tree, tree *));
  static tree add_to_template_args PROTO((tree, tree));
  static tree add_outermost_template_args PROTO((tree, tree));
  static void maybe_adjust_types_for_deduction PROTO((unification_kind_t, tree*,
--- 85,91 ----
  static int list_eq PROTO((tree, tree));
  static tree get_class_bindings PROTO((tree, tree, tree));
  static tree coerce_template_parms PROTO((tree, tree, tree, int, int));
! static tree tsubst_enum	PROTO((tree, tree));
  static tree add_to_template_args PROTO((tree, tree));
  static tree add_outermost_template_args PROTO((tree, tree));
  static void maybe_adjust_types_for_deduction PROTO((unification_kind_t, tree*,
*************** template_class_depth_real (type, count_s
*** 254,268 ****
    int depth;
  
    for (depth = 0; 
!        type && TREE_CODE (type) != FUNCTION_DECL 
! 	 && TREE_CODE (type) != NAMESPACE_DECL;
!        type = TYPE_CONTEXT (type))
!     if (CLASSTYPE_TEMPLATE_INFO (type)
! 	&& PRIMARY_TEMPLATE_P (CLASSTYPE_TI_TEMPLATE (type))
! 	&& ((count_specializations
! 	     && CLASSTYPE_TEMPLATE_SPECIALIZATION (type))
! 	    || uses_template_parms (CLASSTYPE_TI_ARGS (type))))
!       ++depth;
  
    return depth;
  }
--- 254,282 ----
    int depth;
  
    for (depth = 0; 
!        type && TREE_CODE (type) != NAMESPACE_DECL;
!        type = (TREE_CODE (type) == FUNCTION_DECL) 
! 	 ? DECL_REAL_CONTEXT (type) : TYPE_CONTEXT (type))
!     {
!       if (TREE_CODE (type) != FUNCTION_DECL)
! 	{
! 	  if (CLASSTYPE_TEMPLATE_INFO (type)
! 	      && PRIMARY_TEMPLATE_P (CLASSTYPE_TI_TEMPLATE (type))
! 	      && ((count_specializations
! 		   && CLASSTYPE_TEMPLATE_SPECIALIZATION (type))
! 		  || uses_template_parms (CLASSTYPE_TI_ARGS (type))))
! 	    ++depth;
! 	}
!       else 
! 	{
! 	  if (DECL_TEMPLATE_INFO (type)
! 	      && PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (type))
! 	      && ((count_specializations
! 		   && DECL_TEMPLATE_SPECIALIZATION (type))
! 		  || uses_template_parms (DECL_TI_ARGS (type))))
! 	    ++depth;
! 	}
!     }
  
    return depth;
  }
*************** push_template_decl_real (decl, is_friend
*** 1802,1811 ****
--- 1816,1829 ----
  	cp_error ("template with C linkage");
        if (TREE_CODE (decl) == TYPE_DECL && ANON_AGGRNAME_P (DECL_NAME (decl)))
  	cp_error ("template class without a name");
+       if (TREE_CODE (decl) == TYPE_DECL 
+ 	  && TREE_CODE (TREE_TYPE (decl)) == ENUMERAL_TYPE)
+ 	cp_error ("template declaration of `%#T'", TREE_TYPE (decl));
      }
  
    /* Partial specialization.  */
    if (TREE_CODE (decl) == TYPE_DECL && DECL_ARTIFICIAL (decl)
+       && TREE_CODE (TREE_TYPE (decl)) != ENUMERAL_TYPE
        && CLASSTYPE_TEMPLATE_SPECIALIZATION (TREE_TYPE (decl)))
      {
        tree type = TREE_TYPE (decl);
*************** push_template_decl_real (decl, is_friend
*** 1930,1939 ****
  		  ctx, decl);
        if (TREE_CODE (decl) == TYPE_DECL)
  	{
! 	  if (IS_AGGR_TYPE_CODE (TREE_CODE (TREE_TYPE (decl)))
! 	      && CLASSTYPE_TEMPLATE_INFO (TREE_TYPE (decl))
! 	      && CLASSTYPE_TI_TEMPLATE (TREE_TYPE (decl)))
! 	    tmpl = CLASSTYPE_TI_TEMPLATE (TREE_TYPE (decl));
  	  else
  	    {
  	      cp_error ("`%D' does not declare a template type", decl);
--- 1948,1958 ----
  		  ctx, decl);
        if (TREE_CODE (decl) == TYPE_DECL)
  	{
! 	  if ((IS_AGGR_TYPE_CODE (TREE_CODE (TREE_TYPE (decl)))
! 	       || TREE_CODE (TREE_TYPE (decl)) == ENUMERAL_TYPE)
! 	      && TYPE_TEMPLATE_INFO (TREE_TYPE (decl))
! 	      && TYPE_TI_TEMPLATE (TREE_TYPE (decl)))
! 	    tmpl = TYPE_TI_TEMPLATE (TREE_TYPE (decl));
  	  else
  	    {
  	      cp_error ("`%D' does not declare a template type", decl);
*************** push_template_decl_real (decl, is_friend
*** 2037,2044 ****
  
    if (TREE_CODE (decl) == TYPE_DECL && DECL_ARTIFICIAL (decl))
      {
!       CLASSTYPE_TEMPLATE_INFO (TREE_TYPE (tmpl)) = info;
!       if (!ctx || TREE_CODE (ctx) != FUNCTION_DECL)
  	DECL_NAME (decl) = classtype_mangled_name (TREE_TYPE (decl));
      }
    else if (! DECL_LANG_SPECIFIC (decl))
--- 2056,2064 ----
  
    if (TREE_CODE (decl) == TYPE_DECL && DECL_ARTIFICIAL (decl))
      {
!       SET_TYPE_TEMPLATE_INFO (TREE_TYPE (tmpl), info);
!       if ((!ctx || TREE_CODE (ctx) != FUNCTION_DECL)
! 	  && TREE_CODE (TREE_TYPE (decl)) != ENUMERAL_TYPE)
  	DECL_NAME (decl) = classtype_mangled_name (TREE_TYPE (decl));
      }
    else if (! DECL_LANG_SPECIFIC (decl))
*************** lookup_template_class (d1, arglist, in_d
*** 3124,3132 ****
        template = CLASSTYPE_TI_TEMPLATE (TREE_TYPE (d1));
        d1 = DECL_NAME (template);
      }
!   else if (TREE_CODE_CLASS (TREE_CODE (d1)) == 't' && IS_AGGR_TYPE (d1))
      {
!       template = CLASSTYPE_TI_TEMPLATE (d1);
        d1 = DECL_NAME (template);
      }
    else if (TREE_CODE (d1) == TEMPLATE_DECL
--- 3144,3154 ----
        template = CLASSTYPE_TI_TEMPLATE (TREE_TYPE (d1));
        d1 = DECL_NAME (template);
      }
!   else if (TREE_CODE (d1) == ENUMERAL_TYPE 
! 	   || (TREE_CODE_CLASS (TREE_CODE (d1)) == 't' 
! 	       && IS_AGGR_TYPE (d1)))
      {
!       template = TYPE_TI_TEMPLATE (d1);
        d1 = DECL_NAME (template);
      }
    else if (TREE_CODE (d1) == TEMPLATE_DECL
*************** lookup_template_class (d1, arglist, in_d
*** 3229,3235 ****
  	      return error_mark_node;
  	    }
  
! 	  arglist = add_to_template_args (CLASSTYPE_TI_ARGS (context),
  					  arglist);
  	  arg_depth = TMPL_ARGS_DEPTH (arglist);
  	}
--- 3251,3257 ----
  	      return error_mark_node;
  	    }
  
! 	  arglist = add_to_template_args (TYPE_TI_ARGS (context),
  					  arglist);
  	  arg_depth = TMPL_ARGS_DEPTH (arglist);
  	}
*************** lookup_template_class (d1, arglist, in_d
*** 3286,3292 ****
  
  	 the `C<T>' is just the same as `C'.  Outside of the
  	 class, however, such a reference is an instantiation.  */
!       if (comp_template_args (CLASSTYPE_TI_ARGS (template_type),
  			      arglist))
  	{
  	  found = template_type;
--- 3308,3314 ----
  
  	 the `C<T>' is just the same as `C'.  Outside of the
  	 class, however, such a reference is an instantiation.  */
!       if (comp_template_args (TYPE_TI_ARGS (template_type),
  			      arglist))
  	{
  	  found = template_type;
*************** lookup_template_class (d1, arglist, in_d
*** 3338,3382 ****
        push_obstacks (&permanent_obstack, &permanent_obstack);
        
        /* Create the type.  */
!       t = make_lang_type (TREE_CODE (template_type));
!       CLASSTYPE_DECLARED_CLASS (t) 
! 	= CLASSTYPE_DECLARED_CLASS (template_type);
!       TYPE_CONTEXT (t) = FROB_CONTEXT (context);
  	  
!       /* Create a stub TYPE_DECL for it.  */
!       type_decl = build_decl (TYPE_DECL, DECL_NAME (template), t);
!       SET_DECL_ARTIFICIAL (type_decl);
!       DECL_CONTEXT (type_decl) = TYPE_CONTEXT (t);
!       DECL_SOURCE_FILE (type_decl) 
! 	= DECL_SOURCE_FILE (TYPE_STUB_DECL (template_type));
!       DECL_SOURCE_LINE (type_decl) 
! 	= DECL_SOURCE_LINE (TYPE_STUB_DECL (template_type));
!       TYPE_STUB_DECL (t) = TYPE_NAME (t) = type_decl;
  
        /* We're done with the permanent obstack, now.  */
        pop_obstacks ();
  
-       /* Seems to be wanted.  */
-       CLASSTYPE_GOT_SEMICOLON (t) = 1;
- 
        /* Set up the template information.  */
        arglist = copy_to_permanent (arglist);
!       CLASSTYPE_TEMPLATE_INFO (t)
! 	= perm_tree_cons (template, arglist, NULL_TREE);
        DECL_TEMPLATE_INSTANTIATIONS (template) = perm_tree_cons
  	(arglist, t, DECL_TEMPLATE_INSTANTIATIONS (template));
-       SET_CLASSTYPE_IMPLICIT_INSTANTIATION (t);
  
        /* Reset the name of the type, now that CLASSTYPE_TEMPLATE_INFO
  	 is set up.  */
!       DECL_NAME (type_decl) = classtype_mangled_name (t);
        DECL_ASSEMBLER_NAME (type_decl) = DECL_NAME (type_decl);
        if (! uses_template_parms (arglist))
  	{
  	  DECL_ASSEMBLER_NAME (type_decl)
  	    = get_identifier (build_overload_name (t, 1, 1));
  	  
! 	  if (flag_external_templates
  	      && CLASSTYPE_INTERFACE_KNOWN (TREE_TYPE (template))
  	      && ! CLASSTYPE_INTERFACE_ONLY (TREE_TYPE (template)))
  	    add_pending_template (t);
--- 3360,3426 ----
        push_obstacks (&permanent_obstack, &permanent_obstack);
        
        /* Create the type.  */
!       if (TREE_CODE (template_type) == ENUMERAL_TYPE)
! 	{
! 	  if (!uses_template_parms (arglist))
! 	    t = tsubst_enum (template_type, arglist);
! 	  else
! 	    /* We don't want to call tsubst_enum for this type, since
! 	       the values for the enumeration constants may involve
! 	       template parameters.  And, no one should be interested
! 	       in the enumeration constants for such a type.  */
! 	    t = make_node (ENUMERAL_TYPE);
! 	}
!       else
! 	{
! 	  t = make_lang_type (TREE_CODE (template_type));
! 	  CLASSTYPE_DECLARED_CLASS (t) 
! 	    = CLASSTYPE_DECLARED_CLASS (template_type);
! 	  CLASSTYPE_GOT_SEMICOLON (t) = 1;
! 	  SET_CLASSTYPE_IMPLICIT_INSTANTIATION (t);
! 	}
! 
!       /* If we called tsubst_enum above, this information will already
! 	 be set up.  */
!       if (!TYPE_NAME (t))
! 	{
! 	  TYPE_CONTEXT (t) = FROB_CONTEXT (context);
  	  
! 	  /* Create a stub TYPE_DECL for it.  */
! 	  type_decl = build_decl (TYPE_DECL, DECL_NAME (template), t);
! 	  SET_DECL_ARTIFICIAL (type_decl);
! 	  DECL_CONTEXT (type_decl) = TYPE_CONTEXT (t);
! 	  DECL_SOURCE_FILE (type_decl) 
! 	    = DECL_SOURCE_FILE (TYPE_STUB_DECL (template_type));
! 	  DECL_SOURCE_LINE (type_decl) 
! 	    = DECL_SOURCE_LINE (TYPE_STUB_DECL (template_type));
! 	  TYPE_STUB_DECL (t) = TYPE_NAME (t) = type_decl;
! 	}
!       else
! 	type_decl = TYPE_NAME (t);
  
        /* We're done with the permanent obstack, now.  */
        pop_obstacks ();
  
        /* Set up the template information.  */
        arglist = copy_to_permanent (arglist);
!       SET_TYPE_TEMPLATE_INFO (t,
! 			      perm_tree_cons (template, arglist, NULL_TREE));
        DECL_TEMPLATE_INSTANTIATIONS (template) = perm_tree_cons
  	(arglist, t, DECL_TEMPLATE_INSTANTIATIONS (template));
  
        /* Reset the name of the type, now that CLASSTYPE_TEMPLATE_INFO
  	 is set up.  */
!       if (TREE_CODE (t) != ENUMERAL_TYPE)
! 	DECL_NAME (type_decl) = classtype_mangled_name (t);
        DECL_ASSEMBLER_NAME (type_decl) = DECL_NAME (type_decl);
        if (! uses_template_parms (arglist))
  	{
  	  DECL_ASSEMBLER_NAME (type_decl)
  	    = get_identifier (build_overload_name (t, 1, 1));
  	  
! 	  if (TREE_CODE (t) != ENUMERAL_TYPE
! 	      && flag_external_templates
  	      && CLASSTYPE_INTERFACE_KNOWN (TREE_TYPE (template))
  	      && ! CLASSTYPE_INTERFACE_ONLY (TREE_TYPE (template)))
  	    add_pending_template (t);
*************** for_each_template_parm (t, fn, data)
*** 3449,3463 ****
      case POINTER_TYPE:
      case REFERENCE_TYPE:
        return for_each_template_parm (TREE_TYPE (t), fn, data);
      case RECORD_TYPE:
        if (TYPE_PTRMEMFUNC_FLAG (t))
  	return for_each_template_parm (TYPE_PTRMEMFUNC_FN_TYPE (t),
  				       fn, data);
      case UNION_TYPE:
!       if (! CLASSTYPE_TEMPLATE_INFO (t))
  	return 0;
        return for_each_template_parm (TREE_VALUE
! 				     (CLASSTYPE_TEMPLATE_INFO (t)),
  				     fn, data);
      case FUNCTION_TYPE:
        if (for_each_template_parm (TYPE_ARG_TYPES (t), fn, data))
--- 3493,3511 ----
      case POINTER_TYPE:
      case REFERENCE_TYPE:
        return for_each_template_parm (TREE_TYPE (t), fn, data);
+ 
      case RECORD_TYPE:
        if (TYPE_PTRMEMFUNC_FLAG (t))
  	return for_each_template_parm (TYPE_PTRMEMFUNC_FN_TYPE (t),
  				       fn, data);
+       /* Fall through.  */
+ 
      case UNION_TYPE:
!     case ENUMERAL_TYPE:
!       if (! TYPE_TEMPLATE_INFO (t))
  	return 0;
        return for_each_template_parm (TREE_VALUE
! 				     (TYPE_TEMPLATE_INFO (t)),
  				     fn, data);
      case FUNCTION_TYPE:
        if (for_each_template_parm (TYPE_ARG_TYPES (t), fn, data))
*************** for_each_template_parm (t, fn, data)
*** 3541,3556 ****
      case NAMESPACE_DECL:
        return 0;
  
-     case ENUMERAL_TYPE:
-       {
- 	tree v;
- 
- 	for (v = TYPE_VALUES (t); v != NULL_TREE; v = TREE_CHAIN (v))
- 	  if (for_each_template_parm (TREE_VALUE (v), fn, data))
- 	    return 1;
-       }
-       return 0;
- 
        /* constants */
      case INTEGER_CST:
      case REAL_CST:
--- 3589,3594 ----
*************** instantiate_class_template (type)
*** 4207,4230 ****
        tree name = TYPE_IDENTIFIER (tag);
        tree newtag;
  
!       if (TREE_CODE (tag) == ENUMERAL_TYPE)
  	{
! 	  newtag = tsubst_enum (tag, args, field_chain);
! 	  while (*field_chain)
  	    {
! 	      DECL_FIELD_CONTEXT (*field_chain) = type;
! 	      field_chain = &TREE_CHAIN (*field_chain);
  	    }
  	}
        else
! 	newtag = tsubst (tag, args, NULL_TREE);
! 
!       /* Now, we call pushtag to put this NEWTAG into the scope of
! 	 TYPE.  We first set up the IDENTIFIER_TYPE_VALUE to avoid
! 	 pushtag calling push_template_decl.  */
!       if (name)
! 	SET_IDENTIFIER_TYPE_VALUE (name, newtag);
!       pushtag (name, newtag, /*globalize=*/0);
      }
  
    /* Don't replace enum constants here.  */
--- 4245,4290 ----
        tree name = TYPE_IDENTIFIER (tag);
        tree newtag;
  
!       newtag = tsubst (tag, args, NULL_TREE);
!       if (TREE_CODE (newtag) == ENUMERAL_TYPE)
  	{
! 	  extern tree current_local_enum;
! 	  tree prev_local_enum = current_local_enum;
! 
! 	  if (TYPE_VALUES (newtag))
  	    {
! 	      tree v;
! 
! 	      /* We must set things up so that CURRENT_LOCAL_ENUM is the
! 		 CONST_DECL for the last enumeration constant, since the
! 		 CONST_DECLs are chained backwards.  */
! 	      for (v = TYPE_VALUES (newtag); TREE_CHAIN (v); 
! 		   v = TREE_CHAIN (v))
! 		;
! 
! 	      current_local_enum 
! 		= IDENTIFIER_CLASS_VALUE (TREE_PURPOSE (v));
! 	      *field_chain = grok_enum_decls (NULL_TREE);
! 	      current_local_enum = prev_local_enum;
! 
! 	      while (*field_chain)
! 		{
! 		  DECL_FIELD_CONTEXT (*field_chain) = type;
! 		  field_chain = &TREE_CHAIN (*field_chain);
! 		}
  	    }
  	}
        else
! 	{
! 	  /* Now, we call pushtag to put this NEWTAG into the scope of
! 	     TYPE.  We first set up the IDENTIFIER_TYPE_VALUE to avoid
! 	     pushtag calling push_template_decl.  We don't have to do
! 	     this for enums because it will already have been done in
! 	     tsubst_enum.  */
! 	  if (name)
! 	    SET_IDENTIFIER_TYPE_VALUE (name, newtag);
! 	  pushtag (name, newtag, /*globalize=*/0);
! 	}
      }
  
    /* Don't replace enum constants here.  */
*************** list_eq (t1, t2)
*** 4371,4394 ****
    return list_eq (TREE_CHAIN (t1), TREE_CHAIN (t2));
  }
  
- tree 
- lookup_nested_type_by_name (ctype, name)
-         tree ctype, name;
- {
-   tree t;
- 
-   complete_type (ctype);
- 
-   for (t = CLASSTYPE_TAGS (ctype); t; t = TREE_CHAIN (t))
-     {
-       if (name == TREE_PURPOSE (t)
- 	  /* this catches typedef enum { foo } bar; */
- 	  || name == TYPE_IDENTIFIER (TREE_VALUE (t)))
- 	return TREE_VALUE (t);
-     }
-   return NULL_TREE;
- }
- 
  /* If arg is a non-type template parameter that does not depend on template
     arguments, fold it like we weren't in the body of a template.  */
  
--- 4431,4436 ----
*************** tsubst_template_parms (parms, args)
*** 4507,4517 ****
    return r;
  }
  
! /* Substitute the ARGS into the indicated aggregate type T.  If T is
!    not an aggregate type, it is handled as if by tsubst.  IN_DECL is
!    as for tsubst.  If ENTERING_SCOPE is non-zero, T is the context for
!    a template which we are presently tsubst'ing.  Return the
!    subsituted value.  */
  
  tree
  tsubst_aggr_type (t, args, in_decl, entering_scope)
--- 4549,4559 ----
    return r;
  }
  
! /* Substitute the ARGS into the indicated aggregate (or enumeration)
!    type T.  If T is not an aggregate or enumeration type, it is
!    handled as if by tsubst.  IN_DECL is as for tsubst.  If
!    ENTERING_SCOPE is non-zero, T is the context for a template which
!    we are presently tsubst'ing.  Return the subsituted value.  */
  
  tree
  tsubst_aggr_type (t, args, in_decl, entering_scope)
*************** tsubst_aggr_type (t, args, in_decl, ente
*** 4535,4540 ****
--- 4577,4583 ----
  	}
  
        /* else fall through */
+     case ENUMERAL_TYPE:
      case UNION_TYPE:
        if (uses_template_parms (t))
  	{
*************** tsubst_aggr_type (t, args, in_decl, ente
*** 4559,4565 ****
  	     and supposing that we are instantiating f<int, double>,
  	     then our ARGS will be {int, double}, but, when looking up
  	     S we only want {double}.  */
! 	  argvec = tsubst (CLASSTYPE_TI_ARGS (t), args, in_decl);
  
    	  r = lookup_template_class (t, argvec, in_decl, context,
  				     entering_scope);
--- 4602,4608 ----
  	     and supposing that we are instantiating f<int, double>,
  	     then our ARGS will be {int, double}, but, when looking up
  	     S we only want {double}.  */
! 	  argvec = tsubst (TYPE_TI_ARGS (t), args, in_decl);
  
    	  r = lookup_template_class (t, argvec, in_decl, context,
  				     entering_scope);
*************** tsubst (t, args, in_decl)
*** 4614,4619 ****
--- 4657,4663 ----
      {
      case RECORD_TYPE:
      case UNION_TYPE:
+     case ENUMERAL_TYPE:
        return tsubst_aggr_type (t, args, in_decl, /*entering_scope=*/0);
  
      case ERROR_MARK:
*************** tsubst (t, args, in_decl)
*** 4629,4646 ****
      case NAMESPACE_DECL:
        return t;
  
-     case ENUMERAL_TYPE:
-       {
- 	tree ctx = tsubst_aggr_type (TYPE_CONTEXT (t), args, in_decl,
- 				     /*entering_scope=*/1);
- 	if (ctx == NULL_TREE || TREE_CODE (ctx) == NAMESPACE_DECL)
- 	  return t;
- 	else if (ctx == current_function_decl)
- 	  return lookup_name (TYPE_IDENTIFIER (t), 1);
- 	else
- 	  return lookup_nested_type_by_name (ctx, TYPE_IDENTIFIER (t));
-       }
- 
      case INTEGER_TYPE:
        if (t == integer_type_node)
  	return t;
--- 4673,4678 ----
*************** tsubst_copy (t, args, in_decl)
*** 5496,5507 ****
        return do_identifier (DECL_NAME (t), 0, NULL_TREE);
  
      case CONST_DECL:
      case FIELD_DECL:
        if (DECL_CONTEXT (t))
  	{
  	  tree ctx;
- 	  if (TREE_CODE (DECL_CONTEXT (t)) == FUNCTION_DECL)
- 	    return lookup_name (DECL_NAME (t), 0);
  
  	  ctx = tsubst_aggr_type (DECL_CONTEXT (t), args, in_decl,
  				  /*entering_scope=*/1);
--- 5528,5572 ----
        return do_identifier (DECL_NAME (t), 0, NULL_TREE);
  
      case CONST_DECL:
+       {
+ 	tree enum_type;
+ 	tree v;
+ 
+ 	if (!DECL_CONTEXT (t))
+ 	  /* This is a global enumeration constant.  */
+ 	  return t;
+ 
+ 	/* Unfortunately, we cannot just call lookup_name here.
+ 	 Consider:
+ 
+ 	 template <int I> int f() {
+ 	   enum E { a = I };
+ 	   struct S { void g() { E e = a; } };
+ 	 };
+ 
+ 	 When we instantiate f<7>::S::g(), say, lookup_name is not
+ 	 clever enough to find f<7>::a.  */
+ 	enum_type 
+ 	  = tsubst_aggr_type (TREE_TYPE (t), args, in_decl, 
+ 			      /*entering_scope=*/0);
+ 
+ 	for (v = TYPE_VALUES (enum_type); 
+ 	     v != NULL_TREE; 
+ 	     v = TREE_CHAIN (v))
+ 	  if (TREE_PURPOSE (v) == DECL_NAME (t))
+ 	    return TREE_VALUE (v);
+ 
+ 	  /* We didn't find the name.  That should never happen; if
+ 	     name-lookup found it during preliminary parsing, we
+ 	     should find it again here during instantiation.  */
+ 	my_friendly_abort (0);
+       }
+       break;
+ 
      case FIELD_DECL:
        if (DECL_CONTEXT (t))
  	{
  	  tree ctx;
  
  	  ctx = tsubst_aggr_type (DECL_CONTEXT (t), args, in_decl,
  				  /*entering_scope=*/1);
*************** tsubst_expr (t, args, in_decl)
*** 5984,5990 ****
        lineno = TREE_COMPLEXITY (t);
        t = TREE_TYPE (t);
        if (TREE_CODE (t) == ENUMERAL_TYPE)
! 	tsubst_enum (t, args, NULL);
        break;
  
      default:
--- 6049,6055 ----
        lineno = TREE_COMPLEXITY (t);
        t = TREE_TYPE (t);
        if (TREE_CODE (t) == ENUMERAL_TYPE)
! 	tsubst (t, args, NULL_TREE);
        break;
  
      default:
*************** add_maybe_template (d, fns)
*** 7823,7835 ****
    DECL_MAYBE_TEMPLATE (d) = 1;
  }
  
! /* Instantiate an enumerated type.  Used by instantiate_class_template and
!    tsubst_expr.  */
  
  static tree
! tsubst_enum (tag, args, field_chain)
       tree tag, args;
-      tree * field_chain;
  {
    extern tree current_local_enum;
    tree prev_local_enum = current_local_enum;
--- 7888,7898 ----
    DECL_MAYBE_TEMPLATE (d) = 1;
  }
  
! /* Instantiate an enumerated type.  */
  
  static tree
! tsubst_enum (tag, args)
       tree tag, args;
  {
    extern tree current_local_enum;
    tree prev_local_enum = current_local_enum;
*************** tsubst_enum (tag, args, field_chain)
*** 7839,7855 ****
  
    for (e = TYPE_VALUES (tag); e; e = TREE_CHAIN (e))
      {
!       tree elt = build_enumerator (TREE_PURPOSE (e),
! 				   tsubst_expr (TREE_VALUE (e), args,
! 						NULL_TREE));
        TREE_CHAIN (elt) = values;
        values = elt;
      }
  
    finish_enum (newtag, values);
- 
-   if (NULL != field_chain)
-     *field_chain = grok_enum_decls (NULL_TREE);
  
    current_local_enum = prev_local_enum;
  
--- 7902,7926 ----
  
    for (e = TYPE_VALUES (tag); e; e = TREE_CHAIN (e))
      {
!       tree value;
!       tree elt;
! 
!       value = TREE_VALUE (e);
!       if (value)
! 	{
! 	  if (TREE_CODE (value) == NOP_EXPR)
! 	    /* This is the special case where the value is really a
! 	   TEMPLATE_PARM_INDEX.  See finish_enum.  */
! 	    value = TREE_OPERAND (value, 0);
! 	  value = tsubst_expr (value, args, NULL_TREE);
! 	}
! 
!       elt = build_enumerator (TREE_PURPOSE (e), value);
        TREE_CHAIN (elt) = values;
        values = elt;
      }
  
    finish_enum (newtag, values);
  
    current_local_enum = prev_local_enum;
  


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]