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

rokucommunity / brighterscript / #15028

13 Dec 2025 01:37PM UTC coverage: 87.29% (-0.006%) from 87.296%
#15028

push

web-flow
Merge 549ba37ce into a65ebfcad

14407 of 17439 branches covered (82.61%)

Branch coverage included in aggregate %.

36 of 36 new or added lines in 4 files covered. (100.0%)

28 existing lines in 4 files now uncovered.

15090 of 16353 relevant lines covered (92.28%)

24234.91 hits per line

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

89.32
/src/SymbolTable.ts
1
import type { BscType } from './types/BscType';
2
import type { ExtraSymbolData, GetTypeOptions } from './interfaces';
3
import { CacheVerifier } from './CacheVerifier';
1✔
4
import type { ReferenceType } from './types/ReferenceType';
5
import type { UnionType } from './types/UnionType';
6
import { getUniqueType } from './types/helpers';
1✔
7
import { isAnyReferenceType, isNamespaceType, isReferenceType } from './astUtils/reflection';
1✔
8
import { SymbolTypeFlag } from './SymbolTypeFlag';
1✔
9
import type { UninitializedType } from './types/UninitializedType';
10

11
/**
12
 * Stores the types associated with variables and functions in the Brighterscript code
13
 * Can be part of a hierarchy, so lookups can reference parent scopes
14
 */
15
export class SymbolTable implements SymbolTypeGetter {
1✔
16
    constructor(
17
        public name: string,
2,466,956✔
18
        parentProvider?: SymbolTableProvider
19
    ) {
20
        this.resetTypeCache();
2,466,956✔
21
        if (parentProvider) {
2,466,956✔
22
            this.pushParentProvider(parentProvider);
1,168,158✔
23
        }
24
    }
25

26
    /**
27
     * The map of symbols declared directly in this SymbolTable (excludes parent SymbolTable).
28
     * Indexed by lower symbol name
29
     */
30
    private symbolMap = new Map<string, BscSymbol[]>();
2,466,956✔
31

32
    private parentProviders = [] as SymbolTableProvider[];
2,466,956✔
33

34
    private cacheToken: string;
35

36
    private typeCache: Array<Map<string, TypeCacheEntry>>;
37

38
    /**
39
     * Used to invalidate the cache for all symbol tables.
40
     *
41
     * This is not the most optimized solution as cache will be shared across all instances of SymbolTable across all programs,
42
     * but this is the easiest way to handle nested/linked symbol table cache management so we can optimize this in the future some time...
43
     */
44
    static cacheVerifier = new CacheVerifier();
1✔
45

46
    static referenceTypeFactory: (memberKey: string, fullName, flags: SymbolTypeFlag, tableProvider: SymbolTypeGetterProvider) => ReferenceType;
47
    static unionTypeFactory: (types: BscType[]) => UnionType;
48
    static uninitializedTypeFactory: () => UninitializedType;
49

50
    /**
51
     * Push a function that will provide a parent SymbolTable when requested
52
     */
53
    public pushParentProvider(provider: SymbolTableProvider) {
54
        this.cachedCircularReferenceCheck = null;
1,386,478✔
55
        this.parentProviders.push(provider);
1,386,478✔
56
        return () => {
1,386,478✔
57
            this.popParentProvider();
11,510✔
58
        };
59
    }
60

61
    /**
62
     * Pop the current parentProvider
63
     */
64
    public popParentProvider() {
65
        this.cachedCircularReferenceCheck = null;
11,510✔
66
        this.parentProviders.pop();
11,510✔
67
    }
68

69
    /**
70
     * The parent SymbolTable (if there is one)
71
     */
72
    public get parent(): SymbolTable | undefined {
73
        return this.parentProviders[this.parentProviders.length - 1]?.();
651,177✔
74
    }
75

76
    private siblings = new Set<SymbolTable>();
2,466,956✔
77

78
    /**
79
     * Add a sibling symbol table (which will be inspected first before walking upward to the parent
80
     */
81
    public addSibling(sibling: SymbolTable) {
82
        this.siblings.add(sibling);
14,226✔
83
        return () => {
14,226✔
84
            this.siblings.delete(sibling);
10,348✔
85
        };
86
    }
87

88
    /**
89
     * Remove a sibling symbol table
90
     */
91
    public removeSibling(sibling: SymbolTable) {
92
        this.siblings.delete(sibling);
×
93
    }
94

95
    /**
96
     * Does the order of symbols in this symbol table matter?
97
     * Normally, this would only be for symbol tables referencing symbols declared within a function
98
     */
99
    public isOrdered = false;
2,466,956✔
100

101
    public pocketTables = new Array<PocketTable>();
2,466,956✔
102

103
    public addPocketTable(pocketTable: PocketTable) {
104
        pocketTable.table.isPocketTable = true;
326✔
105
        this.pocketTables.push(pocketTable);
326✔
106
        return () => {
326✔
UNCOV
107
            const index = this.pocketTables.findIndex(pt => pt === pocketTable);
×
108
            if (index >= 0) {
×
109
                this.pocketTables.splice(index, 1);
×
110
            }
111
        };
112
    }
113

114
    private isPocketTable = false;
2,466,956✔
115

116
    private getCurrentPocketTableDepth() {
117
        let depth = 0;
677,814✔
118
        let currentTable: SymbolTable = this;
677,814✔
119
        while (currentTable.isPocketTable) {
677,814✔
120
            depth++;
1,484✔
121
            currentTable = currentTable.parent;
1,484✔
122
        }
123
        return depth;
677,814✔
124
    }
125

126
    public getStatementIndexOfPocketTable(symbolTable: SymbolTable) {
127
        return this.pocketTables.find(pt => pt.table === symbolTable)?.index ?? -1;
1,989✔
128
    }
129

130
    private complementsTables = new Set<SymbolTable>();
2,466,956✔
131

132
    /**
133
     * This table complements this other table
134
     * Eg. This is an else branch, it complements a then branch
135
     */
136
    public complementOtherTable(otherTable: SymbolTable) {
137
        this.complementsTables.add(otherTable);
54✔
138
    }
139

140
    public clearSymbols() {
UNCOV
141
        this.symbolMap.clear();
×
142
    }
143

144
    /**
145
     * Checks if the symbol table contains the given symbol by name
146
     * If the identifier is not in this table, it will check the parent
147
     *
148
     * @param name the name to lookup
149
     * @param bitFlags flags to match (See SymbolTypeFlags)
150
     * @returns true if this symbol is in the symbol table
151
     */
152
    hasSymbol(name: string, bitFlags: SymbolTypeFlag): boolean {
153
        let currentTable: SymbolTable = this;
406,460✔
154
        const key = name?.toLowerCase();
406,460!
155
        let result: BscSymbol[];
156
        do {
406,460✔
157
            // look in our map first
158
            if ((result = currentTable.symbolMap.get(key))) {
414,019✔
159
                // eslint-disable-next-line no-bitwise
160
                if (result.find(symbol => symbol.flags & bitFlags)) {
193,233✔
161
                    return true;
193,217✔
162
                }
163
            }
164

165
            //look in pocket tables
166
            for (let pocket of this.pocketTables) {
220,802✔
167
                if ((result = pocket.table.symbolMap.get(key))) {
10!
168
                    // eslint-disable-next-line no-bitwise
UNCOV
169
                    if (result.find(symbol => symbol.flags & bitFlags)) {
×
UNCOV
170
                        return true;
×
171
                    }
172
                }
173
            }
174
            if ((result = currentTable.symbolMap.get(key))) {
220,802✔
175
                // eslint-disable-next-line no-bitwise
176
                if (result.find(symbol => symbol.flags & bitFlags)) {
15!
UNCOV
177
                    return true;
×
178
                }
179
            }
180

181
            //look through any sibling maps next
182
            for (let sibling of currentTable.siblings) {
220,802✔
183
                if ((result = sibling.symbolMap.get(key))) {
2,221✔
184
                    // eslint-disable-next-line no-bitwise
185
                    if (result.find(symbol => symbol.flags & bitFlags)) {
368!
186
                        return true;
368✔
187
                    }
188
                }
189
            }
190
            currentTable = currentTable.parent;
220,434✔
191
        } while (currentTable);
192
        return false;
212,875✔
193
    }
194

195
    /**
196
     * Gets the name/type pair for a given named variable or function name
197
     * If the identifier is not in this table, it will check the parent
198
     *
199
     * @param  name the name to lookup
200
     * @param bitFlags flags to match
201
     * @returns An array of BscSymbols - one for each time this symbol had a type implicitly defined
202
     */
203
    getSymbol(name: string, bitFlags: SymbolTypeFlag, additionalOptions: GetSymbolAdditionalOptions = {}): BscSymbol[] {
84,349✔
204
        let currentTable: SymbolTable = this;
513,608✔
205
        let previousTable: SymbolTable;
206
        const key = name?.toLowerCase();
513,608✔
207
        let result: BscSymbol[];
208
        let memberOfAncestor = false;
513,608✔
209
        const addAncestorInfo = (symbol: BscSymbol) => ({ ...symbol, data: { ...symbol.data, memberOfAncestor: memberOfAncestor } });
513,608✔
210
        let maxStatementIndex = Number.isInteger(additionalOptions?.maxStatementIndex) ? additionalOptions.maxStatementIndex : Number.MAX_SAFE_INTEGER;
513,608!
211
        do {
513,608✔
212

213
            if (previousTable) {
678,622✔
214
                maxStatementIndex = currentTable.isOrdered ? currentTable.getStatementIndexOfPocketTable(previousTable) : Number.MAX_SAFE_INTEGER;
165,014✔
215
            }
216

217
            // look in our map first
218
            let currentResults = currentTable.symbolMap.get(key);
678,622✔
219
            if (currentResults) {
678,622✔
220
                // eslint-disable-next-line no-bitwise
221
                currentResults = currentResults.filter(symbol => symbol.flags & bitFlags)
253,990✔
222
                    .filter(this.getSymbolLookupFilter(currentTable, maxStatementIndex, memberOfAncestor));
223
            }
224

225
            let precedingAssignmentIndex = -1;
678,622✔
226
            if (currentResults?.length > 0 && currentTable.isOrdered && maxStatementIndex >= 0) {
678,622✔
227
                this.sortSymbolsByAssignmentOrderInPlace(currentResults);
2,680✔
228
                const lastResult = currentResults[currentResults.length - 1];
2,680✔
229
                currentResults = [lastResult];
2,680✔
230
                precedingAssignmentIndex = lastResult.data?.definingNode?.statementIndex ?? -1;
2,680!
231
            }
232

233
            if (result?.length > 0) {
678,622✔
234
                // we already have results from a deeper pocketTable
235
                if (currentResults?.length > 0) {
11✔
236
                    result.push(...currentResults);
8✔
237
                }
238
            } else if (currentResults?.length > 0) {
678,611✔
239
                result = currentResults;
252,476✔
240
            }
241

242
            let depth = additionalOptions?.depth ?? currentTable.getCurrentPocketTableDepth();
678,622!
243
            const augmentationResult = currentTable.augmentSymbolResultsWithPocketTableResults(name, bitFlags, result, {
678,622✔
244
                ...additionalOptions,
245
                depth: depth,
246
                maxStatementIndex: maxStatementIndex,
247
                precedingAssignmentIndex: precedingAssignmentIndex
248
            });
249
            result = augmentationResult.symbols;
678,622✔
250
            const needCheckParent = (!augmentationResult.exhaustive && depth > 0);
678,622✔
251

252
            if (result?.length > 0 && !needCheckParent) {
678,622✔
253
                result = result.map(addAncestorInfo);
252,497✔
254
                break;
252,497✔
255
            }
256

257
            if (additionalOptions?.ignoreParentsAndSiblings) {
426,125!
258
                break;
695✔
259
            }
260
            //look through any sibling maps next
261
            for (let sibling of currentTable.siblings) {
425,430✔
262
                result = sibling.getSymbol(key, bitFlags);
78,156✔
263
                if (result?.length > 0) {
78,156✔
264
                    return result.map(addAncestorInfo);
3,274✔
265
                }
266
            }
267
            previousTable = currentTable;
422,156✔
268
            currentTable = currentTable.parent;
422,156✔
269
            memberOfAncestor = true;
422,156✔
270
        } while (currentTable);
271
        return result;
510,334✔
272
    }
273

274
    private augmentSymbolResultsWithPocketTableResults(name: string, bitFlags: SymbolTypeFlag, result: BscSymbol[], additionalOptions: { precedingAssignmentIndex?: number } & GetSymbolAdditionalOptions = {}): { symbols: BscSymbol[]; exhaustive: boolean } {
×
275
        let pocketTableResults: BscSymbol[] = [];
678,622✔
276
        let pocketTablesWeFoundSomethingIn = this.getSymbolDataFromPocketTables(name, bitFlags, additionalOptions);
678,622✔
277
        let pocketTablesAreExhaustive = false;
678,622✔
278
        const depth = additionalOptions.depth ?? 0;
678,622!
279
        for (let i = 0; i < pocketTablesWeFoundSomethingIn.length; i++) {
678,622✔
280
            let tableData = pocketTablesWeFoundSomethingIn[i];
124✔
281
            let pocketTable = tableData.pocketTable;
124✔
282
            pocketTableResults.push(...tableData.results);
124✔
283
            if (pocketTable.willAlwaysBeExecuted) {
124✔
284
                // remove all results before this
285
                pocketTableResults = [...tableData.results];
4✔
286
                pocketTablesAreExhaustive = true;
4✔
287
            }
288
            if (i === 0) {
124✔
289
                continue;
91✔
290
            }
291
            if (pocketTable.table.complementsTables?.size > 0) {
33!
292
                let tableSatisfied = true;
19✔
293
                let allPossibleSatisfiedResults: BscSymbol[] = [];
19✔
294
                // need to check if all tables this complements are satisfied
295
                for (const otherTable of pocketTable.table.complementsTables) {
19✔
296
                    const foundTableData = pocketTablesWeFoundSomethingIn.find((ptd => {
25✔
297
                        return otherTable === ptd.pocketTable.table;
35✔
298
                    }));
299
                    if (foundTableData) {
25✔
300
                        allPossibleSatisfiedResults.push(...foundTableData.results);
23✔
301
                    } else {
302
                        tableSatisfied = false;
2✔
303
                        break;
2✔
304
                    }
305
                }
306
                if (tableSatisfied) {
19✔
307
                    // remove all results before this
308
                    pocketTableResults = [...allPossibleSatisfiedResults, ...tableData.results];
17✔
309
                    pocketTablesAreExhaustive = true;
17✔
310
                }
311
            }
312
        }
313

314
        if (pocketTablesAreExhaustive) {
678,622✔
315
            result = pocketTableResults;
21✔
316
        } else {
317
            // we need to take into account the types before the pocket tables
318
            if (!result) {
678,601✔
319
                // there was no result before the pocket tables
320
                if (pocketTableResults.length > 0) {
426,126✔
321
                    if (depth === 0) {
23✔
322
                        // we got pocket tables results, and this is the top recursion
323
                        // add uninitialized
324
                        result = [{ name: name, type: SymbolTable.uninitializedTypeFactory(), data: {}, flags: bitFlags }, ...pocketTableResults];
5✔
325
                    } else {
326
                        //just return pocket table results:
327
                        result = pocketTableResults;
18✔
328
                    }
329
                } else {
330
                    // result should be undefined
331
                }
332
            } else {
333
                // just add any pocket table results....
334
                result.push(...pocketTableResults);
252,475✔
335
            }
336
        }
337
        // Do the results cover all possible execution paths?
338
        const areResultsExhaustive = pocketTablesAreExhaustive || pocketTablesWeFoundSomethingIn.length === 0;
678,622✔
339
        return { symbols: result, exhaustive: areResultsExhaustive };
678,622✔
340
    }
341

342
    private getSymbolDataFromPocketTables(name: string, bitFlags: SymbolTypeFlag, additionalOptions: { precedingAssignmentIndex?: number } & GetSymbolAdditionalOptions = {}): Array<{ pocketTable: PocketTable; results: BscSymbol[] }> {
×
343
        const possiblePocketTables = this.getPossiblePocketTables({ statementIndex: additionalOptions.maxStatementIndex }, additionalOptions.precedingAssignmentIndex);
678,622✔
344
        const depth = additionalOptions.depth ?? 0;
678,622!
345

346
        const pocketTablesWeFoundSomethingIn = new Array<{ pocketTable: PocketTable; results: BscSymbol[] }>();
678,622✔
347
        for (const pocketTable of possiblePocketTables) {
678,622✔
348
            const pocketTableTypes = pocketTable.table.getSymbol(name, bitFlags, {
808✔
349
                ignoreParentsAndSiblings: true,
350
                maxStatementIndex: Number.MAX_SAFE_INTEGER,
351
                depth: depth + 1
352
            });
353
            if (pocketTableTypes?.length > 0) {
808✔
354
                if (pocketTable.table.isOrdered) {
124✔
355
                    const lastResult = pocketTableTypes[pocketTableTypes.length - 1];
119✔
356
                    pocketTablesWeFoundSomethingIn.push({ pocketTable: pocketTable, results: [lastResult] });
119✔
357
                } else {
358
                    pocketTablesWeFoundSomethingIn.push({ pocketTable: pocketTable, results: pocketTableTypes });
5✔
359
                }
360
            }
361
        }
362
        return pocketTablesWeFoundSomethingIn;
678,622✔
363
    }
364

365

366
    /**
367
     * Adds a new symbol to the table
368
     */
369
    addSymbol(name: string, data: ExtraSymbolData, type: BscType, bitFlags: SymbolTypeFlag) {
370
        if (!name) {
7,658,180!
UNCOV
371
            return;
×
372
        }
373
        const key = name?.toLowerCase();
7,658,180!
374
        if (!this.symbolMap.has(key)) {
7,658,180✔
375
            this.symbolMap.set(key, []);
7,551,688✔
376
        }
377
        this.symbolMap.get(key)?.push({
7,658,180!
378
            name: name,
379
            data: data,
380
            type: type,
381
            flags: bitFlags
382
        });
383
    }
384

385
    /**
386
     * Removes a new symbol from the table
387
     */
388
    removeSymbol(name: string) {
389
        const key = name.toLowerCase();
2,215✔
390
        if (!this.symbolMap.has(key)) {
2,215✔
391
            this.symbolMap.set(key, []);
412✔
392
        }
393
        this.symbolMap.delete(key);
2,215✔
394
    }
395

396
    public getSymbolTypes(name: string, options: GetSymbolTypeOptions, sortByStatementIndex = false): TypeCacheEntry[] {
428,451✔
397
        const symbolArray = this.getSymbol(name, options.flags, {
428,451✔
398
            ignoreParentsAndSiblings: options.ignoreParentTables,
399
            maxStatementIndex: Number.isInteger(options.statementIndex) ? options.statementIndex as number : -1
428,451✔
400
        });
401
        if (!symbolArray) {
428,451✔
402
            return undefined;
176,857✔
403
        }
404
        let symbols: TypeCacheEntry[] = symbolArray?.map(symbol => ({ type: symbol.type, data: symbol.data, flags: symbol.flags }));
251,749!
405

406
        if (sortByStatementIndex) {
251,594!
UNCOV
407
            this.sortSymbolsByAssignmentOrderInPlace(symbols);
×
408
        }
409
        return symbols;
251,594✔
410
    }
411

412
    getSymbolType(name: string, options: GetSymbolTypeOptions): BscType {
413
        const cacheEntry = options.ignoreCacheForRetrieval ? undefined : this.getCachedType(name, options);
857,141!
414
        let resolvedType = cacheEntry?.type;
857,138✔
415
        let doSetCache = !resolvedType;
857,138✔
416
        const originalIsReferenceType = isAnyReferenceType(resolvedType);
857,138✔
417
        let data = cacheEntry?.data || {} as ExtraSymbolData;
857,138✔
418
        let foundFlags: SymbolTypeFlag = cacheEntry?.flags;
857,138✔
419
        if (!resolvedType || originalIsReferenceType) {
857,138✔
420
            let symbolTypes: TypeCacheEntry[];
421
            symbolTypes = this.getSymbolTypes(name, { ...options, statementIndex: options.statementIndex });
425,850✔
422
            data = symbolTypes?.[0]?.data;
425,850✔
423
            foundFlags = symbolTypes?.[0]?.flags;
425,850✔
424
            resolvedType = getUniqueType(symbolTypes?.map(symbol => symbol.type), SymbolTable.unionTypeFactory);
425,850✔
425
        }
426
        if (!resolvedType && options.fullName && options.tableProvider) {
857,138✔
427
            resolvedType = SymbolTable.referenceTypeFactory(name, options.fullName, options.flags, options.tableProvider);
123,952✔
428
        }
429
        const resolvedTypeIsReference = isAnyReferenceType(resolvedType);
857,138✔
430
        const newNonReferenceType = originalIsReferenceType && !isAnyReferenceType(resolvedType) && resolvedType;
857,138!
431
        doSetCache = doSetCache && (options.onlyCacheResolvedTypes ? !resolvedTypeIsReference : true);
857,138✔
432
        if (doSetCache || newNonReferenceType) {
857,138✔
433
            this.setCachedType(name, { type: resolvedType, data: data, flags: foundFlags }, options);
421,846✔
434
        }
435
        options.data ??= {};
857,138✔
436
        if (options.data) {
857,138!
437
            options.data.definingNode = data?.definingNode;
857,138✔
438
            options.data.description = data?.description;
857,138✔
439
            options.data.flags = foundFlags ?? options.flags;
857,138✔
440
            options.data.memberOfAncestor = data?.memberOfAncestor;
857,138✔
441
            options.data.doNotMerge = data?.doNotMerge;
857,138✔
442
            options.data.isAlias = data?.isAlias;
857,138✔
443
            options.data.isInstance = data?.isInstance;
857,138✔
444
            options.data.isFromDocComment = data?.isFromDocComment;
857,138✔
445
            options.data.isBuiltIn = data?.isBuiltIn;
857,138✔
446
            options.data.isFromCallFunc = data?.isFromCallFunc;
857,138✔
447
            options.data.isFromTypeStatement = data?.isFromTypeStatement;
857,138✔
448
        }
449
        return resolvedType;
857,138✔
450
    }
451

452
    isSymbolTypeInstance(name: string) {
453
        const data: ExtraSymbolData = {};
4✔
454
        this.getSymbolType(name, { flags: SymbolTypeFlag.runtime, data: data });
4✔
455
        return data?.isInstance;
4!
456
    }
457

458
    /**
459
     * Adds all the symbols from another table to this one
460
     * It will overwrite any existing symbols in this table
461
     */
462
    mergeSymbolTable(symbolTable: SymbolTable) {
463
        function mergeTables(intoTable: SymbolTable, fromTable: SymbolTable) {
464
            for (let [, value] of fromTable.symbolMap) {
6,387✔
465
                for (const symbol of value) {
5,803✔
466
                    if (symbol.data?.doNotMerge) {
5,825✔
467
                        continue;
50✔
468
                    }
469
                    intoTable.addSymbol(
5,775✔
470
                        symbol.name,
471
                        symbol.data,
472
                        symbol.type,
473
                        symbol.flags
474
                    );
475
                }
476
            }
477
        }
478

479
        mergeTables(this, symbolTable);
6,383✔
480
        for (let pocketTable of symbolTable.pocketTables) {
6,383✔
481
            mergeTables(this, pocketTable.table);
4✔
482
        }
483
    }
484

485
    mergeNamespaceSymbolTables(symbolTable: SymbolTable) {
486
        const disposables = [] as Array<() => void>;
8,899✔
487
        for (let [_name, value] of symbolTable.symbolMap) {
8,899✔
488
            const symbol = value[0];
2,143✔
489
            if (symbol) {
2,143!
490
                if (symbol.data?.doNotMerge) {
2,143!
UNCOV
491
                    continue;
×
492
                }
493
                const existingRuntimeType = this.getSymbolType(symbol.name, { flags: symbol.flags });
2,143✔
494

495
                if (isNamespaceType(existingRuntimeType) && isNamespaceType(symbol.type)) {
2,143✔
496
                    disposables.push(...existingRuntimeType.memberTable.mergeNamespaceSymbolTables(symbol.type.memberTable));
720✔
497
                } else {
498
                    this.addSymbol(
1,423✔
499
                        symbol.name,
500
                        symbol.data,
501
                        symbol.type,
502
                        symbol.flags
503
                    );
504
                    disposables.push(() => this.removeSymbol(symbol.name));
1,423✔
505
                }
506
            }
507
        }
508

509
        for (let siblingTable of symbolTable.siblings) {
8,899✔
510
            disposables.push(...this.mergeNamespaceSymbolTables(siblingTable));
718✔
511
        }
512
        return disposables;
8,899✔
513
    }
514

515
    /**
516
     * Get list of symbols declared directly in this SymbolTable (excludes parent SymbolTable).
517
     */
518
    public getOwnSymbols(bitFlags: SymbolTypeFlag): BscSymbol[] {
519
        let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
5,645✔
520
        // eslint-disable-next-line no-bitwise
521
        symbols = symbols.filter(symbol => symbol.flags & bitFlags);
7,439✔
522
        return symbols;
5,645✔
523
    }
524

525

526
    private cachedCircularReferenceCheck: null | boolean = null;
2,466,956✔
527

528
    private hasCircularReferenceWithAncestor() {
529
        if (this.cachedCircularReferenceCheck === false || this.cachedCircularReferenceCheck === true) {
1,189✔
530
            return this.cachedCircularReferenceCheck;
475✔
531
        }
532
        let foundCircReference = false;
714✔
533
        let p = this.parent;
714✔
534
        while (!foundCircReference && p) {
714✔
535
            foundCircReference = p === this;
949✔
536
            p = p.parent;
949✔
537
        }
538
        this.cachedCircularReferenceCheck = foundCircReference;
714✔
539
        return foundCircReference;
714✔
540
    }
541

542
    /**
543
     * Get list of all symbols declared in this SymbolTable (includes parent SymbolTable).
544
     */
545
    public getAllSymbols(bitFlags: SymbolTypeFlag): BscSymbol[] {
546
        let symbols: BscSymbol[] = [].concat(...this.symbolMap.values());
4,261✔
547
        //look through any sibling maps next
548
        for (let sibling of this.siblings) {
4,261✔
549
            symbols = symbols.concat(sibling.getAllSymbols(bitFlags));
86✔
550
        }
551

552
        if (this.parent && !this.hasCircularReferenceWithAncestor()) {
4,261✔
553
            symbols = symbols.concat(this.parent.getAllSymbols(bitFlags));
1,179✔
554
        }
555
        // eslint-disable-next-line no-bitwise
556
        symbols = symbols.filter(symbol => symbol.flags & bitFlags);
63,046✔
557

558
        //remove duplicate symbols
559
        const symbolsMap = new Map<string, BscSymbol>();
4,261✔
560
        for (const symbol of symbols) {
4,261✔
561
            const lowerSymbolName = symbol.name.toLowerCase();
49,293✔
562
            if (!symbolsMap.has(lowerSymbolName)) {
49,293✔
563
                symbolsMap.set(lowerSymbolName, symbol);
47,306✔
564
            }
565
        }
566
        return [...symbolsMap.values()];
4,261✔
567
    }
568

569
    private resetTypeCache() {
570
        this.typeCache = [
2,474,763✔
571
            undefined,
572
            new Map<string, TypeCacheEntry>(), // SymbolTypeFlags.runtime
573
            new Map<string, TypeCacheEntry>(), // SymbolTypeFlags.typetime
574
            new Map<string, TypeCacheEntry>() // SymbolTypeFlags.runtime & SymbolTypeFlags.typetime
575
        ];
576
        this.cacheToken = SymbolTable.cacheVerifier?.getToken();
2,474,763!
577
    }
578

579
    getCachedType(name: string, options: GetTypeOptions): TypeCacheEntry {
580
        if (SymbolTable.cacheVerifier) {
857,141!
581
            if (!SymbolTable.cacheVerifier?.checkToken(this.cacheToken)) {
857,141!
582
                // we have a bad token
583
                this.resetTypeCache();
7,807✔
584
                return;
7,807✔
585
            }
586
        } else {
587
            // no cache verifier
UNCOV
588
            return;
×
589
        }
590
        const cacheKey = this.getCacheKey(name, options);
849,334✔
591
        return this.typeCache[options.flags]?.get(cacheKey);
849,331!
592
    }
593

594
    setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetTypeOptions) {
595
        if (!cacheEntry || !cacheEntry.type) {
422,771✔
596
            return;
53,668✔
597
        }
598
        if (SymbolTable.cacheVerifier) {
369,103!
599
            if (!SymbolTable.cacheVerifier?.checkToken(this.cacheToken)) {
369,103!
600
                // we have a bad token - remove all other caches
UNCOV
601
                this.resetTypeCache();
×
602
            }
603
        } else {
604
            // no cache verifier
UNCOV
605
            return;
×
606
        }
607
        const cacheKey = this.getCacheKey(name, options);
369,103✔
608
        let existingCachedValue = this.typeCache[options.flags]?.get(cacheKey);
369,103!
609
        if (isReferenceType(cacheEntry.type) && !isReferenceType(existingCachedValue)) {
369,103✔
610
            // No need to overwrite a non-referenceType with a referenceType
611
            return;
123,945✔
612
        }
613
        return this.typeCache[options.flags]?.set(cacheKey, cacheEntry);
245,158!
614
    }
615

616
    private getCacheKey(name: string, options?: { statementIndex?: number | 'end' }) {
617
        return this.isOrdered ? `${name.toLowerCase()}@${options.statementIndex ?? '*'}` : `${name.toLowerCase()}`;
1,218,437✔
618
    }
619

620
    /**
621
     * Serialize this SymbolTable to JSON (useful for debugging reasons)
622
     */
623
    private toJSON() {
UNCOV
624
        return {
×
625
            name: this.name,
UNCOV
626
            siblings: [...this.siblings].map(sibling => sibling.toJSON()),
×
627
            parent: this.parent?.toJSON(),
×
628
            symbols: [
629
                ...new Set(
630
                    [...this.symbolMap.entries()].map(([key, symbols]) => {
UNCOV
631
                        return symbols.map(x => {
×
UNCOV
632
                            return { name: x.name, type: (x.type as any)?.__identifier };
×
633
                        });
634
                    }).flat().sort()
635
                )
636
            ]
637
        };
638
    }
639

640

641
    private sortSymbolsByAssignmentOrderInPlace(symbols: { data?: ExtraSymbolData }[]) {
642
        symbols.sort((a, b) => {
2,680✔
643
            if (!Number.isInteger(a.data?.definingNode?.statementIndex)) {
236!
UNCOV
644
                return -1;
×
645
            }
646
            if (!Number.isInteger(b.data?.definingNode?.statementIndex)) {
236!
UNCOV
647
                return 1;
×
648
            }
649
            if (b.data.definingNode.statementIndex > a.data.definingNode.statementIndex) {
236✔
650
                return -1;
8✔
651
            }
652
            if (b.data.definingNode.statementIndex < a.data.definingNode.statementIndex) {
228!
653
                return 1;
228✔
654
            }
655
            return -1;
×
656
        });
657
        return symbols;
2,680✔
658
    }
659

660

661
    private getPossiblePocketTables(options: { statementIndex?: number | 'end' }, precedingAssignmentIndex = -1) {
×
662
        if (!this.isOrdered) {
678,622✔
663
            return this.pocketTables;
658,330✔
664
        }
665
        const pocketTablesBetweenAssignmentAndPosition = this.pocketTables.filter(pt => {
20,292✔
666
            const isBeforePosition = options.statementIndex === 'end' ? true : options.statementIndex > pt.index;
4,871!
667
            let isAfterPrecedingAssignment = true;
4,871✔
668
            if (Number.isInteger(precedingAssignmentIndex)) {
4,871!
669
                isAfterPrecedingAssignment = precedingAssignmentIndex < pt.index;
4,871✔
670
            }
671
            return isAfterPrecedingAssignment && isBeforePosition;
4,871✔
672
        });
673
        return pocketTablesBetweenAssignmentAndPosition;
20,292✔
674
    }
675

676
    private getSymbolLookupFilter(currentTable: SymbolTable, maxAllowedStatementIndex: number, memberOfAncestor: boolean) {
677
        return (t: BscSymbol) => {
253,435✔
678
            if (!currentTable.isOrdered) {
253,089✔
679
                // order doesn't matter for current table
680
                return true;
249,733✔
681
            }
682

683
            if (maxAllowedStatementIndex >= 0 && t.data?.definingNode) {
3,356!
684
                if (memberOfAncestor || t.data.canUseInDefinedAstNode) {
3,207✔
685
                    // if we've already gone up a level, it's possible to have a variable assigned and used
686
                    // in the same statement, eg. for loop
687
                    return t.data.definingNode.statementIndex <= maxAllowedStatementIndex;
269✔
688

689
                } else {
690
                    return t.data.definingNode.statementIndex < maxAllowedStatementIndex;
2,938✔
691
                }
692
            }
693
            return true;
149✔
694
        };
695
    }
696
}
697

698
export interface BscSymbol {
699
    name: string;
700
    data: ExtraSymbolData;
701
    type: BscType;
702
    flags: SymbolTypeFlag;
703
}
704

705
export interface BscSymbolWithSource extends BscSymbol {
706
    comesFromAncestor: boolean; // this symbol comes from an ancestor symbol table
707
}
708

709
export interface SymbolTypeGetter {
710
    name: string;
711
    getSymbolType(name: string, options: GetSymbolTypeOptions): BscType;
712
    setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetSymbolTypeOptions);
713
    addSibling(symbolTable: SymbolTable);
714
}
715

716
/**
717
 * A function that returns a symbol table.
718
 */
719
export type SymbolTableProvider = () => SymbolTable;
720

721
/**
722
 * A function that returns a symbol types getter - smaller interface used in types
723
 */
724
export type SymbolTypeGetterProvider = () => SymbolTypeGetter;
725

726

727
export interface GetSymbolTypeOptions extends GetTypeOptions {
728
    fullName?: string;
729
    tableProvider?: SymbolTableProvider;
730
}
731

732
export interface TypeCacheEntry {
733
    type: BscType;
734
    data?: ExtraSymbolData;
735
    flags?: SymbolTypeFlag;
736
}
737

738
export interface PocketTable {
739
    table: SymbolTable;
740
    /**
741
     * The index of the statement that contains this table within its parent block
742
     */
743
    index: number;
744
    /**
745
     * Will the code this pocket table represents always be executed?
746
     * Eg. Conditional Compile blocks will always be executed because only valid #if blocks are validated
747
     */
748
    willAlwaysBeExecuted?: boolean;
749
}
750

751
export interface GetSymbolAdditionalOptions {
752
    ignoreParentsAndSiblings?: boolean;
753
    precedingAssignmentIndex?: number;
754
    maxStatementIndex?: number;
755
    depth?: number;
756
}
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