[comp.lang.ada] constraint error question for language lawyers

blakemor@software.org (Alex Blakemore) (11/09/90)

OK, language lawyers of the world.
This one has stumped everyone I've asked locally,  including several 
people who have worked with Ada since there were compilers for it.

Vax Ada 2.1 and two versions of Verdix (5.x and 6.0.3) all behave in the same
(unreasonable) manner - so there must be some obscure rule at work here.
Does anyone
  a. know what that rule might be ?
  b. profess to have a reasonable explanation for its existence.

The Question:
  Why does this procedure raise constraint error?
  It happens on the second assignment to dummy, but
  doesnt happen if dummy.len > 0 and appears to have 
  something to do with the function call "&"

----------------  cut here  -----------------

with text_io;
use text_io;

procedure bozo is
  
  type short is range 0 .. 10;

  type data is array (short range <>) of character;

  type var_text (len : short := 0) is
    record
      text : data (1 .. len);
    end record;

  dummy : var_text; -- unconstrained

  procedure do_nothing (d : data) is 
  begin
    null;
  end do_nothing;

begin

  put_line ("before");
  dummy := (len => 0, text => "");
  put_line ("during");
  dummy := (len  => dummy.len + 1,
            text => dummy.text & 'a');
  put_line ("after");

exception

  when constraint_error =>
    put_line ("constraint_error raised");

end bozo;

---------------- cut here -----------------

Here is the output:

before
during
constraint_error raised

--------------------------------------------------------------------
Alex Blakemore                       CSNET:   blakemore@software.org
Software Productivity Consortium     
2214 Rock Hill Road Herndon, VA 22070 (703) 742-7125

collard@software.org (David Collard) (11/09/90)

In article <1802@software.software.org> blakemor@software.org (Alex Blakemore) writes:
> OK, language lawyers of the world.
> This one has stumped everyone I've asked locally,  including several 
> people who have worked with Ada since there were compilers for it.
> 
> Vax Ada 2.1 and two versions of Verdix (5.x and 6.0.3) all behave in the same
> (unreasonable) manner - so there must be some obscure rule at work here.
> Does anyone
>   a. know what that rule might be ?
>   b. profess to have a reasonable explanation for its existence.
> 
> The Question:
>   Why does this procedure raise constraint error?
>   It happens on the second assignment to dummy, but
>   doesnt happen if dummy.len > 0 and appears to have 
>   something to do with the function call "&"
> 
> ----------------  cut here  -----------------
> 
> with text_io;
> use text_io;
> 
> procedure bozo is
>   
>   type short is range 0 .. 10;
> 
>   type data is array (short range <>) of character;
> 
>   type var_text (len : short := 0) is
>     record
>       text : data (1 .. len);
>     end record;
> 
>   dummy : var_text; -- unconstrained
> 
>   procedure do_nothing (d : data) is 
>   begin
>     null;
>   end do_nothing;
> 
> begin
> 
>   put_line ("before");
>   dummy := (len => 0, text => "");
>   put_line ("during");
>   dummy := (len  => dummy.len + 1,
>             text => dummy.text & 'a');
>   put_line ("after");
> 
> exception
> 
>   when constraint_error =>
>     put_line ("constraint_error raised");
> 
> end bozo;
> 
> ---------------- cut here -----------------
> 
> Here is the output:
> 
> before
> during
> constraint_error raised
> 
> --------------------------------------------------------------------
> Alex Blakemore                       CSNET:   blakemore@software.org
> Software Productivity Consortium     
> 2214 Rock Hill Road Herndon, VA 22070 (703) 742-7125

--This is really just another case of another weirdness about ada, which has
--been discussed here before.  It involves the bounds of an array cannot be
--changed during an aggregate assignment, as shown below:

procedure aggassign is
  type short is new integer range 0..100;
  type intarray is array(short range <>) of integer;
  type rec(a:short := 0) is record
    b : intarray(1..a);
  end record;

  x : rec;
  c : intarray(8..8) := (others => 0);  
  d : intarray(1..1) := (others => 0);
begin
  x := (a => 1, b => d);    -- will NOT raise constraint error
  x := (a => 1, b => c);    -- will raise constraint error

end aggassign;


--The reason your program is the same is because of LRM 4.5.3-5 which
--states that during catenation, when one operand is an element rather
--than an array, it is treated as if it were the same as an array with
--its bounds being the lower  bound of the subtype of the index of
--the array.  In your case this is zero. so your assignment is
--equivalent to:
--
--  dummy := (len => dummy.len + 1,
--            text => dummy.text & (0 => 'a'));
--
--which, as shown in the example above, will raise constraint error.
--
--instead, you can do what you want by explicitly stating the bounds
--of the array of one character as:
--
--  dummy := (len => dummy.len + 1,
--            text => dummy.text & (1 => 'a'));
--
--which works just fine:

with Text_IO; use Text_IO;
procedure bozo is

  type short is range 0..10;
  type data is array(short range <>) of character;
  type var_test(len : short := 0) is
    record
      text : data(1..len);
    end record;

  dummy : var_test;

begin
  put_line("before");
  dummy := (len => 0, text => "");
  put_line("during");

  dummy := (len => dummy.len + 1,                    -- this WILL work
            text => dummy.text & (1 => 'a'));

--  dummy := (len => dummy.len + 1,                  -- this WILL NOT work!
--            text => dummy.text & 'a');
--
--  dummy := (len => dummy.len + 1,                  -- this is equivalent to the
--            text => dummy.text & (0 => 'a'));      -- last one and WILL NOT work!
--
--  dummy := (len => dummy.len + 1,                  -- this WILL NOT work either!
--            text => dummy.text & (5 => 'a'));

  put_line("after");
end bozo;



--

-----------------------------------------------------------------------
D. Thor Collard                      Internet: collard@software.org
Software Productivity Consortium     UUNET:    ...!uunet!software!collard
2214 Rock Hill Rd, Herndon VA 22070  

NCOHEN@IBM.COM ("Norman H. Cohen") (11/09/90)

Alex Blakemore asks why, given the declarations

  type Short is range 0 .. 10;
  type Data is array (Short range <>) of Character;
  type Var_Text (Len : Short := 0) is
    record
      Text : Data (1 .. Len);
    end record;
  Dummy : Var_Text; -- unconstrained

the statements

  Dummy := (Len => 0, Text => "");
  Dummy := (Len  => Dummy.Len + 1,
            Text => Dummy.Text & 'a');

raise Constraint_Error in the second assignment.

The surprise results from the fact that the catenation

   Dummy.Text & 'a'

(where Dummy.Text = "") has bounds of 0 .. 0.  Since the Text component
of type Var_Text has a lower bound of 1, the evaluation of the aggregate

   (Len  => Dummy.Len + 1,
    Text => Dummy.Text & 'a');

raises Constraint_Error.

Now to the legal citations.  RM 4.5.3(4), in discussing the result of a
catenation, states:

     The lower bound of this result is the lower bound of the left
     operand, unless the left operand is a null array, in which case
     the result of the catenation is the right operand.

In this case the left operand IS a null array, so the lower bound must
be that of the right operand.  In this case, however, the right operand
is not an array, but a character, so it has no lower bound.  The next
RM paragraph, 4.5.3(5), provides a rule to cover this case:

     If either operand is of the component type of an array type,
     the result of the catenation is given by the above rules, using
     in place of this operand an array having this operand as its only
     component and having the lower bound of the index subtype of the
     array type as its lower bound.

