This patch implements the proper handling of dimension information on type
conversions. Given a conversion T (Expr), where the expression has type TE,
the following cases arise:

a) If TE has dimension information, the dimensions of the conversion are those
of TE.

b) If TE has no dimension information, dimensions of conversion are those of T.

c) If T and TE belong to different dimension systems, they must have identical
dimensions, unless T is the root type of its system, in which case dimensions
are those of TE, and the conversion can be seen as a "view conversion" that
preserves the dimensions of its argument.

d) If T is a non-dimensioned type, such a Standard.Float, the conversion has no
dimension information.

The following must compile quietly:

   gcc -c  main.adb
   gcc -c -gnatd.F main.adb

---
with Units; use Units;

procedure main with SPARK_Mode is

   subtype Servo_Angle_Type is
       Units.Angle_Type range  -40.0 * Degree .. 40.0 * Degree;

   function Sat_Servo_Angle is new Saturated_Cast (Servo_Angle_Type);
begin
   null;
end main;
---
with Ada.Numerics;

package units with SPARK_Mode is

    type Unit_Type is new Float with  
        Dimension_System =>
        ((Unit_Name => Meter, Unit_Symbol => 'm', Dim_Symbol => 'L'),
         (Unit_Name => Kilogram, Unit_Symbol => "kg", Dim_Symbol => 'M'),
         (Unit_Name => Second, Unit_Symbol => 's', Dim_Symbol => 'T'),
         (Unit_Name => Ampere, Unit_Symbol => 'A', Dim_Symbol => 'I'),
         (Unit_Name => Kelvin, Unit_Symbol => 'K', Dim_Symbol => "Theta"),
         (Unit_Name => Radian, Unit_Symbol => "Rad", Dim_Symbol => "A")),
       Default_Value => 0.0; -- required for matrices

   subtype Angle_Type is Unit_Type with
        Dimension => (Symbol => "Rad", Radian => 1, others => 0);

   Degree : constant Angle_Type := Angle_Type (2.0 * Ada.Numerics.Pi / 360.0);

   generic
      type T is digits <>;
   function Saturated_Cast (val : Float) return T with Inline;
   --  convert a float into a more specific float type, and trim
   --  to the value range
