Skip to content

Remove undefined from optional properties when inferring to index signatures #43086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11775,7 +11775,8 @@ namespace ts {
const propTypes: Type[] = [];
for (const prop of getPropertiesOfType(type)) {
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) {
propTypes.push(getTypeOfSymbol(prop));
const propType = getTypeOfSymbol(prop);
propTypes.push(prop.flags & SymbolFlags.Optional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : propType);
}
}
if (kind === IndexKind.String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [inferenceOptionalPropertiesToIndexSignatures.ts]
declare function foo<T>(obj: { [x: string]: T }): T;

declare const x1: { a: string, b: number };
declare const x2: { a: string, b: number | undefined };
declare const x3: { a: string, b?: number };
declare const x4: { a: string, b?: number | undefined };

let a1 = foo(x1); // string | number
let a2 = foo(x2); // string | number | undefined
let a3 = foo(x3); // string | number
let a4 = foo(x4); // string | number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm - seems like this is the only questionable one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively, because we don't have a separate missing type, undefined represents the missing state in optional properties, so we really should remove undefined when we're inferring to an index signature as index signatures represent the type of those properties that are present.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like things are getting a bit untenable without missing; any plausible implementation of foo with a legal x4 could return undefined. We should revisit it.


// Repro from #43045

const param2 = Math.random() < 0.5 ? 'value2' : null;

const obj = {
param1: 'value1',
...(param2 ? {param2} : {})
};

const query = Object.entries(obj).map(
([k, v]) => `${k}=${encodeURIComponent(v)}`
).join('&');


//// [inferenceOptionalPropertiesToIndexSignatures.js]
"use strict";
let a1 = foo(x1); // string | number
let a2 = foo(x2); // string | number | undefined
let a3 = foo(x3); // string | number
let a4 = foo(x4); // string | number
// Repro from #43045
const param2 = Math.random() < 0.5 ? 'value2' : null;
const obj = {
param1: 'value1',
...(param2 ? { param2 } : {})
};
const query = Object.entries(obj).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
=== tests/cases/compiler/inferenceOptionalPropertiesToIndexSignatures.ts ===
declare function foo<T>(obj: { [x: string]: T }): T;
>foo : Symbol(foo, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 0))
>T : Symbol(T, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 21))
>obj : Symbol(obj, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 24))
>x : Symbol(x, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 32))
>T : Symbol(T, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 21))
>T : Symbol(T, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 21))

declare const x1: { a: string, b: number };
>x1 : Symbol(x1, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 2, 13))
>a : Symbol(a, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 2, 19))
>b : Symbol(b, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 2, 30))

declare const x2: { a: string, b: number | undefined };
>x2 : Symbol(x2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 3, 13))
>a : Symbol(a, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 3, 19))
>b : Symbol(b, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 3, 30))

declare const x3: { a: string, b?: number };
>x3 : Symbol(x3, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 4, 13))
>a : Symbol(a, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 4, 19))
>b : Symbol(b, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 4, 30))

declare const x4: { a: string, b?: number | undefined };
>x4 : Symbol(x4, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 5, 13))
>a : Symbol(a, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 5, 19))
>b : Symbol(b, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 5, 30))

let a1 = foo(x1); // string | number
>a1 : Symbol(a1, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 7, 3))
>foo : Symbol(foo, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 0))
>x1 : Symbol(x1, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 2, 13))

let a2 = foo(x2); // string | number | undefined
>a2 : Symbol(a2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 8, 3))
>foo : Symbol(foo, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 0))
>x2 : Symbol(x2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 3, 13))

let a3 = foo(x3); // string | number
>a3 : Symbol(a3, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 9, 3))
>foo : Symbol(foo, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 0))
>x3 : Symbol(x3, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 4, 13))

let a4 = foo(x4); // string | number
>a4 : Symbol(a4, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 10, 3))
>foo : Symbol(foo, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 0, 0))
>x4 : Symbol(x4, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 5, 13))

// Repro from #43045

const param2 = Math.random() < 0.5 ? 'value2' : null;
>param2 : Symbol(param2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 14, 5))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

const obj = {
>obj : Symbol(obj, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 16, 5))

param1: 'value1',
>param1 : Symbol(param1, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 16, 13))

...(param2 ? {param2} : {})
>param2 : Symbol(param2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 14, 5))
>param2 : Symbol(param2, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 18, 18))

};

const query = Object.entries(obj).map(
>query : Symbol(query, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 21, 5))
>Object.entries(obj).map( ([k, v]) => `${k}=${encodeURIComponent(v)}`).join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --))
>Object.entries(obj).map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
>obj : Symbol(obj, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 16, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))

