TypeScript 4.6
Allowing Code in Constructors Beforesuper()
In JavaScript classes it’s mandatory to callsuper() before referring tothis.TypeScript enforces this as well, though it was a bit too strict inhow it ensured this.In TypeScript, it was previously an error to containany code at the beginning of a constructor if its containing class had any property initializers.
tsclassBase {// ...}classDerivedextendsBase {someProperty =true;constructor() {// error!// have to call 'super()' first because it needs to initialize 'someProperty'.doSomeStuff();super();}}
This made it cheap to check thatsuper() gets called beforethis is referenced, but it ended up rejecting a lot of valid code.TypeScript 4.6 is now much more lenient in that check and permits other code to run beforesuper()., all while still ensuring thatsuper() occurs at the top-level before any references tothis.
We’d like to extend our thanks toJoshua Goldberg forpatiently working with us to land this change!
Control Flow Analysis for Destructured Discriminated Unions
TypeScript is able to narrow types based on what’s called a discriminant property.For example, in the following code snippet, TypeScript is able to narrow the type ofaction based on every time we check against the value ofkind.
tstypeAction =| {kind:"NumberContents";payload:number }| {kind:"StringContents";payload:string };functionprocessAction(action:Action) {if (action.kind ==="NumberContents") {// `action.payload` is a number here.letnum =action.payload *2;// ...}elseif (action.kind ==="StringContents") {// `action.payload` is a string here.conststr =action.payload.trim();// ...}}
This lets us work with objects that can hold different data, but a common field tells uswhich data those objects have.
This is very common in TypeScript; however, depending on your preferences, you might have wanted to destructurekind andpayload in the example above.Perhaps something like the following:
tstypeAction =| {kind:"NumberContents";payload:number }| {kind:"StringContents";payload:string };functionprocessAction(action:Action) {const {kind,payload } =action;if (kind ==="NumberContents") {letnum =payload *2;// ...}elseif (kind ==="StringContents") {conststr =payload.trim();// ...}}
Previously TypeScript would error on these - oncekind andpayload were extracted from the same object into variables, they were considered totally independent.
In TypeScript 4.6, this just works!
When destructuring individual properties into aconst declaration, or when destructuring a parameter into variables that are never assigned to, TypeScript will check for if the destructured type is a discriminated union.If it is, TypeScript can now narrow the types of variables depending on checks of other variablesSo in our example, a check onkind narrows the type ofpayload.
For more information,see the pull request that implemented this analysis.
Improved Recursion Depth Checks
TypeScript has some interesting challenges due to the fact that it’s built on a structural type system that also provides generics.
In a structural type system, object types are compatible based on the members they have.
tsinterfaceSource {prop:string;}interfaceTarget {prop:number;}functioncheck(source:Source,target:Target) {target =source;// error!// Type 'Source' is not assignable to type 'Target'.// Types of property 'prop' are incompatible.// Type 'string' is not assignable to type 'number'.}
Notice that whether or notSource is compatible withTarget has to do with whether theirproperties are assignable.In this case, that’s justprop.
When you introduce generics into this, there are some harder questions to answer.For instance, is aSource<string> assignable to aTarget<number> in the following case?
tsinterfaceSource<T> {prop:Source<Source<T>>;}interfaceTarget<T> {prop:Target<Target<T>>;}functioncheck(source:Source<string>,target:Target<number>) {target =source;}
In order to answer that, TypeScript needs to check whether the types ofprop are compatible.That leads to the another question: is aSource<Source<string>> assignable to aTarget<Target<number>>?To answer that, TypeScript checks whetherprop is compatible forthose types, and ends up checking whetherSource<Source<Source<string>>> is assignable toTarget<Target<Target<number>>>.Keep going for a bit, and you might notice that the type infinitely expands the more you dig in.
TypeScript has a few heuristics here - if a typeappears to be infinitely expanding after encountering a certain depth check, then it considers that the typescould be compatible.This is usually enough, but embarrassingly there were some false-negatives that this wouldn’t catch.
tsinterfaceFoo<T> {prop:T;}declareletx:Foo<Foo<Foo<Foo<Foo<Foo<string>>>>>>;declarelety:Foo<Foo<Foo<Foo<Foo<string>>>>>;x =y;
A human reader can see thatx andy should be incompatible in the above example.While the types are deeply nested, that’s just a consequence of how they were declared.The heuristic was meant to capture cases where deeply-nested types were generated through exploring the types, not from when a developer wrote that type out themselves.
TypeScript 4.6 is now able to distinguish these cases, and correctly errors on the last example.Additionally, because the language is no longer concerned with false-positives from explicitly-written types, TypeScript can conclude that a type is infinitely expanding much earlier, and save a bunch of work in checking for type compatibility.As a result, libraries on DefinitelyTyped likeredux-immutable,react-lazylog, andyup saw a 50% reduction in check-time.
You may already have this change because it was cherry-picked into TypeScript 4.5.3, but it is a notable feature of TypeScript 4.6 which you can read up more abouthere.
Indexed Access Inference Improvements
TypeScript now can correctly infer to indexed access types which immediately index into a mapped object type.
tsinterfaceTypeMap {number:number;string:string;boolean:boolean;}typeUnionRecord<PextendskeyofTypeMap> = {[KinP]: {kind:K;v:TypeMap[K];f: (p:TypeMap[K])=>void;};}[P];functionprocessRecord<KextendskeyofTypeMap>(record:UnionRecord<K>) {record.f(record.v);}// This call used to have issues - now works!processRecord({kind:"string",v:"hello!",// 'val' used to implicitly have the type 'string | number | boolean',// but now is correctly inferred to just 'string'.f: (val)=> {console.log(val.toUpperCase());},});
This pattern was already supported and allowed TypeScript to understand that the call torecord.f(record.v) is valid, but previously the call toprocessRecord would give poor inference results forval
TypeScript 4.6 improves this so that no type assertions are necessary within the call toprocessRecord.
For more information, you canread up on the pull request.
Control Flow Analysis for Dependent Parameters
A signature can be declared with a rest parameter whose type is a discriminated union of tuples.
tsfunctionfunc(...args: ["str",string] | ["num",number]) {// ...}
What this says is that the arguments tofunc depends entirely on the first argument.When the first argument is the string"str", then its second argument has to be astring.When its first argument is the string"num", its second argument has to be anumber.
In cases where TypeScript infers the type of a function from a signature like this, TypeScript can now narrow parameters that depend on each other.
tstypeFunc = (...args: ["a",number] | ["b",string])=>void;constf1:Func = (kind,payload)=> {if (kind ==="a") {payload.toFixed();// 'payload' narrowed to 'number'}if (kind ==="b") {payload.toUpperCase();// 'payload' narrowed to 'string'}};f1("a",42);f1("b","hello");
For more information,see the change on GitHub.
--target es2022
TypeScript’s--target option now supportses2022.This means features like class fields now have a stable output target where they can be preserved.It also means that new built-in functionality like theat() method onArrays,Object.hasOwn, orthecause option onnew Error can be used either with this new--target setting, or with--lib es2022.
This functionality wasimplemented byKagami Sascha Rosylight (saschanaz) over several PRs, and we’re grateful for that contribution!
Removed Unnecessary Arguments inreact-jsx
Previously, when compiling code like the following in--jsx react-jsx
tsxexportconstel =<div>foo</div>;
TypeScript would produce the following JavaScript code:
jsximport {jsxas_jsx }from"react/jsx-runtime";exportconstel =_jsx("div", {children:"foo" },void0);
That lastvoid 0 argument is unnecessary in this emit mode, and removing it can improve bundle sizes.
diff- export const el = _jsx("div", { children: "foo" }, void 0);+ export const el = _jsx("div", { children: "foo" });
Thanks toa pull request fromAlexander Tarasyuk, TypeScript 4.6 now drops thevoid 0 argument.
JSDoc Name Suggestions
In JSDoc, you can document parameters using an@param tag.
js/***@paramx The first operand*@paramy The second operand*/functionadd(x,y) {returnx +y;}
But what happens when these comments fall out of date?What if we renamex andy toa andb?
js/***@paramx {number} The first operand*@paramy {number} The second operand*/functionadd(a,b) {returna +b;}
Previously TypeScript would only tell you about this when performing type-checking on JavaScript files - when using either thecheckJs option, or adding a// @ts-check comment to the top of your file.
You can now get similar information for TypeScript files in your editor!TypeScript now provides suggestions for when parameter names don’t match between your function and its JSDoc comment.

This change was provided courtesy ofAlexander Tarasyuk!
More Syntax and Binding Errors in JavaScript
TypeScript has expanded its set of syntax and binding errors in JavaScript files.You’ll see these new errors if you open JavaScript files in an editor like Visual Studio or Visual Studio Code, or if you run JavaScript code through the TypeScript compiler - even if you don’t turn oncheckJs or add a// @ts-check comment to the top of your files.
As one example, if you have two declarations of aconst in the same scope of a JavaScript file, TypeScript will now issue an error on those declarations.
tsconstfoo =1234;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.// ...constfoo =5678;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.
As another example, TypeScript will let you know if a modifier is being incorrectly used.
tsfunctioncontainer() {exportfunctionfoo() {// ~~~~~~// error: Modifiers cannot appear here.}}
These errors can be disabled by adding a// @ts-nocheck at the top of your file, but we’re interested in hearing some early feedback about how it works for your JavaScript workflow.You can easily try it out for Visual Studio Code by installing theTypeScript and JavaScript Nightly Extension, and read up more on thefirst andsecond pull requests.
TypeScript Trace Analyzer
Occasionally, teams may encounter types that are computationally expensive to create and compare against other types.TypeScript has a--generateTrace flag to help identify some of those expensive types, or sometimes help diagnose issues in the TypeScript compiler.While the information generated by--generateTrace can be useful (especially with some information added in TypeScript 4.6), it can often be hard to read in existing trace visualizers.
We recently published a tool called@typescript/analyze-trace to get a more digestible view of this information.While we don’t expect everyone to needanalyze-trace, we think it can come in handy for any team that is running intobuild performance issues with TypeScript.
For more information,see theanalyze-trace tool’s repo.
Breaking Changes
Object Rests Drop Unspreadable Members from Generic Objects
Object rest expressions now drop members that appear to be unspreadable on generic objects.In the following example…
tsclassThing {someProperty =42;someMethod() {// ...}}functionfoo<TextendsThing>(x:T) {let {someProperty, ...rest } =x;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}
the variablerest used to have the typeOmit<T, "someProperty"> because TypeScript would strictly analyze which other properties were destructured.This doesn’t model how...rest would work in a destructuring from a non-generic type becausesomeMethod would typically be dropped as well.In TypeScript 4.6, the type ofrest isOmit<T, "someProperty" | "someMethod">.
This can also come up in cases when destructuring fromthis.When destructuringthis using a...rest element, unspreadable and non-public members are now dropped, which is consistent with destructuring instances of a class in other places.
tsclassThing {someProperty =42;someMethod() {// ...}someOtherMethod() {let {someProperty, ...rest } =this;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}}
For more details,see the corresponding change here.
JavaScript Files Always Receive Grammar and Binding Errors
Previously, TypeScript would ignore most grammar errors in JavaScript apart from accidentally using TypeScript syntax in a JavaScript file.TypeScript now shows JavaScript syntax and binding errors in your file, such as using incorrect modifiers, duplicate declarations, and more.These will typically be most apparent in Visual Studio Code or Visual Studio, but can also occur when running JavaScript code through the TypeScript compiler.
You can explicitly turn these errors off by inserting a// @ts-nocheck comment at the top of your file.
For more information, see thefirst andsecond implementing pull requests for these features.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Dec 16, 2025