Skip to content

Commit

Permalink
Make the unconstrained type parameter and {} assignability rule not a…
Browse files Browse the repository at this point in the history
…pply under strictNullChecks
  • Loading branch information
weswigham committed Mar 21, 2022
1 parent f9e6ba3 commit 41b6b9f
Show file tree
Hide file tree
Showing 13 changed files with 819 additions and 25 deletions.
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18680,6 +18680,9 @@ namespace ts {
return;
}
reportRelationError(headMessage, source, target);
if (strictNullChecks && source.flags & TypeFlags.TypeVariable && source.symbol?.declarations?.[0] && !getConstraintOfType(<TypeVariable>source) && isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_likely_needs_an_extends_object_constraint));
}
}

function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void {
Expand Down Expand Up @@ -19471,20 +19474,20 @@ namespace ts {
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) {
const constraint = getConstraintOfType(source as TypeVariable);
if (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
if (!strictNullChecks && (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any))) {
// A type variable with no constraint is not related to the non-primitive object type.
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
// 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
else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
else if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState))) {
resetErrorInfo(saveErrorInfo);
return result;
}
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
else if (constraint && (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState))) {
resetErrorInfo(saveErrorInfo);
return result;
}
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ namespace ts {
array.length = outIndex;
}

export function clear(array: {}[]): void {
export function clear(array: unknown[]): void {
array.length = 0;
}

Expand Down Expand Up @@ -1644,7 +1644,7 @@ namespace ts {
/**
* Tests whether a value is an array.
*/
export function isArray(value: any): value is readonly {}[] {
export function isArray(value: any): value is readonly unknown[] {
return Array.isArray ? Array.isArray(value) : value instanceof Array;
}

Expand Down Expand Up @@ -1677,7 +1677,7 @@ namespace ts {
}

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

/** Do nothing and return false */
export function returnFalse(): false {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,10 @@
"category": "Error",
"code": 2207
},
"This type parameter likely needs an `extends object` constraint.": {
"category": "Error",
"code": 2208
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
4 changes: 2 additions & 2 deletions src/harness/harnessGlobals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ globalThis.assert = _chai.assert;
}
assertDeepImpl(a, b, msg);

function arrayExtraKeysObject(a: readonly ({} | null | undefined)[]): object {
const obj: { [key: string]: {} | null | undefined } = {};
function arrayExtraKeysObject(a: readonly unknown[]): object {
const obj: { [key: string]: unknown } = {};
for (const key in a) {
if (Number.isNaN(Number(key))) {
obj[key] = a[key];
Expand Down
2 changes: 1 addition & 1 deletion src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ namespace ts {
}
}

function simpleForwardCall(logger: Logger, actionDescription: string, action: () => {}, logPerformance: boolean): {} {
function simpleForwardCall(logger: Logger, actionDescription: string, action: () => unknown, logPerformance: boolean): unknown {
let start: number | undefined;
if (logPerformance) {
logger.log(actionDescription);
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/isomorphicMappedTypeInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function boxify<T>(obj: T): Boxified<T> {
return result;
}

function unboxify<T>(obj: Boxified<T>): T {
function unboxify<T extends object>(obj: Boxified<T>): T {
let result = {} as T;
for (let k in obj) {
result[k] = unbox(obj[k]);
Expand Down Expand Up @@ -307,7 +307,7 @@ declare type Boxified<T> = {
declare function box<T>(x: T): Box<T>;
declare function unbox<T>(x: Box<T>): T;
declare function boxify<T>(obj: T): Boxified<T>;
declare function unboxify<T>(obj: Boxified<T>): T;
declare function unboxify<T extends object>(obj: Boxified<T>): T;
declare function assignBoxified<T>(obj: Boxified<T>, values: T): void;
declare function f1(): void;
declare function f2(): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ function boxify<T>(obj: T): Boxified<T> {
>result : Symbol(result, Decl(isomorphicMappedTypeInference.ts, 17, 7))
}

function unboxify<T>(obj: Boxified<T>): T {
function unboxify<T extends object>(obj: Boxified<T>): T {
>unboxify : Symbol(unboxify, Decl(isomorphicMappedTypeInference.ts, 22, 1))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))
>Boxified : Symbol(Boxified, Decl(isomorphicMappedTypeInference.ts, 2, 1))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 24, 18))
Expand All @@ -89,13 +89,13 @@ function unboxify<T>(obj: Boxified<T>): T {

for (let k in obj) {
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))

result[k] = unbox(obj[k]);
>result : Symbol(result, Decl(isomorphicMappedTypeInference.ts, 25, 7))
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
>unbox : Symbol(unbox, Decl(isomorphicMappedTypeInference.ts, 10, 1))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 21))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 24, 36))
>k : Symbol(k, Decl(isomorphicMappedTypeInference.ts, 26, 12))
}
return result;
Expand Down
14 changes: 7 additions & 7 deletions tests/baselines/reference/isomorphicMappedTypeInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ function boxify<T>(obj: T): Boxified<T> {
>result : Boxified<T>
}

function unboxify<T>(obj: Boxified<T>): T {
>unboxify : <T>(obj: Boxified<T>) => T
function unboxify<T extends object>(obj: Boxified<T>): T {
>unboxify : <T extends object>(obj: Boxified<T>) => T
>obj : Boxified<T>

let result = {} as T;
Expand Down Expand Up @@ -174,7 +174,7 @@ function f2() {
let v = unboxify(b);
>v : { a: number; b: string; c: boolean; }
>unboxify(b) : { a: number; b: string; c: boolean; }
>unboxify : <T>(obj: Boxified<T>) => T
>unboxify : <T extends object>(obj: Boxified<T>) => T
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }

let x: number = v.a;
Expand Down Expand Up @@ -251,14 +251,14 @@ function f4() {
>boxify(unboxify(b)) : Boxified<{ a: number; b: string; c: boolean; }>
>boxify : <T>(obj: T) => Boxified<T>
>unboxify(b) : { a: number; b: string; c: boolean; }
>unboxify : <T>(obj: Boxified<T>) => T
>unboxify : <T extends object>(obj: Boxified<T>) => T
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }

b = unboxify(boxify(b));
>b = unboxify(boxify(b)) : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
>unboxify(boxify(b)) : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
>unboxify : <T>(obj: Boxified<T>) => T
>unboxify : <T extends object>(obj: Boxified<T>) => T
>boxify(b) : Boxified<{ a: Box<number>; b: Box<string>; c: Box<boolean>; }>
>boxify : <T>(obj: T) => Boxified<T>
>b : { a: Box<number>; b: Box<string>; c: Box<boolean>; }
Expand Down Expand Up @@ -304,7 +304,7 @@ function f5(s: string) {
let v = unboxify(b);
>v : { a: string | number | boolean; b: string | number | boolean; c: string | number | boolean; }
>unboxify(b) : { a: string | number | boolean; b: string | number | boolean; c: string | number | boolean; }
>unboxify : <T>(obj: Boxified<T>) => T
>unboxify : <T extends object>(obj: Boxified<T>) => T
>b : { a: Box<number> | Box<string> | Box<boolean>; b: Box<number> | Box<string> | Box<boolean>; c: Box<number> | Box<string> | Box<boolean>; }

let x: string | number | boolean = v.a;
Expand Down Expand Up @@ -355,7 +355,7 @@ function f6(s: string) {
let v = unboxify(b);
>v : { [x: string]: any; }
>unboxify(b) : { [x: string]: any; }
>unboxify : <T>(obj: Boxified<T>) => T
>unboxify : <T extends object>(obj: Boxified<T>) => T
>b : { [x: string]: Box<number> | Box<string> | Box<boolean>; }

let x: string | number | boolean = v[s];
Expand Down
Loading

0 comments on commit 41b6b9f

Please sign in to comment.