Skip to content
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

Make optional properties assignable to string index signatures #41921

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
6 changes: 5 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18478,7 +18478,11 @@ namespace ts {
continue;
}
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) {
const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors);
const propType = getTypeOfSymbol(prop);
const type = propType.flags & TypeFlags.Undefined || !(kind === IndexKind.String && prop.flags & SymbolFlags.Optional)
? propType
: getTypeWithFacts(propType, TypeFacts.NEUndefined);
const related = isRelatedTo(type, target, reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts(6,1): error TS2322: Type '{ k1: string | undefined; }' is not assignable to type '{ [key: string]: string; }'.
Property 'k1' is incompatible with index signature.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts(10,1): error TS2322: Type '{ 1?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'.
Property '1' is incompatible with index signature.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts(13,5): error TS2322: Type '{ k1?: undefined; }' is not assignable to type '{ [key: string]: string; }'.
Property 'k1' is incompatible with index signature.
Type 'undefined' is not assignable to type 'string'.


==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts (3 errors) ====
declare let optionalProperties: { k1?: string };
declare let undefinedProperties: { k1: string | undefined };

declare let stringDictionary: { [key: string]: string };
stringDictionary = optionalProperties; // ok
stringDictionary = undefinedProperties; // error
~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ k1: string | undefined; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'k1' is incompatible with index signature.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.

declare let probablyArray: { [key: number]: string };
declare let numberLiteralKeys: { 1?: string };
probablyArray = numberLiteralKeys; // error
~~~~~~~~~~~~~
!!! error TS2322: Type '{ 1?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'.
!!! error TS2322: Property '1' is incompatible with index signature.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.

declare let optionalUndefined: { k1?: undefined };
let dict: { [key: string]: string } = optionalUndefined; // error
~~~~
!!! error TS2322: Type '{ k1?: undefined; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'k1' is incompatible with index signature.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
Comment on lines +36 to +41
Copy link
Member Author

@andrewbranch andrewbranch Dec 10, 2020

Choose a reason for hiding this comment

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

This is the only mildly interesting thing about this PR. We check if the property type is undefined before filtering undefined out of it in order to keep this error. (Otherwise we would end up checking if never is assignable to string.)

Copy link
Member

@DanielRosenwasser DanielRosenwasser Jan 4, 2021

Choose a reason for hiding this comment

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

I actually think the ?: string | undefined -> [x: string]: string one is actually weirder.


function f<T>() {
let optional: { k1?: T } = undefined!;
let dict: { [key: string]: T | number } = optional; // ok
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//// [optionalPropertyAssignableToStringIndexSignature.ts]
declare let optionalProperties: { k1?: string };
declare let undefinedProperties: { k1: string | undefined };

declare let stringDictionary: { [key: string]: string };
stringDictionary = optionalProperties; // ok
stringDictionary = undefinedProperties; // error

declare let probablyArray: { [key: number]: string };
declare let numberLiteralKeys: { 1?: string };
probablyArray = numberLiteralKeys; // error

declare let optionalUndefined: { k1?: undefined };
let dict: { [key: string]: string } = optionalUndefined; // error

function f<T>() {
let optional: { k1?: T } = undefined!;
let dict: { [key: string]: T | number } = optional; // ok
}


//// [optionalPropertyAssignableToStringIndexSignature.js]
"use strict";
stringDictionary = optionalProperties; // ok
stringDictionary = undefinedProperties; // error
probablyArray = numberLiteralKeys; // error
var dict = optionalUndefined; // error
function f() {
var optional = undefined;
var dict = optional; // ok
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts ===
declare let optionalProperties: { k1?: string };
>optionalProperties : Symbol(optionalProperties, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 0, 11))
>k1 : Symbol(k1, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 0, 33))

declare let undefinedProperties: { k1: string | undefined };
>undefinedProperties : Symbol(undefinedProperties, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 1, 11))
>k1 : Symbol(k1, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 1, 34))

declare let stringDictionary: { [key: string]: string };
>stringDictionary : Symbol(stringDictionary, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 3, 11))
>key : Symbol(key, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 3, 33))

stringDictionary = optionalProperties; // ok
>stringDictionary : Symbol(stringDictionary, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 3, 11))
>optionalProperties : Symbol(optionalProperties, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 0, 11))