end units;
---
package body units with SPARK_Mode is
   function Saturated_Cast (val : Float) return T is
      ret : T;
   begin
      if val >= Float (T'Last) then
         ret := T'Last;
      elsif val <= Float (T'First) then
         ret := T'First;
      else
         ret := T (val);
      end if;
      return ret;
   end Saturated_Cast;
end units;

Tested on x86_64-pc-linux-gnu, committed on trunk

2017-09-07  Ed Schonberg  <schonb...@adacore.com>

        * sem_dim.adb (Analyze_Dimension_Type_Conversion): New procedure
        to handle properly various cases of type conversions where the
        target type and/or the expression carry dimension information.
        (Dimension_System_Root); If a subtype carries dimension
        information, obtain the source parent type that carries the
        Dimension aspect.

Index: sem_dim.adb
===================================================================
--- sem_dim.adb (revision 251836)
+++ sem_dim.adb (working copy)
@@ -35,6 +35,7 @@
 with Opt;      use Opt;
 with Rtsfind;  use Rtsfind;
 with Sem;      use Sem;
+with Sem_Aux;  use Sem_Aux;
 with Sem_Eval; use Sem_Eval;
 with Sem_Res;  use Sem_Res;
 with Sem_Util; use Sem_Util;
@@ -280,6 +281,14 @@
    --  both the identifier and the parent type of N are not dimensionless,
    --  return an error.
 
+   procedure Analyze_Dimension_Type_Conversion (N : Node_Id);
+   --  Type conversions handle conversions between literals and dimensioned
+   --  types, from dimensioned types to their base type, and between different
+   --  dimensioned systems. Dimensions of the conversion are obtained either
+   --  from those of the expression, or from the target type, and dimensional
+   --  consistency must be checked when converting between values belonging
+   --  to different dimensioned systems.
+
    procedure Analyze_Dimension_Unary_Op (N : Node_Id);
    --  Subroutine of Analyze_Dimension for unary operators. For Plus, Minus and
    --  Abs operators, propagate the dimensions from the operand to N.
@@ -301,6 +310,11 @@
    --  dimension" if Description_Needed. if N is dimensionless, return "'[']",
    --  or "is dimensionless" if Description_Needed.
 
+   function Dimension_System_Root (T : Entity_Id) return Entity_Id;
+   --  Given a type that has dimension information, return the type that is the
+   --  root of its dimension system, e.g. Mks_Type. If T is not a dimensioned
+   --  type, i.e. a standard numeric type, return Empty.
+
    procedure Dim_Warning_For_Numeric_Literal (N : Node_Id; Typ : Entity_Id);
    --  Issue a warning on the given numeric literal N to indicate that the
    --  compiler made the assumption that the literal is not dimensionless
@@ -1191,13 +1205,7 @@
             Analyze_Dimension_Subtype_Declaration (N);
 
          when  N_Type_Conversion =>
-            if In_Instance
-              and then Exists (Dimensions_Of (Expression (N)))
-            then
-               Set_Dimensions (N, Dimensions_Of (Expression (N)));
-            else
-               Analyze_Dimension_Has_Etype (N);
-            end if;
+            Analyze_Dimension_Type_Conversion (N);
 
          when N_Unary_Op =>
             Analyze_Dimension_Unary_Op (N);
@@ -1384,26 +1392,6 @@
                return Dimensions_Of (Etype (N));
             end if;
 
-         --  A type conversion may have been inserted to rewrite other
-         --  expressions, e.g. function returns. Dimensions are those of
-         --  the target type, unless this is a conversion in an instance,
-         --  in which case the proper dimensions are those of the operand,
-
-         elsif Nkind (N) = N_Type_Conversion then
-            if In_Instance
-              and then Is_Generic_Actual_Type (Etype (Expression (N)))
-            then
-               return Dimensions_Of (Etype (Expression (N)));
-
-            elsif In_Instance
-              and then Exists (Dimensions_Of (Expression (N)))
-            then
-               return Dimensions_Of (Expression (N));
-
-            else
-               return Dimensions_Of (Etype (N));
-            end if;
-
          --  Otherwise return the default dimensions
 
          else
@@ -2339,6 +2327,56 @@
       end if;
    end Analyze_Dimension_Subtype_Declaration;
 
+   ---------------------------------------
+   -- Analyze_Dimension_Type_Conversion --
+   ---------------------------------------
+
+   procedure Analyze_Dimension_Type_Conversion (N : Node_Id) is
+      Expr_Root   : constant Entity_Id :=
+                      Dimension_System_Root (Etype (Expression (N)));
+      Target_Root : constant Entity_Id :=
+                      Dimension_System_Root (Etype (N));
+
+   begin
+      --  If the expression has dimensions and the target type has dimensions,
+      --  the conversion has the dimensions of the expression. Consistency is
+      --  checked below. Converting to a non-dimensioned type such as Float
+      --  ignores the dimensions of the expression.
+
+      if Exists (Dimensions_Of (Expression (N)))
+        and then Present (Target_Root)
+      then
+         Set_Dimensions (N, Dimensions_Of (Expression (N)));
+
+      --  Otherwise the dimensions are those of the target type.
+
+      else
+         Analyze_Dimension_Has_Etype (N);
+      end if;
+
+      --  A conversion between types in different dimension systems (e.g. MKS
+      --  and British units) must respect the dimensions of expression and
+      --  type, It is up to the user to provide proper conversion factors.
+
+      --  Upward conversions to root type of a dimensioned system are legal,
+      --  and correspond to "view conversions", i.e. preserve the dimensions
+      --  of the expression; otherwise conversion must be between types with
+      --  then same dimensions. Conversions to a non-dimensioned type such as
+      --  Float lose the dimensions of the expression.
+
+      if Present (Expr_Root)
+       and then Present (Target_Root)
+       and then Etype (N) /= Target_Root
+       and then Dimensions_Of (Expression (N)) /= Dimensions_Of (Etype (N))
+      then
+         Error_Msg_N ("dimensions mismatch in conversion", N);
+         Error_Msg_N
+           ("\expression " & Dimensions_Msg_Of (Expression (N), True), N);
+         Error_Msg_N
+           ("\target type " & Dimensions_Msg_Of (Etype (N), True), N);
+      end if;
+   end Analyze_Dimension_Type_Conversion;
+
    --------------------------------
    -- Analyze_Dimension_Unary_Op --
    --------------------------------
@@ -2665,6 +2703,24 @@
           or else Dimensions_Of (T1) = Dimensions_Of (T2);
    end Dimensions_Match;
 
+   ---------------------------
+   -- Dimension_System_Root --
+   ---------------------------
+
+   function Dimension_System_Root (T : Entity_Id) return Entity_Id is
+      Root : Entity_Id;
+
+   begin
+      Root := Base_Type (T);
+
+      if Has_Dimension_System (Root) then
+         return First_Subtype (Root);   --  for example Dim_Mks
+
+      else
+         return Empty;
+      end if;
+   end Dimension_System_Root;
+
    ----------------------------------------
    -- Eval_Op_Expon_For_Dimensioned_Type --
    ----------------------------------------

Reply via email to