• 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

91.86
/pol-core/bscript/compiler/analyzer/SemanticAnalyzer.cpp
1
#include "SemanticAnalyzer.h"
2

3
#include <algorithm>
4
#include <iterator>
5
#include <list>
6
#include <ranges>
7
#include <set>
8

9
#include "bscript/compiler/Report.h"
10
#include "bscript/compiler/analyzer/Constants.h"
11
#include "bscript/compiler/analyzer/FlowControlScope.h"
12
#include "bscript/compiler/analyzer/FunctionVariableScope.h"
13
#include "bscript/compiler/analyzer/LocalVariableScope.h"
14
#include "bscript/compiler/analyzer/LocalVariableScopes.h"
15
#include "bscript/compiler/ast/Argument.h"
16
#include "bscript/compiler/ast/BasicForLoop.h"
17
#include "bscript/compiler/ast/BinaryOperator.h"
18
#include "bscript/compiler/ast/Block.h"
19
#include "bscript/compiler/ast/CaseDispatchDefaultSelector.h"
20
#include "bscript/compiler/ast/CaseDispatchGroup.h"
21
#include "bscript/compiler/ast/CaseDispatchGroups.h"
22
#include "bscript/compiler/ast/CaseDispatchSelectors.h"
23
#include "bscript/compiler/ast/CaseStatement.h"
24
#include "bscript/compiler/ast/ClassBody.h"
25
#include "bscript/compiler/ast/ClassDeclaration.h"
26
#include "bscript/compiler/ast/ClassInstance.h"
27
#include "bscript/compiler/ast/ClassParameterDeclaration.h"
28
#include "bscript/compiler/ast/ConstDeclaration.h"
29
#include "bscript/compiler/ast/ConstantPredicateLoop.h"
30
#include "bscript/compiler/ast/CstyleForLoop.h"
31
#include "bscript/compiler/ast/DoWhileLoop.h"
32
#include "bscript/compiler/ast/ForeachLoop.h"
33
#include "bscript/compiler/ast/FunctionBody.h"
34
#include "bscript/compiler/ast/FunctionCall.h"
35
#include "bscript/compiler/ast/FunctionExpression.h"
36
#include "bscript/compiler/ast/FunctionParameterDeclaration.h"
37
#include "bscript/compiler/ast/FunctionParameterList.h"
38
#include "bscript/compiler/ast/FunctionReference.h"
39
#include "bscript/compiler/ast/GeneratedFunction.h"
40
#include "bscript/compiler/ast/Identifier.h"
41
#include "bscript/compiler/ast/IndexBinding.h"
42
#include "bscript/compiler/ast/IntegerValue.h"
43
#include "bscript/compiler/ast/JumpStatement.h"
44
#include "bscript/compiler/ast/MemberAccess.h"
45
#include "bscript/compiler/ast/ModuleFunctionDeclaration.h"
46
#include "bscript/compiler/ast/Program.h"
47
#include "bscript/compiler/ast/ProgramParameterDeclaration.h"
48
#include "bscript/compiler/ast/RepeatUntilLoop.h"
49
#include "bscript/compiler/ast/ReturnStatement.h"
50
#include "bscript/compiler/ast/SequenceBinding.h"
51
#include "bscript/compiler/ast/StringValue.h"
52
#include "bscript/compiler/ast/TopLevelStatements.h"
53
#include "bscript/compiler/ast/UninitializedFunctionDeclaration.h"
54
#include "bscript/compiler/ast/UserFunction.h"
55
#include "bscript/compiler/ast/VarStatement.h"
56
#include "bscript/compiler/ast/VariableAssignmentStatement.h"
57
#include "bscript/compiler/ast/VariableBinding.h"
58
#include "bscript/compiler/ast/WhileLoop.h"
59
#include "bscript/compiler/astbuilder/SimpleValueCloner.h"
60
#include "bscript/compiler/model/ClassLink.h"
61
#include "bscript/compiler/model/CompilerWorkspace.h"
62
#include "bscript/compiler/model/FunctionLink.h"
63
#include "bscript/compiler/model/ScopeName.h"
64
#include "bscript/compiler/model/Variable.h"
65
#include "bscript/compiler/optimizer/ConstantValidator.h"
66
#include "clib/strutil.h"
67
#include "filefmt.h"
68

