cxx-ada-anonymous-enums

C++ Ada anonymous enums

Top-level C++ anonymous enumerations lose their constants and appear as incomplete internal types in generated Ada specifications.

AcceptedApplies standaloneSince 1.2.0

Where it applies.

How each patchset treats this bundle on each GCC major
PatchsetGCC 13GCC 14GCC 15GCC 16
1.2.0 (latest)Patchedgcc-13-1413.2.0Patchedgcc-13-1414.2.0Patchedgcc-15-1615.3.0Patchedgcc-15-1616.2.0
1.1.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.2.0
1.0.1Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0
1.0.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0

Explanation.

Top-level anonymous C++ enumerations are represented internally by synthetic type declarations. The Ada spec dumper skips those declarations as compiler artifacts, which loses their enumerators and leaves any signature using the anonymous type with an incomplete anon_anon_... declaration.

Both ordinary sequential values and explicitly valued sparse enumerations reach the broken path:

enum { Sequential_Zero, Sequential_One };
enum { Sparse_Minus = -1, Sparse_Five = 5 };

extern "C" int
sequential_value (decltype (Sequential_Zero) value);

extern "C" int
sparse_value (decltype (Sparse_Minus) value);

The unpatched mapper emits incomplete internal types and no constants (the numeric suffixes are compiler-internal and vary):

type anon_anon_0;
function sequential_value (value : anon_anon_0) return int;

type anon_anon_1;
function sparse_value (value : anon_anon_1) return int;

The corrected output assigns consistent synthetic Ada type names within the unit. Sequential values remain an enumeration; sparse or negative values use an integer subtype and constants so their exact C++ values are preserved (the numeric suffixes still vary):

type anon_enum1704 is
  (Sequential_Zero,
   Sequential_One)
with Convention => C;

subtype anon_enum1706 is int;
Sparse_Minus : constant anon_enum1706 := -1;
Sparse_Five : constant anon_enum1706 := 5;

function sequential_value (value : anon_enum1704) return int;
function sparse_value (value : anon_enum1706) return int;

The patch emits an anonymous enumeration once, maps every later reference to the same synthetic name, and retains the original enumerator names in the enclosing Ada package. The executable regression compiles the generated Ada and passes sequential, negative, and sparse values to C++ at -O0 and -O2.

Run it against an unpatched or patched compiler root:

./bundles/cxx-ada-anonymous-enums/run-test.sh \
  TOOLCHAIN_ROOT GCC_VERSION unpatched
./bundles/cxx-ada-anonymous-enums/run-test.sh \
  TOOLCHAIN_ROOT GCC_VERSION patched

Patch.

Variant gcc-13-14

Applies to 13.2.0, 14.2.0. Source flavors: linux, darwin_arm64.

