Skip to content

Commit 8cd4b7a

Browse files
authored
Make the unconstrained type parameter and {} assignability rule not apply under strictNullChecks (#48366)
* Make the unconstrained type parameter and {} assignability rule not apply under strictNullChecks * Fix lint, PR feedback
1 parent b5a3a05 commit 8cd4b7a

13 files changed

+819
-25
lines changed

src/compiler/checker.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -18702,6 +18702,9 @@ namespace ts {
1870218702
return;
1870318703
}
1870418704
reportRelationError(headMessage, source, target);
18705+
if (strictNullChecks && source.flags & TypeFlags.TypeVariable && source.symbol?.declarations?.[0] && !getConstraintOfType(source as TypeVariable) && isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
18706+
associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_probably_needs_an_extends_object_constraint));
18707+
}
1870518708
}
1870618709

1870718710
function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void {
@@ -19492,20 +19495,20 @@ namespace ts {
1949219495
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
1949319496
if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) {
1949419497
const constraint = getConstraintOfType(source as TypeVariable);
19495-
if (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
19498+
if (!strictNullChecks && (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any))) {
1949619499
// A type variable with no constraint is not related to the non-primitive object type.
1949719500
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) {
1949819501
resetErrorInfo(saveErrorInfo);
1949919502
return result;
1950019503
}
1950119504
}
1950219505
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
19503-
else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
19506+
else if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState))) {
1950419507
resetErrorInfo(saveErrorInfo);
1950519508
return result;
1950619509
}
1950719510
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
19508-
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
19511+
else if (constraint && (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState))) {
1950919512
resetErrorInfo(saveErrorInfo);
1951019513
return result;
1951119514
}

src/compiler/core.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ namespace ts {
301301
array.length = outIndex;
302302
}
303303

