cxx-ada-template-record-termination

C++ Ada template record termination

The C++ Ada spec dumper omits the terminating semicolon from trivial record declarations emitted for concrete class-template instances.

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.

dump_ada_template writes concrete class-template instances inside nested Ada packages. For trivial records, dump_ada_structure deliberately leaves the terminating semicolon to its caller, but the template caller did not add it. The resulting aspect ends at C_Pass_By_Copy and GNAT rejects the generated specification.

A minimal offending declaration is a trivial concrete template instance:

template <typename T>
struct Plain { T value; };

template struct Plain<int>;

The unpatched mapper produces an unterminated record declaration:

package Plain_int is
   type Plain is limited record
      value : aliased int;
   end record
   with Convention => C_Pass_By_Copy
end;

The corrected output terminates the representation aspect before the package continues:

package Plain_int is
   type Plain is limited record
      value : aliased int;
   end record
   with Convention => C_Pass_By_Copy;
end;

The patch terminates records only when the structure printer did not already do so while emitting methods or static fields. The executable regression covers the shared path with primary, partial-specialization, full-specialization, nested, constrained, and template-template instances.

Run it against an unpatched or patched compiler root:

./bundles/cxx-ada-template-record-termination/run-test.sh \
  TOOLCHAIN_ROOT GCC_VERSION unpatched
./bundles/cxx-ada-template-record-termination/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.

+77 −0 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +2−0modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -1917 +1917 @@dump_ada_template (pretty_printer *buffer, tree t, int spc)
19171917 pp_string (buffer, " is limited ");
19181918
19191919 dump_ada_node (buffer, instance, t, spc, false, false);
1920Added line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance))
1921Added line. pp_semicolon (buffer);
19201922 pp_newline (buffer);
19211923 spc -= INDENT_INCR;
19221924 newline_and_indent (buffer, spc);
gcc/testsuite/g++.dg/ada-spec/template-record-termination.C +75−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/template-record-termination.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 template_record_termination_c.ads "with Convention => C_Pass_By_Copy;" } } */
4Added line.
5Added line. template <typename T>
6Added line. struct Plain
7Added line. {
8Added line. T value;
9Added line. };
10Added line.
11Added line. template struct Plain<int>;
12Added line.
13Added line. template <typename T, bool Select>
14Added line. struct Choice;
15Added line.
16Added line. template <typename T>
17Added line. struct Choice<T, false>
18Added line. {
19Added line. T value;
20Added line. };
21Added line.
22Added line. template struct Choice<int, false>;
23Added line.
24Added line. template <typename T>
25Added line. struct Specialized
26Added line. {
27Added line. T value;
28Added line. };
29Added line.
30Added line. template <>
31Added line. struct Specialized<bool>
32Added line. {
33Added line. unsigned value;
34Added line. };
35Added line.
36Added line. using Bool_Specialized = Specialized<bool>;
37Added line.
38Added line. template <typename T>
39Added line. struct Outer
40Added line. {
41Added line. template <typename U>
42Added line. struct Inner
43Added line. {
44Added line. U value;
45Added line. };
46Added line. };
47Added line.
48Added line. template struct Outer<int>;
49Added line.
50Added line. template <typename T>
51Added line. concept Integral_Sized = sizeof (T) >= sizeof (int);
52Added line.
53Added line. template <Integral_Sized T>
54Added line. struct Constrained
55Added line. {
56Added line. T value;
57Added line. };
58Added line.
59Added line. template struct Constrained<int>;
60Added line.
61Added line. template <typename T>
62Added line. struct Item
63Added line. {
64Added line. T value;
65Added line. };
66Added line.
67Added line. template <template <typename> class Container, typename T>
68Added line. struct Wrapper
69Added line. {
70Added line. Container<T> item;
71Added line. };
72Added line.
73Added line. template struct Wrapper<Item, int>;
74Added line.
75Added line. /* { dg-final { cleanup-ada-spec } } */
76

Variant gcc-15-16

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