([k, v]) => `${k}=${encodeURIComponent(v)}`
>k : Symbol(k, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 22, 6))
>v : Symbol(v, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 22, 8))
>k : Symbol(k, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 22, 6))
>encodeURIComponent : Symbol(encodeURIComponent, Decl(lib.es5.d.ts, --, --))
>v : Symbol(v, Decl(inferenceOptionalPropertiesToIndexSignatures.ts, 22, 8))

).join('&');
>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --))

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
=== tests/cases/compiler/inferenceOptionalPropertiesToIndexSignatures.ts ===
declare function foo<T>(obj: { [x: string]: T }): T;
>foo : <T>(obj: { [x: string]: T; }) => T
>obj : { [x: string]: T; }
>x : string

declare const x1: { a: string, b: number };
>x1 : { a: string; b: number; }
>a : string
>b : number

declare const x2: { a: string, b: number | undefined };
>x2 : { a: string; b: number | undefined; }
>a : string
>b : number | undefined

declare const x3: { a: string, b?: number };
>x3 : { a: string; b?: number | undefined; }
>a : string
>b : number | undefined

declare const x4: { a: string, b?: number | undefined };
>x4 : { a: string; b?: number | undefined; }
>a : string
>b : number | undefined

let a1 = foo(x1); // string | number
>a1 : string | number
>foo(x1) : string | number
>foo : <T>(obj: { [x: string]: T; }) => T
>x1 : { a: string; b: number; }

let a2 = foo(x2); // string | number | undefined
>a2 : string | number | undefined
>foo(x2) : string | number | undefined
>foo : <T>(obj: { [x: string]: T; }) => T
>x2 : { a: string; b: number | undefined; }

let a3 = foo(x3); // string | number
>a3 : string | number
>foo(x3) : string | number
>foo : <T>(obj: { [x: string]: T; }) => T
>x3 : { a: string; b?: number | undefined; }

let a4 = foo(x4); // string | number
>a4 : string | number
>foo(x4) : string | number
>foo : <T>(obj: { [x: string]: T; }) => T
>x4 : { a: string; b?: number | undefined; }

// Repro from #43045

const param2 = Math.random() < 0.5 ? 'value2' : null;
>param2 : "value2" | null
>Math.random() < 0.5 ? 'value2' : null : "value2" | null
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>'value2' : "value2"
>null : null

const obj = {
>obj : { param2?: string | undefined; param1: string; }
>{ param1: 'value1', ...(param2 ? {param2} : {})} : { param2?: string | undefined; param1: string; }

param1: 'value1',
>param1 : string
>'value1' : "value1"

...(param2 ? {param2} : {})
>(param2 ? {param2} : {}) : { param2: string; } | {}
>param2 ? {param2} : {} : { param2: string; } | {}
>param2 : "value2" | null
>{param2} : { param2: string; }
>param2 : string
>{} : {}

};

const query = Object.entries(obj).map(
>query : string
>Object.entries(obj).map( ([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&') : string
>Object.entries(obj).map( ([k, v]) => `${k}=${encodeURIComponent(v)}`).join : (separator?: string | undefined) => string
>Object.entries(obj).map( ([k, v]) => `${k}=${encodeURIComponent(v)}`) : string[]
>Object.entries(obj).map : <U>(callbackfn: (value: [string, string], index: number, array: [string, string][]) => U, thisArg?: any) => U[]
>Object.entries(obj) : [string, string][]
>Object.entries : { <T>(o: { [s: string]: T; } | ArrayLike<T>): [string, T][]; (o: {}): [string, any][]; }
>Object : ObjectConstructor
>entries : { <T>(o: { [s: string]: T; } | ArrayLike<T>): [string, T][]; (o: {}): [string, any][]; }
>obj : { param2?: string | undefined; param1: string; }
>map : <U>(callbackfn: (value: [string, string], index: number, array: [string, string][]) => U, thisArg?: any) => U[]

([k, v]) => `${k}=${encodeURIComponent(v)}`
>([k, v]) => `${k}=${encodeURIComponent(v)}` : ([k, v]: [string, string]) => string
>k : string
>v : string
>`${k}=${encodeURIComponent(v)}` : string
>k : string
>encodeURIComponent(v) : string
>encodeURIComponent : (uriComponent: string | number | boolean) => string
>v : string

).join('&');
>join : (separator?: string | undefined) => string
>'&' : "&"

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @strict: true
// @target: esnext

declare function foo<T>(obj: { [x: string]: T }): T;

declare const x1: { a: string, b: number };
declare const x2: { a: string, b: number | undefined };
declare const x3: { a: string, b?: number };
declare const x4: { a: string, b?: number | undefined };

let a1 = foo(x1); // string | number
let a2 = foo(x2); // string | number | undefined
let a3 = foo(x3); // string | number
let a4 = foo(x4); // string | number

// Repro from #43045

const param2 = Math.random() < 0.5 ? 'value2' : null;

const obj = {
param1: 'value1',
...(param2 ? {param2} : {})
};

const query = Object.entries(obj).map(
([k, v]) => `${k}=${encodeURIComponent(v)}`
).join('&');