5. Advice & gotchas

This section of the manual regroups together guidance that we consider vital to develop with Libadalang, but unlike the tutorial, this is meant as a grab bag that will be useful to new developers as well as experienced Libadalang developers. We advise people using Libadalang to come back to this section from time to time to see if there are new advices & gotchas.

5.1. Ada parsing: Identifiers starting with a $

Historically, Libadalang has parsed identifiers starting with a dollar ($) as regular identifiers, even though it is not valid Ada. The aim for that was to provide minimal support for GNATprep style preprocessing.

However, nowadays, full support for preprocessing has been implemented in Libadalang, via the File_Readers and Preprocessing APIs. If you use those, identifiers starting with $ will be interpreted as preprocessor identifiers. Else, they will just be parsed normally.

Despite Libadalang having a real preprocessor, this behavior has been kept for backward compatibility with some LAL projects, notably Ada-Renaissance, and will be preserved onwards.

5.2. Ada API: Up & down casting Ada_Node instances

In Analysis, a hierarchy of tagged types is declared, rooting in the Ada_Node type. It is important to understand that those node types are equivalent to a class wide access to a node.

They’re in reality kind of a fat pointer, containing a pointer to the syntactic node, and some more context, like generic instantiation information.

A consequence of this design choice is that you cannot use the regular Ada conversion functions to up/down cast node types:

1 A : Ada_Node := Unit.Root;
2
3 --  This code is invalid and will raise an error
4 B : Compilation_Unit := Compilation_Unit (A);
5
6 --  This code is valid and will work as you expect
7 C : Compilation_Unit := A.As_Compilation_Unit;

5.3. Ada API: Equality between Ada_Node and derived types

5.3.1. Summary

As said previously, nodes types are like fat pointers to a node, something like (pointer_to_node, more_entity_info).

For complex reasons we’re exposing an "=" operator on classwide nodes, and that’s the one you’ll use by default, but when instantiating generic containers with nodes, if you need an equivalent function, you cannot use "=", because that will use the default equality operator. Instead, you need to use the Ada_Node.Equals function:

with Libadalang.Analysis; use Libadalang.Analysis;

...

package Node_Sets is new Ada.Containers.Hashed_Sets
  (Element_Type        => Ada_Node,
   Hash                => Hash,
   Equivalent_Elements => Equals,
   "="                 => Equals);

5.4. Standard unit peculiarities

In Ada, the Standard package is special: is acting such as a built-in compilation unit and is the root of all other units: these are technically child units of Standard. It is the package that defines “native” declarations such as Boolean, Integer but also Character, Wide_Wide_String and the Constraint_Error exception.

Libadalang implements this package as a special analysis unit, heavily inspired from GNAT’s own implementation of this package (see GNAT’s -gnatS flag):