+77 −0 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +2−0modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -1964 +1964 @@dump_ada_template (pretty_printer *pp, tree t, int spc)
19641964 pp_string (pp, " is limited ");
19651965
19661966 dump_ada_node (pp, instance, t, spc, false, false);
1967Added line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance))
1968Added line. pp_semicolon (pp);
19671969 pp_newline (pp);
19681970 spc -= INDENT_INCR;
19691971 newline_and_indent (pp, spc);
gcc/testsuite/g++.dg/ada-spec/template-record-termination.C +75−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/template-record-termination.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 template_record_termination_c.ads "with Convention => C_Pass_By_Copy;" } } */
4Added line.
5Added line. template <typename T>
6Added line. struct Plain
7Added line. {
8Added line. T value;
9Added line. };
10Added line.
11Added line. template struct Plain<int>;
12Added line.
13Added line. template <typename T, bool Select>
14Added line. struct Choice;
15Added line.
16Added line. template <typename T>
17Added line. struct Choice<T, false>
18Added line. {
19Added line. T value;
20Added line. };
21Added line.
22Added line. template struct Choice<int, false>;
23Added line.
24Added line. template <typename T>
25Added line. struct Specialized
26Added line. {
27Added line. T value;
28Added line. };
29Added line.
30Added line. template <>
31Added line. struct Specialized<bool>
32Added line. {
33Added line. unsigned value;
34Added line. };
35Added line.
36Added line. using Bool_Specialized = Specialized<bool>;
37Added line.
38Added line. template <typename T>
39Added line. struct Outer
40Added line. {
41Added line. template <typename U>
42Added line. struct Inner
43Added line. {
44Added line. U value;
45Added line. };
46Added line. };
47Added line.
48Added line. template struct Outer<int>;
49Added line.
50Added line. template <typename T>
51Added line. concept Integral_Sized = sizeof (T) >= sizeof (int);
52Added line.
53Added line. template <Integral_Sized T>
54Added line. struct Constrained
55Added line. {
56Added line. T value;
57Added line. };
58Added line.
59Added line. template struct Constrained<int>;
60Added line.
61Added line. template <typename T>
62Added line. struct Item
63Added line. {
64Added line. T value;
65Added line. };
66Added line.
67Added line. template <template <typename> class Container, typename T>
68Added line. struct Wrapper
69Added line. {
70Added line. Container<T> item;
71Added line. };
72Added line.
73Added line. template struct Wrapper<Item, int>;
74Added line.
75Added line. /* { dg-final { cleanup-ada-spec } } */
76

Tests.

template-record-termination.C C++ · 75 lines
/* { dg-do compile } */
/* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
/* { dg-final { scan-file template_record_termination_c.ads "with Convention => C_Pass_By_Copy;" } } */

template <typename T>
struct Plain
{
  T value;
};

template struct Plain<int>;

template <typename T, bool Select>
struct Choice;

template <typename T>
struct Choice<T, false>
{
  T value;
};

template struct Choice<int, false>;

template <typename T>
struct Specialized
{
  T value;
};

template <>
struct Specialized<bool>
{
  unsigned value;
};

using Bool_Specialized = Specialized<bool>;

template <typename T>
struct Outer
{
  template <typename U>
  struct Inner
  {
    U value;
  };
};

template struct Outer<int>;

template <typename T>
concept Integral_Sized = sizeof (T) >= sizeof (int);

template <Integral_Sized T>
struct Constrained
{
  T value;
};

template struct Constrained<int>;

template <typename T>
struct Item
{
  T value;
};

template <template <typename> class Container, typename T>
struct Wrapper
{
  Container<T> item;
};

template struct Wrapper<Item, int>;

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

Download · View in repository

template_record_termination_consumer.adb Ada · 6 lines
with Template_Record_Termination_C;

procedure Template_Record_Termination_Consumer is
begin
   null;
end Template_Record_Termination_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-template-record-termination/tests/template-record-termination.C"
ada_fixture="$root/bundles/cxx-ada-template-record-termination/tests/template_record_termination_consumer.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-template-record-test.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 template-record-termination.C
  )

  spec="$case_dir/template_record_termination_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" -c template_record_termination_consumer.adb
  ) >"$case_dir/build.log" 2>&1
  build_status=$?
  set -e

  if [[ "$state" == unpatched ]]; then
    [[ $build_status -ne 0 ]] || {
      echo "error: unpatched template record regression unexpectedly compiled at -O$optimization" >&2
      exit 1
    }
    grep -Eiq 'missing.*;|aspect.*requires|declaration expected' "$case_dir/build.log" || {
      cat "$case_dir/build.log"
      exit 1
    }
    echo "cxx-ada-template-record-termination -O$optimization: expected rejection (GCC $version)"
    continue
  fi

  [[ $build_status -eq 0 ]] || {
    cat "$case_dir/build.log"
    exit 1
  }
  count=$(grep -Fc "with Convention => C_Pass_By_Copy;" "$spec")
  [[ $count -eq 7 ]] || {
    echo "error: expected 7 terminated template records, found $count" >&2
    exit 1
  }
  echo "cxx-ada-template-record-termination -O$optimization: patched (GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-template-record-termination/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.