+108 −6 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +85−6modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -2030 +2030 @@dump_ada_enum_type (pretty_printer *buffer, tree node, tree type, int spc)
20302030 }
20312031}
20322032
2033Added line. /* Dump in BUFFER the declaration of an anonymous enumeral NODE. Its
2034Added line. enumerators are members of the enclosing C++ scope, so do not prefix
2035Added line. non-sequential values with the synthetic Ada type name. */
2036Added line.
2037Added line. static void
2038Added line. dump_ada_anonymous_enum (pretty_printer *buffer, tree node, int spc)
2039Added line. {
2040Added line. INDENT (spc);
2041Added line. pp_string (buffer, is_simple_enum (node) ? "type " : "subtype ");
2042Added line. dump_anonymous_type_name (buffer, node);
2043Added line. pp_string (buffer, " is ");
2044Added line.
2045Added line. if (is_simple_enum (node))
2046Added line. dump_ada_enum_type (buffer, node, NULL_TREE, spc);
2047Added line. else
2048Added line. {
2049Added line. pp_string (buffer, TYPE_UNSIGNED (node) ? "unsigned" : "int");
2050Added line.
2051Added line. for (tree value = TYPE_VALUES (node); value; value = TREE_CHAIN (value))
2052Added line. {
2053Added line. tree int_val = TREE_VALUE (value);
2054Added line.
2055Added line. if (TREE_CODE (int_val) != INTEGER_CST)
2056Added line. int_val = DECL_INITIAL (int_val);
2057Added line.
2058Added line. pp_semicolon (buffer);
2059Added line. newline_and_indent (buffer, spc);
2060Added line. pp_ada_tree_identifier (buffer, TREE_PURPOSE (value), node, false);
2061Added line. pp_string (buffer, " : constant ");
2062Added line. dump_anonymous_type_name (buffer, node);
2063Added line. pp_string (buffer, " := ");
2064Added line. dump_ada_node (buffer, int_val, node, spc, false, true);
2065Added line. }
2066Added line. }
2067Added line.
2068Added line. pp_semicolon (buffer);
2069Added line. }
2070Added line.
20332071/* Return true if NODE is the _Float32/_Float32x type. */
20342072
20352073static bool
@@ -2160 +2198 @@dump_ada_node (pretty_printer *buffer, tree node, tree type, int spc,
21602198
21612199 case ENUMERAL_TYPE:
21622200 if (name_only)
2163Removed line. dump_ada_node (buffer, TYPE_NAME (node), node, spc, false, true);
2201Added line. {
2202Added line. tree enum_name = TYPE_NAME (node);
2203Added line. tree enum_id = (enum_name && TREE_CODE (enum_name) == TYPE_DECL
2204Added line. ? DECL_NAME (enum_name) : enum_name);
2205Added line.
2206Added line. if (enum_id
2207Added line. && (*IDENTIFIER_POINTER (enum_id) != '.'
2208Added line. && *IDENTIFIER_POINTER (enum_id) != '$'))
2209Added line. dump_ada_node (buffer, enum_name, node, spc, false, true);
2210Added line. else
2211Added line. dump_anonymous_type_name (buffer, node);
2212Added line. }
21642213 else
21652214 dump_ada_enum_type (buffer, node, type, spc);
21662215 break;
@@ -2850 +2899 @@dump_ada_declaration (pretty_printer *buffer, tree t, tree type, int spc)
28502899 if (cpp_check && cpp_check (t, IS_TEMPLATE))
28512900 return dump_ada_template (buffer, t, spc);
28522901
2853Removed line. /* Skip enumeral values: will be handled as part of the type itself. */
2902Added line. /* Named enumeral values are handled as part of the type itself. C++ does
2903Added line. may not expose a TYPE_DECL for a top-level anonymous enum, so emit its
2904Added line. synthetic type and all enumerators when the first CONST_DECL is seen. */
28542905 if (TREE_CODE (t) == CONST_DECL && TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE)
2855Removed line. return 0;
2906Added line. {
2907Added line. tree enum_type = TREE_TYPE (t);
2908Added line.
2909Added line. if (TYPE_NAME (enum_type)
2910Added line. || !bitmap_set_bit (dumped_anonymous_types, TYPE_UID (enum_type)))
2911Added line. return 0;
2912Added line.
2913Added line. dump_ada_anonymous_enum (buffer, enum_type, spc);
2914Added line. return 1;
2915Added line. }
28562916
28572917 if (TREE_CODE (t) == TYPE_DECL)
28582918 {
@@ -2905 +2965 @@dump_ada_declaration (pretty_printer *buffer, tree t, tree type, int spc)
29052965 TREE_VISITED (stub) = 1;
29062966 }
29072967
2908Removed line. /* Skip unnamed or anonymous structs/unions/enum types. */
2968Added line. /* Emit anonymous enums under a synthetic type name so their enumerators
2969Added line. remain available in the enclosing Ada package. */
29092970 if (!orig
2910Removed line. && (RECORD_OR_UNION_TYPE_P (TREE_TYPE (t))
2911Removed line. || TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE)
2971Added line. && TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE
2972Added line. && ((!decl_name && !name)
2973Added line. || (decl_name
2974Added line. && (*IDENTIFIER_POINTER (decl_name) == '.'
2975Added line. || *IDENTIFIER_POINTER (decl_name) == '$')
2976Added line. && TYPE_NAME (TREE_TYPE (t)) == t)))
2977Added line. {
2978Added line. tree enum_type = TREE_TYPE (t);
2979Added line.
2980Added line. if (!bitmap_set_bit (dumped_anonymous_types, TYPE_UID (enum_type)))
2981Added line. return 0;
2982Added line.
2983Added line. dump_ada_anonymous_enum (buffer, enum_type, spc);
2984Added line. TREE_VISITED (t) = 1;
2985Added line. return 1;
2986Added line. }
2987Added line.
2988Added line. /* Skip unnamed or anonymous structs/unions. */
2989Added line. if (!orig
2990Added line. && RECORD_OR_UNION_TYPE_P (TREE_TYPE (t))
29122991 && !decl_name
29132992 && !name)
29142993 return 0;
gcc/testsuite/g++.dg/ada-spec/anonymous-enums.C +23−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/anonymous-enums.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sequential_Zero" } } */
4Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sequential_One" } } */
5Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Minus : constant anon_enum[0-9]+ := -1;" } } */
6Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Five : constant anon_enum[0-9]+ := 5;" } } */
7Added line.
8Added line. enum { Sequential_Zero, Sequential_One };
9Added line. enum { Sparse_Minus = -1, Sparse_Five = 5 };
10Added line.
11Added line. extern "C" int
12Added line. sequential_value (decltype (Sequential_Zero) value)
13Added line. {
14Added line. return value;
15Added line. }
16Added line.
17Added line. extern "C" int
18Added line. sparse_value (decltype (Sparse_Minus) value)
19Added line. {
20Added line. return value;
21Added line. }
22Added line.
23Added line. /* { dg-final { cleanup-ada-spec } } */
24

Variant gcc-15-16

Applies to 15.3.0, 16.1.0, 16.2.0. Source flavors: linux, darwin_arm64.

+108 −6 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +85−6modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -2077 +2077 @@dump_ada_enum_type (pretty_printer *pp, tree node, tree type, int spc)
20772077 }
20782078}
20792079
2080Added line. /* Dump in PP the declaration of an anonymous enumeral NODE. Its enumerators
2081Added line. are members of the enclosing C++ scope, so do not prefix non-sequential
2082Added line. values with the synthetic Ada type name. */
2083Added line.
2084Added line. static void
2085Added line. dump_ada_anonymous_enum (pretty_printer *pp, tree node, int spc)
2086Added line. {
2087Added line. INDENT (spc);
2088Added line. pp_string (pp, is_simple_enum (node) ? "type " : "subtype ");
2089Added line. dump_anonymous_type_name (pp, node);
2090Added line. pp_string (pp, " is ");
2091Added line.
2092Added line. if (is_simple_enum (node))
2093Added line. dump_ada_enum_type (pp, node, NULL_TREE, spc);
2094Added line. else
2095Added line. {
2096Added line. pp_string (pp, TYPE_UNSIGNED (node) ? "unsigned" : "int");
2097Added line.
2098Added line. for (tree value = TYPE_VALUES (node); value; value = TREE_CHAIN (value))
2099Added line. {
2100Added line. tree int_val = TREE_VALUE (value);
2101Added line.
2102Added line. if (TREE_CODE (int_val) != INTEGER_CST)
2103Added line. int_val = DECL_INITIAL (int_val);
2104Added line.
2105Added line. pp_semicolon (pp);
2106Added line. newline_and_indent (pp, spc);
2107Added line. pp_ada_tree_identifier (pp, TREE_PURPOSE (value), node, false);
2108Added line. pp_string (pp, " : constant ");
2109Added line. dump_anonymous_type_name (pp, node);
2110Added line. pp_string (pp, " := ");
2111Added line. dump_ada_node (pp, int_val, node, spc, false, true);
2112Added line. }
2113Added line. }
2114Added line.
2115Added line. pp_semicolon (pp);
2116Added line. }
2117Added line.
20802118/* Return true if NODE is the __bf16 type. */
20812119
20822120static bool
@@ -2220 +2258 @@dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
22202258
22212259 case ENUMERAL_TYPE:
22222260 if (name_only)
2223Removed line. dump_ada_node (pp, TYPE_NAME (node), node, spc, false, true);
2261Added line. {
2262Added line. tree enum_name = TYPE_NAME (node);
2263Added line. tree enum_id = (enum_name && TREE_CODE (enum_name) == TYPE_DECL
2264Added line. ? DECL_NAME (enum_name) : enum_name);
2265Added line.
2266Added line. if (enum_id
2267Added line. && (*IDENTIFIER_POINTER (enum_id) != '.'
2268Added line. && *IDENTIFIER_POINTER (enum_id) != '$'))
2269Added line. dump_ada_node (pp, enum_name, node, spc, false, true);
2270Added line. else
2271Added line. dump_anonymous_type_name (pp, node);
2272Added line. }
22242273 else
22252274 dump_ada_enum_type (pp, node, type, spc);
22262275 break;
@@ -2917 +2966 @@dump_ada_declaration (pretty_printer *pp, tree t, tree type, int spc)
29172966 if (cpp_check && cpp_check (t, IS_TEMPLATE))
29182967 return dump_ada_template (pp, t, spc);
29192968
2920Removed line. /* Skip enumeral values: will be handled as part of the type itself. */
2969Added line. /* Named enumeral values are handled as part of the type itself. C++ does
2970Added line. may not expose a TYPE_DECL for a top-level anonymous enum, so emit its
2971Added line. synthetic type and all enumerators when the first CONST_DECL is seen. */
29212972 if (TREE_CODE (t) == CONST_DECL && TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE)
2922Removed line. return 0;
2973Added line. {
2974Added line. tree enum_type = TREE_TYPE (t);
2975Added line.
2976Added line. if (TYPE_NAME (enum_type)
2977Added line. || !bitmap_set_bit (dumped_anonymous_types, TYPE_UID (enum_type)))
2978Added line. return 0;
2979Added line.
2980Added line. dump_ada_anonymous_enum (pp, enum_type, spc);
2981Added line. return 1;
2982Added line. }
29232983
29242984 if (TREE_CODE (t) == TYPE_DECL)
29252985 {
@@ -2972 +3032 @@dump_ada_declaration (pretty_printer *pp, tree t, tree type, int spc)
29723032 TREE_VISITED (stub) = 1;
29733033 }
29743034
2975Removed line. /* Skip unnamed or anonymous structs/unions/enum types. */
3035Added line. /* Emit anonymous enums under a synthetic type name so their enumerators
3036Added line. remain available in the enclosing Ada package. */
29763037 if (!orig
2977Removed line. && (RECORD_OR_UNION_TYPE_P (TREE_TYPE (t))
2978Removed line. || TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE)
3038Added line. && TREE_CODE (TREE_TYPE (t)) == ENUMERAL_TYPE
3039Added line. && ((!decl_name && !name)
3040Added line. || (decl_name
3041Added line. && (*IDENTIFIER_POINTER (decl_name) == '.'
3042Added line. || *IDENTIFIER_POINTER (decl_name) == '$')
3043Added line. && TYPE_NAME (TREE_TYPE (t)) == t)))
3044Added line. {
3045Added line. tree enum_type = TREE_TYPE (t);
3046Added line.
3047Added line. if (!bitmap_set_bit (dumped_anonymous_types, TYPE_UID (enum_type)))
3048Added line. return 0;
3049Added line.
3050Added line. dump_ada_anonymous_enum (pp, enum_type, spc);
3051Added line. TREE_VISITED (t) = 1;
3052Added line. return 1;
3053Added line. }
3054Added line.
3055Added line. /* Skip unnamed or anonymous structs/unions. */
3056Added line. if (!orig
3057Added line. && RECORD_OR_UNION_TYPE_P (TREE_TYPE (t))
29793058 && !decl_name
29803059 && !name)
29813060 return 0;
gcc/testsuite/g++.dg/ada-spec/anonymous-enums.C +23−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/anonymous-enums.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sequential_Zero" } } */
4Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sequential_One" } } */
5Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Minus : constant anon_enum[0-9]+ := -1;" } } */
6Added line. /* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Five : constant anon_enum[0-9]+ := 5;" } } */
7Added line.
8Added line. enum { Sequential_Zero, Sequential_One };
9Added line. enum { Sparse_Minus = -1, Sparse_Five = 5 };
10Added line.
11Added line. extern "C" int
12Added line. sequential_value (decltype (Sequential_Zero) value)
13Added line. {
14Added line. return value;
15Added line. }
16Added line.
17Added line. extern "C" int
18Added line. sparse_value (decltype (Sparse_Minus) value)
19Added line. {
20Added line. return value;
21Added line. }
22Added line.
23Added line. /* { dg-final { cleanup-ada-spec } } */
24