package Standard is

  pragma Pure (Standard);

  type Boolean is (False, True);

  type Integer is range L .. H;
  subtype Natural  is Integer range L .. H;
  subtype Positive is Integer range L .. H;
  type Short_Short_Integer is range L .. H;
  type Short_Integer       is range L .. H;
  type Long_Integer        is range L .. H;
  type Long_Long_Integer   is range L .. H;

  type Short_Float     is digits D range L .. H;
  type Float           is digits D range L .. H;
  type Long_Float      is digits D range L .. H;
  type Long_Long_Float is digits D range L .. H;

  type Character is ('A');
  type Wide_Character is ('A');
  type Wide_Wide_Character is ('A');

  package ASCII is
     NUL   : constant Character := Character'Val (16#00#);
     SOH   : constant Character := Character'Val (16#01#);
     STX   : constant Character := Character'Val (16#02#);
     ETX   : constant Character := Character'Val (16#03#);
     EOT   : constant Character := Character'Val (16#04#);
     ENQ   : constant Character := Character'Val (16#05#);
     ACK   : constant Character := Character'Val (16#06#);
     BEL   : constant Character := Character'Val (16#07#);
     BS    : constant Character := Character'Val (16#08#);
     HT    : constant Character := Character'Val (16#09#);
     LF    : constant Character := Character'Val (16#0A#);
     VT    : constant Character := Character'Val (16#0B#);
     FF    : constant Character := Character'Val (16#0C#);
     CR    : constant Character := Character'Val (16#0D#);
     SO    : constant Character := Character'Val (16#0E#);
     SI    : constant Character := Character'Val (16#0F#);
     DLE   : constant Character := Character'Val (16#10#);
     DC1   : constant Character := Character'Val (16#11#);
     DC2   : constant Character := Character'Val (16#12#);
     DC3   : constant Character := Character'Val (16#13#);
     DC4   : constant Character := Character'Val (16#14#);
     NAK   : constant Character := Character'Val (16#15#);
     SYN   : constant Character := Character'Val (16#16#);
     ETB   : constant Character := Character'Val (16#17#);
     CAN   : constant Character := Character'Val (16#18#);
     EM    : constant Character := Character'Val (16#19#);
     SUB   : constant Character := Character'Val (16#1A#);
     ESC   : constant Character := Character'Val (16#1B#);
     FS    : constant Character := Character'Val (16#1C#);
     GS    : constant Character := Character'Val (16#1D#);
     RS    : constant Character := Character'Val (16#1E#);
     US    : constant Character := Character'Val (16#1F#);
     DEL   : constant Character := Character'Val (16#7F#);
     Exclam     : constant Character := '!';
     Quotation  : constant Character := '""';
     Sharp      : constant Character := '#';
     Dollar     : constant Character := '$';
     Percent    : constant Character := '%';
     Ampersand  : constant Character := '&';
     Colon      : constant Character := ':';
     Semicolon  : constant Character := ';';
     Query      : constant Character := '?';
     At_Sign    : constant Character := '@';
     L_Bracket  : constant Character := '[';
     Back_Slash : constant Character := '\';
     R_Bracket  : constant Character := ']';
     Circumflex : constant Character := '^';
     Underline  : constant Character := '_';
     Grave      : constant Character := '`';
     L_Brace    : constant Character := '{';
     Bar        : constant Character := '|';
     R_Brace    : constant Character := '}';
     Tilde      : constant Character := '~';
     LC_A : constant Character := 'a';
     LC_B : constant Character := 'b';
     LC_C : constant Character := 'c';
     LC_D : constant Character := 'd';
     LC_E : constant Character := 'e';
     LC_F : constant Character := 'f';
     LC_G : constant Character := 'g';
     LC_H : constant Character := 'h';
     LC_I : constant Character := 'i';
     LC_J : constant Character := 'j';
     LC_K : constant Character := 'k';
     LC_L : constant Character := 'l';
     LC_M : constant Character := 'm';
     LC_N : constant Character := 'n';
     LC_O : constant Character := 'o';
     LC_P : constant Character := 'p';
     LC_Q : constant Character := 'q';
     LC_R : constant Character := 'r';
     LC_S : constant Character := 's';
     LC_T : constant Character := 't';
     LC_U : constant Character := 'u';
     LC_V : constant Character := 'v';
     LC_W : constant Character := 'w';
     LC_X : constant Character := 'x';
     LC_Y : constant Character := 'y';
     LC_Z : constant Character := 'z';
  end ASCII;

  type String is array (Positive range <>) of Character;
  pragma Pack (String);

  type Wide_String is array (Positive range <>) of Wide_Character;
  pragma Pack (Wide_String);

  type Wide_Wide_String is array (Positive range <>) of Wide_Wide_Character;

  type Duration is delta D range L .. H;
  for Duration'Small use S;

  type Universal_Int_Type_ is range -1 .. 1;
  type Universal_Real_Type_ is digits 16;
  type Universal_Fixed_Type_ is digits 16;

  package root_types_ is
     type root_integer is range -1 .. 1;
     type root_real digits 16;
  end root_types_;

  Constraint_Error : exception;
  Program_Error    : exception;
  Storage_Error    : exception;
  Tasking_Error    : exception;
  Abort_Signal     : exception;
end Standard;

Warning: this is a stub, so don’t rely on the implementation using this specific source as it could change in future versions of Libadalang.

Also be aware that, in order for Libadalang to use the right values for range bounds, digits and 'Small, it is necessary to communicate the relevant target information (Libadalang.Target_Info) to Libadalang’s analysis context, either calling Libadalang.Analysis.Set_Target_Information, or letting Libadalang.Analysis.Create_Context_From_Project fetch this information from the project file.

Note that the Character, Wide_Character and Wide_Wide_Character types are represented with partial definitions:

type Character is ('A');
type Wide_Character is ('A');
type Wide_Wide_Character is ('A');

Defining all values for each is not realistic, as for instance Wide_Wide_Character has 4 billion values: it is not reasonable to allocate memory for all of them. Nevertheless, undefined characters are properly supported and synthesized on-demand, which means that any characters from any sets can be properly handled by Libadalang.

5.5. The origin parameter

In Libadalang many semantic properties, like P_Referenced_Decl, have an origin parameter. That’s because for many semantic queries, the answer to your query might differ depending on where you come from.

For example in the following code:

package P is
   type T is private;
private
   type T is record
      A, B: Integer;
   end record;
end P;