LCOV - code coverage report
Current view: top level - spb-proto-compiler/ast - ast-messages-order.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 84.8 % 145 123
Test Date: 2025-05-23 14:18:14 Functions: 96.0 % 25 24

            Line data    Source code
       1              : /***************************************************************************\
       2              : * Name        : ast render                                                  *
       3              : * Description : resolve types in ast tree                                   *
       4              : * Author      : antonin.kriz@gmail.com                                      *
       5              : * ------------------------------------------------------------------------- *
       6              : * This is free software; you can redistribute it and/or modify it under the *
       7              : * terms of the MIT license. A copy of the license can be found in the file  *
       8              : * "LICENSE" at the root of this distribution.                               *
       9              : \***************************************************************************/
      10              : 
      11              : #include "ast-types.h"
      12              : #include "dumper/header.h"
      13              : #include "proto-field.h"
      14              : #include "proto-file.h"
      15              : #include "proto-message.h"
      16              : #include <algorithm>
      17              : #include <array>
      18              : #include <cerrno>
      19              : #include <cstddef>
      20              : #include <parser/char_stream.h>
      21              : #include <algorithm>
      22              : #include <string_view>
      23              : 
      24              : namespace
      25              : {
      26          192 : [[nodiscard]] auto is_imported( std::string_view full_type, const proto_files & imports ) -> bool
      27              : {
      28          192 :     return std::any_of( imports.begin( ), imports.end( ),
      29            6 :                         [ full_type ]( const auto & import ) -> bool
      30              :                         {
      31            6 :                             return full_type.size( ) > import.package.name.size( ) &&
      32            8 :                                 full_type[ import.package.name.size( ) ] == '.' &&
      33            8 :                                 full_type.starts_with( import.package.name );
      34          192 :                         } );
      35              : }
      36              : 
      37         1710 : [[nodiscard]] auto is_enum( std::string_view type, const proto_enums & enums ) -> bool
      38              : {
      39         1710 :     return std::any_of( enums.begin( ), enums.end( ), [ type ]( const auto & enum_field ) -> bool
      40         4067 :                         { return type == enum_field.name; } );
      41              : }
      42              : 
      43          794 : [[nodiscard]] auto is_sub_message( std::string_view type, const proto_messages & messages ) -> bool
      44              : {
      45          794 :     return std::any_of( messages.begin( ), messages.end( ),
      46         1618 :                         [ type ]( const auto & message ) -> bool { return type == message.name; } );
      47              : }
      48              : 
      49          747 : [[nodiscard]] auto is_resolved_sub_message( std::string_view type, const proto_messages & messages )
      50              :     -> bool
      51              : {
      52          747 :     return std::any_of( messages.begin( ), messages.end( ), [ type ]( const auto & message ) -> bool
      53        22693 :                         { return type == message.name && message.resolved > 0; } );
      54              : }
      55              : 
      56          956 : [[nodiscard]] auto is_sub_oneof( std::string_view type, const proto_oneofs & oneofs ) -> bool
      57              : {
      58          956 :     return std::any_of( oneofs.begin( ), oneofs.end( ),
      59          962 :                         [ type ]( const auto & oneof ) -> bool { return type == oneof.name; } );
      60              : }
      61              : 
      62              : struct search_state
      63              : {
      64              :     enum resolve_mode
      65              :     {
      66              :         //- only check if type is already defined
      67              :         dependencies_only,
      68              :         //- for optional fields create an std::unique_ptr< T > type and forward declare T
      69              :         //- to break cyclic dependency
      70              :         optional_pointers,
      71              :     };
      72              : 
      73              :     resolve_mode mode        = dependencies_only;
      74              :     size_t resolved_messages = 0;
      75              :     const proto_files & imports;
      76              :     const proto_file & file;
      77              : };
      78              : 
      79              : /**
      80              :  * @brief search ctx to hold relation 'message -> parent_message'
      81              :  *        the relation is not stored in proto_message because its not needed until now
      82              :  *        and the messages get sorted (moved) later so the parent pointers will be invalid anyways
      83              :  *        the parent relation is used for type dependency check and for proper order of structs
      84              :  * definition in the generated *.pb.h header file, because C++ needs proper order of type
      85              :  * dependencies. The proper order is defined by the value of `.resolved` for every message
      86              :  *
      87              :  */
      88              : struct search_ctx
      89              : {
      90              :     proto_message & message;
      91              :     //- parent message (can be null for top level)
      92              :     search_ctx * p_parent;
      93              :     search_state & state;
      94              : };
      95              : 
      96              : /**
      97              :  * @brief if the field is self-referencing and can be forward declared
      98              :  *        field label must be LABEL_OPTIONAL or LABEL_PTR or LABEL_REPEATED
      99              :  *
     100              :  * @param field filed
     101              :  * @param ctx search context
     102              :  * @return true if field can be forward declared
     103              :  */
     104          855 : [[nodiscard]] auto is_self( proto_field & field, const search_ctx & ctx ) -> bool
     105              : {
     106          855 :     if( field.type == proto_field::Type::MESSAGE && field.type_name == ctx.message.name )
     107              :     {
     108            6 :         switch( field.label )
     109              :         {
     110            0 :         case proto_field::Label::NONE:
     111            0 :             throw_parse_error( ctx.state.file, field.name,
     112            0 :                                "Field '" + std::string( field.name ) +
     113            0 :                                    "' cannot be self-referencing (make it optional)" );
     114            2 :         case proto_field::Label::OPTIONAL:
     115            2 :             field.label = proto_field::Label::PTR;
     116            2 :             return true;
     117            4 :         case proto_field::Label::REPEATED:
     118              :         case proto_field::Label::PTR:
     119            4 :             return true;
     120              :         }
     121              :     }
     122          849 :     return false;
     123              : }
     124              : 
     125              : /**
     126              :  * @brief if this dependency can be forward declared, do it
     127              :  *        field label must be LABEL_REPEATED or LABEL_PTR
     128              :  *
     129              :  * @param field field
     130              :  * @param ctx search context
     131              :  * @return true if field can be forward declared
     132              :  */
     133          190 : [[nodiscard]] auto is_forwarded( proto_field & field, search_ctx & ctx ) -> bool
     134              : {
     135          190 :     if( ctx.p_parent == nullptr )
     136            0 :         return false;
     137              : 
     138         5127 :     for( auto & message : ctx.p_parent->message.messages )
     139              :     {
     140         5062 :         if( field.type_name == message.name )
     141              :         {
     142          125 :             switch( field.label )
     143              :             {
     144            0 :             case proto_field::Label::NONE:
     145          125 :                 return false;
     146           59 :             case proto_field::Label::OPTIONAL:
     147           59 :                 switch( ctx.state.mode )
     148              :                 {
     149           58 :                 case search_state::dependencies_only:
     150           58 :                     return false;
     151              : 
     152            1 :                 case search_state::optional_pointers:
     153            1 :                     field.label = proto_field::Label::PTR;
     154              :                     //
     155              :                     //- revert the mode to dependencies_only and try again
     156              :                     //- reason is to create as little pointers as possible
     157              :                     //
     158            1 :                     ctx.state.mode = search_state::dependencies_only;
     159              :                 }
     160              :                 [[fallthrough]];
     161              :             case proto_field::Label::REPEATED:
     162              :             case proto_field::Label::PTR:
     163           67 :                 ctx.p_parent->message.forwards.insert( message.name );
     164           67 :                 return true;
     165              :             }
     166              :         }
     167              :     }
     168           65 :     return false;
     169              : }
     170              : 
     171              : /**
     172              :  * @brief if this dependency references the parent message and can be forward declared
     173              :  *        field label must be LABEL_OPTIONAL or LABEL_REPEATED or LABEL_PTR
     174              :  *
     175              :  * @param field field
     176              :  * @param ctx search context
     177              :  * @return true if field can be forward declared
     178              :  */
     179          579 : [[nodiscard]] auto is_parent( proto_field & field, const search_ctx & ctx ) -> bool
     180              : {
     181          579 :     if( ctx.p_parent == nullptr )
     182            0 :         return false;
     183              : 
     184          579 :     if( field.type_name == ctx.p_parent->message.name )
     185              :     {
     186            1 :         switch( field.label )
     187              :         {
     188            0 :         case proto_field::Label::NONE:
     189            0 :             throw_parse_error( ctx.state.file, field.name,
     190            0 :                                "Field '" + std::string( field.name ) +
     191            0 :                                    "' cannot reference parent (make it optional)" );
     192            1 :         case proto_field::Label::OPTIONAL:
     193            1 :             field.label = proto_field::Label::PTR;
     194            1 :             return true;
     195            0 :         case proto_field::Label::REPEATED:
     196              :         case proto_field::Label::PTR:
     197            0 :             return true;
     198              :         }
     199              :     }
     200          578 :     return false;
     201              : }
     202              : 
     203         1002 : [[nodiscard]] auto is_defined_in_parents( std::string_view type, const search_ctx & ctx ) -> bool
     204              : {
     205         1002 :     if( ctx.p_parent == nullptr )
     206          192 :         return false;
     207              : 
     208          810 :     const auto & message = ctx.p_parent->message;
     209         1187 :     if( is_enum( type, message.enums ) || is_resolved_sub_message( type, message.messages ) ||
     210          377 :         is_sub_oneof( type, message.oneofs ) )
     211              :     {
     212          433 :         return true;
     213              :     }
     214              : 
     215          377 :     return is_defined_in_parents( type, *ctx.p_parent );
     216              : }
     217              : 
     218          539 : [[nodiscard]] auto all_types_are_resolved( const proto_messages & messages ) -> bool
     219              : {
     220          539 :     return std::all_of( messages.begin( ), messages.end( ),
     221         1599 :                         []( const auto & message ) { return message.resolved > 0; } );
     222              : }
     223              : 
     224          466 : void mark_message_as_resolved( search_ctx & ctx )
     225              : {
     226          466 :     assert( ctx.message.resolved == 0 );
     227              : 
     228          466 :     ctx.message.resolved = ++ctx.state.resolved_messages;
     229          466 : }
     230              : 
     231         1873 : [[nodiscard]] auto resolve_message_field( search_ctx & ctx, proto_field & field ) -> bool
     232              : {
     233              :     //- check only the first type (before .) and leave the rest for the C++ compiler to check
     234              :     //- TODO: check full type name
     235         1873 :     const auto type_name = field.type_name.substr( 0, field.type_name.find( '.' ) );
     236              : 
     237         2728 :     return is_scalar( field.type ) || is_self( field, ctx ) ||
     238          849 :         is_enum( field.type_name, ctx.message.enums ) ||
     239          744 :         is_sub_message( type_name, ctx.message.messages ) ||
     240          579 :         is_sub_oneof( type_name, ctx.message.oneofs ) || is_parent( field, ctx ) ||
     241          578 :         is_defined_in_parents( type_name, ctx ) ||
     242         4601 :         is_imported( field.type_name, ctx.state.imports ) || is_forwarded( field, ctx );
     243              : }
     244              : 
     245           63 : [[nodiscard]] auto resolve_field( search_ctx & ctx, const proto_field & field ) -> bool
     246              : {
     247           63 :     const auto type_name = field.type_name.substr( 0, field.type_name.find( '.' ) );
     248              : 
     249          114 :     return is_scalar( field.type ) || is_enum( field.type_name, ctx.message.enums ) ||
     250           50 :         is_sub_message( type_name, ctx.message.messages ) ||
     251          114 :         is_defined_in_parents( type_name, ctx ) ||
     252           63 :         is_imported( field.type_name, ctx.state.imports );
     253              : }
     254              : 
     255          623 : void resolve_message_fields( search_ctx & ctx )
     256              : {
     257          623 :     if( ctx.message.resolved > 0 )
     258            0 :         return;
     259              : 
     260         2373 :     for( auto & field : ctx.message.fields )
     261              :     {
     262         1873 :         if( !resolve_message_field( ctx, field ) )
     263          123 :             return;
     264              :     }
     265              : 
     266          552 :     for( const auto & map : ctx.message.maps )
     267              :     {
     268           52 :         if( !resolve_field( ctx, map.value ) )
     269            0 :             return;
     270              :     }
     271              : 
     272          504 :     for( const auto & oneof : ctx.message.oneofs )
     273              :     {
     274           15 :         for( const auto & field : oneof.fields )
     275              :         {
     276           11 :             if( !resolve_field( ctx, field ) )
     277            0 :                 return;
     278              :         }
     279              :     }
     280              : 
     281          500 :     if( all_types_are_resolved( ctx.message.messages ) )
     282          466 :         mark_message_as_resolved( ctx );
     283              : }
     284              : 
     285         1045 : void resolve_message_dependencies( search_ctx & ctx )
     286              : {
     287         1045 :     if( ctx.message.resolved > 0 )
     288          422 :         return;
     289              : 
     290         1643 :     for( auto & message : ctx.message.messages )
     291              :     {
     292              :         auto sub_ctx = search_ctx{
     293              :             .message  = message,
     294              :             .p_parent = &ctx,
     295         1020 :             .state    = ctx.state,
     296         1020 :         };
     297              : 
     298         1020 :         resolve_message_dependencies( sub_ctx );
     299              :     }
     300          623 :     resolve_message_fields( ctx );
     301              : }
     302              : 
     303            0 : [[noreturn]] void dump_unresolved_message( const proto_messages & messages,
     304              :                                            const proto_file & file )
     305              : {
     306            0 :     for( const auto & message : messages )
     307              :     {
     308            0 :         if( message.resolved == 0 )
     309              :         {
     310            0 :             throw_parse_error( file, message.name, "type dependency can't be resolved" );
     311              :         }
     312              :     }
     313            0 :     throw_parse_error( file, file.content, "type dependency can't be resolved" );
     314              : }
     315              : 
     316          466 : void sort_messages( proto_messages & messages )
     317              : {
     318          466 :     std::sort( messages.begin( ), messages.end( ),
     319         2541 :                []( const auto & a, const auto & b ) { return a.resolved < b.resolved; } );
     320              : 
     321          918 :     for( auto & message : messages )
     322              :     {
     323          452 :         sort_messages( message.messages );
     324              :     }
     325          466 : }
     326              : 
     327              : }// namespace
     328              : 
     329           14 : void resolve_messages_order( proto_file & file )
     330              : {
     331           14 :     auto state = search_state{
     332              :         .mode              = search_state::dependencies_only,
     333              :         .resolved_messages = 0,
     334           14 :         .imports           = file.file_imports,
     335              :         .file              = file,
     336           14 :     };
     337              : 
     338           39 :     while( !all_types_are_resolved( file.package.messages ) )
     339              :     {
     340           25 :         const auto resolved_messages = state.resolved_messages;
     341              : 
     342           25 :         auto top_level_ctx = search_ctx{
     343           25 :             .message  = file.package,
     344              :             .p_parent = nullptr,
     345              :             .state    = state,
     346           25 :         };
     347              : 
     348           25 :         resolve_message_dependencies( top_level_ctx );
     349              : 
     350           25 :         if( resolved_messages == state.resolved_messages )
     351              :         {
     352            1 :             if( state.mode == search_state::dependencies_only )
     353              :             {
     354              :                 //- no messages were resolved using only dependencies?
     355              :                 //- try to break cyclic dependency by using forward pointers declaration
     356            1 :                 state.mode = search_state::optional_pointers;
     357            1 :                 continue;
     358              :             }
     359              : 
     360            0 :             dump_unresolved_message( file.package.messages, file );
     361              :         }
     362              :     }
     363              : 
     364           14 :     sort_messages( file.package.messages );
     365           14 : }
        

Generated by: LCOV version 2.0-1