Tests.

anonymous-enums.C C++ · 23 lines
/* { dg-do compile } */
/* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
/* { dg-final { scan-file anonymous_enums_c.ads "Sequential_Zero" } } */
/* { dg-final { scan-file anonymous_enums_c.ads "Sequential_One" } } */
/* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Minus : constant anon_enum[0-9]+ := -1;" } } */
/* { dg-final { scan-file anonymous_enums_c.ads "Sparse_Five : constant anon_enum[0-9]+ := 5;" } } */

enum { Sequential_Zero, Sequential_One };
enum { Sparse_Minus = -1, Sparse_Five = 5 };

extern "C" int
sequential_value (decltype (Sequential_Zero) value)
{
  return value;
}

extern "C" int
sparse_value (decltype (Sparse_Minus) value)
{
  return value;
}

/* { dg-final { cleanup-ada-spec } } */

Download · View in repository

anonymous_enums_consumer.adb Ada · 13 lines
with Interfaces.C; use Interfaces.C;
with Anonymous_Enums_C;

procedure Anonymous_Enums_Consumer is
   package Bindings renames Anonymous_Enums_C;
begin
   if Bindings.sequential_value (Bindings.Sequential_One) /= 1
     or else Bindings.sparse_value (Bindings.Sparse_Minus) /= -1
     or else Bindings.sparse_value (Bindings.Sparse_Five) /= 5
   then
      raise Program_Error with "anonymous enumeration values were lost";
   end if;
