-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Breaking Changes
These changes list where implementation differs between versions as the spec and compiler are simplified and inconsistencies are corrected.
For breaking changes to the compiler/services API, please check the API Breaking Changes page.
Promise.resolve now uses the Awaited type to unwrap Promise-like types passed to it.
This means that it more often returns the right Promise type, but that improved type can break existing code if it was expecting any or unknown instead of a Promise.
For more information, see the original change.
When TypeScript first supported type-checking and compilation for JavaScript, it accidentally supported a feature called import elision. In short, if an import is not used as a value, or the compiler can detect that the import doesn't refer to a value at runtime, the compiler will drop the import during emit.
This behavior was questionable, especially the detection of whether the import doesn't refer to a value, since it means that TypeScript has to trust sometimes-inaccurate declaration files. In turn, TypeScript now preserves imports in JavaScript files.
// Input:
import { someValue, SomeClass } from "some-module";
/** @type {SomeType} */
let val = someValue;
// Previous Output:
import { someValue } from "some-module";
/** @type {SomeClass} */
let val = someValue;
// Current Output:
import { someValue, SomeClass } from "some-module";
/** @type {SomeType} */
let val = someValue;More information is available at the implementing change.
Previously, TypeScript incorrectly prioritized the typesVersions field over the exports field when resolving through a package.json under --moduleResolution node16.
If this change impacts your library, you may need to add types@ version selectors in your package.json's exports field.
{
"type": "module",
"main": "./dist/main.js"
"typesVersions": {
"<4.8": { ".": ["4.8-types/main.d.ts"] },
"*": { ".": ["modern-types/main.d.ts"] }
},
"exports": {
".": {
+ "types@<4.8": "4.8-types/main.d.ts",
+ "types": "modern-types/main.d.ts",
"import": "./dist/main.js"
}
}
}For more information, see this pull request.
Originally, the constraint of all type parameters in TypeScript was {} (the empty object type).
Eventually the constraint was changed to unknown which also permits null and undefined.
Outside of strictNullChecks, these types are interchangeable, but within strictNullChecks, unknown is not assignable to {}.
In TypeScript 4.8, under strictNullChecks, the type-checker disables a type safety hole that was maintained for backwards-compatibility, where type parameters were considered to always be assignable to {}, object, and any other structured types with all-optional properties.
function foo<T>(x: T) {
const a: {} = x;
// ~
// Type 'T' is not assignable to type '{}'.
const b: object = x;
// ~
// Type 'T' is not assignable to type 'object'.
const c: { foo?: string, bar?: number } = x;
// ~
// Type 'T' is not assignable to type '{ foo?: string | undefined; bar?: number | undefined; }'.
}In such cases, you may need a type assertion on x, or a constraint of {} on T.
function foo<T extends {}>(x: T) {
// Works
const a: {} = x;
// Works
const b: object = x;
}This behavior can come up in calls to Object.keys:
function keysEqual<T>(x: T, y: T) {
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
if (xKeys.length !== yKeys.length) return false;
for (let i = 0; i < xKeys.length; i++) {
if (xKeys[i] !== yKeys[i]) return false;
}
return true;
}For the above, you might see an error message that looks like this:
No overload matches this call.
Overload 1 of 2, '(o: {}): string[]', gave the following error.
Argument of type 'T' is not assignable to parameter of type '{}'.
Overload 2 of 2, '(o: object): string[]', gave the following error.
Argument of type 'T' is not assignable to parameter of type 'object'.Appropriately performing runtime checks to narrow the type, or using a type-assertion, may be the best way to deal with these new errors.
For more information, take a look at the breaking PR here.
See Changes for Older Releases
When writing a ...spread in JSX, TypeScript now enforces stricter checks that the given type is actually an object.
As a results, values with the types unknown and never (and more rarely, just bare null and undefined) can no longer be spread into JSX elements.
So for the following example:
import * as React from "react";
interface Props {
stuff?: string;
}
function MyComponent(props: unknown) {
return <div {...props} />;
}you'll now receive an error like the following:
Spread types may only be created from object types.
This makes this behavior more consistent with spreads in object literals.
For more details, see the change on GitHub.
When a symbol value is used in a template string, it will trigger a runtime error in JavaScript.
let str = `hello ${Symbol()}`;
// TypeError: Cannot convert a Symbol value to a stringAs a result, TypeScript will issue an error as well; however, TypeScript now also checks if a generic value that is constrained to a symbol in some way is used in a template string.
function logKey<S extends string | symbol>(key: S): S {
// Now an error.
console.log(`${key} is the key`);
return key;
}
function get<T, K extends keyof T>(obj: T, key: K) {
// Now an error.
console.log(`Grabbing property '${key}'.`);
return obj[key];
}TypeScript will now issue the following error:
Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
In some cases, you can get around this by wrapping the expression in a call to String, just like the error message suggests.
function logKey<S extends string | symbol>(key: S): S {
// Now an error.
console.log(`${String(key)} is the key`);
return key;
}In others, this error is too pedantic, and you might not ever care to even allow symbol keys when using keyof.
In such cases, you can switch to string & keyof ...:
function get<T, K extends keyof T>(obj: T, key: K) {
// Now an error.
console.log(`Grabbing property '${key}'.`);
return obj[key];
}For more information, you can see the implementing pull request.
If you're creating LanguageService instances, then provided LanguageServiceHosts will need to provide a readFile method.
This change was necessary to support the new moduleDetection compiler option.
You can read more on the change here.
A readonly tuple will now treat its length property as readonly.
This was almost never witnessable for fixed-length tuples, but was an oversight which could be observed for tuples with trailing optional and rest element types.
As a result, the following code will now fail:
function overwriteLength(tuple: readonly [string, string, string]) {
// Now errors.
tuple.length = 7;
}You can read more on this change here.
Object rest expressions now drop members that appear to be unspreadable on generic objects. In the following example...
class Thing {
someProperty = 42;
someMethod() {
// ...
}
}
function foo<T extends Thing>(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 variable rest used to have the type Omit<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 because someMethod would typically be dropped as well.
In TypeScript 4.6, the type of rest is Omit<T, "someProperty" | "someMethod">.
This can also come up in cases when destructuring from this.
When destructuring this using a ...rest element, unspreadable and non-public members are now dropped, which is consistent with destructuring instances of a class in other places.
class Thing {
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.
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 the first and second implementing pull requests for these features.
TypeScript 4.5 contains changes to its built-in declaration files which may affect your compilation; however, these changes were fairly minimal, and we expect most code will be unaffected.
Because Awaited is now used in lib.d.ts and as a result of await, you may see certain generic types change that might cause incompatibilities.
This may cause issues when providing explicit type arguments to functions like Promise.all, Promise.allSettled, etc.
Often, you can make a fix by removing type arguments altogether.
- Promise.all<boolean, boolean>(...)
+ Promise.all(...)More involved cases will require you to replace a list of type arguments with a single type argument of a tuple-like type.
- Promise.all<boolean, boolean>(...)
+ Promise.all<[boolean, boolean]>(...)However, there will be occasions when a fix will be a little bit more involved, and replacing the types with a tuple of the original type arguments won't be enough.
One example where this occasionally comes up is when an element is possibly a Promise or non-Promise.
In those cases, it's no longer okay to unwrap the underlying element type.
- Promise.all<boolean | undefined, boolean | undefined>(...)
+ Promise.all<[Promise<boolean> | undefined, Promise<boolean> | undefined]>(...)Template strings in TypeScript previously just used the + operator when targeting ES3 or ES5;
however, this leads to some divergences between the use of .valueOf() and .toString() which ends up being less spec-compliant.
This is usually not noticeable, but is particularly important when using upcoming standard library additions like Temporal.
TypeScript now uses calls to .concat() on strings.
This gives code the same behavior regardless of whether it targets ES3 and ES5, or ES2015 and later.
Most code should be unaffected, but you might now see different results on values that define separate valueOf() and toString() methods.
import moment = require("moment");
// Before: "Moment: Wed Nov 17 2021 16:23:57 GMT-0800"
// After: "Moment: 1637195037348"
console.log(`Moment: ${moment()}`);More more information, see the original issue.
It's an easy mistake to accidentally forget about the compilerOptions section in a tsconfig.json.
To help catch this mistake, in TypeScript 4.5, it is an error to add a top-level field which matches any of the available options in compilerOptions without having also defined compilerOptions in that tsconfig.json.
TypeScript no longer allows types to be assignable to conditional types that use infer, or that are distributive.
Doing so previously often ended up causing major performance issues.
For more information, see the specific change on GitHub.
As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web contexts), have changed.
You can consult our list of known lib.dom.d.ts changes to understand what is impacted.
In earlier versions of TypeScript, calling an import from CommonJS, AMD, and other non-ES module systems would set the this value of the called function.
Specifically, in the following example, when calling fooModule.foo(), the foo() method will have fooModule set as the value of this.
// Imagine this is our imported module, and it has an export named 'foo'.
let fooModule = {
foo() {
console.log(this);
}
};
fooModule.foo();This is not the way exported functions in ECMAScript are supposed to work when we call them.
That's why TypeScript 4.4 intentionally discards the this value when calling imported functions, by using the following emit.
// Imagine this is our imported module, and it has an export named 'foo'.
let fooModule = {
foo() {
console.log(this);
}
};
// Notice we're actually calling '(0, fooModule.foo)' now, which is subtly different.
(0, fooModule.foo)();For more information, you can read up more here.
Users running with the --strict flag may see new errors around catch variables being unknown due to the new --useUnknownForCatchVariables flag, especially if the existing code assumes only Error values have been caught.
This often results in error messages such as:
Property 'message' does not exist on type 'unknown'.
Property 'name' does not exist on type 'unknown'.
Property 'stack' does not exist on type 'unknown'.
Object is of type 'unknown'.
To get around this, you can specifically add runtime checks to ensure that the thrown type matches your expected type.
Otherwise, you can just use a type assertion, add an explicit : any to your catch variable, or turn off --useUnknownInCatchVariables.
In prior versions, TypeScript introduced "Always Truthy Promise checks" to catch code where an await may have been forgotten;
however, the checks only applied to named declarations.
That meant that while this code would correctly receive an error...
async function foo(): Promise<boolean> {
return false;
}
async function bar(): Promise<string> {
const fooResult = foo();
if (fooResult) { // <- error! :D
return "true";
}
return "false";
}...the following code would not.
async function foo(): Promise<boolean> {
return false;
}
async function bar(): Promise<string> {
if (foo()) { // <- no error :(
return "true";
}
return "false";
}TypeScript 4.4 now flags both. For more information, read up on the original change.
The following code is now an error because abstract properties may not have initializers:
abstract class C {
abstract prop = 1;
// ~~~~
// Property 'prop' cannot have an initializer because it is marked abstract.
}Instead, you may only specify a type for the property:
abstract class C {
abstract prop: number;
}Certain enums are considered union enums when their members are either automatically filled in, or trivially written.
In those cases, an enum can recall each value that it potentially represents.
In TypeScript 4.3, if a value with a union enum type is compared with a numeric literal that it could never be equal to, then the type-checker will isue an error.
enum E {
A = 0,
B = 1,
}
function doSomething(x: E) {
// Error! This condition will always return 'false' since the types 'E' and '-1' have no overlap.
if (x === -1) {
// ...
}
}As a workaround, you can re-write an annotation to include the appropriate literal type.
enum E {
A = 0,
B = 1,
}
// Include -1 in the type, if we're really certain that -1 can come through.
function doSomething(x: E | -1) {
if (x === -1) {
// ...
}
}You can also use a type-assertion on the value.
enum E {
A = 0,
B = 1,
}
function doSomething(x: E) {
// Use a type asertion on 'x' because we know we're not actually just dealing with values from 'E'.
if ((x as number) === -1) {
// ...
}
}Alternatively, you can re-declare your enum to have a non-trivial initializer so that any number is both assignable and comparable to that enum. This may be useful if the intent is for the enum to specify a few well-known values.
enum E {
// the leading + on 0 opts TypeScript out of inferring a union enum.
A = +0,
B = 1,
}For more details, see the original change
When a yield expression is captured, but isn't contextually typed (i.e. TypeScript can't figure out what the type is), TypeScript will now issue an implicit any error.
function* g1() {
const value = yield 1; // report implicit any error
}
function* g2() {
yield 1; // result is unused, no error
}
function* g3() {
const value: string = yield 1; // result is contextually typed by type annotation of `value`, no error.
}
function* g3(): Generator<number, void, string> {
const value = yield 1; // result is contextually typed by return-type annotation of `g3`, no error.
}See more details in the corresponding changes.
Type arguments were already not allowed in JavaScript, but in TypeScript 4.2, the parser will parse them in a more spec-compliant way. So when writing the following code in a JavaScript file:
f<T>(100)TypeScript will parse it as the following JavaScript:
(f < T) > (100)This may impact you if you were leveraging TypeScript's API to parse type constructs in JavaScript files, which may have occurred when trying to parse Flow files.
In JavaScript, it is a runtime error to use a non-object type on the right side of the in operator.
TypeScript 4.2 ensures this can be caught at design-time.
"foo" in 42
// ~~
// error! The right-hand side of an 'in' expression must not be a primitive.This check is fairly conservative for the most part, so if you have received an error about this, it is likely an issue in the code.
Members marked as abstract can no longer be marked as async.
The fix here is to remove the async keyword, since callers are only concerned with the return type.
When writing code like the following
new Promise(resolve => {
doSomethingAsync(() => {
doSomething();
resolve();
})
})You may get an error like the following:
resolve()
~~~~~~~~~
error TS2554: Expected 1 arguments, but got 0.
An argument for 'value' was not provided.
This is because resolve no longer has an optional parameter, so by default, it must now be passed a value.
Often this catches legitimate bugs with using Promises.
The typical fix is to pass it the correct argument, and sometimes to add an explicit type argument.
new Promise<number>(resolve => {
// ^^^^^^^^
doSomethingAsync(value => {
doSomething();
resolve(value);
// ^^^^^
})
})However, sometimes resolve() really does need to be called without an argument.
In these cases, we can give Promise an explicit void generic type argument (i.e. write it out as Promise<void>).
This leverages new functionality in TypeScript 4.1 where a potentially-void trailing parameter can become optional.
new Promise<void>(resolve => {
// ^^^^^^
doSomethingAsync(() => {
doSomething();
resolve();
})
})TypeScript 4.1 ships with a quick fix to help fix this break.
Note: This change, and the description of the previous behavior, apply only under --strictNullChecks.
Previously, when an any or unknown appeared on the left-hand side of an &&, it was assumed to be definitely truthy, which made the type of the expression the type of the right-hand side:
// Before:
function before(x: any, y: unknown) {
const definitelyThree = x && 3; // 3
const definitelyFour = y && 4; // 4
}
// Passing any falsy values here demonstrates that `definitelyThree` and `definitelyFour`
// are not, in fact, definitely 3 and 4 at runtime.
before(false, 0);In TypeScript 4.1, under --strictNullChecks, when any or unknown appears on the left-hand side of an &&, the type of the expression is any or unknown, respectively:
// After:
function after(x: any, y: unknown) {
const maybeThree = x && 3; // any
const maybeFour = y && 4; // unknown
}This change introduces new errors most frequently where TypeScript previously failed to notice that an unknown in an && expression may not produce a boolean:
function isThing(x: unknown): boolean {
return x && typeof x === "object" && x.hasOwnProperty("thing");
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error!
// Type 'unknown' is not assignable to type 'boolean'.
}If x is a falsy value other than false, the function will return it, in conflict with the boolean return type annotation. The error can be resolved by replacing the first x in the return expression with !!x.
See more details on the implementing pull request.
In JavaScript, object spreads (like { ...foo }) don't operate over falsy values.
So in code like { ...foo }, foo will be skipped over if it's null or undefined.
Many users take advantage of this to spread in properties "conditionally".
interface Person {
name: string;
age: number;
location: string;
}
interface Animal {
name: string;
owner: Person;
}
function copyOwner(pet?: Animal) {
return {
...(pet && pet.owner),
otherStuff: 123
}
}
// We could also use optional chaining here:
function copyOwner(pet?: Animal) {
return {
...(pet?.owner),
otherStuff: 123
}
}Here, if pet is defined, the properties of pet.owner will be spread in - otherwise, no properties will be spread into the returned object.
The return type of copyOwner was previously a union type based on each spread:
{ x: number } | { x: number, name: string, age: number, location: string }
This modeled exactly how the operation would occur: if pet was defined, all the properties from Person would be present; otherwise, none of them would be defined on the result.
It was an all-or-nothing operation.
However, we've seen this pattern taken to the extreme, with hundreds of spreads in a single object, each spread potentially adding in hundreds or thousands of properties. It turns out that for various reasons, this ends up being extremely expensive, and usually for not much benefit.
In TypeScript 4.1, the returned type instead uses all-optional properties.
{
x: number;
name?: string;
age?: number;
location?: string;
}
This ends up performing better and generally displaying better too.
For more details, see the original change.
TypeScript would previously relate parameters that didn't correspond to each other by relating them to the type any.
With changes in TypeScript 4.1, the language now skips this process entirely.
This means that some cases of assignability will now fail, but it also means that some cases of overload resolution can fail as well.
For example, the overloads of util.promisify in Node.js may select a different overload in TypeScript 4.1, sometimes causing different errors downstream.
As a workaround, you may be best using a type assertion to squelch errors.
Previously, it was only an error for properties to override accessors, or accessors to override properties, when using useDefineForClassFields; however, TypeScript now always issues an error when declaring a property in a derived class that would override a getter or setter in the base class.
class Base {
get foo() {
return 100;
}
set foo() {
// ...
}
}
class Derived extends Base {
foo = 10;
// ~~~
// error!
// 'foo' is defined as an accessor in class 'Base',
// but is overridden here in 'Derived' as an instance property.
}class Base {
prop = 10;
}
class Derived extends Base {
get prop() {
// ~~~~
// error!
// 'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor.
return 100;
}
}When using the delete operator in strictNullChecks, the operand must now be any, unknown, never, or be optional (in that it contains undefined in the type).
Otherwise, use of the delete operator is an error.
interface Thing {
prop: string;
}
function f(x: Thing) {
delete x.prop;
// ~~~~~~
// error! The operand of a 'delete' operator must be optional.
}See more details on the implementing pull request.
See more details on the implementing pull request.
TypeScript recently implemented the optional chaining operator, but we've received user feedback that the behavior of optional chaining (?.) with the non-null assertion operator (!) is extremely counter-intuitive.
Specifically, in previous versions, the code
foo?.bar!.bazwas interpreted to be equivalent to the following JavaScript.
(foo?.bar).bazIn the above code the parentheses stop the "short-circuiting" behavior of optional chaining, so if foo is undefined, accessing baz will cause a runtime error.
The Babel team who pointed this behavior out, and most users who provided feedback to us, believe that this behavior is wrong.
We do too!
The thing we heard the most was that the ! operator should just "disappear" since the intent was to remove null and undefined from the type of bar.
In other words, most people felt that the original snippet should be interpreted as
foo?.bar.bazwhich just evaluates to undefined when foo is undefined.
This is a breaking change, but we believe most code was written with the new interpretation in mind.
Users who want to revert to the old behavior can add explicit parentheses around the left side of the ! operator.
(foo?.bar)!.bazFor more information, see the corresponding pull request.
The JSX Specification forbids the use of the } and > characters in text positions.
TypeScript and Babel have both decided to enforce this rule to be more comformant.
The new way to insert these characters is to use an HTML escape code (e.g. <span> 2 > 1 </div>) or insert an expression with a string literal (e.g. <span> 2 {">"} 1 </div>).
In the presence of code like this, you'll get an error message along the lines of
Unexpected token. Did you mean `{'>'}` or `>`?
Unexpected token. Did you mean `{'}'}` or `}`?
For example:
let directions = <span>Navigate to: Menu Bar > Tools > Options</div>
// ~ ~
// Unexpected token. Did you mean `{'>'}` or `>`?For more information, see the corresponding pull request.
Generally, an intersection type like A & B is assignable to C if either A or B is assignable to C; however, sometimes that has problems with optional properties.
For example, take the following:
interface A {
a: number; // notice this is 'number'
}
interface B {
b: string;
}
interface C {
a?: boolean; // notice this is 'boolean'
b: string;
}
declare let x: A & B;
declare let y: C;
y = x;In previous versions of TypeScript, this was allowed because while A was totally incompatible with C, B was compatible with C.
In TypeScript 3.9, so long as every type in an intersection is a concrete object type, the type system will consider all of the properties at once.
As a result, TypeScript will see that the a property of A & B is incompatible with that of C:
Type 'A & B' is not assignable to type 'C'.
Types of property 'a' are incompatible.
Type 'number' is not assignable to type 'boolean | undefined'.
For more information on this change, see the corresponding pull request.
There are a few cases where you might end up with types that describe values that just don't exist. For example
declare function smushObjects<T, U>(x: T, y: U): T & U;
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
declare let x: Circle;
declare let y: Square;
let z = smushObjects(x, y);
console.log(z.kind);This code is slightly weird because there's really no way to create an intersection of a Circle and a Square - they have two incompatible kind fields.
In previous versions of TypeScript, this code was allowed and the type of kind itself was never because "circle" & "square" described a set of values that could never exist.
In TypeScript 3.9, the type system is more aggressive here - it notices that it's impossible to intersect Circle and Square because of their kind properties.
So instead of collapsing the type of z.kind to never, it collapses the type of z itself (Circle & Square) to never.
That means the above code now errors with:
Property 'kind' does not exist on type 'never'.
Most of the breaks we observed seem to correspond with slightly incorrect type declarations. For more details, see the original pull request.
In older versions of TypeScript, get and set accessors in classes were emitted in a way that made them enumerable; however, this wasn't compliant with the ECMAScript specification which states that they must be non-enumerable.
As a result, TypeScript code that targeted ES5 and ES2015 could differ in behavior.
With recent changes, TypeScript 3.9 now conforms more closely with ECMAScript in this regard.
In previous versions of TypeScript, a type parameter constrained to any could be treated as any.
function foo<T extends any>(arg: T) {
arg.spfjgerijghoied; // no error!
}This was an oversight, so TypeScript 3.9 takes a more conservative approach and issues an error on these questionable operations.
function foo<T extends any>(arg: T) {
arg.spfjgerijghoied;
// ~~~~~~~~~~~~~~~
// Property 'spfjgerijghoied' does not exist on type 'T'.
}See the original pull request for more details.
In previous TypeScript versions, declarations like export * from "foo" would be dropped in our JavaScript output if foo didn't export any values.
This sort of emit is problematic because it's type-directed and can't be emulated by Babel.
TypeScript 3.9 will always emit these export * declarations.
In practice, we don't expect this to break much existing code, but bundlers may have a harder time tree-shaking the code.
You can see the specific changes in the original pull request.
When targeting module systems like CommonJS in ES5 and above, TypeScript will use get accessors to emulate live bindings so that changes to a variable in one module are witnessed in any exporting modules. This change is meant to make TypeScript's emit more compliant with ECMAScript modules.
For more details, see the PR that applies this change.
TypeScript now hoists exported declarations to the top of the file when targeting module systems like CommonJS in ES5 and above. This change is meant to make TypeScript's emit more compliant with ECMAScript modules. For example, code like
export * from "mod";
export const nameFromMod = 0;previously had output like
__exportStar(exports, require("mod"));
exports.nameFromMod = 0;However, because exports now use get-accessors, this assignment would throw because __exportStar now makes get-accesors which can't be overridden with a simple assignment. Instead, TypeScript 3.9 emits the following:
exports.nameFromMod = void 0;
__exportStar(exports, require("mod"));
exports.nameFromMod = 0;See the original pull request for more information.
Previously, excess properties were unchecked when assigning to unions where any type had an index signature - even if that excess property could never satisfy that index signature. In TypeScript 3.8, the type-checker is stricter, and only "exempts" properties from excess property checks if that property could plausibly satisfy an index signature.
const obj1: { [x: string]: number } | { a: number };
obj1 = { a: 5, c: 'abc' }
// ~
// Error!
// The type '{ [x: string]: number }' no longer exempts 'c'
// from excess property checks on '{ a: number }'.
let obj2: { [x: string]: number } | { [x: number]: number };
obj2 = { a: 'abc' };
// ~
// Error!
// The types '{ [x: string]: number }' and '{ [x: number]: number }' no longer exempts 'a'
// from excess property checks against '{ [x: number]: number }',
// and it *is* sort of an excess property because 'a' isn't a numeric property name.
// This one is more subtle.In the following code, param is now marked with an error under noImplicitAny.
function foo(f: () => void) {
// ...
}
foo((param?) => {
// ...
});This is because there is no corresponding parameter for the type of f in foo.
This seems unlikely to be intentional, but it can be worked around by providing an explicit type for param.
Historically, TypeScript's support for checking JavaScript has been lax in certain ways in order to provide an approachable experience.
For example, users often used Object in JSDoc to mean, "some object, I dunno what", we've treated it as any.
// @ts-check
/**
* @param thing {Object} some object, i dunno what
*/
function doSomething(thing) {
let x = thing.x;
let y = thing.y;
thing();
}This is because treating it as TypeScript's Object type would end up in code reporting uninteresting errors, since the Object type is an extremely vague type with few capabilities other than methods like toString and valueOf.
However, TypeScript does have a more useful type named object (notice that lowercase o).
The object type is more restrictive than Object, in that it rejects all primitive types like string, boolean, and number.
Unfortunately, both Object and object were treated as any in JSDoc.
Because object can come in handy and is used significantly less than Object in JSDoc, we've removed the special-case behavior in JavaScript files when using noImplicitAny so that in JSDoc, the object type really refers to the non-primitive object type.
As per the ECMAScript specification, class declarations with methods named constructor are now constructor functions, regardless of whether they are declared using identifier names, or string names.
class C {
"constructor"() {
console.log("I am the constructor now.");
}
}A notable exception, and the workaround to this break, is using a computed property whose name evaluates to "constructor".
class D {
["constructor"]() {
console.log("I'm not a constructor - just a plain method!");
}
}Many declarations have been removed or changed within lib.dom.d.ts.
This includes (but isn't limited to) the following:
- The global
windowis no longer defined as typeWindow- instead, it is defined as typeWindow & typeof globalThis. In some cases, it may be better to refer to its type astypeof window. -
GlobalFetchis gone. Instead, useWindowOrWorkerGlobalScope - Certain non-standard properties on
Navigatorare gone. - The
experimental-webglcontext is gone. Instead, usewebglorwebgl2.
In JavaScript files, TypeScript will only consult immediately preceding JSDoc comments to figure out declared types.
/**
* @param {string} arg
*/
/**
* oh, hi, were you trying to type something?
*/
function whoWritesFunctionsLikeThis(arg) {
// 'arg' has type 'any'
}Previously keywords were not allowed to contain escape sequences. TypeScript 3.6 disallows them.
while (true) {
\u0063ontinue;
// ~~~~~~~~~~~~~
// error! Keywords cannot contain escape characters.
}In TypeScript 3.5, generic type parameters without an explicit constraint are now implicitly constrained to unknown, whereas previously the implicit constraint of type parameters was the empty object type {}.
In practice, {} and unknown are pretty similar, but there are a few key differences:
-
{}can be indexed with a string (k["foo"]), though this is an implicitanyerror under--noImplicitAny. -
{}is assumed to not benullorundefined, whereasunknownis possibly one of those values. -
{}is assignable toobject, butunknownis not.
On the caller side, this typically means that assignment to object will fail, and methods on Object like toString, toLocaleString, valueOf, hasOwnProperty, isPrototypeOf, and propertyIsEnumerable will no longer be available.
function foo<T>(x: T): [T, string] {
return [x, x.toString()]
// ~~~~~~~~ error! Property 'toString' does not exist on type 'T'.
}As a workaround, you can add an explicit constraint of {} to a type parameter to get the old behavior.
// vvvvvvvvvv
function foo<T extends {}>(x: T): [T, string] {
return [x, x.toString()]
}From the caller side, failed inferences for generic type arguments will result in unknown instead of {}.
function parse<T>(x: string): T {
return JSON.parse(x);
}
// k has type 'unknown' - previously, it was '{}'.
const k = parse("...");As a workaround, you can provide an explicit type argument:
// 'k' now has type '{}'
const k = parse<{}>("...");The index signature { [s: string]: any } in TypeScript behaves specially: it's a valid assignment target for any object type.
This is a special rule, since types with index signatures don't normally produce this behavior.
Since its introduction, the type unknown in an index signature behaved the same way:
let dict: { [s: string]: unknown };
// Was OK
dict = () => {};In general this rule makes sense; the implied constraint of "all its properties are some subtype of unknown" is trivially true of any object type.
However, in TypeScript 3.5, this special rule is removed for { [s: string]: unknown }.
This was a necessary change because of the change from {} to unknown when generic inference has no candidates.
Consider this code:
declare function someFunc(): void;
declare function fn<T>(arg: { [k: string]: T }): void;
fn(someFunc);In TypeScript 3.4, the following sequence occurred:
- No candidates were found for
T -
Tis selected to be{} -
someFuncisn't assignable toargbecause there are no special rules allowing arbitrary assignment to{ [k: string]: {} } - The call is correctly rejected
Due to changes around unconstrained type parameters falling back to unknown (see above), arg would have had the type { [k: string]: unknown }, which anything is assignable to, so the call would have incorrectly been allowed.
That's why TypeScript 3.5 removes the specialized assignability rule to permit assignment to { [k: string]: unknown }.
Note that fresh object literals are still exempt from this check.
const obj = { m: 10 };
// OK
const dict: { [s: string]: unknown } = obj;Depending on the intended behavior of { [s: string]: unknown }, several alternatives are available:
{ [s: string]: any }{ [s: string]: {} }objectunknownany
We recommend sketching out your desired use cases and seeing which one is the best option for your particular use case.
TypeScript has a feature called excess property checking in object literals. This feature is meant to detect typos for when a type isn't expecting a specific property.
type Style = {
alignment: string,
color?: string
};
const s: Style = {
alignment: "center",
colour: "grey"
// ^^^^^^ error!
};In TypeScript 3.4 and earlier, certain excess properties were allowed in situations where they really shouldn't have been.
Consider this code:
type Point = {
x: number;
y: number;
};
type Label