Updated Breaking Changes (markdown)

Daniel Rosenwasser
2019-05-16 00:20:11 -07:00
parent 41df881a80
commit 06ef63019c

@@ -8,7 +8,13 @@ These changes list where implementation differs between versions as the spec and
In TypeScript 3.5, [generic type parameters without an explicit constraint are now implicitly constrained to `unknown`](https://github.com/Microsoft/TypeScript/pull/30637), whereas previously the implicit constraint of type parameters was the empty object type `{}`.
This means that methods on `Object` like `toString`, `toLocaleString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf`, and `propertyIsEnumerable` will no longer be available.
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 implicit `any` error under `--noImplicitAny`.
* `{}` is assumed to not be `null` or `undefined`, whereas `unknown` is possibly one of those values.
* `{}` is assignable to `object`, but `unknown` is not.
On the caller side, this typically means is that assignment to `object` will fail, and methods on `Object` like `toString`, `toLocaleString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf`, and `propertyIsEnumerable` will no longer be available.
```ts
function foo<T>(x: T): [T, string] {
@@ -26,6 +32,236 @@ function foo<T extends {}>(x: T): [T, string] {
}
```
From the caller side, failed inferences for generic type arguments will result in `unknown` instead of `{}`.
```ts
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:
```ts
// 'k' now has type '{}'
const k = parse<{}>("...");
```
## `{ [k: string]: unknown }` is no longer a wildcard assignment target
### Background
The index signature `{ [s: string]: any }` in TypeScript behaves specially: It is a valid assignment target for any object type.
This is a *special rule* - the definition of index signatures would not normally produce this behavior.
Since its introduction, the type `unknown` in an index signature behaved the same way:
```ts
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.
### Rationale and Change
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:
```ts
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`
* `T` is selected to be `{}`
* `someFunc` isn't assignable to `arg` because there are no special rules allowing arbitrary assignment to `{ [k: string]: {} }`
* The call is correctly rejected
In TypeScript 3.5, *prior to this breaking change*, the following sequence occurred:
* No candidates were found for `T`
* `T` is selected to be `unknown`
* `someFunc` is assignable to `arg` because of the special rule allowing arbitrary assignment to `{ [k: string]: unknown }`
* The call is incorrectly allowed
In TypeScript 3.5, *with* this change, the following sequence occurrs:
* No candidates were found for `T`
* `T` is selected to be `unknown`
* `someFunc` not assignable to `arg` because it doesn't have a compatible index signature
* The call is correctly rejected
Note that the existing behavior allowing the inference of an index signature from a type originating in a fresh object literal is still preserved:
```ts
const obj = { m: 10 };
// OK
const dict: { [s: string]: unknown } = obj;
```
### Workarounds
Some codebases have adopted `{ [s: string]: unknown }` to mean "an arbitrary object".
Depending on the intended behavior, several alternatives are available:
* `object`
* `{ [s: string]: any }`
* `{ [s: string]: {} }`
We recommend sketching out your desired use cases and seeing which one is the best option for your particular use case.
## Improved excess property checks in union types
### Background
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.
```ts
type Style = {
alignment: string,
color?: string
};
const s: Style = {
alignment: "center",
colour: "grey"
// ^^^^^^ error!
};
```
### Rationale and Change
In TypeScript 3.4 and earlier, certain excess properties were allowed in situations where they really shouldn't have been.
Consider this code:
```ts
type Point = {
x: number;
y: number;
};
type Label = {
name: string;
};
const pl: Point | Label = {
x: 0,
y: 0,
name: true // <- danger!
};
```
Excess property checking was previously only capable of detecting properties which weren't present in *any* member of a target union type.
In TypeScript 3.5, these excess properties are now correctly detected, and the sample above correctly issues an error.
Note that it's still legal to be assignable to multiple parts of a union:
```ts
const pl: Point | Label = {
x: 0,
y: 0,
name: "origin" // OK
};
```
### Workarounds
We have not witnessed examples where this checking hasn't caught legitimate issues, but in a pinch, any of the workarounds to disable excess property checking will apply:
* Add a type assertion onto the object (e.g. `{ myProp: SomeType } as ExpectedType`)
* Add an index signature to the expected type to signal that unspecified properties are expected (e.g. `interface ExpectedType { myProp: SomeType; [prop: string]: unknown }`)
## Fixes to Unsound Writes to Indexed Access Types
### Background
TypeScript allows you to represent the abstract operation of accessing a property of an object via the name of that property:
```ts
type A = {
s: string;
n: number;
};
function read<K extends keyof A>(arg: A, key: K): A[K] {
return arg[key];
}
const a: A = { s: "", n: 0 };
const x = read(a, "s"); // x: string
```
While commonly used for reading values from an object, you can also use this for writes:
```ts
function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
arg[key] = value;
}
```
### Change and Rationale
In TypeScript 3.4, the logic used to validate a *write* was much too permissive:
```ts
function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
// ???
arg[key] = "hello, world";
}
// Breaks the object by putting a string where a number should be
write(a, "n");
```
In TypeScript 3.5, this logic is fixed and the above sample correctly issues an error.
### Workarounds
Most instances of this error represent potential errors in the relevant code.
One example we found looked like this:
```ts
type T = {
a: string,
x: number,
y: number
};
function write<K extends keyof T>(obj: T, k: K) {
// Trouble waiting
obj[k] = 1;
}
const someObj: T = { a: "", x: 0, y: 0 };
// Note: write(someObj, "a") never occurs, so the code is technically bug-free (?)
write(someObj, "x");
write(someObj, "y");
```
This function can be fixed to only accept keys which map to numeric properties:
```ts
// Generic helper type that produces the keys of an object
// type which map to properties of some other specific type
type KeysOfType<TObj, TProp, K extends keyof TObj = keyof TObj> = K extends K ? TObj[K] extends TProp ? K : never : never;
function write(obj: SomeObj, k: KeysOfType<SomeObj, number>) {
// OK
obj[k] = 1;
}
const someObj: SomeObj = { a: "", x: 0, y: 0 };
write(someObj, "x");
write(someObj, "y");
// Correctly an error
write(someObj, "a");
```
## `lib.d.ts` includes the `Omit` helper type
TypeScript 3.5 includes a new `Omit` helper type.
@@ -40,6 +276,52 @@ Two workarounds may be used here:
1. Delete the duplicate declaration and use the one provided in `lib.d.ts`.
2. Export the existing declaration from a module file or a namespace to avoid a global collision. Existing usages can use an `import` or explicit reference to your project's old `Omit` type.
## `Object.keys` rejects primitives in ES5
### Background
In ECMAScript 5 environments, `Object.keys` throws an exception if passed any non-`object` argument:
```ts
// Throws if run in an ES5 runtime
Object.keys(10);
```
In ECMAScript 2015, `Object.keys` returns `[]` if its argument is a primitive:
```ts
// [] in ES6 runtime
Object.keys(10);
```
### Rationale and Change
This is a potential source of error that wasn't previously identified.
In TypeScript 3.5, if `target` (or equivalently `lib`) is `ES5`, calls to `Object.keys` must pass a valid `object`.
### Workarounds
In general, errors here represent possible exceptions in your application and should be treated as such.
If you happen to know through other means that a value is an `object`, a type assertion is appropriate:
```ts
function fn(arg: object | number, isArgActuallyObject: boolean) {
if (isArgActuallyObject) {
const k = Object.keys(arg as object);
}
}
```
Note that this change interacts with the change in generic inference from `{}` to `unknown`, because `{}` is a valid `object` whereas `unknown` isn't:
```ts
declare function fn<T>(): T;
// Was OK in TypeScript 3.4, errors in 3.5 under --target ES5
Object.keys(fn());
```
# TypeScript 3.4
## Top-level `this` is now typed
@@ -1731,7 +2013,7 @@ class C {
foo(arguments: any) { // Invalid: "arguments" is not allow as a function argument
var eval = 10; // Invalid: "eval" is not allowed as the left-hand-side expression
arguments = []; // Invalid: arguments object is immutable
}
}
}
```
For complete list of strict mode restrictions, please see Annex C - The Strict Mode of ECMAScript of ECMA-262 6<sup>th</sup> Edition.