69
namespace Pol::Bscript::Compiler
70
{
71
SemanticAnalyzer::SemanticAnalyzer( CompilerWorkspace& workspace, Report& report )
2,305✔
72
    : workspace( workspace ),
2,305✔
73
      report( report ),
2,305✔
74
      globals( VariableScope::Global, report ),
2,305✔
75
      locals( VariableScope::Local, report ),
2,305✔
76
      captures( VariableScope::Capture, report ),
2,305✔
77
      break_scopes( locals, report ),
2,305✔
78
      continue_scopes( locals, report ),
2,305✔
79
      local_scopes( locals, report ),
2,305✔
80
      capture_scopes( captures, report )
4,610✔
81
{
82
  current_scope_names.push( ScopeName::Global );
2,305✔
83
}
2,305✔
84

85
SemanticAnalyzer::~SemanticAnalyzer() = default;
2,305✔
86

87
void SemanticAnalyzer::register_const_declarations( CompilerWorkspace& workspace, Report& report )
2,317✔
88
{
89
  for ( auto& constant : workspace.const_declarations )
161,395✔
90
  {
91
    report_function_name_conflict( workspace, report, constant->source_location,
477,234✔
92
                                   constant->name.string(), "constant" );
318,156✔
93
    workspace.constants.create( *constant );
159,078✔
94
  }
95
}
2,317✔
96

97
void SemanticAnalyzer::analyze()
2,305✔
98
{
99
  workspace.top_level_statements->accept( *this );
2,305✔
100
  if ( auto& program = workspace.program )
2,305✔
101
  {
102
    program->accept( *this );
450✔
103
  }
104

105
  for ( auto& user_function : workspace.user_functions )
5,582✔
106
  {
107
    // Function expressions are analyzed within visit_function_expression.
108
    if ( !user_function->expression )
3,277✔
109
    {
110
      user_function->accept( *this );
2,737✔
111
    }
112
  }
113

114
  // Class declarations do not have var statements as children, so we do not get
115
  // 'duplicate variable' errors.
116
  for ( auto& class_decl : workspace.class_declarations )
2,925✔
117
  {
118
    class_decl->accept( *this );
620✔
119
  }
120

121
  workspace.global_variable_names = globals.get_names();
2,305✔
122
}
2,305✔
123

124
void SemanticAnalyzer::visit_basic_for_loop( BasicForLoop& node )
72✔
125
{
126
  if ( locals.find( node.identifier ) )
144✔
127
  {
128
    report.error( node, "FOR iterator '{}' hides a local variable.", node.identifier );
2✔
129
    return;
4✔
130
  }
131
  if ( report_function_name_conflict( node.source_location, node.identifier, "for loop iterator" ) )
70✔
132
  {
133
    return;
2✔
134
  }
135

136
  node.first().accept( *this );
68✔
137
  node.last().accept( *this );
68✔
138

139
  LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
68✔
140
  scope.create( node.identifier, WarnOn::Never, node.source_location );
68✔
141
  scope.create( "_" + node.identifier + "_end", WarnOn::Never, node.source_location );
68✔
142

143
  FlowControlScope break_scope( break_scopes, node.source_location, node.get_label(),
68✔
144
                                node.break_label );
136✔
145
  FlowControlScope continue_scope( continue_scopes, node.source_location, node.get_label(),
68✔
146
                                   node.continue_label );
136✔
147

148
  node.block().accept( *this );
68✔
149
}
68✔
150

151
void SemanticAnalyzer::visit_block( Block& block )
5,810✔
152
{
153
  LocalVariableScope scope( local_scopes, block.local_variable_scope_info );
5,810✔
154

155
  visit_children( block );
5,810✔
156
}
5,810✔
157

158
void SemanticAnalyzer::visit_index_binding( IndexBinding& node )
89✔
159
{
160
  u8 index = 0;
89✔
161

162
  if ( node.binding_count() > 127 )
89✔
163
  {
164
    report.error( node, "Too many binding elements. Maximum is 127." );
2✔
165
  }
166

167
  for ( const auto& child : node.bindings() )
559✔
168
  {
169
    if ( auto member_binding = dynamic_cast<VariableBinding*>( &child.get() ) )
470✔
170
    {
171
      if ( member_binding->rest )
443✔
172
      {
173
        if ( node.children.back().get() != member_binding )
29✔
174
          report.error( *member_binding, "Index rest binding must be the last in the list." );
2✔
175

176
        node.rest_index = index;
29✔
177
      }
178
    }
179

180
    ++index;
470✔
181
  }
89✔
182

183
  visit_children( node );
89✔
184
}
89✔
185

186
void SemanticAnalyzer::visit_class_declaration( ClassDeclaration& node )
620✔
187
{
188
  const auto& class_name = node.name;
620✔
189
  std::set<std::string, Clib::ci_cmp_pred> named_baseclasses;
620✔
190
  std::list<ClassDeclaration*> to_visit;
620✔
191
  std::set<ClassDeclaration*> visited;
620✔
192

193
  report.debug( node, "Class '{}' declared with {} parameters", class_name,
×
194
                node.parameters().size() );
620✔
195

196
  for ( const auto& base_class_link : node.base_class_links )
1,026✔
197
  {
198
    const auto& baseclass_name = base_class_link->name;
406✔
199
    auto itr = workspace.all_class_locations.find( baseclass_name );
406✔
200
    if ( itr == workspace.all_class_locations.end() )
406✔
201
    {
202
      report.error( base_class_link->source_location,
2✔
203
                    "Class '{}' references unknown base class '{}'", class_name, baseclass_name );
204
    }
205
    else
206
    {
207
      if ( auto cd = base_class_link->class_declaration() )
404✔
208
        to_visit.push_back( cd );
404✔
209
      else
210
        node.internal_error( "no class linked for base class" );
×
211
    }
212

213
    if ( Clib::caseInsensitiveEqual( baseclass_name, class_name ) )
406✔
214
    {
215
      report.error( base_class_link->source_location,
2✔
216
                    "Class '{}' references itself as a base class.", class_name );
217
    }
218

219
    bool previously_referenced =
220
        named_baseclasses.find( baseclass_name ) != named_baseclasses.end();
406✔
221

222
    if ( previously_referenced )
406✔
223
    {
224
      report.error( base_class_link->source_location,
2✔
225
                    "Class '{}' references base class '{}' multiple times.", class_name,
226
                    baseclass_name );
227
    }
228
    else
229
    {
230
      named_baseclasses.emplace( baseclass_name );
404✔
231
      report.debug( base_class_link->source_location, "Class '{}' references base class '{}'",
404✔
232
                    class_name, baseclass_name );
233
    }
234
  }
235

236
  for ( auto to_visit_itr = to_visit.begin(); to_visit_itr != to_visit.end();
1,143✔
237
        to_visit_itr = to_visit.erase( to_visit_itr ) )
523✔
238
  {
239
    auto cd = *to_visit_itr;
523✔
240
    if ( visited.find( cd ) != visited.end() )
523✔
241
    {
242
      continue;
53✔
243
    }
244

245
    visited.insert( cd );
470✔
246

247
    if ( cd == &node )
470✔
248
    {
249
      report.error( node, "Class '{}' references itself as a base class through inheritance.",
8✔
250
                    class_name );
251
    }
252

253
    for ( const auto& base_class_link : cd->base_class_links )
589✔
254
    {
255
      if ( auto base_cd = base_class_link->class_declaration() )
119✔
256
      {
257
        to_visit.push_back( base_cd );
119✔
258
      }
259
      else
260
      {
261
        cd->internal_error( "no class linked for base class" );
×
262
      }
263
    }
264
  }
265

266
  for ( const auto& uninit_function_ref : node.uninit_functions() )
686✔
267
  {
268
    const auto& uninit_function = uninit_function_ref.get();
66✔
269
    if ( auto exiting_method_itr = node.methods.find( uninit_function.name );
66✔
270
         exiting_method_itr != node.methods.end() )
66✔
271
    {
272
      report.error( uninit_function.source_location,
2✔
273
                    "In uninitialized function declaration: A method named '{}' is already "
274
                    "defined in class '{}'.\n"
275
                    "  See also: {}",
276
                    uninit_function.name, class_name, exiting_method_itr->second->source_location );
2✔
277
    }
278
    else if ( uninit_function.type == UserFunctionType::Constructor && node.constructor_link )
77✔
279
    {
280
      report.error(
2✔
281
          uninit_function.source_location,
2✔
282
          "In uninitialized function declaration: A constructor is already defined in class '{}'.\n"
283
          "  See also: {}",
284
          class_name, node.constructor_link->source_location );
2✔
285
    }
286
  }
620✔
287
}
620✔
288

289
void SemanticAnalyzer::analyze_class( ClassDeclaration* class_decl )
425✔
290
{
291
  if ( analyzed_classes.contains( class_decl ) )
425✔
292
    return;
116✔
293

294
  // Since only non-static classes (ie. those with constructors) can have uninitialized
295
  // functions, skip analysis if the class does not have a (possibly inherited) constructor.
296
  //
297
  // The check for `user_function()` is just a safety check to prevent null pointer dereference:
298
  // other checks would report on this not being linked.
299
  if ( !class_decl->constructor_link || !class_decl->constructor_link->user_function() )
618✔
300
  {
NEW
301
    analyzed_classes.insert( class_decl );
×
NEW
302
    return;
×
303
  }
304

305
  std::vector<std::reference_wrapper<UninitializedFunctionDeclaration>> all_uninit_functions;
309✔
306
  std::list<ClassDeclaration*> to_visit{ class_decl };
309✔
307
  std::set<ClassDeclaration*> visited;
309✔
308
  std::map<std::string, UserFunction*, Clib::ci_cmp_pred> all_methods;
309✔
309

310
  for ( auto* cd : to_visit )
1,061✔
311
  {
312
    if ( visited.contains( cd ) )
752✔
313
    {
314
      continue;
49✔
315
    }
316

317
    visited.insert( cd );
703✔
318

319
    auto cd_uninit_functions = cd->uninit_functions();
703✔
320
    std::ranges::move( cd_uninit_functions, std::back_inserter( all_uninit_functions ) );
703✔
321

322
    for ( const auto& [method_name, method_link] : cd->methods )
972✔
323
    {
324
      if ( auto uf = method_link->user_function() )
269✔
325
      {
326
        // Only add the method if it doesn't already exist: a base class' method
327
        // should not overwrite a child class' method.
328
        if ( !all_methods.contains( method_name ) )
269✔
329
        {
330
          all_methods[method_name] = uf;
215✔
331
        }
332
      }
333
      else
334
      {
NEW
335
        cd->internal_error( fmt::format( "no user function linked for method {}::{}",
×
NEW
336
                                         class_decl->name, method_name ) );
×
337
      }
338
    }
339

340
    for ( const auto& base_class_link : cd->base_class_links )
1,146✔
341
    {
342
      if ( auto base_cd = base_class_link->class_declaration() )
443✔
343
      {
344
        to_visit.push_back( base_cd );
443✔
345
      }
346
      else
347
      {
NEW
348
        cd->internal_error( fmt::format( "no class linked for {} baseclass {}", class_decl->name,
×
NEW
349
                                         base_class_link->name ) );
×
350
      }
351
    }
352
  }
703✔
353

354
  auto report_error_if_not_same =
355
      [&]( UserFunction* defined_func, UninitializedFunctionDeclaration* uninit_func )
70✔
356
  {
357
    if ( defined_func->is_variadic() != uninit_func->is_variadic() ||
70✔
358
         defined_func->parameter_count() != uninit_func->parameter_count() ||
124✔
359
         defined_func->type != uninit_func->type )
54✔
360
    {
361
      std::string details = fmt::format(
362
          "Expecting {} with {}{} parameters, got {} with {}{} parameters.", uninit_func->type,
16✔
363
          uninit_func->parameter_count(), uninit_func->is_variadic() ? "+" : "", defined_func->type,
16✔
364
          defined_func->parameter_count(), defined_func->is_variadic() ? "+" : "" );
32✔
365

366
      report.error( defined_func->source_location,
16✔
367
                    "Class method '{}' does not correctly implement uninitialized function '{}':\n"
368
                    "  {}\n"
369
                    "  See also: {}",
370
                    defined_func->scoped_name(), uninit_func->scoped_name(), details,
32✔
371
                    uninit_func->source_location );
16✔
372
    }
16✔
373
  };
70✔
374

375
  for ( const auto& uninit_fun_ref : all_uninit_functions )
385✔
376
  {
377
    auto& uninit_func = uninit_fun_ref.get();
76✔
NEW
378
    report.debug( uninit_func, "Class '{}' inherits uninitialized function '{}::{}'",
×
379
                  class_decl->name, uninit_func.scope, uninit_func.name );
76✔
380

381
    if ( uninit_func.type == UserFunctionType::Constructor )
76✔
382
    {
383
      report_error_if_not_same( class_decl->constructor_link->user_function(), &uninit_func );
16✔
384
    }
385
    else if ( auto method_itr = all_methods.find( uninit_func.name );
60✔
386
              method_itr != all_methods.end() )
60✔
387
    {
388
      report_error_if_not_same( method_itr->second, &uninit_func );
52✔
389
    }
390
    else if ( auto nonmethod_func = std::ranges::find_if(
16✔
391
                  workspace.user_functions,
8✔
392
                  [&]( const auto& uf )
14✔
393
                  {
394
                    return Clib::caseInsensitiveEqual( uf->name, uninit_func.name ) &&
16✔
395
                           uf->scope == class_decl->name;
16✔
396
                  } );
397
              nonmethod_func != workspace.user_functions.end() )
8✔
398
    {
399
      // This will _always_ error, reporting the defined function as static, and the uninit function
400
      // as method.
401
      report_error_if_not_same( nonmethod_func->get(), &uninit_func );
2✔
402
    }
403
    else
404
    {
405
      // A "not implemented" error will happen if the function is static and unreferenced (ie. does
406
      // not exist in workspace.user_functions but exists in
407
      // function_resolver.available_user_function_parse_trees).
408
      report.error( class_decl->source_location,
6✔
409
                    "Class '{}' does not implement uninitialized function '{}::{}'\n"
410
                    "  See also: {}",
411
                    class_decl->name, uninit_func.scope, uninit_func.name,
6✔
412
                    uninit_func.source_location );
6✔
413
    }
414
  }
415

416
  analyzed_classes.insert( class_decl );
309✔
417
}
309✔
418

419
class CaseDispatchDuplicateSelectorAnalyzer : public NodeVisitor
420
{
421
public:
422
  explicit CaseDispatchDuplicateSelectorAnalyzer( Report& report ) : report( report ) {}
110✔
423

424
  void visit_block( Block& ) override
348✔
425
  {
426
    // just don't recurse into children
427
  }
348✔
428

429
  void visit_integer_value( IntegerValue& node ) override
160✔
430
  {
431
    auto seen = already_seen_integers.find( node.value );
160✔
432
    if ( seen != already_seen_integers.end() )
160✔
433
    {
434
      report.error( node,
2✔
435
                    "case statement already has a selector for integer value {}.\n"
436
                    "  See also: {}",
437
                    node.value, ( *seen ).second->source_location );
2✔
438
    }
439
    else
440
    {
441
      already_seen_integers[node.value] = &node;
158✔
442
    }
443
  }
160✔
444

445
  void visit_string_value( StringValue& node ) override
102✔
446
  {
447
    auto seen = already_seen_strings.find( node.value );
102✔
448
    if ( seen != already_seen_strings.end() )
102✔
449
    {
450
      report.error( node,
2✔
451
                    "case statement already has a selector for string value {}.\n"
452
                    "  See also: {}",
453
                    Clib::getencodedquotedstring( node.value ), ( *seen ).second->source_location );
4✔
454
    }
455
    else
456
    {
457
      already_seen_strings[node.value] = &node;
100✔
458
    }
459
  }
102✔
460

461
  void visit_case_dispatch_default_selector( CaseDispatchDefaultSelector& node ) override
62✔
462
  {
463
    if ( already_seen_default )
62✔
464
    {
465
      report.error( node,
2✔
466
                    "case statement already has a default clause.\n"
467
                    "  See also: {}",
468
                    already_seen_default->source_location );
2✔
469
    }
470
    else
471
    {
472
      already_seen_default = &node;
60✔
473
    }
474
  }
62✔
475

476
private:
477
  Report& report;
478

479
  CaseDispatchDefaultSelector* already_seen_default = nullptr;
480
  std::map<int, IntegerValue*> already_seen_integers;
481
  std::map<std::string, StringValue*> already_seen_strings;
482
};
483

484

485
void SemanticAnalyzer::visit_case_statement( CaseStatement& case_ast )
110✔
486
{
487
  CaseDispatchDuplicateSelectorAnalyzer duplicate_detector( report );
110✔
488
  case_ast.dispatch_groups().accept( duplicate_detector );
110✔
489

490
  FlowControlScope break_scope( break_scopes, case_ast.source_location, case_ast.get_label(),
110✔
491
                                case_ast.break_label );
220✔
492

493
  visit_children( case_ast );
110✔
494
}
110✔
495

496
void SemanticAnalyzer::visit_case_dispatch_group( CaseDispatchGroup& dispatch_group )
348✔
497
{
498
  FlowControlScope break_scope( break_scopes, dispatch_group.source_location, "",
348✔
499
                                dispatch_group.break_label );
348✔
500

501
  visit_children( dispatch_group );
348✔
502
}
348✔
503

504
class CaseDispatchSelectorAnalyzer : public NodeVisitor
505
{
506
public:
507
  explicit CaseDispatchSelectorAnalyzer( Report& report ) : report( report ) {}
348✔
508

509
  void visit_identifier( Identifier& identifier ) override
×
510
  {
511
    report.error( identifier, "Case selector '{}' is not a constant.", identifier.name() );
×
512
  }
×
513

514
  void visit_string_value( StringValue& sv ) override
102✔
515
  {
516
    if ( sv.value.size() >= 254 )
102✔
517
    {
518
      report.error( sv, "String expressions in CASE statements must be <= 253 characters." );
×
519
    }
520
  }
102✔
521

522
private:
523
  Report& report;
524
};
525

526
void SemanticAnalyzer::visit_case_dispatch_selectors( CaseDispatchSelectors& selectors )
348✔
527
{
528
  visit_children( selectors );
348✔
529

530
  CaseDispatchSelectorAnalyzer selector_analyzer( report );
348✔
531
  selectors.accept( selector_analyzer );
348✔
532
}
348✔
533

534
void SemanticAnalyzer::visit_cstyle_for_loop( CstyleForLoop& loop )
65✔
535
{
536
  visit_loop_statement( loop );
65✔
537
}
65✔
538

539
void SemanticAnalyzer::visit_do_while_loop( DoWhileLoop& do_while )
11✔
540
{
541
  visit_loop_statement( do_while );
11✔
542
}
11✔
543

544
void SemanticAnalyzer::visit_foreach_loop( ForeachLoop& node )
472✔
545
{
546
  if ( report_function_name_conflict( node.source_location, node.iterator_name,
472✔
547
                                      "foreach iterator" ) )
548
  {
549
    return;
2✔
550
  }
551

552
  node.expression().accept( *this );
470✔
553

554
  LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
470✔
555
  scope.create( node.iterator_name, WarnOn::Never, node.source_location );
470✔
556
  scope.create( "_" + node.iterator_name + "_expr", WarnOn::Never, node.source_location );
470✔
557
  scope.create( "_" + node.iterator_name + "_iter", WarnOn::Never, node.source_location );
470✔
558

559
  FlowControlScope break_scope( break_scopes, node.source_location, node.get_label(),
470✔
560
                                node.break_label );
940✔
561
  FlowControlScope continue_scope( continue_scopes, node.source_location, node.get_label(),
470✔
562
                                   node.continue_label );
940✔
563

564
  node.block().accept( *this );
470✔
565
}
470✔
566

567
ScopeName& SemanticAnalyzer::current_scope_name()
11,326✔
568
{
569
  return current_scope_names.top();
11,326✔
570
}
571

572
void SemanticAnalyzer::visit_function_call( FunctionCall& fc )
17,970✔
573
{
574
  bool is_super_call =
575
      // The linked function is a SuperFunction
576
      ( fc.function_link->user_function() &&
17,970✔
577
        fc.function_link->user_function()->type == UserFunctionType::Super ) ||
53,754✔
578

579
      ( !fc.function_link->function() &&  // no linked function
18,290✔
580
        fc.scoped_name &&                 // there is a name in the call (ie. not an expression)
476✔
581
        Clib::caseInsensitiveEqual( fc.scoped_name->string(),
408✔
582
                                    Compiler::SUPER ) );  // the name is "super"
17,970✔
583

584
  // No function linked through FunctionResolver
585
  if ( !fc.function_link->function() )
17,970✔
586
  {
587
    if ( is_super_call && !globals.find( Compiler::SUPER ) && !locals.find( Compiler::SUPER ) )
496✔
588
    {
589
      report.error( fc, "In call to 'super': No base class defines a constructor." );
2✔
590
      return;  // skip "Unknown identifier" error
2✔
591
    }
592

593
    // Method name may be set to variable name, eg: `var foo; foo();` If so,
594
    // clear it out and insert it at the children start to set as callee.
595
    if ( fc.scoped_name )
474✔
596
    {
597
      // If the a function is a class, then it did not define a constructor (since
598
      // there was no function linked).
599

600
      // If a function call is eg. `Animal()` with no scope, check the string as-is.
601
      std::string class_name = fc.string();
202✔
602

603
      auto class_itr = workspace.all_class_locations.find( class_name );
202✔
604
      if ( class_itr == workspace.all_class_locations.end() )
202✔
605
      {
606
        class_name = fc.scoped_name->scope.string();
194✔
607

608
        class_itr = workspace.all_class_locations.find( class_name );
194✔
609
        if ( class_itr == workspace.all_class_locations.end() )
194✔
610
        {
611
          class_name.clear();
186✔
612
        }
613
      }
614

615
      if ( !class_name.empty() )
202✔
616
      {
617
        // There may be a variable named the same as the class, eg:
618
        //
619
        //   class Animal() var Animal; endclass
620
        //
621
        // If that is the case, there will be a global named
622
        // `class_name::class_name`. We will not error in this case.
623
        if ( Clib::caseInsensitiveEqual( fc.scoped_name->name, class_name ) &&
42✔
624
             !globals.find( ScopableName( class_name, class_name ).string() ) )
42✔
625
        {
626
          bool has_base_classes = false;
10✔
627

628
          if ( auto class_decl_itr = workspace.class_declaration_indexes.find( class_name );
10✔
629
               class_decl_itr != workspace.class_declaration_indexes.end() &&
20✔
630
               workspace.class_declarations[class_decl_itr->second]->parameters().size() > 0 )
20✔
631
          {
632
            has_base_classes = true;
4✔
633
          }
634

635
          auto msg =
636
              fmt::format( "In function call: Class '{}' {} define a constructor.", class_name,
637
                           has_base_classes ? "and its base class(es) do not" : "does not" );
10✔
638

639
          auto func_itr = workspace.all_function_locations.find(
10✔
640
              ScopableName( class_name, class_name ).string() );
20✔
641

642
          if ( func_itr != workspace.all_function_locations.end() )
10✔
643
          {
644
            msg += fmt::format( "\n  See also (missing 'this' parameter?): {}", func_itr->second );
8✔
645
          }
646

647
          report.error( fc, msg );
10✔
648
          return;  // skip "Unknown identifier" error
10✔
649
        }
10✔
650
      }
651

652
      auto callee = std::make_unique<Identifier>( fc.source_location, *fc.scoped_name );
192✔
653
      fc.children.insert( fc.children.begin(), std::move( callee ) );
192✔
654
      fc.scoped_name.reset();
192✔
655
    }
202✔
656

657
    // For function calls where the callee is not an identifier, take the
658
    // arguments as-is. We don't support named args, as we don't know the
659
    // function to execute until runtime.
660
    auto any_named = std::find_if( fc.children.begin() + 1, fc.children.end(),
464✔
661
                                   []( const std::unique_ptr<Node>& node )
899✔
662
                                   {
663
                                     const auto& arg_name =
664
                                         static_cast<Argument*>( node.get() )->identifier;
899✔
665
                                     return arg_name != nullptr;
899✔
666
                                   } );
667

668
    if ( any_named != fc.children.end() )
464✔
669
    {
670
      report.error( fc, "In function call: Cannot use named arguments here." );
2✔
671

672
      return;
2✔
673
    }
674

675
    visit_children( fc );
462✔
676

677
    return;
462✔
678
  }
679

680
  // here we turn the arguments passed (which can be named or positional)
681
  // into the final_arguments vector, which is just one parameter per
682
  // argument, in the correct order.
683

684
  typedef std::map<std::string, std::unique_ptr<Expression>> ArgumentList;
685
  ArgumentList arguments_passed;
17,494✔
686

687
  typedef std::list<std::unique_ptr<Expression>> VariadicArguments;
688
  VariadicArguments variadic_arguments;
17,494✔
689

690
  bool any_named = false;
17,494✔
691
  auto uf = fc.function_link->user_function();
17,494✔
692

693
  std::vector<std::unique_ptr<Argument>> arguments = fc.take_arguments();
17,494✔
694
  auto parameters = fc.parameters();
17,494✔
695
  bool has_class_inst_parameter = false;
17,494✔
696

697
  bool in_super_func = false;
17,494✔
698
  bool in_constructor_func = false;
17,494✔
699
  bool in_generated_function = false;
17,494✔
700

701
  if ( !user_functions.empty() )
17,494✔
702
  {
703
    if ( user_functions.top()->type == UserFunctionType::Super )
7,414✔
704
    {
705
      in_super_func = true;
239✔
706
    }
707
    else if ( user_functions.top()->type == UserFunctionType::Constructor )
7,175✔
708
    {
709
      in_constructor_func = true;
386✔
710
      in_generated_function = dynamic_cast<GeneratedFunction*>( user_functions.top() );
386✔
711
    }
712
  }
713

714
  if ( uf )
17,494✔
715
  {
716
    // A super() call can only be used in a constructor function.
717
    if ( is_super_call && !in_constructor_func )
4,754✔
718
    {
719
      report.error( fc, "In call to '{}': super() can only be used in constructor functions.",
×
720
                    uf->name );
2✔
721
      return;
2✔
722
    }
723
    // Constructor functions are defined as `Constr( this )` and called
724
    // statically via `Constr()`. Provide a `this` parameter at this function
725
    // call site. Only do this when calling constructors outside of a
726
    // compiler-generated function (ie. super or generated constructor)
727
    else if ( uf->type == UserFunctionType::Constructor && !in_generated_function &&
4,752✔
728
              !in_super_func )
504✔
729
    {
730
      // A super call inherits the `this` argument
731
      if ( is_super_call )
265✔
732
      {
733
        // Super will use "this" argument
734
        arguments.insert( arguments.begin(),
×
735
                          std::make_unique<Argument>(
×
736
                              fc.source_location,
×
737
                              std::make_unique<Identifier>( fc.source_location, "this" ), false ) );
×
738

739
        report.debug( fc, "using ctor Identifier is_super_call={} in_super_func={} uf->name={}",
×
740
                      is_super_call, in_super_func, uf->name );
×
741
      }
742
      else
743
      {
744
        // Should never happen
745
        if ( uf->class_link == nullptr || uf->class_link->class_declaration() == nullptr )
265✔
746
        {
747
          uf->internal_error(
×
748
              fmt::format( "no class declaration found for user function '{}'", uf->name ) );
×
749
        }
750

751
        // Constructor will create a new "this" instance
752
        arguments.insert( arguments.begin(),
265✔
753
                          std::make_unique<Argument>(
265✔
754
                              fc.source_location,
265✔
755
                              std::make_unique<ClassInstance>(
265✔
756
                                  fc.source_location, uf->class_link->class_declaration() ),
265✔
757
                              false ) );
265✔
758

UNCOV
759
        report.debug( fc, "using ClassInstance is_super_call={} in_super_func={} uf->name={}",
×
760
                      is_super_call, in_super_func, uf->name );
265✔
761

762
        // Check class for uninit functions defined.
763
        analyze_class( uf->class_link->class_declaration() );
265✔
764
      }
765
      // Since a `this` argument is generated for constructor functions, disallow passing an
766
      // argument named `this`.
767
      has_class_inst_parameter = true;
265✔
768
    }
769
    else if ( uf->type == UserFunctionType::Super )
4,487✔
770
    {
771
      // Super will use "this" argument
772
      arguments.insert( arguments.begin(),
154✔
773
                        std::make_unique<Argument>(
154✔
774
                            fc.source_location,
154✔
775
                            std::make_unique<Identifier>( fc.source_location, "this" ), false ) );
308✔
776

777
      report.debug( fc, "using super Identifier is_super_call={} in_super_func={} uf->name={}",
154✔
778
                    is_super_call, in_super_func, uf->name );
154✔
779
    }
780
  }
781

782
  auto is_callee_variadic = parameters.size() && parameters.back().get().rest;
17,492✔
783

784
  const auto method_name = fc.string();
17,492✔
785

786
  for ( auto& arg_unique_ptr : arguments )
40,233✔
787
  {
788
    auto& arg = *arg_unique_ptr;
22,747✔
789
    std::string arg_name = arg.identifier ? arg.identifier->string() : "";
22,747✔
790

791
    if ( arg.spread )
22,747✔
792
    {
793
      if ( !uf )  // a module function
182✔
794
      {
795
        report.error( arg,
2✔
796
                      "In call to '{}': Spread operator cannot be used in module function call.",
797
                      method_name );
798
        return;
2✔
799
      }
800
      else if ( !uf->is_variadic() )
180✔
801
      {
802
        report.error( arg,
2✔
803
                      "In call to '{}': Spread operator can only be used in variadic functions.",
804
                      method_name );
805
        return;
2✔
806
      }
807
      else if ( arguments_passed.size() < parameters.size() - 1 )
178✔
808
      {
809
        report.error( arg,
2✔
810
                      "In call to '{}': Spread operator can only be used for arguments on or after "
811
                      "the formal rest parameter.",
812
                      method_name );
813
        return;
2✔
814
      }
815
    }
816

817
    if ( arg_name.empty() )
22,741✔
818
    {
819
      // Allow spread elements to come after named arguments, eg:
820
      //
821
      //  `foo( optA := 1, optB := 2, ... c )`
822
      //
823
      if ( any_named && !arg.spread )
21,815✔
824
      {
825
        report.error( arg, "In call to '{}': Unnamed args cannot follow named args.", method_name );
×
826
        return;
×
827
      }
828

829
      // Too many arguments passed?
830
      if ( arguments_passed.size() >= parameters.size() )
21,815✔
831
      {
832
        // Allowed if variadic
833
        if ( is_callee_variadic )
295✔
834
        {
835
          variadic_arguments.push_back( arg.take_expression() );
291✔
836

837
          // Do not add to `arguments_passed`, so continue.
838
          continue;
291✔
839
        }
840
        else
841
        {
842
          auto expected_args =
843
              static_cast<int>( parameters.size() ) - ( has_class_inst_parameter ? 1 : 0 );
4✔
844

845
          report.error( arg, "In call to '{}': Too many arguments passed.  Expected {}, got {}.",
×
846
                        method_name, expected_args, arguments.size() );
4✔
847
          continue;
4✔
848
        }
4✔
849
      }
850
      else
851
      {
852
        arg_name = parameters.at( arguments_passed.size() ).get().name.string();
21,520✔
853
      }
854
    }
855
    else
856
    {
857
      any_named = true;
926✔
858

859
      if ( has_class_inst_parameter && !in_super_func && !is_super_call &&
926✔
860
           Clib::caseInsensitiveEqual( arg_name, "this" ) )
926✔
861
      {
862
        report.error( arg, "In call to '{}': Cannot pass 'this' to constructor function.",
×
863
                      method_name );
864
        return;
×
865
      }
866
    }
867

868
    if ( arguments_passed.find( arg_name ) != arguments_passed.end() )
22,446✔
869
    {
870
      report.error( arg, "In call to '{}': Parameter '{}' passed more than once.", method_name,
×
871
                    arg_name );
872
      return;
×
873
    }
874

875
    // Inside a call to super(), if the arg is un-scoped, find the base class it belongs to. Error
876
    // if ambiguous.
877
    if ( is_super_call )
22,446✔
878
    {
879
      if ( arg.identifier && arg.identifier->global() && arg.identifier->string() != "this" )
338✔
880
      {
881
        std::string base_class;
17✔
882
        std::string first_location;
17✔
883
        std::string err_msg;
17✔
884

885
        auto add_location = [this]( std::string& where, const std::string& class_name )
57✔
886
        {
887
          fmt::format_to( std::back_inserter( where ), "  See: {}", class_name );
19✔
888

889
          auto funct_itr = workspace.all_function_locations.find(
19✔
890
              ScopableName( class_name, class_name ).string() );
38✔
891
          if ( funct_itr != workspace.all_function_locations.end() )
19✔
892
          {
893
            fmt::format_to( std::back_inserter( where ), " {}\n", funct_itr->second );
38✔
894
          }
895
          else
896
          {
897
            where += "\n";
×
898
          }
899
        };
19✔
900

901
        for ( auto& param_ref : parameters )
83✔
902
        {
903
          auto& param = param_ref.get();
66✔
904
          const auto& param_name = param.name.name;
66✔
905
          if ( Clib::caseInsensitiveEqual( param_name, arg_name ) )
66✔
906
          {
907
            if ( !base_class.empty() )
19✔
908
            {
909
              if ( err_msg.empty() )
2✔
910
              {
911
                fmt::format_to( std::back_inserter( err_msg ),
4✔
912
                                "In call to '{}': Ambiguous parameter '{}'.\n{}", method_name,
913
                                param_name, first_location );
914
              }
915

916
              add_location( err_msg, param.name.scope.string() );
2✔
917
            }
918
            else
919
            {
920
              base_class = param.name.scope.string();
17✔
921

922
              add_location( first_location, base_class );
17✔
923
            }
924
          }
925
        }
926
        if ( !err_msg.empty() )
17✔
927
        {
928
          report.error( fc, err_msg );
2✔
929
        }
930
        else
931
        {
932
          arg_name = ScopableName( base_class, arg_name ).string();
15✔
933
        }
934
      }
17✔
935
    }
936

937

938
    arguments_passed[arg_name] = arg.take_expression();
22,446✔
939
  }
22,747✔
940

941
  std::vector<std::unique_ptr<Node>> final_arguments;
17,486✔
942

943
  for ( auto& param_ref : parameters )
50,372✔
944
  {
945
    FunctionParameterDeclaration& param = param_ref.get();
32,904✔
946
    auto itr = arguments_passed.find( param.name.string() );
32,904✔
947
    if ( itr == arguments_passed.end() )
32,904✔
948
    {
949
      if ( auto default_value = param.default_value() )
10,468✔
950
      {
951
        SimpleValueCloner cloner( report, default_value->source_location );
10,423✔
952
        auto final_argument = cloner.clone( *default_value );
10,423✔
953

954
        if ( final_argument )
10,423✔
955
        {
956
          final_arguments.push_back( std::move( final_argument ) );
10,421✔
957
        }
958
        else
959
        {
960
          report.error(
×
961
              param, "In call to '{}': Unable to create argument from default for parameter '{}'.",
962
              method_name, param.name );
2✔
963
          return;
2✔
964
        }
965
      }
10,425✔
966
      else if ( !param.rest )
45✔
967
      {
968
        report.error( fc,
×
969
                      "In call to '{}': Parameter '{}' was not passed, and there is no default.",
970
                      method_name, param.name );
16✔
971
        return;
16✔
972
      }
973
    }
974
    else
975
    {
976
      final_arguments.push_back( std::move( ( *itr ).second ) );
22,436✔
977
      arguments_passed.erase( itr );
22,436✔
978
    }
979
  }
980

981
  if ( is_callee_variadic )
17,468✔
982
  {
983
    // Push the leftover arguments into the call.
984
    for ( auto& arg : variadic_arguments )
538✔
985
    {
986
      final_arguments.push_back( std::move( arg ) );
291✔
987
    }
988
  }
989
  else
990
  {
991
    for ( auto& unused_argument : arguments_passed )
17,223✔
992
    {
993
      report.error( *unused_argument.second,
2✔
994
                    "In call to '{}': Parameter '{}' passed by name, but the function has no "
995
                    "such parameter.",
996
                    method_name, unused_argument.first );
2✔
997
    }
998
    if ( !arguments_passed.empty() || arguments.size() > parameters.size() )
17,221✔
999
      return;
6✔
1000
  }
1001

1002
  fc.children = std::move( final_arguments );
17,462✔
1003

1004
  // do this afterwards, so that named parameters will not be looked up as identifiers.
1005
  visit_children( fc );
17,462✔
1006
}
17,644✔
1007

1008
void SemanticAnalyzer::visit_function_parameter_list( FunctionParameterList& node )
3,277✔
1009
{
1010
  // A rest parameter can only be the last parameter in the list. Since we
1011
  // iterate in reverse, start at `true` and set to `false` after first
1012
  // iteration.
1013
  bool can_have_rest_parameter = true;
3,277✔
1014

1015
  for ( auto& child : std::views::reverse( node.children ) )
7,413✔
1016
  {
1017
    child->accept( *this );
4,136✔
1018

1019
    bool has_rest_parameter = static_cast<FunctionParameterDeclaration*>( child.get() )->rest;
4,136✔
1020

1021
    if ( has_rest_parameter && !can_have_rest_parameter )
4,136✔
1022
    {
1023
      report.error( *child, "Rest parameter must be the last parameter in the list." );
2✔
1024
    }
1025

1026
    can_have_rest_parameter = false;
4,136✔
1027
  }
1028
}
3,277✔
1029

1030
void SemanticAnalyzer::visit_function_parameter_declaration( FunctionParameterDeclaration& node )
4,136✔
1031
{
1032
  auto node_name = node.name.string();
4,136✔
1033
  if ( auto default_value = node.default_value() )
4,136✔
1034
  {
1035
    ConstantValidator validator;
323✔
1036
    // By accident, 0-parameter system function calls are allowed as constant values.
1037
    // They are not allowed as default parameters, though.
1038
    if ( !validator.validate( *default_value ) || dynamic_cast<FunctionCall*>( default_value ) )
323✔
1039
    {
1040
      report.error( node,
6✔
1041
                    "Parameter '{}' has a disallowed default.  Only simple operands are allowed as "
1042
                    "default arguments.",
1043
                    node_name );
1044
      // but continue, to avoid unknown identifier errors
1045
    }
1046
  }
323✔
1047
  if ( auto existing = locals.find( node_name ) )
8,272✔
1048
  {
1049
    report.error( node, "Parameter '{}' already defined.", node_name );
×
1050
    return;
×
1051
  }
4,136✔
1052

1053
  if ( node.rest && node.default_value() )
4,136✔
1054
  {
1055
    report.error( node, "Rest parameter '{}' cannot have a default value.", node_name );
2✔
1056
    return;
2✔
1057
  }
1058

1059
  WarnOn warn_on = node.unused ? WarnOn::IfUsed : WarnOn::IfNotUsed;
4,134✔
1060

1061
  if ( report_function_name_conflict( node.source_location, node_name, "function parameter" ) )
4,134✔
1062
  {
1063
    warn_on = WarnOn::Never;
2✔
1064
  }
1065

1066
  local_scopes.current_local_scope()->create( node_name, warn_on, node.source_location );
4,134✔
1067
}
4,136✔
1068

1069
void SemanticAnalyzer::visit_function_expression( FunctionExpression& node )
540✔
1070
{
1071
  if ( auto user_function = node.function_link->user_function() )
540✔
1072
  {
1073
    // Create a new capture scope for this function. It must be in a new C++
1074
    // scope for to add the captures to
1075
    // `user_function->capture_variable_scope_info` via the user function
1076
    // visitor.
1077
    {
1078
      FunctionVariableScope new_capture_scope( captures );
540✔
1079
      LocalVariableScope capture_scope( capture_scopes,
540✔
1080
                                        user_function->capture_variable_scope_info );
540✔
1081
      FunctionVariableScope new_function_scope( locals );
540✔
1082
      visit_user_function( *user_function );
540✔
1083
    }
540✔
1084

1085
    // Since the capture_scope was popped (above), any _existing_ capture scope refers to
1086
    // the parent function expression in the tree. Adjust that function to inherit this function's
1087
    // captures.
1088
    if ( auto cap_scope = capture_scopes.current_local_scope() )
540✔
1089
    {
1090
      for ( auto& variable : user_function->capture_variable_scope_info.variables )
417✔
1091
      {
1092
        // If the capture is not local, we must capture it
1093
        if ( !locals.find( variable->name ) )
604✔
1094
        {
1095
          // If already captured, set the variables capture to the existing.
1096
          if ( auto captured = captures.find( variable->name ) )
354✔
1097
          {
1098
            variable->capturing = captured;
96✔
1099
          }
1100
          // Otherwise, create new.
1101
          else if ( !captures.find( variable->name ) )
162✔
1102
          {
1103
            // Create a new capture variable in the parent function, setting
1104
            // this function expression's captured variable to this newly
1105
            // created one.
1106
            variable->capturing = cap_scope->capture( variable->capturing );
81✔
1107
          }
177✔
1108
        }
1109
      }
1110
    }
1111
  }
1112
}
540✔
1113

1114
void SemanticAnalyzer::visit_function_reference( FunctionReference& node )
744✔
1115
{
1116
  if ( auto function = node.function_link->user_function() )
744✔
1117
  {
1118
    if ( function->type == UserFunctionType::Super )
740✔
1119
    {
1120
      report.error( node, "Cannot reference super() function." );
2✔
1121
    }
1122
    else if ( function->type == UserFunctionType::Constructor )
738✔
1123
    {
1124
      if ( auto class_decl = function->class_link->class_declaration() )
160✔
1125
        analyze_class( class_decl );
160✔
1126
      else
1127
      {
1128
        // Should never happen, since a constructor function would always have a
1129
        // link to its class.
NEW
1130
        function->internal_error( fmt::format(
×
NEW
1131
            "no class declaration found for class constructor '{}'", function->name ) );
×
1132
      }
1133
    }
1134
  }
1135
  else
1136
  {
1137
    report.error( node, "User function '{}' not found", node.name );
4✔
1138
  }
1139
}
744✔
1140

1141
void SemanticAnalyzer::visit_identifier( Identifier& node )
30,145✔
1142
{
1143
  // Resolution order:
1144
  //
1145
  // if scoped: locals -> globals
1146
  // otherwise: local function -> local captures -> ancestor (above) functions -> globals
1147
  //
1148
  const auto& name = node.scoped_name.string();
30,145✔
1149

1150
  // If there is a scope, whether it is (":foo") empty or not ("Animal:foo"),
1151
  // we need to check both globals and locals.
1152
  if ( !node.scoped_name.scope.empty() )
30,145✔
1153
  {
1154
    if ( !node.scoped_name.scope.global() )
282✔
1155
    {
1156
      if ( auto local = locals.find( name ) )
524✔
1157
      {
1158
        local->mark_used();
191✔
1159
        node.variable = local;
191✔
1160
      }
262✔
1161
    }
1162
    // Did not find it in locals, check globals
1163
    if ( !node.variable )
564✔
1164
    {
1165
      if ( auto scoped_global = globals.find( name ) )
182✔
1166
      {
1167
        node.variable = scoped_global;
83✔
1168
      }
91✔
1169
    }
1170
  }
1171
  else
1172
  {
1173
    if ( auto local = locals.find( name ) )
59,726✔
1174
    {
1175
      local->mark_used();
20,403✔
1176
      node.variable = local;
20,403✔
1177
    }
1178
    else if ( auto captured = captures.find( name ) )
18,920✔
1179
    {
1180
      // Should already be marked used as it's not newly created (done below).
1181
      // There is no `captures.find_in_ancestors()` check because if an upper
1182
      // capture was found, we still need to capture it for our own function (done
1183
      // below).
1184
      node.variable = captured;
186✔
1185
    }
1186
    else if ( auto ancestor = locals.find_in_ancestors( name ) )
18,548✔
1187
    {
1188
      // Capture the variable. In a deeply nested capture, this will reference the
1189
      // local in the ancestor function. The function expression visitor will swap
1190
      // the 'capturing' to a local-safe variable.
1191
      node.variable = capture_scopes.current_local_scope()->capture( ancestor );
312✔
1192
      node.variable->mark_used();
312✔
1193
    }
1194
    else if ( auto global = globals.find( name ) )
17,924✔
1195
    {
1196
      node.variable = global;
8,922✔
1197
    }
1198
    else if ( !current_scope_name().global() )
40✔
1199
    {
1200
      const auto scoped_name = ScopableName( current_scope_name(), node.name() ).string();
12✔
1201

1202
      // We do not support nested classes, so if there is a `current_scope`, it would only _ever_
1203
      // exist in globals.
1204
      if ( auto scoped_global = globals.find( scoped_name ) )
24✔
1205
      {
1206
        node.variable = scoped_global;
12✔
1207
      }
12✔
1208
    }
57,571✔
1209
  }
1210

1211
  if ( !node.variable )
60,290✔
1212
  {
1213
    report.error( node, "Unknown identifier '{}'.", name );
36✔
1214
    return;
36✔
1215
  }
1216
}
30,145✔
1217

1218
void SemanticAnalyzer::visit_variable_binding( VariableBinding& node )
762✔
1219
{
1220
  if ( auto variable = create_variable( node.source_location, node.scoped_name.scope.string(),
762✔
1221
                                        node.scoped_name.name ) )
1,524✔
1222
  {
1223
    node.variable = std::move( variable );
762✔
1224
    visit_children( node );
762✔
1225
  }
762✔
1226
}
762✔
1227

1228
void SemanticAnalyzer::visit_jump_statement( JumpStatement& node )
643✔
1229
{
1230
  auto& scopes = node.jump_type == JumpStatement::Break ? break_scopes : continue_scopes;
643✔
1231

1232
  if ( auto scope = scopes.find( node.label ) )
643✔
1233
  {
1234
    node.flow_control_label = scope->flow_control_label;
643✔
1235
    node.local_variables_to_remove = locals.count() - scope->local_variables_size;
643✔
1236
  }
1237
  else
1238
  {
1239
    auto type_str = node.jump_type == JumpStatement::Break ? "break" : "continue";
×
1240

1241
    if ( !node.label.empty() && break_scopes.any() )
×
1242
      report.error( node, "Label '{}' not found for {}", node.label, type_str );
×
1243
    else
1244
      report.error( node, "Cannot {} here.", type_str );
×
1245
  }
1246
}
643✔
1247

1248

1249
void SemanticAnalyzer::visit_loop_statement( LoopStatement& loop )
531✔
1250
{
1251
  FlowControlScope continue_scope( continue_scopes, loop.source_location, loop.get_label(),
531✔
1252
                                   loop.continue_label );
1,062✔
1253
  FlowControlScope break_scope( break_scopes, loop.source_location, loop.get_label(),
531✔
1254
                                loop.break_label );
1,062✔
1255

1256
  visit_children( loop );
531✔
1257
}
531✔
1258

1259
void SemanticAnalyzer::visit_program( Program& program )
450✔
1260
{
1261
  LocalVariableScope scope( local_scopes, program.local_variable_scope_info );
450✔
1262

1263
  visit_children( program );
450✔
1264
}
450✔
1265

1266
void SemanticAnalyzer::visit_program_parameter_declaration( ProgramParameterDeclaration& node )
208✔
1267
{
1268
  if ( auto existing = locals.find( node.name ) )
416✔
1269
  {
1270
    report.error( node, "Parameter '{}' already defined.", node.name );
×
1271
    return;
×
1272
  }
208✔
1273
  WarnOn warn_on = node.unused ? WarnOn::IfUsed : WarnOn::IfNotUsed;
208✔
1274

1275
  if ( report_function_name_conflict( node.source_location, node.name, "program parameter" ) )
208✔
1276
  {
1277
    warn_on = WarnOn::Never;
2✔
1278
  }
1279

1280
  local_scopes.current_local_scope()->create( node.name, warn_on, node.source_location );
208✔
1281
}
1282

1283
void SemanticAnalyzer::visit_repeat_until_loop( RepeatUntilLoop& node )
38✔
1284
{
1285
  visit_loop_statement( node );
38✔
1286
}
38✔
1287

1288
void SemanticAnalyzer::visit_return_statement( ReturnStatement& node )
4,514✔
1289
{
1290
  if ( !user_functions.empty() )
4,514✔
1291
  {
1292
    auto uf = user_functions.top();
4,210✔
1293

1294
    if ( uf->type == UserFunctionType::Constructor && !node.children.empty() )
4,210✔
1295
    {
1296
      report.error( node, "Cannot return a value from a constructor function." );
2✔
1297
    }
1298
  }
1299

1300
  visit_children( node );
4,514✔
1301
}
4,514✔
1302

1303
void SemanticAnalyzer::visit_sequence_binding( SequenceBinding& node )
111✔
1304
{
1305
  u8 index = 0;
111✔
1306
  VariableBinding* previous_rest_binding = nullptr;
111✔
1307

1308
  if ( node.binding_count() > 127 )
111✔
1309
  {
1310
    report.error( node, "Too many binding elements. Maximum is 127." );
×
1311
  }
1312

1313
  for ( const auto& child : node.children )
445✔
1314
  {
1315
    if ( auto index_binding = dynamic_cast<VariableBinding*>( child.get() ) )
334✔
1316
    {
1317
      if ( index_binding->rest )
319✔
1318
      {
1319
        if ( previous_rest_binding != nullptr )
73✔
1320
        {
1321
          report.error( *index_binding,
2✔
1322
                        "Only one rest binding is allowed.\n"
1323
                        "  See also: {}",
1324
                        previous_rest_binding->source_location );
2✔
1325
        }
1326

1327
        previous_rest_binding = index_binding;
73✔
1328
        node.rest_index = index;
73✔
1329
      }
1330
    }
1331

1332
    ++index;
334✔
1333
  }
1334

1335
  visit_children( node );
111✔
1336
}
111✔
1337

1338
void SemanticAnalyzer::visit_user_function( UserFunction& node )
3,277✔
1339
{
1340
  // Track current scope for use in visit_identifier
1341
  current_scope_names.push( ScopeName( node.scope ) );
3,277✔
1342
  user_functions.emplace( &node );
3,277✔
1343
  if ( node.exported )
3,277✔
1344
  {
1345
    if ( !node.scope.empty() )
557✔
1346
    {
1347
      report.error( node, "Exported function '{}' cannot be scoped.", node.scoped_name() );
2✔
1348
    }
1349
    else
1350
    {
1351
      unsigned max_name_length = sizeof( Pol::Bscript::BSCRIPT_EXPORTED_FUNCTION::funcname ) - 1;
555✔
1352
      if ( node.name.length() > max_name_length )
555✔
1353
      {
1354
        report.error( node,
×
1355
                      "Exported function name '{}' is too long at {} characters.  Max length: {}",
1356
                      node.name, node.name.length(), max_name_length );
2✔
1357
      }
1358
    }
1359
  }
1360

1361
  LocalVariableScope scope( local_scopes, node.local_variable_scope_info );
3,277✔
1362
  visit_children( node );
3,277✔
1363
  user_functions.pop();
3,277✔
1364
  current_scope_names.pop();
3,277✔
1365
}
3,277✔
1366

1367
void SemanticAnalyzer::visit_var_statement( VarStatement& node )
5,632✔
1368
{
1369
  if ( auto variable = create_variable( node.source_location, node.scope, node.name ) )
11,264✔
1370
  {
1371
    node.variable = std::move( variable );
5,628✔
1372
    visit_children( node );
5,628✔
1373
  }
5,632✔
1374
}
5,632✔
1375

1376
void SemanticAnalyzer::visit_variable_assignment_statement( VariableAssignmentStatement& node )
1,909✔
1377
{
1378
  visit_children( node );
1,909✔
1379

1380
  if ( auto bop = dynamic_cast<BinaryOperator*>( &node.rhs() ) )
1,909✔
1381
  {
1382
    if ( bop->token_id == TOK_ASSIGN )
177✔
1383
    {
1384
      if ( auto second_ident = dynamic_cast<Identifier*>( &bop->lhs() ) )
3✔
1385
      {
1386
        if ( node.identifier().variable == second_ident->variable )
×
1387
        {
1388
          // we have something like
1389
          //      a := a := expr;
1390
          report.warning( node, "Double-assignment to the same variable '{}'.",
×
1391
                          node.identifier().name() );
×
1392
        }
1393
      }
1394
    }
1395
  }
1396
}
1,909✔
1397

1398
void SemanticAnalyzer::visit_while_loop( WhileLoop& node )
193✔
1399
{
1400
  visit_loop_statement( node );
193✔
1401
}
193✔
1402

1403
void SemanticAnalyzer::visit_constant_loop( ConstantPredicateLoop& node )
224✔
1404
{
1405
  visit_loop_statement( node );
224✔
1406
}
224✔
1407

1408
std::shared_ptr<Variable> SemanticAnalyzer::create_variable( const SourceLocation& source_location,
6,394✔
1409
                                                             const std::string& scope,
1410
                                                             const std::string& name )
1411
{
1412
  auto maybe_scoped_name = ScopableName( scope, name ).string();
6,394✔
1413

1414
  // Since this is not scoped check, we cannot have `Animal::FOO` and a constant `FOO`.
1415
  if ( auto constant = workspace.constants.find( name ) )
6,394✔
1416
  {
1417
    report.error( source_location,
×
1418
                  "Cannot define a variable with the same name as constant '{}'.\n"
1419
                  "  See also: {}",
1420
                  name, constant->source_location );
4✔
1421
    return {};
4✔
1422
  }
1423

1424
  report_function_name_conflict( source_location, maybe_scoped_name, "variable" );
6,390✔
1425

1426
  if ( auto local_scope = local_scopes.current_local_scope() )
6,390✔
1427
  {
1428
    return local_scope->create( maybe_scoped_name, WarnOn::Never, source_location );
3,776✔
1429
  }
1430
  else
1431
  {
1432
    if ( auto existing = globals.find( maybe_scoped_name ) )
5,228✔
1433
    {
1434
      report.error( source_location,
×
1435
                    "Global variable '{}' already defined.\n"
1436
                    "  See also: {}",
1437
                    maybe_scoped_name, existing->source_location );
×
1438
      return {};
×
1439
    }
2,614✔
1440

1441
    return globals.create( maybe_scoped_name, 0, WarnOn::Never, source_location );
2,614✔
1442
  }
1443
}
6,394✔
1444

1445
bool SemanticAnalyzer::report_function_name_conflict( const SourceLocation& referencing_loc,
11,274✔
1446
                                                      const std::string& function_name,
1447
                                                      const std::string& element_description )
1448
{
1449
  return report_function_name_conflict(
22,548✔
1450
      workspace, report, referencing_loc,
11,274✔
1451
      ScopableName( current_scope_name(), function_name ).string(), element_description );
22,548✔
1452
}
1453

1454
bool SemanticAnalyzer::report_function_name_conflict( const CompilerWorkspace& workspace,
170,352✔
1455
                                                      Report& report,
1456
                                                      const SourceLocation& referencing_loc,
1457
                                                      const std::string& function_name,
1458
                                                      const std::string& element_description )
1459
{
1460
  auto func_itr = workspace.all_function_locations.find( function_name );
170,352✔
1461
  if ( func_itr != workspace.all_function_locations.end() )
170,352✔
1462
  {
1463
    const SourceLocation& function_loc = ( *func_itr ).second;
16✔
1464
    report.error( referencing_loc,
16✔
1465
                  "Cannot define a {} with the same name as function '{}'.\n"
1466
                  "  Defined here: {}",
1467
                  element_description, function_name, function_loc );
1468
    return true;
16✔
1469
  }
1470
  return false;
170,336✔
1471
}
1472

1473
}  // 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