304-
export function clear(array: {}[]): void {
304+
export function clear(array: unknown[]): void {
305305
array.length = 0;
306306
}
307307

@@ -1644,7 +1644,7 @@ namespace ts {
16441644
/**
16451645
* Tests whether a value is an array.
16461646
*/
1647-
export function isArray(value: any): value is readonly {}[] {
1647+
export function isArray(value: any): value is readonly unknown[] {
16481648
return Array.isArray ? Array.isArray(value) : value instanceof Array;
16491649
}
16501650

@@ -1677,7 +1677,7 @@ namespace ts {
16771677
}
16781678

16791679
/** Does nothing. */
1680-
export function noop(_?: {} | null | undefined): void { }
1680+
export function noop(_?: unknown): void { }
16811681

16821682
/** Do nothing and return false */
16831683
export function returnFalse(): false {

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,10 @@
15061506
"category": "Error",
15071507
"code": 2207
15081508
},
1509+
"This type parameter probably needs an `extends object` constraint.": {
1510+
"category": "Error",
1511+
"code": 2208
1512+
},
15091513

15101514
"Duplicate identifier '{0}'.": {
15111515
"category": "Error",

src/harness/harnessGlobals.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ globalThis.assert = _chai.assert;
2020
}
2121
assertDeepImpl(a, b, msg);
2222

23-
function arrayExtraKeysObject(a: readonly ({} | null | undefined)[]): object {
24-
const obj: { [key: string]: {} | null | undefined } = {};
23+
function arrayExtraKeysObject(a: readonly unknown[]): object {
24+
const obj: { [key: string]: unknown } = {};
2525
for (const key in a) {
2626
if (Number.isNaN(Number(key))) {
2727
obj[key] = a[key];

src/services/shims.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ namespace ts {
554554
}
555555
}
556556

557-
function simpleForwardCall(logger: Logger, actionDescription: string, action: () => {}, logPerformance: boolean): {} {
557+
function simpleForwardCall(logger: Logger, actionDescription: string, action: () => unknown, logPerformance: boolean): unknown {
558558
let start: number | undefined;
559559
if (logPerformance) {
560560
logger.log(actionDescription);

tests/baselines/reference/isomorphicMappedTypeInference.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function boxify<T>(obj: T): Boxified<T> {
2323
return result;
2424
}
2525

26-
function unboxify<T>(obj: Boxified<T>): T {
26+
function unboxify<T extends object>(obj: Boxified<T>): T {
2727
let result = {} as T;
2828
for (let k in obj) {
2929
result[k] = unbox(obj[k]);
@@ -307,7 +307,7 @@ declare type Boxified<T> = {
307307
declare function box<T>(x: T): Box<T>;
308308
declare function unbox<T>(x: Box<T>): T;
309309
declare function boxify<T>(obj: T): Boxified<T>;
310-
declare function unboxify<T>(obj: Boxified<T>): T;
310+
declare function unboxify<T extends object>(obj: Boxified<T>): T;
311311
declare function assignBoxified<T>(obj: Boxified<T>, values: T): void;
312312
declare function f1(): void;
313313
declare function f2(): void;

tests/baselines/reference/isomorphicMappedTypeInference.symbols

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ function boxify<T>(obj: T): Boxified<T> {
7575
>result : Symbol(result, Decl(isomorphicMappedTypeInference.ts, 17, 7))
7676
}
7777

78-
function unboxify<T>(obj: Boxified<T>): T {
78+
function unboxify<T extends object>(obj: Boxified<T>): T {
7979
>unboxify : Symbol(unboxify, Decl(isomorphicMappedTypeInference.ts, 22, 1))
8080
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
81-
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
81+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))
8282
>Boxified : Symbol(Boxified, Decl(isomorphicMappedTypeInference.ts, 2, 1))
8383
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
8484
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
@@ -89,13 +89,13 @@ function unboxify<T>(obj: Boxified<T>): T {
8989

9090
for (let k in obj) {
9191
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
92-
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
92+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))
9393

9494
result[k] = unbox(obj[k]);
9595
>result : Symbol(result, Decl(isomorphicMappedTypeInference.ts, 25, 7))
9696
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
9797
>unbox : Symbol(unbox, Decl(isomorphicMappedTypeInference.ts, 10, 1))
98-
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
98+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))
9999
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
100100
}
101101
return result;

tests/baselines/reference/isomorphicMappedTypeInference.types

+7-7
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ function boxify<T>(obj: T): Boxified<T> {
6060
>result : Boxified<T>
6161
}
6262

63-
function unboxify<T>(obj: Boxified<T>): T {
64-
>unboxify : <T>(obj: Boxified<T>) => T
63+
function unboxify<T extends object>(obj: Boxified<T>): T {
64+
>unboxify : <T extends object>(obj: Boxified<T>) => T
6565
>obj : Boxified<T>
6666

6767
let result = {} as T;
@@ -174,7 +174,7 @@ function f2() {
174174
let v = unboxify(b);
175175
>v : { a: number; b: string; c: boolean; }
176176
>unboxify(b) : { a: number; b: string; c: boolean; }
177-
>unboxify : <T>(obj: Boxified<T>) => T
177+
>unboxify : <T extends object>(obj: Boxified<T>) => T
178178
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
179179

180180
let x: number = v.a;
@@ -251,14 +251,14 @@ function f4() {
251251
>boxify(unboxify(b)) : Boxified<{ a: number; b: string; c: boolean; }>
252252
>boxify : <T>(obj: T) => Boxified<T>
253253
>unboxify(b) : { a: number; b: string; c: boolean; }
254-
>unboxify : <T>(obj: Boxified<T>) => T
254+
>unboxify : <T extends object>(obj: Boxified<T>) => T
255255
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
256256

257257
b = unboxify(boxify(b));
258258
>b = unboxify(boxify(b)) : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
259259
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
260260
>unboxify(boxify(b)) : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
261-
>unboxify : <T>(obj: Boxified<T>) => T
261+
>unboxify : <T extends object>(obj: Boxified<T>) => T
262262
>boxify(b) : Boxified<{ a: Box<number>; b: Box<string>; c: Box<boolean>; }>
263263
>boxify : <T>(obj: T) => Boxified<T>
264264
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
@@ -304,7 +304,7 @@ function f5(s: string) {
304304
let v = unboxify(b);
305305
>v : { a: string | number | boolean; b: string | number | boolean; c: string | number | boolean; }
306306
>unboxify(b) : { a: string | number | boolean; b: string | number | boolean; c: string | number | boolean; }
307-
>unboxify : <T>(obj: Boxified<T>) => T
307+
>unboxify : <T extends object>(obj: Boxified<T>) => T
308308
>b : { a: Box<number> | Box<string> | Box<boolean>; b: Box<number> | Box<string> | Box<boolean>; c: Box<number> | Box<string> | Box<boolean>; }
309309

310310
let x: string | number | boolean = v.a;
@@ -355,7 +355,7 @@ function f6(s: string) {
355355
let v = unboxify(b);
356356
>v : { [x: string]: any; }
357357
>unboxify(b) : { [x: string]: any; }
358-
>unboxify : <T>(obj: Boxified<T>) => T
358+
>unboxify : <T extends object>(obj: Boxified<T>) => T
359359
>b : { [x: string]: Box<number> | Box<string> | Box<boolean>; }
360360

361361
let x: string | number | boolean = v[s];

0 commit comments

Comments
 (0)