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

source-academy / py-slang / 23358181246

20 Mar 2026 06:58PM UTC coverage: 42.048% (+7.1%) from 34.925%
23358181246

Pull #104

github

web-flow
Merge 77ac9acf7 into 8feccc8f9
Pull Request #104: feat: replace hand-written parser with Nearley grammar

402 of 1286 branches covered (31.26%)

Branch coverage included in aggregate %.

543 of 618 new or added lines in 22 files covered. (87.86%)

36 existing lines in 2 files now uncovered.

1409 of 3021 relevant lines covered (46.64%)

52.91 hits per line

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

65.47
/src/resolver/resolver.ts
1
import { StmtNS, ExprNS } from "../ast-types";
1✔
2
type Expr = ExprNS.Expr;
3
type Stmt = StmtNS.Stmt;
4
import { Token } from "../tokenizer/tokenizer";
1✔
5
import { TokenType } from "../tokens";
1✔
6
import { ResolverErrors } from "./errors";
1✔
7
import { FeatureValidator } from "../validator/types";
8

9
import levenshtein from "fast-levenshtein";
1✔
10
// const levenshtein = require('fast-levenshtein');
11

12
const RedefineableTokenSentinel = new Token(TokenType.AT, "", 0, 0, 0);
1✔
13

14
export class Environment {
1✔
15
  source: string;
16
  // The parent of this environment
17
  enclosing: Environment | null;
18
  names: Map<string, Token>;
19
  // Function names in the environment.
20
  functions: Set<string>;
21
  // Names that are from import bindings, like 'y' in `from x import y`.
22
  // This only set at the top level environment. Child environments do not
23
  // copy this field.
24
  moduleBindings: Set<string>;
25
  definedNames: Set<string>;
26
  constructor(source: string, enclosing: Environment | null, names: Map<string, Token>) {
27
    this.source = source;
104✔
28
    this.enclosing = enclosing;
104✔
29
    this.names = names;
104✔
30
    this.functions = new Set();
104✔
31
    this.moduleBindings = new Set();
104✔
32
    this.definedNames = new Set();
104✔
33
  }
34

35
  /*
36
   * Does a full lookup up the environment chain for a name.
37
   * Returns the distance of the name from the current environment.
38
   * If name isn't found, return -1.
39
   * */
40
  lookupName(identifier: Token): number {
41
    const name = identifier.lexeme;
21✔
42
    let distance = 0;
21✔
43
    // eslint-disable-next-line @typescript-eslint/no-this-alias
44
    let curr: Environment | null = this;
21✔
45
    while (curr !== null) {
21✔
46
      if (curr.names.has(name)) {
32✔
47
        break;
18✔
48
      }
49
      distance += 1;
14✔
50
      curr = curr.enclosing;
14✔
51
    }
52
    return curr === null ? -1 : distance;
21✔
53
  }
54

55
  /* Looks up the name but only for the current environment. */
56
  lookupNameCurrentEnv(identifier: Token): Token | undefined {
57
    return this.names.get(identifier.lexeme);
17✔
58
  }
59
  lookupNameCurrentEnvWithError(identifier: Token) {
60
    if (this.lookupName(identifier) < 0) {
21✔
61
      throw new ResolverErrors.NameNotFoundError(
3✔
62
        identifier.line,
63
        identifier.col,
64
        this.source,
65
        identifier.indexInSource,
66
        identifier.indexInSource + identifier.lexeme.length,
67
        this.suggestName(identifier),
68
      );
69
    }
70
  }
71
  lookupNameParentEnvWithError(identifier: Token) {
72
    const name = identifier.lexeme;
1✔
73
    const parent = this.enclosing;
1✔
74

75
    if (parent === null || !parent.names.has(name)) {
1!
76
      throw new ResolverErrors.NameNotFoundError(
×
77
        identifier.line,
78
        identifier.col,
79
        this.source,
80
        identifier.indexInSource,
81
        identifier.indexInSource + name.length,
82
        this.suggestName(identifier),
83
      );
84
    }
85
  }
86
  declareName(identifier: Token) {
87
    this.names.set(identifier.lexeme, identifier);
41✔
88
    this.definedNames.add(identifier.lexeme);
41✔
89
  }
90
  // Same as declareName but allowed to re-declare later.
91
  declarePlaceholderName(identifier: Token) {
92
    const lookup = this.lookupNameCurrentEnv(identifier);
17✔
93
    if (lookup !== undefined) {
17!
94
      throw new ResolverErrors.NameReassignmentError(
×
95
        identifier.line,
96
        identifier.col,
97
        this.source,
98
        identifier.indexInSource,
99
        identifier.indexInSource + identifier.lexeme.length,
100
        lookup,
101
      );
102
    }
103
    this.names.set(identifier.lexeme, RedefineableTokenSentinel);
17✔
104
  }
105
  suggestNameCurrentEnv(identifier: Token): string | null {
106
    const name = identifier.lexeme;
×
107
    let minDistance = Infinity;
×
108
    let minName = null;
×
109
    for (const declName of this.names.keys()) {
×
110
      const dist = levenshtein.get(name, declName);
×
111
      if (dist < minDistance) {
×
112
        minDistance = dist;
×
113
        minName = declName;
×
114
      }
115
    }
116
    return minName;
×
117
  }
118
  /*
119
   * Finds name closest to name in all environments up to builtin environment.
120
   * Calculated using min levenshtein distance.
121
   * */
122
  suggestName(identifier: Token): string | null {
123
    const name = identifier.lexeme;
3✔
124
    let minDistance = Infinity;
3✔
125
    let minName = null;
3✔
126
    // eslint-disable-next-line @typescript-eslint/no-this-alias
127
    let curr: Environment | null = this;
3✔
128
    while (curr !== null) {
3✔
129
      for (const declName of curr.names.keys()) {
6✔
130
        const dist = levenshtein.get(name, declName);
208✔
131
        if (dist < minDistance) {
208✔
132
          minDistance = dist;
6✔
133
          minName = declName;
6✔
134
        }
135
      }
136
      curr = curr.enclosing;
6✔
137
    }
138
    if (minDistance >= 4) {
3✔
139
      // This is pretty far, so just return null
140
      return null;
1✔
141
    }
142
    return minName;
2✔
143
  }
144
}
145
export class Resolver implements StmtNS.Visitor<void>, ExprNS.Visitor<void> {
1✔
146
  source: string;
147
  ast: Stmt;
148
  // change the environment to be suite scope as in python
149
  environment: Environment | null;
150
  functionScope: Environment | null;
151
  private validators: FeatureValidator[];
152

153
  constructor(source: string, ast: Stmt, validators: FeatureValidator[] = []) {
×
154
    this.source = source;
43✔
155
    this.ast = ast;
43✔
156
    this.validators = validators;
43✔
157
    // The global environment
158
    this.environment = new Environment(
43✔
159
      source,
160
      null,
161
      new Map([
162
        // misc library
163
        ["_int", new Token(TokenType.NAME, "_int", 0, 0, 0)],
164
        ["_int_from_string", new Token(TokenType.NAME, "_int_from_string", 0, 0, 0)],
165
        ["abs", new Token(TokenType.NAME, "abs", 0, 0, 0)],
166
        ["char_at", new Token(TokenType.NAME, "char_at", 0, 0, 0)],
167
        ["error", new Token(TokenType.NAME, "error", 0, 0, 0)],
168
        ["input", new Token(TokenType.NAME, "input", 0, 0, 0)],
169
        ["isinstance", new Token(TokenType.NAME, "isinstance", 0, 0, 0)],
170
        ["max", new Token(TokenType.NAME, "max", 0, 0, 0)],
171
        ["min", new Token(TokenType.NAME, "min", 0, 0, 0)],
172
        ["print", new Token(TokenType.NAME, "print", 0, 0, 0)],
173
        ["range", new Token(TokenType.NAME, "range", 0, 0, 0)],
174
        ["random_random", new Token(TokenType.NAME, "random_random", 0, 0, 0)],
175
        ["round", new Token(TokenType.NAME, "round", 0, 0, 0)],
176
        ["str", new Token(TokenType.NAME, "str", 0, 0, 0)],
177
        ["time_time", new Token(TokenType.NAME, "time_time", 0, 0, 0)],
178

179
        // math constants
180
        ["math_pi", new Token(TokenType.NAME, "math_pi", 0, 0, 0)],
181
        ["math_e", new Token(TokenType.NAME, "math_e", 0, 0, 0)],
182
        ["math_inf", new Token(TokenType.NAME, "math_inf", 0, 0, 0)],
183
        ["math_nan", new Token(TokenType.NAME, "math_nan", 0, 0, 0)],
184
        ["math_tau", new Token(TokenType.NAME, "math_tau", 0, 0, 0)],
185

186
        // math library
187
        ["math_acos", new Token(TokenType.NAME, "math_acos", 0, 0, 0)],
188
        ["math_acosh", new Token(TokenType.NAME, "math_acosh", 0, 0, 0)],
189
        ["math_asin", new Token(TokenType.NAME, "math_asin", 0, 0, 0)],
190
        ["math_asinh", new Token(TokenType.NAME, "math_asinh", 0, 0, 0)],
191
        ["math_atan", new Token(TokenType.NAME, "math_atan", 0, 0, 0)],
192
        ["math_atan2", new Token(TokenType.NAME, "math_atan2", 0, 0, 0)],
193
        ["math_atanh", new Token(TokenType.NAME, "math_atanh", 0, 0, 0)],
194
        ["math_cbrt", new Token(TokenType.NAME, "math_cbrt", 0, 0, 0)],
195
        ["math_ceil", new Token(TokenType.NAME, "math_ceil", 0, 0, 0)],
196
        ["math_comb", new Token(TokenType.NAME, "math_comb", 0, 0, 0)],
197
        ["math_copysign", new Token(TokenType.NAME, "math_copysign", 0, 0, 0)],
198
        ["math_cos", new Token(TokenType.NAME, "math_cos", 0, 0, 0)],
199
        ["math_cosh", new Token(TokenType.NAME, "math_cosh", 0, 0, 0)],
200
        ["math_degrees", new Token(TokenType.NAME, "math_degrees", 0, 0, 0)],
201
        ["math_erf", new Token(TokenType.NAME, "math_erf", 0, 0, 0)],
202
        ["math_erfc", new Token(TokenType.NAME, "math_erfc", 0, 0, 0)],
203
        ["math_exp", new Token(TokenType.NAME, "math_exp", 0, 0, 0)],
204
        ["math_exp2", new Token(TokenType.NAME, "math_exp2", 0, 0, 0)],
205
        ["math_expm1", new Token(TokenType.NAME, "math_expm1", 0, 0, 0)],
206
        ["math_fabs", new Token(TokenType.NAME, "math_fabs", 0, 0, 0)],
207
        ["math_factorial", new Token(TokenType.NAME, "math_factorial", 0, 0, 0)],
208
        ["math_floor", new Token(TokenType.NAME, "math_floor", 0, 0, 0)],
209
        ["math_fma", new Token(TokenType.NAME, "math_fma", 0, 0, 0)],
210
        ["math_fmod", new Token(TokenType.NAME, "math_fmod", 0, 0, 0)],
211
        ["math_gamma", new Token(TokenType.NAME, "math_gamma", 0, 0, 0)],
212
        ["math_gcd", new Token(TokenType.NAME, "math_gcd", 0, 0, 0)],
213
        ["math_isfinite", new Token(TokenType.NAME, "math_isfinite", 0, 0, 0)],
214
        ["math_isinf", new Token(TokenType.NAME, "math_isinf", 0, 0, 0)],
215
        ["math_isnan", new Token(TokenType.NAME, "math_isnan", 0, 0, 0)],
216
        ["math_isqrt", new Token(TokenType.NAME, "math_isqrt", 0, 0, 0)],
217
        ["math_lcm", new Token(TokenType.NAME, "math_lcm", 0, 0, 0)],
218
        ["math_ldexp", new Token(TokenType.NAME, "math_ldexp", 0, 0, 0)],
219
        ["math_lgamma", new Token(TokenType.NAME, "math_lgamma", 0, 0, 0)],
220
        ["math_log", new Token(TokenType.NAME, "math_log", 0, 0, 0)],
221
        ["math_log10", new Token(TokenType.NAME, "math_log10", 0, 0, 0)],
222
        ["math_log1p", new Token(TokenType.NAME, "math_log1p", 0, 0, 0)],
223
        ["math_log2", new Token(TokenType.NAME, "math_log2", 0, 0, 0)],
224
        ["math_nextafter", new Token(TokenType.NAME, "math_nextafter", 0, 0, 0)],
225
        ["math_perm", new Token(TokenType.NAME, "math_perm", 0, 0, 0)],
226
        ["math_pow", new Token(TokenType.NAME, "math_pow", 0, 0, 0)],
227
        ["math_radians", new Token(TokenType.NAME, "math_radians", 0, 0, 0)],
228
        ["math_remainder", new Token(TokenType.NAME, "math_remainder", 0, 0, 0)],
229
        ["math_sin", new Token(TokenType.NAME, "math_sin", 0, 0, 0)],
230
        ["math_sinh", new Token(TokenType.NAME, "math_sinh", 0, 0, 0)],
231
        ["math_sqrt", new Token(TokenType.NAME, "math_sqrt", 0, 0, 0)],
232
        ["math_tan", new Token(TokenType.NAME, "math_tan", 0, 0, 0)],
233
        ["math_tanh", new Token(TokenType.NAME, "math_tanh", 0, 0, 0)],
234
        ["math_trunc", new Token(TokenType.NAME, "math_trunc", 0, 0, 0)],
235
        ["math_ulp", new Token(TokenType.NAME, "math_ulp", 0, 0, 0)],
236
      ]),
237
    );
238
    this.functionScope = null;
43✔
239
  }
240

241
  private runValidators(node: StmtNS.Stmt | ExprNS.Expr): void {
242
    for (const v of this.validators) v.validate(node, this.environment ?? undefined);
422!
243
  }
244

245
  resolve(stmt: Stmt[] | Stmt | Expr[] | Expr | null) {
246
    if (stmt === null) {
172!
247
      return;
×
248
    }
249
    if (stmt instanceof Array) {
172✔
250
      // Resolve all top-level functions first. Python allows functions declared after
251
      // another function to be used in that function.
252
      for (const st of stmt) {
74✔
253
        if (st instanceof StmtNS.FunctionDef) {
96✔
254
          this.environment?.declarePlaceholderName(st.name);
17✔
255
        }
256
      }
257
      for (const st of stmt) {
74✔
258
        st.accept(this);
96✔
259
      }
260
    } else {
261
      stmt.accept(this);
98✔
262
    }
263
  }
264

265
  varDeclNames(names: Map<string, Token>): Token[] | null {
266
    const res = Array.from(names.values()).filter(
×
267
      name =>
268
        // Filter out functions and module bindings.
269
        // Those will be handled separately, so they don't
270
        // need to be hoisted.
271
        !this.environment?.functions.has(name.lexeme) &&
×
272
        !this.environment?.moduleBindings.has(name.lexeme),
273
    );
274
    return res.length === 0 ? null : res;
×
275
  }
276

277
  functionVarConstraint(identifier: Token): void {
278
    if (this.functionScope == null) {
21✔
279
      return;
16✔
280
    }
281
    let curr = this.environment;
5✔
282
    while (curr !== this.functionScope) {
5✔
283
      if (curr !== null && curr.names.has(identifier.lexeme)) {
×
284
        const token = curr.names.get(identifier.lexeme);
×
285
        if (token === undefined) {
×
286
          throw new Error("placeholder error");
×
287
        }
288
        throw new ResolverErrors.NameReassignmentError(
×
289
          identifier.line,
290
          identifier.col,
291
          this.source,
292
          identifier.indexInSource,
293
          identifier.indexInSource + identifier.lexeme.length,
294
          token,
295
        );
296
      }
297
      curr = curr?.enclosing ?? null;
×
298
    }
299
  }
300

301
  //// STATEMENTS
302
  visitFileInputStmt(stmt: StmtNS.FileInput): void {
303
    this.runValidators(stmt);
43✔
304
    // Create a new environment.
305
    const oldEnv = this.environment;
43✔
306
    this.environment = new Environment(this.source, this.environment, new Map());
43✔
307
    this.resolve(stmt.statements);
43✔
308
    // Grab identifiers from that new environment. That are NOT functions.
309
    // stmt.varDecls = this.varDeclNames(this.environment.names)
310
    this.environment = oldEnv;
21✔
311
  }
312

313
  visitFunctionDefStmt(stmt: StmtNS.FunctionDef) {
314
    this.runValidators(stmt);
17✔
315
    this.environment?.declareName(stmt.name);
15✔
316
    this.environment?.functions.add(stmt.name.lexeme);
15✔
317

318
    // Create a new environment.
319
    const oldEnv = this.environment;
15✔
320
    // Assign the parameters to the new environment.
321
    const newEnv = new Map(stmt.parameters.map(param => [param.lexeme, param]));
15✔
322
    this.environment = new Environment(this.source, this.environment, newEnv);
15✔
323
    // const params = new Map(
324
    //     stmt.parameters.map(param => [param.lexeme, param])
325
    // );
326
    // if (this.environment !== null) {
327
    //     this.environment.names = params;
328
    // }
329
    this.functionScope = this.environment;
15✔
330
    this.resolve(stmt.body);
15✔
331
    // Grab identifiers from that new environment. That are NOT functions.
332
    // stmt.varDecls = this.varDeclNames(this.environment.names)
333
    // Restore old environment
334
    this.functionScope = null;
9✔
335
    this.environment = oldEnv;
9✔
336
  }
337

338
  visitAnnAssignStmt(stmt: StmtNS.AnnAssign): void {
339
    this.runValidators(stmt);
4✔
340
    this.resolve(stmt.ann);
2✔
341
    this.resolve(stmt.value);
2✔
342
    this.functionVarConstraint(stmt.target.name);
2✔
343
    this.environment?.declareName(stmt.target.name);
2✔
344
  }
345

346
  visitAssignStmt(stmt: StmtNS.Assign): void {
347
    this.runValidators(stmt);
25✔
348
    const target = stmt.target;
22✔
349
    if (target instanceof ExprNS.Subscript) {
22✔
350
      this.resolve(target.value); // resolve the object (e.g. xs)
1✔
351
      this.resolve(target.index); // resolve the index (e.g. 0)
1✔
352
      this.resolve(stmt.value); // resolve the assigned value
1✔
353
      return;
1✔
354
    }
355
    this.resolve(stmt.value);
21✔
356
    this.functionVarConstraint(target.name);
19✔
357
    this.environment?.declareName(target.name);
19✔
358
  }
359

360
  visitAssertStmt(stmt: StmtNS.Assert): void {
NEW
361
    this.runValidators(stmt);
×
UNCOV
362
    this.resolve(stmt.value);
×
363
  }
364
  visitForStmt(stmt: StmtNS.For): void {
365
    this.runValidators(stmt);
7✔
366
    this.environment?.declareName(stmt.target);
4✔
367
    this.resolve(stmt.iter);
4✔
368
    this.resolve(stmt.body);
4✔
369
  }
370

371
  visitIfStmt(stmt: StmtNS.If): void {
NEW
372
    this.runValidators(stmt);
×
373
    this.resolve(stmt.condition);
×
374
    this.resolve(stmt.body);
×
375
    this.resolve(stmt.elseBlock);
×
376
  }
377
  // @TODO we need to treat all global statements as variable declarations in the global
378
  // scope.
379
  visitGlobalStmt(stmt: StmtNS.Global): void {
NEW
380
    this.runValidators(stmt);
×
381
    // Do nothing because global can also be declared in our
382
    // own scope.
383
  }
384
  // @TODO nonlocals mean that any variable following that name in the current env
385
  // should not create a variable declaration, but instead point to an outer variable.
386
  visitNonLocalStmt(stmt: StmtNS.NonLocal): void {
387
    this.runValidators(stmt);
3✔
388
    this.environment?.lookupNameParentEnvWithError(stmt.name);
1✔
389
  }
390

391
  visitReturnStmt(stmt: StmtNS.Return): void {
NEW
392
    this.runValidators(stmt);
×
393
    if (stmt.value !== null) {
×
394
      this.resolve(stmt.value);
×
395
    }
396
  }
397

398
  visitWhileStmt(stmt: StmtNS.While): void {
399
    this.runValidators(stmt);
4✔
400
    this.resolve(stmt.condition);
2✔
401
    this.resolve(stmt.body);
2✔
402
  }
403
  visitSimpleExprStmt(stmt: StmtNS.SimpleExpr): void {
404
    this.runValidators(stmt);
11✔
405
    this.resolve(stmt.expression);
11✔
406
  }
407

408
  visitFromImportStmt(stmt: StmtNS.FromImport): void {
409
    this.runValidators(stmt);
1✔
410
    for (const entry of stmt.names) {
1✔
411
      const binding = entry.alias ?? entry.name;
1✔
412
      this.environment?.declareName(binding);
1✔
413
      this.environment?.moduleBindings.add(binding.lexeme);
1✔
414
    }
415
  }
416

417
  visitContinueStmt(stmt: StmtNS.Continue): void {
418
    this.runValidators(stmt);
1✔
419
  }
420
  visitBreakStmt(stmt: StmtNS.Break): void {
421
    this.runValidators(stmt);
1✔
422
  }
423
  visitPassStmt(stmt: StmtNS.Pass): void {
424
    this.runValidators(stmt);
8✔
425
  }
426

427
  //// EXPRESSIONS
428
  visitVariableExpr(expr: ExprNS.Variable): void {
429
    this.runValidators(expr);
21✔
430
    this.environment?.lookupNameCurrentEnvWithError(expr.name);
21✔
431
  }
432
  visitLambdaExpr(expr: ExprNS.Lambda): void {
433
    this.runValidators(expr);
3✔
434
    // Create a new environment.
435
    const oldEnv = this.environment;
3✔
436
    // Assign the parameters to the new environment.
437
    const newEnv = new Map(expr.parameters.map(param => [param.lexeme, param]));
3✔
438
    this.environment = new Environment(this.source, this.environment, newEnv);
3✔
439
    this.resolve(expr.body);
3✔
440
    // Restore old environment
441
    this.environment = oldEnv;
3✔
442
  }
443
  visitMultiLambdaExpr(expr: ExprNS.MultiLambda): void {
NEW
444
    this.runValidators(expr);
×
445
    // Create a new environment.
446
    const oldEnv = this.environment;
×
447
    // Assign the parameters to the new environment.
448
    const newEnv = new Map(expr.parameters.map(param => [param.lexeme, param]));
×
449
    this.environment = new Environment(this.source, this.environment, newEnv);
×
450
    this.resolve(expr.body);
×
451
    // Grab identifiers from that new environment.
452
    expr.varDecls = Array.from(this.environment.names.values());
×
453
    // Restore old environment
454
    this.environment = oldEnv;
×
455
  }
456
  visitUnaryExpr(expr: ExprNS.Unary): void {
NEW
457
    this.runValidators(expr);
×
UNCOV
458
    this.resolve(expr.right);
×
459
  }
460
  visitGroupingExpr(expr: ExprNS.Grouping): void {
NEW
461
    this.runValidators(expr);
×
UNCOV
462
    this.resolve(expr.expression);
×
463
  }
464
  visitBinaryExpr(expr: ExprNS.Binary): void {
NEW
465
    this.runValidators(expr);
×
466
    this.resolve(expr.left);
×
467
    this.resolve(expr.right);
×
468
  }
469
  visitBoolOpExpr(expr: ExprNS.BoolOp): void {
NEW
470
    this.runValidators(expr);
×
471
    this.resolve(expr.left);
×
472
    this.resolve(expr.right);
×
473
  }
474
  visitCompareExpr(expr: ExprNS.Compare): void {
NEW
475
    this.runValidators(expr);
×
476
    this.resolve(expr.left);
×
477
    this.resolve(expr.right);
×
478
  }
479

480
  visitCallExpr(expr: ExprNS.Call): void {
481
    this.runValidators(expr);
7✔
482
    this.resolve(expr.callee);
7✔
483
    this.resolve(expr.args);
7✔
484
  }
485
  visitTernaryExpr(expr: ExprNS.Ternary): void {
NEW
486
    this.runValidators(expr);
×
487
    this.resolve(expr.predicate);
×
488
    this.resolve(expr.consequent);
×
489
    this.resolve(expr.alternative);
×
490
  }
491
  visitNoneExpr(expr: ExprNS.None): void {
NEW
492
    this.runValidators(expr);
×
493
  }
494
  visitLiteralExpr(expr: ExprNS.Literal): void {
495
    this.runValidators(expr);
2✔
496
  }
497
  visitBigIntLiteralExpr(expr: ExprNS.BigIntLiteral): void {
498
    this.runValidators(expr);
30✔
499
  }
500
  visitComplexExpr(expr: ExprNS.Complex): void {
NEW
501
    this.runValidators(expr);
×
502
  }
503
  visitListExpr(expr: ExprNS.List): void {
504
    this.runValidators(expr);
6✔
505
    this.resolve(expr.elements);
3✔
506
  }
507
  visitSubscriptExpr(expr: ExprNS.Subscript): void {
NEW
508
    this.runValidators(expr);
×
509
    this.resolve(expr.value);
×
510
    this.resolve(expr.index);
×
511
  }
512
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc