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

polserver / polserver / 17472095514

04 Sep 2025 05:37PM UTC coverage: 59.828% (+0.02%) from 59.805%
17472095514

push

github

web-flow
Fix generated function (constructor, super) registration (#809)

* move compiler-generated ctor registration to within class declaration registration; update test error messages

* add more ctor inheritance tests

* skip "Unknown identifier" error for calling undefined ctor

* add test for actual, previously crashing source

* add test for actual, previously crashing source

* move compiler-generator super registration to within class declaration registration

* add super local var test

* make register_available_generated_function private

* remove ClassDeclaration::has_super_ctor

This property was based on if FunctionResolver created the function, so
the logic can be moved to FunctionResolver.

* add core-changes

* use constant for "super" string

68 of 71 new or added lines in 4 files covered. (95.77%)

3 existing lines in 2 files now uncovered.

43721 of 73078 relevant lines covered (59.83%)

433591.61 hits per line

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

93.32
/pol-core/bscript/compiler/astbuilder/FunctionResolver.cpp
1
#include "FunctionResolver.h"
2

3
#include <list>
4

5
#include "bscript/compiler/Profile.h"
6
#include "bscript/compiler/Report.h"
7
#include "bscript/compiler/ast/ClassDeclaration.h"
8
#include "bscript/compiler/ast/Function.h"
9
#include "bscript/compiler/ast/ModuleFunctionDeclaration.h"
10
#include "bscript/compiler/ast/UserFunction.h"
11
#include "bscript/compiler/astbuilder/AvailableParseTree.h"
12
#include "bscript/compiler/file/SourceFileIdentifier.h"
13
#include "bscript/compiler/file/SourceLocation.h"
14
#include "bscript/compiler/model/ClassLink.h"
15
#include "bscript/compiler/model/CompilerWorkspace.h"
16
#include "bscript/compiler/model/FunctionLink.h"
17
#include "bscript/compiler/model/ScopeName.h"
18
#include "clib/strutil.h"
19

20
namespace Pol::Bscript::Compiler
21
{
22
FunctionResolver::FunctionResolver( CompilerWorkspace& workspace, Report& report )
2,406✔
23
    : workspace( workspace ), report( report )
2,406✔
24
{
25
}
2,406✔
26
FunctionResolver::~FunctionResolver() = default;
2,406✔
27

28
void FunctionResolver::force_reference( const ScopableName& name, const SourceLocation& loc )
540✔
29
{
30
  register_function_link( name, std::make_shared<FunctionLink>( loc, "" /* calling scope */ ) );
540✔
31
}
540✔
32

33
void FunctionResolver::force_reference( const ScopeName& scope_name, const SourceLocation& loc )
162✔
34
{
35
  register_class_link( scope_name, std::make_shared<ClassLink>( loc, scope_name.string() ) );
162✔
36
}
162✔
37

38
void FunctionResolver::register_available_generated_function( const SourceLocation& loc,
225✔
39
                                                              const ScopableName& name,
40
                                                              Node* context, UserFunctionType type )
41
{
42
  auto agf = std::make_unique<AvailableGeneratedFunction>( loc, context, name, type );
225✔
43
  register_available_function_parse_tree( loc, name, std::move( agf ) );
225✔
44
}
225✔
45

46
void FunctionResolver::register_available_user_function(
2,470✔
47
    const SourceLocation& source_location,
48
    EscriptGrammar::EscriptParser::FunctionDeclarationContext* ctx, bool force_reference )
49
{
50
  ScopableName name( ScopeName::Global, ctx->IDENTIFIER()->getText() );
2,470✔
51

52
  // Exported functions are always forced to be referenced so the code generator
53
  // will emit the function's instructions, even though there is no FunctionCall
54
  // to it.
55
  register_available_user_function_parse_tree( source_location, ctx, std::move( name ),
2,470✔
56
                                               force_reference || ctx->EXPORTED() );
2,470✔
57
}
2,470✔
58

59

60
void FunctionResolver::register_available_scoped_function(
723✔
61
    const SourceLocation& source_location, const ScopeName& class_name,
62
    EscriptGrammar::EscriptParser::FunctionDeclarationContext* ctx )
63
{
64
  ScopableName name( class_name, ctx->IDENTIFIER()->getText() );
723✔
65
  register_available_user_function_parse_tree( source_location, ctx, std::move( name ),
723✔
66
                                               ctx->EXPORTED() );
723✔
67
}
723✔
68

69
void FunctionResolver::register_available_class_declaration(
697✔
70
    const SourceLocation& loc, const ScopeName& class_name,
71
    EscriptGrammar::EscriptParser::ClassDeclarationContext* ctx,
72
    Node* top_level_statements_child_node )
73
{
74
  register_available_class_decl_parse_tree( loc, ctx, class_name, top_level_statements_child_node );
697✔
75
}
697✔
76

77
void FunctionResolver::register_class_link( const ScopeName& name,
1,224✔
78
                                            std::shared_ptr<ClassLink> class_link )
79
{
80
  if ( resolve_if_existing( name, class_link ) )
1,224✔
81
    return;
831✔
82

83
  unresolved_classes[name].push_back( std::move( class_link ) );
393✔
84
}
85

86
void FunctionResolver::register_function_link( const ScopableName& name,
20,926✔
87
                                               std::shared_ptr<FunctionLink> function_link )
88
{
89
  const auto& calling_scope = function_link->calling_scope;
20,926✔
90
  const auto& unscoped_name = name.name;
20,926✔
91

92
  // If a call scope was given, _only_ check that one.
93
  if ( !name.scope.global() )
20,926✔
94
  {
95
    if ( resolve_if_existing( name, function_link ) )
1,252✔
96
      return;
64✔
97
  }
98
  else
99
  {
100
    // If calling scope present, check, eg. inside Animal class, `foo()` checks
101
    // `Animal::foo()`.
102
    if ( !calling_scope.empty() &&
20,431✔
103
         resolve_if_existing( { calling_scope, unscoped_name }, function_link ) )
20,431✔
104
      return;
×
105

106
    // Check global scope, only if calling scope is empty. If we are inside eg.
107
    // `class Animal` and call `foo()`, we do not want to link to an
108
    // _already-registered_ global function `foo()`. Adding this unresolved
109
    // function link with a empty scope name will eventually resolve it to the
110
    // correctly scoped function.
111
    if ( calling_scope.empty() &&
38,591✔
112
         resolve_if_existing( ScopableName( ScopeName::Global, unscoped_name ), function_link ) )
38,591✔
113
      return;
13,141✔
114
  }
115

116
  report.debug( function_link->source_location, "registering funct link {}", name.string() );
7,721✔
117

118
  unresolved_function_links[name].push_back( std::move( function_link ) );
7,721✔
119
}
120

121
std::string FunctionResolver::register_function_expression(
540✔
122
    const SourceLocation& source_location,
123
    EscriptGrammar::EscriptParser::FunctionExpressionContext* ctx )
124
{
125
  auto name = function_expression_name( source_location );
540✔
126
  auto apt =
127
      std::make_unique<AvailableParseTree>( source_location, ctx, ScopeName::Global, nullptr );
540✔
128
  available_user_function_parse_trees[name] = std::move( apt );
540✔
129
  return name;
1,080✔
130
}
540✔
131

132
void FunctionResolver::register_module_function( ModuleFunctionDeclaration* mf )
124,182✔
133
{
134
  const auto& name = mf->name;
124,182✔
135

136
  auto itr = available_user_function_parse_trees.find( name );
124,182✔
137
  if ( itr != available_user_function_parse_trees.end() )
124,182✔
138
  {
139
    const auto& previous = ( *itr ).second;
2✔
140

141
    report.error( previous->source_location,
2✔
142
                  "User Function '{}' conflicts with Module Function of the same name.\n"
143
                  "  Module Function declaration: {}",
144
                  name, mf->source_location );
2✔
145
  }
146

147
  resolved_functions[{ ScopeName::Global, name }] = mf;
124,182✔
148
  resolved_functions[{ mf->scope, name }] = mf;
124,182✔
149
}
124,182✔
150

151
void FunctionResolver::register_user_function( const std::string& scope, UserFunction* uf )
3,199✔
152
{
153
  ScopableName scoped_name( scope, uf->name );
3,199✔
154
  auto itr = resolved_functions.find( scoped_name );
3,199✔
155
  if ( itr != resolved_functions.end() )
3,199✔
156
  {
157
    // Throw an exception, this should _never_ happen. If this does, then an
158
    // AvailableParseTree was visited twice.
159
    auto msg = fmt::format( "duplicate user function definition for {}: {} vs {}", uf->name,
×
160
                            uf->source_location, ( *itr ).second->source_location );
×
161
    uf->internal_error( msg );
×
162
  }
×
163

164
  resolved_functions[scoped_name] = uf;
3,199✔
165
  report.debug( uf->source_location, "registering uf {}", scoped_name );
3,199✔
166

167
  // Constructors are registered in global scope
168
  if ( uf->type == UserFunctionType::Constructor )
3,199✔
169
  {
170
    ScopableName global_name( ScopeName::Global, uf->name );
424✔
171

172
    resolved_functions[global_name] = uf;
424✔
173
    report.debug( uf->source_location, "registering uf {}", global_name );
424✔
174
  }
424✔
175
}
3,199✔
176

177
bool FunctionResolver::super_function_created( ClassDeclaration* cd ) const
711✔
178
{
179
  return available_user_function_parse_trees.contains(
2,133✔
180
             fmt::format( "{}::{}", cd->name, Compiler::SUPER ) ) ||
2,740✔
181
         resolved_functions.contains( { cd->name, Compiler::SUPER } );
2,029✔
182
}
183

184
void FunctionResolver::register_class_declaration( ClassDeclaration* cd )
515✔
185
{
186
  resolved_classes[cd->name] = cd;
515✔
187

188
  // Since we are _registering_ the class declaration, that means either the
189
  // user has actively instantiated the class via `Foo()` or a base class has
190
  // requested `Foo`. Register all the class' methods and include them at
191
  // instruction generation. See also: `Optimizer::optimize` where Methods and
192
  // Constructors are force-referenced.
193
  //
194
  // The `methods` object only contains methods, as the constructor is in the
195
  // `constructor_link`.
196
  for ( const auto& [method_name, function_link] : cd->methods )
724✔
197
  {
198
    register_function_link( { cd->name, method_name }, function_link );
209✔
199
  }
200

201
  // Keep track of class children for constructor generation.
202
  for ( const auto& link : cd->base_class_links )
854✔
203
  {
204
    class_children[link->name].push_back( cd );
339✔
205
  }
206

207
  // If this class declaration has no constructor, check base classes that may already be resolved.
208
  if ( !cd->constructor_link || !super_function_created( cd ) )
1,030✔
209
  {
210
    std::set<ClassDeclaration*> visited;
509✔
211
    std::list<std::shared_ptr<ClassLink>> to_visit;
509✔
212

213
    to_visit.insert( to_visit.end(), cd->base_class_links.begin(), cd->base_class_links.end() );
509✔
214
    for ( const auto& link : to_visit )
762✔
215
    {
216
      if ( auto base_cd = link->class_declaration() )
313✔
217
      {
218
        if ( visited.find( base_cd ) != visited.end() )
60✔
219
        {
NEW
220
          continue;
×
221
        }
222
        visited.insert( base_cd );
60✔
223

224
        if ( base_cd->constructor_link )
120✔
225
        {
226
          if ( !cd->constructor_link )
120✔
227
          {
228
            ScopableName ctor_name( cd->name, cd->name );
11✔
229
            cd->constructor_link = std::make_unique<FunctionLink>( cd->source_location, cd->name,
11✔
230
                                                                   true /* requires_ctor */ );
11✔
231

232
            register_function_link( ctor_name, cd->constructor_link );
11✔
233

234
            register_available_generated_function( cd->source_location, ctor_name, cd,
11✔
235
                                                   UserFunctionType::Constructor );
236
          }
11✔
237

238
          if ( !super_function_created( cd ) )
60✔
239
          {
240
            ScopableName child_super( cd->name, Compiler::SUPER );
60✔
241

242
            register_available_generated_function( cd->source_location, child_super, cd,
60✔
243
                                                   UserFunctionType::Super );
244
          }
60✔
245

246
          break;
60✔
247
        }
248

NEW
249
        to_visit.insert( to_visit.end(), base_cd->base_class_links.begin(),
×
NEW
250
                         base_cd->base_class_links.end() );
×
251
      }
252
    }
253
  }
509✔
254

255
  // If this class declaration has a constructor, and there are classes that inherit
256
  // from it, generate a constructor for my descendants that do not have one.
257
  if ( cd->constructor_link && class_children.contains( cd->name ) )
1,030✔
258
  {
259
    std::set<ClassDeclaration*> visited;
166✔
260
    std::list<ClassDeclaration*> to_visit;
166✔
261

262
    to_visit.insert( to_visit.end(), class_children[cd->name].begin(),
166✔
263
                     class_children[cd->name].end() );
166✔
264

265
    for ( const auto& child_cd : to_visit )
439✔
266
    {
267
      if ( visited.contains( child_cd ) )
273✔
268
        continue;
24✔
269

270
      visited.insert( child_cd );
249✔
271

272
      if ( !super_function_created( child_cd ) )
249✔
273
      {
274
        ScopableName child_super( child_cd->name, Compiler::SUPER );
143✔
275

276
        register_available_generated_function( child_cd->source_location, child_super, child_cd,
143✔
277
                                               UserFunctionType::Super );
278
      }
143✔
279

280
      if ( !child_cd->constructor_link )
498✔
281
      {
282
        ScopableName child_ctor( child_cd->name, child_cd->name );
11✔
283
        child_cd->constructor_link = std::make_unique<FunctionLink>(
11✔
284
            child_cd->source_location, child_cd->name, true /* requires_ctor */ );
11✔
285

286
        register_function_link( child_ctor, child_cd->constructor_link );
11✔
287

288
        register_available_generated_function( cd->source_location, child_ctor, child_cd,
11✔
289
                                               UserFunctionType::Constructor );
290
      }
11✔
291

292
      to_visit.insert( to_visit.end(), class_children[child_cd->name].begin(),
249✔
293
                       class_children[child_cd->name].end() );
249✔
294
    }
295
  }
166✔
296
}
515✔
297

298
bool FunctionResolver::resolve(
3,920✔
299
    std::vector<std::unique_ptr<AvailableSecondPassTarget>>& to_build_ast )
300
{
301
  // Attempt to link all unresolved function links
302
  for ( auto unresolved_itr = unresolved_function_links.begin();
3,920✔
303
        unresolved_itr != unresolved_function_links.end(); )
11,731✔
304
  {
305
    for ( auto function_link_itr = ( *unresolved_itr ).second.begin();
7,811✔
306
          function_link_itr != ( *unresolved_itr ).second.end(); )
23,548✔
307
    {
308
      auto& function_link = *function_link_itr;
15,737✔
309
      const auto& calling_scope = function_link->calling_scope;
15,737✔
310
      const auto& name = ( *unresolved_itr ).first;
15,737✔
311
      const auto& unscoped_name = name.name;
15,737✔
312
      const auto& call_scope = name.scope;
15,737✔
313
      bool is_super_scoped = call_scope.super();
15,737✔
314

315
      // Link the function if possible, otherwise try to build it.
316
      auto link_handled = [&]( const ScopableName& key )
16,724✔
317
      {
318
        // A super:: call cannot be handled by the current call scope.
319
        if ( call_scope.super() && Clib::caseInsensitiveEqual( key.scope.string(), calling_scope ) )
16,724✔
320
        {
321
          return false;
82✔
322
        }
323

324
        if ( resolve_if_existing( key, function_link ) )
16,642✔
325
        {
326
          // Remove this function link from the list of links.
327
          function_link_itr = ( *unresolved_itr ).second.erase( function_link_itr );
7,451✔
328
          return true;
7,451✔
329
        };
330

331
        if ( build_if_available( to_build_ast, calling_scope, key ) )
9,191✔
332
        {
333
          // Keep the link in the list, as it may be resolved later.
334
          ++function_link_itr;
3,712✔
335
          return true;
3,712✔
336
        }
337
        return false;
5,479✔
338
      };
15,737✔
339

340
      auto handled_by_scopes = [&]( const std::string& starting_scope )
7,112✔
341
      {
342
        std::set<std::string> visited;
7,112✔
343
        std::list<std::string> to_check( { starting_scope } );
21,336✔
344
        bool handled = false;
7,112✔
345

346
        for ( auto to_check_itr = to_check.begin(); !handled && to_check_itr != to_check.end();
9,421✔
347
              to_check_itr = to_check.erase( to_check_itr ) )
2,309✔
348
        {
349
          if ( visited.find( *to_check_itr ) != visited.end() )
7,445✔
350
            continue;
691✔
351

352
          visited.insert( *to_check_itr );
7,425✔
353

354
          if ( link_handled( { *to_check_itr, unscoped_name } ) )
7,425✔
355
          {
356
            return true;
5,136✔
357
          }
358

359
          auto cd_itr = resolved_classes.find( *to_check_itr );
2,330✔
360
          if ( cd_itr == resolved_classes.end() )
2,330✔
361
            continue;
671✔
362

363
          auto cd = cd_itr->second;
1,659✔
364

365
          for ( const auto& base_cd_link : cd->base_class_links )
2,647✔
366
          {
367
            if ( auto base_cd = base_cd_link->class_declaration() )
1,029✔
368
            {
369
              to_check.push_back( base_cd->name );
381✔
370
            }
371
            // If using a call to super:: and this base class has not been
372
            // loaded, we can't resolve the link just yet: we wait until the
373
            // base class list (at least, for this current `cd`) is loaded.
374
            else if ( is_super_scoped )
648✔
375
            {
376
              return false;
41✔
377
            }
378
          }
379
        }
380

381
        return false;
1,976✔
382
      };
14,224✔
383

384
      report.debug( function_link->source_location, "resolving funct link {}", name );
15,737✔
385

386
      // If a call scope was given (except super::), we check that call scope
387
      // and its ancestors _without_ checking Global (ie. skipping the check
388
      // done in the `else` block)
389
      if ( !call_scope.empty() && !call_scope.super() )
15,737✔
390
      {
391
        if ( handled_by_scopes( call_scope.string() ) )
5,791✔
392
          continue;
11,163✔
393
      }
394
      else
395
      {
396
        if ( !calling_scope.empty() && handled_by_scopes( calling_scope ) )
9,946✔
397
          continue;
604✔
398

399
        // Check global scope (if not explicitly calling super:: scope)
400
        if ( !call_scope.super() && link_handled( { ScopeName::Global, unscoped_name } ) )
9,342✔
401
          continue;
6,068✔
402
      }
403

404
      // Link was not handled, move to the next one
405
      ++function_link_itr;
4,574✔
406
    }
407

408
    // If all links were resolved, remove the entry.
409
    if ( ( *unresolved_itr ).second.empty() )
7,811✔
410
    {
411
      unresolved_itr = unresolved_function_links.erase( unresolved_itr );
3,563✔
412
    }
413
    else
414
    {
415
      // Do not complain here, as the identifier in `[scope::]name` may be a variable.
416
      // Error handled in SemanticAnalyzer.
417
      ++unresolved_itr;
4,248✔
418
    }
419
  }
420

421
  // If any class link was resolved, we need to trigger
422
  // `CompilerWorkspaceBuilder::build_referenced_user_functions` to run the
423
  // resolution loop again.
424
  bool any_classes_linked = false;
3,920✔
425

426
  for ( auto unresolved_itr = unresolved_classes.begin();
3,920✔
427
        unresolved_itr != unresolved_classes.end(); )
4,375✔
428
  {
429
    auto scope = unresolved_itr->first;
455✔
430

431
    for ( auto class_link_itr = ( *unresolved_itr ).second.begin();
455✔
432
          class_link_itr != ( *unresolved_itr ).second.end(); )
1,127✔
433
    {
434
      auto& class_link = *class_link_itr;
672✔
435

436
      report.debug( class_link->source_location, "resolving class link {}", scope.string() );
672✔
437

438
      // Link the function if possible, otherwise try to build it.
439
      if ( resolve_if_existing( scope, class_link ) )
672✔
440
      {
441
        // Remove this function link from the list of links.
442
        class_link_itr = ( *unresolved_itr ).second.erase( class_link_itr );
343✔
443
        any_classes_linked = true;
343✔
444
      }
445
      else if ( build_if_available( to_build_ast, scope ) )
329✔
446
      {
447
        // Keep the link in the list, as it may be resolved later.
448
        class_link_itr = ( *unresolved_itr ).second.erase( class_link_itr );
4✔
449
      }
450
      else
451
      {
452
        ++class_link_itr;
325✔
453
      }
454
    }
455

456
    // If all links were resolved, remove the entry.
457
    if ( ( *unresolved_itr ).second.empty() )
455✔
458
    {
459
      unresolved_itr = unresolved_classes.erase( unresolved_itr );
224✔
460
    }
461
    else
462
    {
463
      ++unresolved_itr;
231✔
464
    }
465
  }
455✔
466

467
  return any_classes_linked || !to_build_ast.empty();
3,920✔
468
}
469

470
std::string FunctionResolver::function_expression_name( const SourceLocation& source_location )
1,080✔
471
{
472
  return fmt::format( "funcexpr@{}:{}:{}", source_location.source_file_identifier->index,
1,080✔
473
                      source_location.range.start.line_number,
1,080✔
474
                      source_location.range.start.character_column );
2,160✔
475
}
476

477
void FunctionResolver::register_available_function_parse_tree(
3,418✔
478
    const SourceLocation& source_location, const ScopableName& name,
479
    std::unique_ptr<AvailableSecondPassTarget> apt )
480
{
481
  const auto& unscoped_name = name.name;
3,418✔
482

483
  // Eg. "foo" or "Animal::foo"
484
  auto scoped_name = name.string();
3,418✔
485

486
  // Eg. "", "Animal"
487
  auto scope = name.scope.string();
3,418✔
488

489
  report.debug( source_location, "registering funct apt {}", scoped_name );
3,418✔
490

491
  auto itr = available_user_function_parse_trees.find( scoped_name );
3,418✔
492
  if ( itr != available_user_function_parse_trees.end() )
3,418✔
493
  {
494
    auto& previous = ( *itr ).second;
2✔
495

496
    report.error( source_location,
2✔
497
                  "Function '{}' defined more than once.\n"
498
                  "  Previous declaration: {}",
499
                  scoped_name, previous->source_location );
2✔
500
  }
501

502
  auto itr2 = resolved_functions.find( { scope, unscoped_name } );
3,418✔
503
  if ( itr2 != resolved_functions.end() )
3,418✔
504
  {
505
    auto* previous = ( *itr2 ).second;
2✔
506

507
    std::string what = dynamic_cast<ModuleFunctionDeclaration*>( previous ) == nullptr
2✔
508
                           ? "User Function"
509
                           : "Module Function";
4✔
510

511
    report.error( source_location,
×
512
                  "User Function '{}' conflicts with {} of the same name.\n"
513
                  "  {} declaration: {}",
514
                  scoped_name, what, what, previous->source_location );
2✔
515
  }
2✔
516

517
  auto itr3 = available_class_decl_parse_trees.find( scope );
3,418✔
518
  if ( itr3 != available_class_decl_parse_trees.end() )
3,418✔
519
  {
520
    auto& previous = ( *itr3 ).second;
×
521

522
    report.error( source_location,
×
523
                  "User Function '{}' conflicts with Class of the same name.\n"
524
                  "  Class declaration: {}",
525
                  scoped_name, previous->source_location );
×
526
  }
527

528
  available_user_function_parse_trees[scoped_name] = std::move( apt );
3,418✔
529
}
3,418✔
530

531
void FunctionResolver::register_available_user_function_parse_tree(
3,193✔
532
    const SourceLocation& source_location, antlr4::ParserRuleContext* ctx, const ScopableName& name,
533
    bool force_reference )
534
{
535
  register_available_function_parse_tree(
3,193✔
536
      source_location, name,
537
      std::make_unique<AvailableParseTree>( source_location, ctx, name.scope, nullptr ) );
6,386✔
538

539
  if ( force_reference )
3,193✔
540
  {
541
    // just make sure there is an entry, so that we build an AST for it
542
    register_function_link( name, std::make_shared<FunctionLink>(
570✔
543
                                      source_location, name.scope.string() /* calling scope */ ) );
1,140✔
544
  }
545
}
3,193✔
546

547
void FunctionResolver::register_available_class_decl_parse_tree(
697✔
548
    const SourceLocation& source_location, antlr4::ParserRuleContext* ctx,
549
    const ScopeName& scope_name, Node* top_level_statements_child_node )
550
{
551
  const auto name = scope_name.string();
697✔
552
  report.debug( source_location, "registering class apt ({}).", name );
697✔
553
  auto itr = available_user_function_parse_trees.find( name );
697✔
554
  if ( itr != available_user_function_parse_trees.end() )
697✔
555
  {
556
    auto& previous = ( *itr ).second;
2✔
557

558
    report.error( source_location,
2✔
559
                  "Class '{}' conflicts with User Function of the same name.\n"
560
                  "  Previous declaration: {}",
561
                  name, previous->source_location );
2✔
562
  }
563

564
  if ( auto itr2 = workspace.all_class_locations.find( name );
697✔
565
       itr2 != workspace.all_class_locations.end() )
697✔
566
  {
567
    auto& previous = ( *itr2 ).second;
4✔
568
    report.error( source_location,
4✔
569
                  "Class '{}' defined more than once.\n"
570
                  "  Previous declaration: {}",
571
                  name, previous );
572
  }
573

574
  auto itr3 = resolved_functions.find( { ScopeName::Global, name } );
697✔
575
  if ( itr3 != resolved_functions.end() )
697✔
576
  {
577
    auto* previous = ( *itr3 ).second;
×
578

579
    std::string what = dynamic_cast<ModuleFunctionDeclaration*>( previous ) == nullptr
×
580
                           ? "User Function"
581
                           : "Module Function";
×
582

583
    report.error( source_location,
×
584
                  "Class '{}' conflicts with {} of the same name.\n"
585
                  "  {} declaration: {}",
586
                  name, what, what, previous->source_location );
×
587
  }
×
588

589
  auto apt = std::make_unique<AvailableParseTree>( source_location, ctx, scope_name,
590
                                                   top_level_statements_child_node );
697✔
591
  available_class_decl_parse_trees[name] = std::move( apt );
697✔
592
}
697✔
593

594
Function* FunctionResolver::check_existing( const ScopableName& key,
37,568✔
595
                                            bool requires_constructor ) const
596
{
597
  auto itr = resolved_functions.find( key );
37,568✔
598
  if ( itr != resolved_functions.end() )
37,568✔
599
  {
600
    if ( requires_constructor )
20,658✔
601
    {
602
      auto uf = dynamic_cast<UserFunction*>( ( *itr ).second );
733✔
603
      if ( !uf || uf->type != UserFunctionType::Constructor )
733✔
604
      {
605
        return nullptr;
2✔
606
      }
607
    }
608

609
    return ( *itr ).second;
20,656✔
610
  }
611
  return nullptr;
16,910✔
612
}
613

614
ClassDeclaration* FunctionResolver::check_existing( const ScopeName& key ) const
1,896✔
615
{
616
  auto itr = resolved_classes.find( key );
1,896✔
617
  if ( itr != resolved_classes.end() )
1,896✔
618
  {
619
    return ( *itr ).second;
1,174✔
620
  }
621
  return nullptr;
722✔
622
}
623

624
bool FunctionResolver::build_if_available(
9,191✔
625
    std::vector<std::unique_ptr<AvailableSecondPassTarget>>& to_build_ast,
626
    const std::string& calling_scope, const ScopableName& call )
627
{
628
  AvailableParseTreeMap::iterator itr;
9,191✔
629

630
  // If a call scope was given, _only_ check that one (except if super:: provided)
631
  // eg. `Animal::foo()` will only search for `Animal::foo()`, disregarding a possible parent
632
  // scoped `::foo()`.
633
  if ( !call.global() && !call.scope.super() )
9,191✔
634
  {
635
    itr = available_user_function_parse_trees.find( call.string() );
2,654✔
636
    if ( itr != available_user_function_parse_trees.end() )
2,654✔
637
    {
638
      to_build_ast.push_back( std::move( ( *itr ).second ) );
673✔
639
      available_user_function_parse_trees.erase( itr );
673✔
640
      report.debug( to_build_ast.back()->source_location, "adding to build funct [call] {}: {}",
673✔
641
                    *to_build_ast.back(), call.string() );
1,346✔
642
      return true;
673✔
643
    }
644

645
    // Check if there is a class available with that scope, as it could provide this function
646
    // later.
647
    if ( build_if_available( to_build_ast, call.scope ) )
1,981✔
648
    {
649
      return true;
284✔
650
    }
651

652
    // Nothing found. Can't build a scoped function call.
653
    return false;
1,697✔
654
  }
655

656
  // Inside a scope, eg. `Animal`...
657
  if ( !calling_scope.empty() )
6,537✔
658
  {
659
    // Check if exists in given `scope`, eg. `foo()` checks `Animal::foo()`
660
    auto handled_by_scope = [&]( const std::string& scope )
276✔
661
    {
662
      // Skip checking current scope if doing `super::` call.
663
      if ( call.scope.super() && Clib::caseInsensitiveEqual( scope, calling_scope ) )
276✔
664
      {
665
        return false;
×
666
      }
667
      auto scoped_call_name = fmt::format( "{}::{}", scope, call.name );
276✔
668
      itr = available_user_function_parse_trees.find( scoped_call_name );
276✔
669
      if ( itr != available_user_function_parse_trees.end() )
276✔
670
      {
671
        to_build_ast.push_back( std::move( ( *itr ).second ) );
×
672
        available_user_function_parse_trees.erase( itr );
×
673
        report.debug( to_build_ast.back()->source_location, "adding to build funct [scoped] {}: {}",
×
674
                      *to_build_ast.back(), scoped_call_name );
×
675
        return true;
×
676
      }
677
      return false;
276✔
678
    };
276✔
679

680
    std::set<std::string> visited;
224✔
681
    std::list<std::string> to_check( { calling_scope } );
672✔
682

683
    for ( auto to_check_itr = to_check.begin(); to_check_itr != to_check.end();
510✔
684
          to_check_itr = to_check.erase( to_check_itr ) )
286✔
685
    {
686
      auto cd_itr = resolved_classes.find( *to_check_itr );
286✔
687
      if ( cd_itr == resolved_classes.end() )
286✔
688
        continue;
10✔
689

690
      auto cd = cd_itr->second;
286✔
691
      if ( visited.find( cd->name ) != visited.end() )
286✔
692
        continue;
10✔
693

694
      visited.insert( cd->name );
276✔
695

696
      if ( handled_by_scope( cd->name ) )
276✔
697
      {
698
        break;
×
699
      }
700

701
      for ( const auto& base_cd_link : cd->base_class_links )
477✔
702
      {
703
        if ( auto base_cd = base_cd_link->class_declaration() )
201✔
704
        {
705
          to_check.push_back( base_cd->name );
62✔
706
        }
707
      }
708
    }
709
  }
224✔
710

711
  // Check if there is a class available with the function name.
712
  if ( build_if_available( to_build_ast, call.name ) )
6,537✔
713
  {
714
    return true;
229✔
715
  }
716

717
  // If call scope is empty, check if function is a constructor, eg. Foo() -> Foo::Foo
718
  // The register_user_function will only register the function in global scope if it _is_ a
719
  // constructor.
720
  if ( call.scope.global() )
6,308✔
721
  {
722
    auto scoped_call_name = fmt::format( "{}::{}", call.name, call.name );
6,308✔
723
    itr = available_user_function_parse_trees.find( scoped_call_name );
6,308✔
724
    if ( itr != available_user_function_parse_trees.end() )
6,308✔
725
    {
726
      to_build_ast.push_back( std::move( ( *itr ).second ) );
223✔
727
      available_user_function_parse_trees.erase( itr );
223✔
728
      report.debug( to_build_ast.back()->source_location, "adding to build funct [ctor?] {}: {}",
223✔
729
                    *to_build_ast.back(), scoped_call_name );
223✔
730
      return true;
223✔
731
    }
732
  }
6,308✔
733

734
  // Check if exists in global scope.
735
  itr = available_user_function_parse_trees.find( call.name );
6,085✔
736
  if ( itr != available_user_function_parse_trees.end() )
6,085✔
737
  {
738
    to_build_ast.push_back( std::move( ( *itr ).second ) );
2,303✔
739
    available_user_function_parse_trees.erase( itr );
2,303✔
740
    report.debug( to_build_ast.back()->source_location, "adding to build funct [global] {}: {}",
2,303✔
741
                  *to_build_ast.back(), call.name );
2,303✔
742
    return true;
2,303✔
743
  }
744

745
  return false;
3,782✔
746
}
224✔
747

748
bool FunctionResolver::build_if_available(
8,847✔
749
    std::vector<std::unique_ptr<AvailableSecondPassTarget>>& to_build_ast, const ScopeName& scope )
750
{
751
  auto itr = available_class_decl_parse_trees.find( scope.string() );
8,847✔
752
  if ( itr != available_class_decl_parse_trees.end() )
8,847✔
753
  {
754
    to_build_ast.push_back( std::move( ( *itr ).second ) );
517✔
755
    available_class_decl_parse_trees.erase( itr );
517✔
756
    report.debug( to_build_ast.back()->source_location, "adding to build class [call.scope] {}: {}",
517✔
757
                  *to_build_ast.back(), scope.string() );
1,034✔
758
    return true;
517✔
759
  }
760

761
  return false;
8,330✔
762
}
763

764
bool FunctionResolver::resolve_if_existing( const ScopableName& key,
37,568✔
765
                                            std::shared_ptr<FunctionLink>& function_link )
766
{
767
  auto resolved_function = check_existing( key, function_link->require_ctor );
37,568✔
768

769
  if ( resolved_function )
37,568✔
770
  {
771
    function_link->link_to( resolved_function );
20,656✔
772
    report.debug( function_link->source_location, "linking {} to {}::{} @ {}", key,
20,656✔
773
                  resolved_function->scope, resolved_function->name, (void*)resolved_function );
20,656✔
774
    return true;
20,656✔
775
  }
776

777
  return false;
16,912✔
778
}
779

780
bool FunctionResolver::resolve_if_existing( const ScopeName& scope,
1,896✔
781
                                            std::shared_ptr<ClassLink>& class_link )
782
{
783
  auto resolved_class = check_existing( scope );
1,896✔
784

785
  if ( resolved_class )
1,896✔
786
  {
787
    class_link->link_to( resolved_class );
1,174✔
788
    report.debug( class_link->source_location, "linking class {} to {} @ {}", scope.string(),
1,174✔
789
                  resolved_class->name, (void*)resolved_class );
1,174✔
790
    return true;
1,174✔
791
  }
792

793
  return false;
722✔
794
};
795
}  // 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