December 20, 2019·4 min read·engineeringtypescriptcompilerjavascript

TypeScript 3.7.4 handles computed top-level this assignments

The maintenance release skips ordinary symbol declaration for dynamic top-level this property names. Its regression test preserves a precise strict JavaScript diagnostic.

A JavaScript assignment can also serve as evidence about a program's declarations. TypeScript's binder interprets some property assignments that way, but a computed property name does not always identify a static name it can declare. TypeScript 3.7.4 adds a guard for that distinction on top-level this assignments.

The npm registry dates 3.7.4 to December 20, 2019. The comparison with 3.7.3 shows the binder checking for a dynamic name before taking the source-file declaration path. It also adds a regression test and the expected checking result.

The change concerns a specific form of input. Its test uses a top-level assignment through this with a string-concatenation expression as its key. The compiler needs to handle that source without treating the computed expression as an ordinary statically named declaration.

I value this maintenance work because TypeScript's usefulness extends to analyzing JavaScript that has no TypeScript annotations. That support depends on recognizing patterns in ordinary executable code. The inference needs a clear boundary when a familiar-looking assignment supplies less static information than a declaration requires.

Assignment patterns contribute symbol information

The release-tagged binder handles this property assignments according to their containing context. Class members and source files have different paths. The source-file comment states that a top-level property assignment declares a symbol in module exports or script locals, depending on the context.

That behavior helps explain why the binder examines an assignment at all. JavaScript code can introduce an interface through assignments instead of explicit property declarations. The analysis uses those recognizable patterns to construct information for later checking.

A property whose name is dynamic does not fit every static declaration operation. The patch adds an early break for hasDynamicName in the source-file case. Only after that guard does the code choose between the CommonJS export path and the script-local path.

The ordering is significant. The dynamic-name check precedes both source-file branches. It is not merely a condition applied to one of the two destination symbol tables. The correction states that this ordinary declaration path should not process such a name in either case.

The regression test is deliberately ordinary JavaScript

The new test case enables JavaScript input, JavaScript checking, no emission and strict checking. Its embedded source file contains a single assignment.

this["a" + "b"] = 0;

The expression evaluates to a property name when JavaScript runs. The regression baseline nevertheless records the concatenation expression with type string. In this checking context, the top-level this has type typeof globalThis, with no matching string index signature.

The expected diagnostic is TS7053. It explains that a string expression cannot index typeof globalThis because that type lacks a string index signature. That is a checking result about the program's declared type environment.

It is not a JavaScript syntax ban on computed properties. The test is designed to reach analysis and produce the relevant strict-mode diagnostic. Skipping a binder declaration path does not imply the compiler should pretend that every dynamic access is type-safe.

This distinction is useful when reading maintenance tests. A test with an expected error can demonstrate correct compiler behavior. The goal is an accurate diagnostic for the source, rather than making every regression example compile without complaint.

Class assignments retain a different dynamic path

The surrounding source shows dynamic names receiving different handling in class contexts. There, the binder can call a helper that creates an anonymous computed declaration and adds a late-bound assignment to the containing symbol. The source-file guard should not be generalized into removing all support for dynamic this properties.

That context is part of the design explanation. A class supplies an owning symbol around which the compiler can retain a computed assignment. The source-file branch has its own rules for exports and locals. The patch applies to the branch where the ordinary declaration assumption is inappropriate.

The comparison does not establish a new runtime transformation for this assignment. The regression configuration explicitly uses noEmit. Its evidence concerns binding and checking, so describing the patch as a JavaScript behavior change would go beyond what the source demonstrates.

My preference is to keep that boundary explicit. The useful result is that the compiler can represent the input well enough to issue the intended diagnostic. A maintenance correction in analysis does not need an invented runtime consequence to matter.

A patch can preserve an error while fixing analysis

The release illustrates a specific requirement for a language tool. It needs to process programs outside the subset it considers type-correct, because diagnostics are one of its main outputs. Invalid indexing must remain a source error that the tool can explain.

TypeScript 3.7.4 adds the dynamic-name guard at the source-file binding boundary and records the strict JavaScript result in a regression baseline. The patch preserves the distinction between executable computed access, declaration inference and the later check against the known global type.