end Anonymous_Enums_Consumer;

Download · View in repository

run-test.sh shell · 78 lines
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 3 ]]; then
  echo "usage: $0 TOOLCHAIN_ROOT GCC_VERSION unpatched|patched" >&2
  exit 2
fi

root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
source "$root/scripts/regression-common.sh"
resolve_regression_toolchain "$1"
version=$2
state=$3
[[ "$state" == unpatched || "$state" == patched ]] || {
  echo "error: state must be unpatched or patched" >&2
  exit 2
}

gxx="$REGRESSION_TOOLCHAIN/bin/g++"
[[ -x "$gxx" ]] || {
  echo "error: no g++ in $REGRESSION_TOOLCHAIN" >&2
  exit 1
}

cxx_fixture="$root/bundles/cxx-ada-anonymous-enums/tests/anonymous-enums.C"
ada_fixture="$root/bundles/cxx-ada-anonymous-enums/tests/anonymous_enums_consumer.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-anonymous-enums.XXXXXX")
trap 'rm -rf "$work"' EXIT

for optimization in 0 2; do
  case_dir="$work/O$optimization"
  mkdir -p "$case_dir"
  cp "$cxx_fixture" "$ada_fixture" "$case_dir/"
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$gxx" -std=gnu++20 -c "-O$optimization" \
      -fdump-ada-spec-slim anonymous-enums.C
  )

  spec="$case_dir/anonymous_enums_c.ads"
  [[ -f "$spec" ]] || {
    echo "error: g++ did not generate $spec" >&2
    exit 1
  }

  set +e
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f \
      "-O$optimization" anonymous_enums_consumer.adb \
      -largs anonymous-enums.o -lstdc++
    "${REGRESSION_ENV[@]}" ./anonymous_enums_consumer
  ) >"$case_dir/run.log" 2>&1
  run_status=$?
  set -e

  if [[ "$state" == unpatched ]]; then
    [[ $run_status -ne 0 ]] || {
      echo "error: unpatched anonymous-enum regression unexpectedly passed" >&2
      exit 1
    }
    grep -Eiq 'anon_anon_[0-9]+.*(undefined|incomplete)|Sequential_One.*not declared' \
      "$case_dir/run.log" || {
      cat "$case_dir/run.log"
      exit 1
    }
    echo "cxx-ada-anonymous-enums -O$optimization: expected omission (GCC $version)"
    continue
  fi

  [[ $run_status -eq 0 ]] || {
    cat "$case_dir/run.log"
    exit 1
  }
  grep -Eq 'type anon_enum[0-9]+ is' "$spec"
  grep -Eq 'subtype anon_enum[0-9]+ is int;' "$spec"
  echo "cxx-ada-anonymous-enums -O$optimization: patched (GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-anonymous-enums/patches/VARIANT.patch
Build the compiler
PATH=/path/to/bootstrap/bin:$PATH ./scripts/build-gnat.sh SOURCE BUILD INSTALL
Run the regression
./scripts/run-regressions.sh INSTALL 1.2.0 GCC_MAJOR patched

Metadata.