• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 17504812320

05 Sep 2025 09:23PM UTC coverage: 59.904% (+0.08%) from 59.826%
17504812320

push

github

web-flow
Add Escript support for uninitialized class functions (#808)

* update grammar

* AST nodes and test

* Address self-review comments
- Replace `super` comment with something more explanatory
- Rename `make_user_function`

* add class analysis for uninit functions

* fix ast test after disallowing static uninit funcs

* use std::ranges::find_if

* add tests

* use SUPER constant; add test

* Remove unreachable code leftover from #809

Since #809, the super function is only generated for a class when
registering a parent class, and therefore it will always have a body. A
"No base class defines a constructor" will only occur if super was never
generated, and is handled in the "No function linked through
FunctionResolver" section of semantic analyzer.

* Address review comments
- use std::ranges::move instead of move iterators
- use std::is_same_v<> vs std::is_same<>::value
- use switch vs chained ternary conditionals

* update escript guide, create tests that are in escript guide

* add core-changes

* Error on uninit and defined constructor

* Move methods' FunctionLink construction to builder

This allows the error message for the uninit function to have a "See
Also" that point to the defined function.

* Move error checks for uninit and defined functions

These semantic checks should be in the ... SemanticAnalyzer.

* fix typo in core-changes

154 of 168 new or added lines in 7 files covered. (91.67%)

1 existing line in 1 file now uncovered.

43860 of 73217 relevant lines covered (59.9%)

427918.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.26
/pol-core/bscript/compiler/astbuilder/UserFunctionBuilder.cpp
1
#include "UserFunctionBuilder.h"
2

3
#include "bscript/compiler/Report.h"
4
#include "bscript/compiler/ast/Argument.h"
5
#include "bscript/compiler/ast/ClassBody.h"
6
#include "bscript/compiler/ast/ClassDeclaration.h"
7
#include "bscript/compiler/ast/ClassParameterDeclaration.h"
8
#include "bscript/compiler/ast/ClassParameterList.h"
9
#include "bscript/compiler/ast/Expression.h"
10
#include "bscript/compiler/ast/FunctionBody.h"
11
#include "bscript/compiler/ast/FunctionCall.h"
12
#include "bscript/compiler/ast/FunctionParameterDeclaration.h"
13
#include "bscript/compiler/ast/FunctionParameterList.h"
14
#include "bscript/compiler/ast/Identifier.h"
15
#include "bscript/compiler/ast/Statement.h"
16
#include "bscript/compiler/ast/TopLevelStatements.h"
17
#include "bscript/compiler/ast/UninitializedFunctionDeclaration.h"
18
#include "bscript/compiler/ast/UserFunction.h"
19
#include "bscript/compiler/ast/VarStatement.h"
20
#include "bscript/compiler/astbuilder/BuilderWorkspace.h"
21
#include "bscript/compiler/astbuilder/FunctionResolver.h"
22
#include "bscript/compiler/model/ClassLink.h"
23
#include "bscript/compiler/model/CompilerWorkspace.h"
24
#include "bscript/compiler/model/FunctionLink.h"
25
#include "bscript/compiler/model/ScopeName.h"
26

27
#include <algorithm>
28

29
using EscriptGrammar::EscriptParser;
30

31
namespace Pol::Bscript::Compiler
32
{
33
UserFunctionBuilder::UserFunctionBuilder( const SourceFileIdentifier& source_file_identifier,
3,779✔
34
                                          BuilderWorkspace& workspace )
3,779✔
35
    : CompoundStatementBuilder( source_file_identifier, workspace )
3,779✔
36
{
37
}
3,779✔
38

39
std::unique_ptr<UserFunction> UserFunctionBuilder::function_declaration(
2,599✔
40
    EscriptParser::FunctionDeclarationContext* ctx, const std::string& class_name )
41
{
42
  std::string name = text( ctx->IDENTIFIER() );
2,599✔
43
  return make_function_like<UserFunction>( name, ctx, ctx->EXPORTED(), class_name,
5,198✔
44
                                           ctx->ENDFUNCTION() );
5,198✔
45
}
2,599✔
46

47
std::unique_ptr<UserFunction> UserFunctionBuilder::function_expression(
540✔
48
    EscriptGrammar::EscriptParser::FunctionExpressionContext* ctx )
49
{
50
  std::string name = FunctionResolver::function_expression_name( location_for( *ctx->AT() ) );
540✔
51
  return make_function_like<UserFunction>( name, ctx, false, "", ctx->RBRACE() );
1,080✔
52
}
540✔
53

54
std::unique_ptr<ClassDeclaration> UserFunctionBuilder::class_declaration(
640✔
55
    EscriptGrammar::EscriptParser::ClassDeclarationContext* ctx, Node* class_body )
56
{
57
  std::string class_name = text( ctx->IDENTIFIER() );
640✔
58

59
  if ( Clib::caseInsensitiveEqual( class_name, Compiler::SUPER ) )
640✔
60
  {
61
    workspace.report.error( location_for( *ctx->IDENTIFIER() ),
2✔
62
                            "The class name 'super' is reserved." );
63
    return nullptr;
2✔
64
  }
65

66
  std::vector<std::unique_ptr<ClassParameterDeclaration>> parameters;
638✔
67
  std::vector<std::unique_ptr<UninitializedFunctionDeclaration>> uninit_functions;
638✔
68
  std::vector<std::shared_ptr<ClassLink>> base_classes;
638✔
69
  ClassMethodMap methods;
638✔
70
  std::unique_ptr<FunctionLink> constructor_link;
638✔
71
  bool is_child = false;
638✔
72

73
  if ( auto function_parameters = ctx->classParameters() )
638✔
74
  {
75
    if ( auto param_list = function_parameters->classParameterList() )
638✔
76
    {
77
      for ( auto parameter_name : param_list->IDENTIFIER() )
691✔
78
      {
79
        auto baseclass_name = text( parameter_name );
412✔
80

81
        auto class_param_decl = std::make_unique<ClassParameterDeclaration>(
82
            location_for( *parameter_name ), baseclass_name );
412✔
83

84
        // Register with the FunctionResolver the class parameter's constructor
85
        // link. It will get resolved to the class constructor during the
86
        // second-pass AST visiting.
87
        base_classes.push_back(
412✔
88
            std::make_shared<ClassLink>( location_for( *parameter_name ), baseclass_name ) );
824✔
89

90
        workspace.function_resolver.register_class_link( ScopeName( baseclass_name ),
412✔
91
                                                         base_classes.back() );
412✔
92

93
        workspace.function_resolver.register_function_link(
412✔
94
            ScopableName( baseclass_name, baseclass_name ), class_param_decl->constructor_link );
824✔
95

96
        is_child = true;
412✔
97
        parameters.push_back( std::move( class_param_decl ) );
412✔
98
      }
691✔
99
    }
100
  }
101

102
  if ( auto classBody = ctx->classBody() )
638✔
103
  {
104
    for ( auto classStatement : classBody->classStatement() )
1,593✔
105
    {
106
      if ( auto func_decl = classStatement->functionDeclaration() )
955✔
107
      {
108
        auto func_name = text( func_decl->IDENTIFIER() );
840✔
109
        auto func_loc = location_for( *func_decl );
840✔
110

111
        // Register the user function as an available parse tree only if it is not `super` for child
112
        // classes.
113
        auto is_super = Clib::caseInsensitiveEqual( func_name, Compiler::SUPER );
840✔
114

115
        if ( is_super && is_child )
840✔
116
        {
117
          workspace.report.error( func_loc, "The 'super' function is reserved for child classes." );
2✔
118
        }
119
        else
120
        {
121
          workspace.function_resolver.register_available_scoped_function( func_loc, class_name,
838✔
122
                                                                          func_decl );
123
        }
124

125

126
        // Check if the function is a constructor:
127
        // 1. The function has parameters.
128
        if ( auto param_list = func_decl->functionParameters()->functionParameterList() )
840✔
129
        {
130
          if ( auto func_params = param_list->functionParameter(); !func_params.empty() )
785✔
131
          {
132
            std::string parameter_name = text( func_params.front()->IDENTIFIER() );
785✔
133

134
            // 2. The first parameter is named `this`.
135
            if ( Clib::caseInsensitiveEqual( parameter_name, "this" ) )
785✔
136
            {
137
              // 3. The function name is the same as the class name: constructor
138
              if ( Clib::caseInsensitiveEqual( func_name, class_name ) )
718✔
139
              {
140
                constructor_link = std::make_unique<FunctionLink>( func_loc, class_name,
454✔
141
                                                                   true /* requires_ctor */ );
908✔
142
              }
143
              // 3b. Otherwise: method
144
              else if ( !methods.contains( func_name ) )
264✔
145
              {
146
                methods[func_name] = std::make_shared<FunctionLink>( func_loc, func_name );
262✔
147
              }
148
            }
149
          }
1,570✔
150
        }
151

152
        workspace.compiler_workspace.all_function_locations.emplace(
840✔
153
            ScopableName( class_name, func_name ).string(), func_loc );
1,680✔
154
      }
840✔
155
      else if ( auto var_statement = classStatement->varStatement() )
115✔
156
      {
157
        std::vector<std::unique_ptr<Statement>> statements;
45✔
158

159
        add_var_statements( var_statement, class_name, statements );
45✔
160

161
        for ( auto& statement : statements )
90✔
162
        {
163
          class_body->children.push_back( std::move( statement ) );
45✔
164
        }
165
      }
45✔
166
      else if ( auto uninit_func_decl = classStatement->uninitFunctionDeclaration() )
70✔
167
      {
168
        auto func_name = text( uninit_func_decl->IDENTIFIER() );
70✔
169
        auto func_loc = location_for( *uninit_func_decl );
70✔
170

171
        // An uninit function cannot be named `super`, as the child class would
172
        // not be able to define a function named `super`.
173
        if ( Clib::caseInsensitiveEqual( func_name, Compiler::SUPER ) )
70✔
174
        {
175
          report.error( func_loc, "An uninitialized function cannot be named 'super'." );
2✔
176
        }
177
        else
178
        {
179
          auto uf = make_function_like<UninitializedFunctionDeclaration>(
180
              func_name, uninit_func_decl, false, class_name, uninit_func_decl->SEMI() );
68✔
181

182
          if ( uf->type == UserFunctionType::Static )
68✔
183
          {
184
            report.error( uf->source_location,
2✔
185
                          "In uninitialized function declaration: Static functions cannot be "
186
                          "marked as uninitialized." );
187
          }
188
          else
189
          {
190
            uninit_functions.push_back( std::move( uf ) );
66✔
191
          }
192
        }
68✔
193
      }
70✔
194
    }
638✔
195
  }
196

197
  auto parameter_list =
198
      std::make_unique<ClassParameterList>( location_for( *ctx ), std::move( parameters ) );
638✔
199

200

201
  auto class_decl = std::make_unique<ClassDeclaration>(
202
      location_for( *ctx ), class_name, std::move( parameter_list ), std::move( constructor_link ),
638✔
203
      std::move( methods ), class_body, std::move( base_classes ), std::move( uninit_functions ) );
1,276✔
204

205
  // Only register the ClassDeclaration's ctor FunctionLink if there _is_ a ctor.
206
  if ( class_decl->constructor_link )
1,276✔
207
  {
208
    workspace.function_resolver.register_function_link( ScopableName( class_name, class_name ),
454✔
209
                                                        class_decl->constructor_link );
454✔
210
  }
211

212
  return class_decl;
638✔
213
}
640✔
214

215
template <typename FunctionTypeNode, typename ParserContext>
216
std::unique_ptr<FunctionTypeNode> UserFunctionBuilder::make_function_like(
3,207✔
217
    const std::string& name, ParserContext* ctx, bool exported, const std::string& class_name,
218
    antlr4::tree::TerminalNode* end_token )
219
{
220
  std::vector<std::unique_ptr<FunctionParameterDeclaration>> parameters;
3,207✔
221
  bool class_method = false;
3,207✔
222
  if ( auto function_parameters = ctx->functionParameters() )
3,207✔
223
  {
224
    if ( auto param_list = function_parameters->functionParameterList() )
3,167✔
225
    {
226
      // Determine if the function is a class method by checking if the first parameter is named
227
      // `this`. Only check if the function is a method (ie. class name is not empty).
228
      bool first = !class_name.empty();
2,358✔
229
      for ( auto param : param_list->functionParameter() )
6,273✔
230
      {
231
        ScopableName parameter_name( ScopeName::None, text( param->IDENTIFIER() ) );
3,915✔
232
        bool is_this_arg = false;
3,915✔
233

234
        if ( first )
3,915✔
235
        {
236
          if ( Clib::caseInsensitiveEqual( parameter_name.string(), "this" ) )
849✔
237
          {
238
            class_method = true;
782✔
239
            is_this_arg = true;
782✔
240
          }
241

242
          first = false;
849✔
243
        }
244

245
        std::unique_ptr<FunctionParameterDeclaration> parameter_declaration;
3,915✔
246
        bool byref = param->BYREF() != nullptr || is_this_arg;
3,915✔
247
        bool unused = param->UNUSED() != nullptr;
3,915✔
248
        bool rest = param->ELLIPSIS() != nullptr;
3,915✔
249

250
        if ( auto expr_ctx = param->expression() )
3,915✔
251
        {
252
          auto default_value = expression( expr_ctx );
278✔
253
          parameter_declaration = std::make_unique<FunctionParameterDeclaration>(
278✔
254
              location_for( *param ), std::move( parameter_name ), byref, unused, rest,
278✔
255
              std::move( default_value ) );
278✔
256
        }
278✔
257
        else
258
        {
259
          parameter_declaration = std::make_unique<FunctionParameterDeclaration>(
3,637✔
260
              location_for( *param ), std::move( parameter_name ), byref, unused, rest );
7,274✔
261
        }
262

263
        parameters.push_back( std::move( parameter_declaration ) );
3,915✔
264
      }
265
    }
266
  }
267
  auto parameter_list =
3,207✔
268
      std::make_unique<FunctionParameterList>( location_for( *ctx ), std::move( parameters ) );
3,207✔
269

270
  constexpr bool expression =
3,207✔
271
      std::is_same<ParserContext, EscriptGrammar::EscriptParser::FunctionExpressionContext>::value;
272

273
  bool constructor_method = class_method && Clib::caseInsensitiveEqual( name, class_name );
3,207✔
274

275
  UserFunctionType type = !class_method        ? UserFunctionType::Static
3,989✔
276
                          : constructor_method ? UserFunctionType::Constructor
782✔
277
                                               : UserFunctionType::Method;
278

279

280
  if constexpr ( std::is_same_v<FunctionTypeNode, UserFunction> )
281
  {
282
    std::shared_ptr<ClassLink> class_link;
3,139✔
283
    if ( !class_name.empty() )
3,139✔
284
    {
285
      class_link = std::make_shared<ClassLink>( location_for( *ctx ), class_name );
834✔
286
      workspace.function_resolver.register_class_link( ScopeName( class_name ), class_link );
834✔
287
      auto cd = class_link->class_declaration();
834✔
288

289
      // Should never happen, since the only reason this user function can be
290
      // visited is because the class has been registered.
291
      if ( !cd )
834✔
NEW
292
        class_link->source_location.internal_error( "ClassLink has no ClassDeclaration" );
×
293
    }
294

295
    in_constructor_function.push( type == UserFunctionType::Constructor );
3,139✔
296
    auto body =
6,278✔
297
        std::make_unique<FunctionBody>( location_for( *ctx ), block_statements( ctx->block() ) );
6,278✔
298

299
    in_constructor_function.pop();
3,139✔
300

301
    return std::make_unique<FunctionTypeNode>(
302
        location_for( *ctx ), exported, expression, type, class_name, std::move( name ),
3,139✔
303
        std::move( parameter_list ), std::move( body ), location_for( *end_token ),
3,139✔
304
        std::move( class_link ) );
9,417✔
305
  }
3,139✔
306
  else if constexpr ( std::is_same_v<FunctionTypeNode, UninitializedFunctionDeclaration> )
307
  {
308
    return std::make_unique<UninitializedFunctionDeclaration>(
309
        location_for( *ctx ), type, class_name, std::move( name ), std::move( parameter_list ) );
136✔
310
  }
311
}
3,207✔
312
}  // namespace Pol::Bscript::Compiler
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc