Initializing a discriminated record

(If it matters, alr tells me that this is gnat_external=12.3.1; notice the pragma)

I was recently in a situation along these lines:

pragma Ada_2022;

   --  ...

   type Enum is (A, B, C);

   type Disc_Rec (Kind : Enum) is record
      case Kind is
         when A => Field : Natural;
         when others => null;
      end case;
   end record;

   --  ... in what follows, `Thing` is of type `constant Enum` and `Value` is of type 

         D : Disc_Rec := (
            case Thing is
               when A => Disc_Rec'(Kind => A, Field => Value),
               when B | C => Disc_Rec'(Kind => Thing)               --  LINE 37
         );

gnat tells me:

test_enum.adb:37:48: error: no single variant is associated with all values of the subtype of discriminant value "Kind"

Column 47 lands on Thing.

I understand the message, but I’d have thought it clear that since Thing has the type B or C in line 37, Thing has all values of the subtype that matter in this branch. Indeed, if Disc_Rec does not have the case statement that defines Field, and I remove the assignment to Field in line 36, the compiler sings happily. So the problem doesn’t seem to be Thing so much as that the optional Field… which isn’t needed in this branch.

Is there a way to make something like this work? My current workaround handles the branches explicitly (when A => ... , when B => ..., when C => ...), but In a practical case, Kind could have a lot of variants.

Sometimes you can use a subtype for things like this

subtype Non_A is Enum range B .. C;
...
when B | C => Disc_Rec'(Kind => Non_A (Thing) )

(not tested)

In the general case, you may need a function

function New_Disc_Rec (Disc : in Enum; Value : in Natural := 0) return Disc_Rec is
   Result : Disc_Rec (Kind => Disc);
begin -- New_Disc_Rec
   case Disc is
   when A =>
      Result.Field := Value;
   when others =>
      null;
   end case;

   return Result;
end New_Disc_Rec;

D : Disc_Rec := New_Disc_Rec (Thing, Value);

Note that you have to declare a variable to return (an extended return will also work), as the discriminant in an aggregate must be static, and a parameter never is.

You can also change up your case statement to use “static” values, which means more case statement lines but satisfies the requirement that a discriminant record aggregate must be static.

type Enum is (A, B, C);

    type Disc_Rec (Kind : Enum) is record
        case Kind is
            when A => Field : Natural;
            when others => null;
        end case;
    end record;
   
    Thing : constant Enum := A;
    Value : constant Natural := 0;

    D : Disc_Rec(Thing) := (
    case Thing is
        when A     => (Kind => A, Field => Value),
        when B     => (Kind => B),      
        when C     => (Kind => C)
    );

If you go the function route as Jeffrey suggests (I also prefer the function route), I would definitely recommend the extended return syntax (not shown above) as it is cleaner to read for a lot of folks and the compiler can also optimize it easier.

I was trying to avoid that. I don’t want to have to elaborate on each enum variant if I can avoid it.

That work in the case that matters fo rme, but I see how this is not a general solution.

How does that even compile? shouldn’t it fail on the declaration of Result when Disc = A?

Oh, I see how it works. Somehow I had misread it.

I like the second solution best. Thanks!

Oh, I’d not seen extended return statements before! thanks