# Implementing Annotations **How to use the internal API to implement built-in annotations in `tao_idl` or a compiler that uses `tao_idl`.** **Table of Contents:** * [IDL Annotations](#idl-annotations) * [What Can Be Annotated](#what-can-be-annotated) * [Special Cases of Annotations](#special-cases-of-annotations) * [Union Discriminators](#union-discriminators) * [Base Types in Sequences](#base-types-in-sequences) * [Base Types in Arrays](#base-types-in-arrays) * [Defining Annotations](#defining-annotations) * [`@document` Example](#document-example) * [What Can Go in Annotations](#what-can-go-in-annotations) * [Reading Annotations in the AST](#reading-annotations-in-the-ast) * [Reading `@document` Annotations](#reading-document-annotations) * [Reading Annotations Manually](#reading-annotations-manually) * [Reading Special Cases of Annotations](#reading-special-cases-of-annotations) * [Union Discriminators](#union-discriminators-1) * [Base Types in Sequences](#base-types-in-sequences-1) * [Base Types in Arrays](#base-types-in-arrays-1) * [Extending Annotation Support](#extending-annotation-support) * [Limitations](#limitations) * [History](#history) * [TAO 2.5.5](#tao-255) * [TAO 2.5.6](#tao-256) * [TAO 2.5.7](#tao-257) * [TAO 2.5.10](#tao-2510) ## IDL Annotations Annotations are a feature of IDLv4 that allows IDL authors to pass hints to the IDL compiler that can change compiler behavior and code generation. They are similar to some uses of `#pragma`, but are more powerful because they are integrated with IDL and are more expressive. In the latest IDL specification as of writing, version 4.2, they are described in section 7.4.15.1. The concept behind annotations exists in other languages like Java and Python, as decorators, and in C++11 and C#, as attributes. Like Java and Python, annotations can appear in front of declarations, have `@` at the beginning, and look like function calls. Here is an example of what IDL using some OMG standard annotations might look like: ``` enum Urgency_t { SUPPLEMENTARY, @default_literal INFORMATIVE, CRITICAL }; @unit("Hour(s)") @range(min=0,max=23) typedef short Hours_t; @unit("Day(s)") typedef unsigned long Days_t; struct Time_t { Hours_t hours; Days_t days; }; @mutable struct Report { @key unsigned long index; @optional Time_t expiration; @optional Urgency_t urgency; string message; }; ``` ## What Can Be Annotated Annotations "may be applied to any IDL constructs or sub-constructs", as defined by the OMG. This is very vague and the OMG has not clarified this as of IDL 4.2 [(Limitation #1)](#limitation1). Because of the lack of standardization of what can be annotated and how, annotations for specific IDL elements have to be added on a as-needed basis. You can put annotations before the following things in IDL and a backend using TAO IDL's front-end library can read them: - modules - `typedef`s - constants - structures and their member values - unions and their cases - enumerations and their enumerators - interfaces, porttypes, eventtypes, components and all of their direct contents - valuetypes and most of their direct contents with the exception of these kinds of valuetype statements: - import: not supported by TAO - typeid: not supported by TAO within valuetypes - typeprefix: No corresponding AST Node to attach annotations to These are the general cases. The rest of the cases are defined in the next section. If an annotation application isn't listed in the general case list or the special case section, it is almost certainly not supported and might cause warnings if the usage is a general case and syntax errors if the usage a special case. See ["Extending Annotation Support"](#extending-annotation-support) if you're interested in adding it and are familiar with GNU Bison. ### Special Cases of Annotations The annotations on all the elements in the list above can be accessed using the `annotations()` method covered later in [Reading Annotations in the AST](#reading-annotations-in-the-ast). In the cases listed below, the annotation is used within the declaration and therefore require special access methods. See ["Reading Special Cases of Annotations"](#reading-special-cases-of-annotations) for how to have a backend read these kinds of annotations. #### Union Discriminators **[See Compiler Example](#union-discriminators-1)** ``` enum GradingSystem_t { PASS_FAIL, PASS_70, PASS_80 }; union Grade_t switch (@key GradingSystem_t) { case PASS_FAIL: boolean pass; case PASS_70: case PASS_80: default: short grade; }; ``` #### Base Types in Sequences **[See Compiler Example](#base-types-in-sequences-1)** ``` struct Event { short data; }; typedef sequence<@external Report, 12> Dozen_Events; ``` #### Base Types in Arrays **[See Compiler Example](#base-types-in-arrays-1)** ``` struct Event { short data; }; typedef Dozen_Events Event @external [12]; ``` ## Defining Annotations Annotations should be defined after the AST is initialized and ready to be used, but before any user defined IDL is processed. The recommended place for this is `BE_post_init ()` which is located in `be/be_init.cpp` in `tao_idl`. Annotations are nodes in the AST and could be defined by hand, simulating what happens in `fe/idl.yy`. However a string parsing utility has been added just for this purpose, `idl_global->eval (const char* idl, bool disable_output = false)`. `eval ()` processes IDL as if it were in an IDL file so annotations can be defined using the IDL annotation notation. You can disable warning and error messages generated by parsing by passing `true` as the second argument. ### `@document` Example As a simple example, if we wanted to make an annotation that inserted comments into the product files for documentation purposes, we could design an annotation like this: ``` @annotation document { enum API_Type { INTERNAL_API, USER_API, LEGACY_API }; string comment; API_Type api_type default INTERNAL_API; boolean deprecated default FALSE; }; ``` To use it without defining it in every IDL file, we need to embed it into `BE_post_init()`. ```C++ void BE_post_init (char *[], long) { // ... if (idl_global->idl_version_ > IDL_VERSION_3) { idl_global->eval ( "@annotation document {\n" " enum API_Type {\n" " INTERNAL_API,\n" " USER_API,\n" " LEGACY_API\n" " };\n" " string comment;\n" " API_Type api_type default INTERNAL_API;\n" " boolean deprecated default FALSE;\n" "};\n" ); } // ... } ``` The new lines aren't strictly necessary but might help if a error occurs during `eval` because it will refer to the line number of this string as though it was a file called `builtin-N`, where `N` is times `eval` has been called. By default TAO\_IDL uses IDL3 and this will cause an error when parsing the annotations. Version is controlled using `--idl-version` command line argument and ultimately `idl_global->idl_version_`. In the example above we would have to pass `--idl-version 4`. We can set it to use IDL4 by default in `BE_init ()`: ```C++ int BE_init (int &, ACE_TCHAR *[]) { // ... idl_global->default_idl_version_ = IDL_VERSION_4; // ... } void BE_post_init (char *[], long) { // Same as above ... } ``` In TAO\_IDL, `idl_global->default_idl_version_` sets `idl_global->idl_version_` after `BE_init` is called but before arguments are parsed. This gives the user a chance to override it if they really want to and allows them to query the version we're setting using `--default-idl-version`. Alternatively if it is desired to retain compatibility with older versions of TAO, use the `TAO_IDL_HAS_ANNOTATIONS` macro. ```C++ int BE_init (int &, ACE_TCHAR *[]) { // ... #ifdef TAO_IDL_HAS_ANNOTATIONS idl_global->default_idl_version_ = IDL_VERSION_4; #endif // ... } void BE_post_init (char *[], long) { // ... #ifdef TAO_IDL_HAS_ANNOTATIONS if (idl_global->idl_version_ > IDL_VERSION_3) { idl_global->eval ( // ... ); } #endif // ... } ``` This would also be used when reading the annotations later. Once the annotation is declared, it can be used in IDL: ``` @document("Struct with 1 member") struct struct1 { short x; }; @document( comment="Struct with 2 members", api_type=USER_API ) struct struct2 { short x, y; }; @document( comment="Struct with 3 members", api_type=LEGACY_API, deprecated=TRUE ) struct struct3 { short x, y, z; }; ``` However it won't do anything because nothing using the AST is looking for it, so it will be ignored. To make the program aware of the annotations, see ["Reading Annotations in the AST"](#reading-annotations-in-the-ast) below. ### What Can Go in Annotations - Annotation members can be of any type that constants can be. This includes booleans, integers, floats, enumerations, characters, and strings. - Enumerations, constants, and typedefs can be declared inside the annotation declaration, however they can not used outside the annotation expect for when passing them as parameters to the same annotation. Otherwise normal scope rules apply: Valid constant types and values from outside the annotation can be used inside it. ## Reading Annotations in the AST To get the annotations for most nodes types, use `node->annotations ().find (annotation_decl)` where `annotation_decl` can be the `AST_Annotation_Decl` object or its canonical internal TAO IDL name (see next paragraph). This will return the last `AST_Annotation_Appl*` of that type on the node or `NULL` if there no annotation of that type. Because `AST_Annotation_Appls::find` can take a `AST_Annotation_Decl`, they can be looked up after `idl_eval` creates them and cached for a slightly faster `find`. Internally, annotation local names are prefixed with `@` to prevent clashes with other elements in IDL with the same name. For example when trying to use `AST_Annotation_Appls::find (const char*)` with annotation named `bar` in a module named `foo`, the proper internal scoped name to pass as the second argument is either `foo::@bar` or `::foo::@bar` if we want to be explicit that `foo` is in the root module. In IDL, this annotation's full name would be `@foo::bar`or `@::foo::bar`. After that check, you can use index operators `[const char*]` on the annotation to get the individual members and `value()` to get the value. The last part is not straightforward, as the value is an `AST_Expression` object and `AST_Expression` is a complex class that handles constant values in TAO\_IDL. There are examples below but see `AST_Expression::AST_ExprValue` for how values can be accessed. See `include/ast_expression.h` and `ast/ast_expression.cpp` for how to use `AST_Expression`. ### Reading `@document` Annotations In this example we will use the [`@document` annotation defined above](#document-example) to generate Doxygen comments in the C++ code generated. For simplicity's sake, we will limit this example to `struct`s defined in TAO client headers. This can be accomplished by modifying the struct visitor in `be/be_visitor_structure/structure_ch.cpp`. At the top of the file, these includes should be added: ```C++ #include "ast_annotation_member.h" #include "utl_string.h" #include "ast_enum_val.h" ``` About midway though the file, in `int be_visitor_structure_ch::visit_structure (be_structure *node)` right before ```C++ *os << be_nl_2 << "struct " // ... ``` these lines would also need to be added: ```C++ AST_Annotation_Appl *document = node->annotations ().find ("::@document"); if (document) { const char *comment = dynamic_cast ((*document)["comment"])-> value ()->ev ()->u.strval->get_string (); bool deprecated = dynamic_cast ((*document)["deprecated"])-> value ()->ev ()->u.bval; /* * This is more complicated because we are trying to get the name of * the enumerator. If we just wanted the number value, we could treat the * AST_Expresssion from the annotation member as an unsigned long by using * ev ()->u.ulval. */ const char *api_type = 0; AST_Expression *api_type_val = dynamic_cast ((*document)["api_type"])-> value (); AST_Enum *api_type_enum = api_type_val->enum_parent(); if (api_type_enum) { AST_EnumVal *enum_val = api_type_enum->lookup_by_value (api_type_val); if (enum_val) { api_type = enum_val->local_name ()->get_string (); } } *os << "/**" << be_nl << " * " << comment << be_nl ; if (api_type) { *os << " *" << be_nl << " * API_TYPE: " << api_type << be_nl ; } if (deprecated) { *os << " *" << be_nl << " * \\deprecated This is deprecated" << be_nl ; } *os << " */"; } ``` Using the [`@document` use example from above](#document-usage), these are inserted into the client header file: ```C++ // ... /** * Struct with 1 member * * API_TYPE: INTERNAL_API */ struct struct1 { // ... }; // ... /** * Struct with 2 members * * API_TYPE: USER_API */ struct struct2 { // ... }; /** * Struct with 3 members * * API_TYPE: LEGACY_API * * \deprecated This is deprecated */ struct struct3 { // ... }; // ... ``` #### Reading Annotations Manually `AST_Annotation_Appls::find ()` is convenient but only returns the last annotation of the passed annotation type. If we want the first one, handle multiple annotations of the same type, read all the annotations, or some other subset, we will have to do what `find ()` is doing for us, which is just iterating over the `AST_Annotation_Appl`s and checking `annotation_decl ()`. ```C++ AST_Annotation_Appls &annotations = /* ... */; AST_Annotation_Decl *annotation = /* ... */; for (AST_Annotation_Appls::iterator i = annotations.begin (); i != annotations.end (); ++i) { AST_Annotation_Appl *appl = i->get (); if (appl && appl->annotation_decl () == annotation) { // Do work with annotation application } } ``` ### Reading Special Cases of Annotations Annotations placed before a definition in a scope are interpreted as annotating the node that is being defined. Annotations in other places require special grammar and special handling in the API. The following cases show how to read annotations from these special cases. **NOTE:** To access these methods on a type that has been "`typedef`-ed", it must be resolved completely using `AST_Type *primitive_base_type ()` and a `dynamic_cast` to the correct type as these methods are specific to these classes. #### Union Discriminators **[See IDL Example](#union-discriminators)** ```C++ AST_Union *node = /* ... */; AST_Annotation_Appl *document = node->disc_annotations ().find ("::@key"); ``` #### Base Types in Sequences **[See IDL Example](#base-types-in-sequences)** ```C++ AST_Sequence *node = /* ... */; AST_Annotation_Appl *document = node->base_type_annotations ().find ("::@external"); ``` #### Base Types in Arrays **[See IDL Example](#base-types-in-arrays)** ```C++ AST_Array *node = /* ... */; AST_Annotation_Appl *document = node->base_type_annotations ().find ("::@external"); ``` ## Extending Annotation Support **NOTE: This section assumes familarity with GNU Bison and only covers the general concept of extending annotation support.** How to extend support for annotations on a particular IDL element depends on a few things. In the `fe/idl.ypp` bison file, if the annotation would be matched by the `at_least_one_definition` token, like it would when annotating a structure, or by the `at_least_one_export` token, like it would be when annotating a interface operation, the change is simple in principle: 1. Make sure the `AST_Decl*` node of what you want to annotate is being passed up the to generic annotation code in `at_least_one_export` or `at_least_one_definition`. 1. Implement `virutal bool annotatable() const` in the `AST_*` files of the node type to return `true`. The default implementation of `AST_Decl` returns `false`. If you want to implement a annotation that goes within an IDL element, like annotating a union discriminator, that is more complicated. It will involve modifying the grammar to accept the annotations and adding a special cases method to the node's class, like in ["Reading Special Cases of Annotations"](#reading-special-cases-of-annotations). Finally, if you do extend annotation support, please update the annotation test in `$TAO_ROOT/tests/IDLv4/annotations/be_init.cpp` and this file, specifically the ["What Can Be Annotated"](#what-can-be-annotated) and ["History"](#history) sections. Also update ["Reading Special Cases of Annotations"](#reading-special-cases-of-annotations) if you've added support for a special case. ## Limitations The current limitations exist in TAO\_IDL annotation implementation as of writing: 1. Because of lack of any specification in IDL for where annotations can go, annotations in places other than what's listed in ["What Can be Annotated"](#what-can-be-annotated) can cause syntax errors, even if the IDL works with other IDL tools. 2. Even though this is implicitly allowed by the IDL specification, Annotations whose local names clash with IDL keywords are not supported. This includes the OMG standard annotations `default` and `oneway`. ## History ### TAO 2.5.5 **Any usage of `UTL_find_annotation(X, Y)` should be replaced with `X.find(Y)`.** After TAO 2.5.4 was released, which introduced annotations, there was a change to fix memory leaks caused by annotations. This change involved replacing `typedef ACE_Vector AST_Annotation_Appls` with a class of the same name. This also allowed for moving `UTL_find_annotation` into `AST_Annotation_Appls` as `find` for a nicer design. ### TAO 2.5.6 The TAO IDL Frontend no longer internally prefixes annotation names and annotation member names with `_cxx_` if they are also a C++ keyword. ### TAO 2.5.7 - The TAO IDL Frontend now supports annotations on interfaces, operations, and attributes. - `idl_global->eval` now will produce error and warning messages. This can be silenced by passing `true` as a second argument. - Expanded documentation on what can be annotated and how to extend annotation support. ### TAO 2.5.10 - Annotation support extended: - All the direct contents of interfaces - The porttypes, eventtypes, components and all their direct contents - Valuetypes and most of their direct contents