stringDictionary = undefinedProperties; // error
>stringDictionary : Symbol(stringDictionary, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 3, 11))
>undefinedProperties : Symbol(undefinedProperties, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 1, 11))

declare let probablyArray: { [key: number]: string };
>probablyArray : Symbol(probablyArray, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 7, 11))
>key : Symbol(key, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 7, 30))

declare let numberLiteralKeys: { 1?: string };
>numberLiteralKeys : Symbol(numberLiteralKeys, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 8, 11))
>1 : Symbol(1, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 8, 32))

probablyArray = numberLiteralKeys; // error
>probablyArray : Symbol(probablyArray, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 7, 11))
>numberLiteralKeys : Symbol(numberLiteralKeys, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 8, 11))

declare let optionalUndefined: { k1?: undefined };
>optionalUndefined : Symbol(optionalUndefined, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 11, 11))
>k1 : Symbol(k1, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 11, 32))

let dict: { [key: string]: string } = optionalUndefined; // error
>dict : Symbol(dict, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 12, 3))
>key : Symbol(key, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 12, 13))
>optionalUndefined : Symbol(optionalUndefined, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 11, 11))

function f<T>() {
>f : Symbol(f, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 12, 56))
>T : Symbol(T, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 14, 11))

let optional: { k1?: T } = undefined!;
>optional : Symbol(optional, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 15, 4))
>k1 : Symbol(k1, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 15, 16))
>T : Symbol(T, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 14, 11))
>undefined : Symbol(undefined)

let dict: { [key: string]: T | number } = optional; // ok
>dict : Symbol(dict, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 16, 4))
>key : Symbol(key, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 16, 14))
>T : Symbol(T, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 14, 11))
>optional : Symbol(optional, Decl(optionalPropertyAssignableToStringIndexSignature.ts, 15, 4))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/optionalPropertyAssignableToStringIndexSignature.ts ===
declare let optionalProperties: { k1?: string };
>optionalProperties : { k1?: string | undefined; }
>k1 : string | undefined

declare let undefinedProperties: { k1: string | undefined };
>undefinedProperties : { k1: string | undefined; }
>k1 : string | undefined

declare let stringDictionary: { [key: string]: string };
>stringDictionary : { [key: string]: string; }
>key : string

stringDictionary = optionalProperties; // ok
>stringDictionary = optionalProperties : { k1?: string | undefined; }
>stringDictionary : { [key: string]: string; }
>optionalProperties : { k1?: string | undefined; }

stringDictionary = undefinedProperties; // error
>stringDictionary = undefinedProperties : { k1: string | undefined; }
>stringDictionary : { [key: string]: string; }
>undefinedProperties : { k1: string | undefined; }

declare let probablyArray: { [key: number]: string };
>probablyArray : { [key: number]: string; }
>key : number

declare let numberLiteralKeys: { 1?: string };
>numberLiteralKeys : { 1?: string | undefined; }
>1 : string | undefined

probablyArray = numberLiteralKeys; // error
>probablyArray = numberLiteralKeys : { 1?: string | undefined; }
>probablyArray : { [key: number]: string; }
>numberLiteralKeys : { 1?: string | undefined; }

declare let optionalUndefined: { k1?: undefined };
>optionalUndefined : { k1?: undefined; }
>k1 : undefined

let dict: { [key: string]: string } = optionalUndefined; // error
>dict : { [key: string]: string; }
>key : string
>optionalUndefined : { k1?: undefined; }

function f<T>() {
>f : <T>() => void

let optional: { k1?: T } = undefined!;
>optional : { k1?: T | undefined; }
>k1 : T | undefined
>undefined! : never
>undefined : undefined

let dict: { [key: string]: T | number } = optional; // ok
>dict : { [key: string]: number | T; }
>key : string
>optional : { k1?: T | undefined; }
}

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

declare let optionalProperties: { k1?: string };
declare let undefinedProperties: { k1: string | undefined };

declare let stringDictionary: { [key: string]: string };
stringDictionary = optionalProperties; // ok
stringDictionary = undefinedProperties; // error

declare let probablyArray: { [key: number]: string };
declare let numberLiteralKeys: { 1?: string };
probablyArray = numberLiteralKeys; // error

declare let optionalUndefined: { k1?: undefined };
let dict: { [key: string]: string } = optionalUndefined; // error

function f<T>() {
let optional: { k1?: T } = undefined!;
let dict: { [key: string]: T | number } = optional; // ok
}