That is, 'a' is taken to be a shorthand for (Short'First => a).  Since
Short'First, or 0, is lower bound of the right operand, and since the
left operand is null, 0 is the lower bound of the catenation.

The actual raising of Constraint_Error is governed by RM 4.3.1(3),
which discusses the evaluation of record aggregates and states:

     A check is made that the value of each subcomponent of the
     aggregate belongs to the subtype of this subcomponent.
     The exception CONSTRAINT_ERROR is raised if this check fails.

The only questionable rule in all of this is the rule that the value of
L&R, where L is a null array, is the value of R.  This seems natural at
first glance, but since Ada can have null arrays with different bounds
(0 .. -1, 1 .. 0, etc.) this conflicts with the usual rule (for L
nonnull) that the lower bound of the result is that of its left operand.
Alex Blakemore has provided an example where this nonuniform rule leads
to a nasty surprise.

I would modify the program as follows:  Add the subtype declaration

   subtype Positive_Short is Short range 1 .. Short'Last;

and use Positive_Short rather than Short as the index subtype of the
array type Data.  Since the Text component of the record type Var_Text
has a lower bound of one, this is more appropriate.  The Ada rules
tend to work more sensibly when the lower bound of an array is the lower
bound of its index subtype.  (In this case, 'a' would be taken as a
shorthand for (Positive_Short'First => 'a'), which has a lower bound of
1, so the catenation ""&'a' would have a lower bound of 1.)

sampson@cod.NOSC.MIL (Charles H. Sampson) (11/10/90)

In article <1802@software.software.org> blakemor@software.org (Alex Blakemore) writes:
>OK, language lawyers of the world.
>This one has stumped everyone I've asked locally,  including several 
>people who have worked with Ada since there were compilers for it.
>
>Vax Ada 2.1 and two versions of Verdix (5.x and 6.0.3) all behave in the same
>(unreasonable) manner - so there must be some obscure rule at work here.
>Does anyone
>  a. know what that rule might be ?
>  b. profess to have a reasonable explanation for its existence.
>
>  [Using the following declarations, the second assignment raises a
>   constraint error.]
>  
>  type short is range 0 .. 10;
>  type data is array (short range <>) of character;
>  type var_text (len : short := 0) is
>    record
>      text : data (1 .. len);
>    end record;
>  dummy : var_text; -- unconstrained
>
>  dummy := (len => 0, text => "");
>  dummy := (len  => dummy.len + 1,
>            text => dummy.text & 'a');

     O.K., I'll give it a try, but don't expect a dazzling flood of light
on the subject, because there are still some puzzling things around.  To
begin with, I'll obscure the issue even more.  Alsys Ada 4.3 also fails.
However, both Alsys and VAX Ada 2.0 succeed (no constraint error) when
the component text is declared as string(1 .. len)!

     The root problem is the one pointed out by collard@software.org
(David Collard) in article <1809@software.software.org>: "Sliding" is
not allowed when assigning to an array component of an aggregate; the
indexes of the value and the indexes of the component must be equal.
Therefore the indexes of dummy.text & 'a' must be (1 .. 1).  If you
look at section 4.5.3 of the LRM to see what those indexes are, you see
that this case, a null string concatenated with a single component value,
has been omitted.  As Collard pointed out, using

              text => dummy.text & (1 => 'a')

works.  This is because the second component is now an array whose indexes
are (1 .. 1), not a single component value, and 4.5.3 says that the
indexes of the concatenated value are (1 .. 1), as required.  Notice that
this form will always work, because if the left operand of a concatenation
is not null, the indexes of the right operand don't matter.

     After some moderately tricky programming, I was able to verify that
Alsys and VAX Ada both consider the lower bound of

                       dummy.text & 'a'

to be 0.  When the type of text is changed to string(1 .. len), the lower
bound becomes 1.  Why?  I don't know.  Could be an accident.  Could be a
concious decision by the implementors.

     Another question: Why was this case left out of 4.5.3?  Was it an
oversight or was it a concious decision?  It could have been a concious
decision, because any value chosen in this case would be arbitrary, but
if it is I've never heard anybody discuss it.  Then, of course, there is
the underlying question to this whole flail: Why isn't sliding allowed
when assigning a value to a component of an aggregrate (and the other
cases when sliding is not allowed)?

     Now I sit back and wait for the real Ada lawyers to point out that
this problem is clearly and explicitly covered on some page of the LRM
that I would never think to look at.

                              Charlie Sampson

rosen@zeus.enst.fr (Jean Pierre Rosen) (11/17/90)

In article <1802@software.software.org>, blakemor@software.org (Alex Blakemore) writes:
> OK, language lawyers of the world.
> This one has stumped everyone I've asked locally,  including several 
> people who have worked with Ada since there were compilers for it.
> ... 
>   type short is range 0 .. 10;
> 
>   type data is array (short range <>) of character;
> 
>   type var_text (len : short := 0) is
>     record
>       text : data (1 .. len);
>     end record;
> 
>   dummy := (len  => dummy.len + 1,
>             text => dummy.text & 'a');
> 
OKAY, the question is "what is the subtype (i.e. bounds) of dummy.text?
4.5.3(4) says that if the left-hand part of a catenation operator is a 
null array (as in this case), the lower bound is taken from the right operand
4.5.3(5) says that if the operant is of a component type, its lower bound is
taken from the index subtype. Therefore, 'a' is considered of subtype
data(0..0). BUT TEXT EXPECTS SUBTYPE data(1..1)! And array sliding does
NOT occur within aggregates (sliding occurs only in array assignments, 5.2.1)
=> CONSTRAINT_ERROR.
Clean way of solving the problem:
type extended_index is range 0..10;
subtype index is extended_index range 1..extended_index'LAST;
type data is array(index range <>) of character;
 
You might complain about strange language rules, but note that this is
actually a design error: you must express that the 0 is not a suitable value
for indexing your array. Once you express your design carefully, the problem
disappears.

"ADA DOES NOT CREATE PROBLEMS; IT JUST MAKES HIDDEN PROBLEMS APPARENT"

jamesth@microsoft.UUCP (James THIELE) (12/01/90)

In article <9011091437.AA20896@ajpo.sei.cmu.edu> NCOHEN@IBM.COM ("Norman H. Cohen") writes:
| Alex Blakemore asks why, given the declarations
| 
|   type Short is range 0 .. 10;
|   type Data is array (Short range <>) of Character;
|   type Var_Text (Len : Short := 0) is
|     record
|       Text : Data (1 .. Len);
|     end record;
|   Dummy : Var_Text; -- unconstrained
| 
| the statements
| 
|   Dummy := (Len => 0, Text => "");
|   Dummy := (Len  => Dummy.Len + 1,
|             Text => Dummy.Text & 'a');
| 
| raise Constraint_Error in the second assignment.
| 
[62 lines of "legalese" deleted]

When I worked with Ada, it always seemed that the manual was like the
"Colossal Cave" -- you always had to go several places to get what you needed.

But my favorite comment always was:

	"Ada is PASCAL for lawyers."

James Thiele
microsoft!jamesth