From 93bd2fb12a1df83d966c0f466e3a9e755f67f711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 13 Sep 2024 14:10:56 +0200 Subject: [PATCH 1/6] Fixed an issue with mapped property symbol not displaying added `| undefined` when its origin symbol was optional --- src/compiler/checker.ts | 4 +-- .../quickInfoMappedPropertyUnionUndefined1.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4d4f1bf69a808..068e35de5b64c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8378,8 +8378,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { // try to reuse the existing annotation const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - // explicitly add `| undefined` to optional mapped properties whose type contains `undefined` (and not `missing`) - const addUndefined = addUndefinedForParameter || !!(symbol.flags & SymbolFlags.Property && symbol.flags & SymbolFlags.Optional && isOptionalDeclaration(declWithExistingAnnotation) && (symbol as MappedSymbol).links?.mappedType && containsNonMissingUndefinedType(type)); + // explicitly add `| undefined` to mapped properties whose type contains `undefined` (and not `missing`) + const addUndefined = addUndefinedForParameter || !!(symbol.flags & SymbolFlags.Property && (symbol as MappedSymbol).links?.mappedType && containsNonMissingUndefinedType(type)); const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined); if (result) { restoreFlags(); diff --git a/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined1.ts b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined1.ts new file mode 100644 index 0000000000000..7e739e9531e83 --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined1.ts @@ -0,0 +1,29 @@ +/// + +// @strict: true +// @exactOptionalPropertyTypes: true + +// https://github.com/microsoft/TypeScript/issues/59948 + +//// type OptionalToUnionWithUndefined = { +//// [K in keyof T]: T extends Record ? T[K] : T[K] | undefined; +//// }; +//// +//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>; +//// type Literal/*2*/ = { a?: string | undefined }; +//// +//// type Res1/*3*/ = Required; +//// type Res2/*4*/ = Required; + +verify.quickInfoAt("1", `type Intermidiate = { + a?: string | undefined; +}`); +verify.quickInfoAt("2", `type Literal = { + a?: string | undefined; +}`); +verify.quickInfoAt("3", `type Res1 = { + a: string | undefined; +}`); +verify.quickInfoAt("4", `type Res2 = { + a: string | undefined; +}`); From 73be18ffb1cbc114845b18b8304f8d7fddec4a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 13 Sep 2024 14:17:49 +0200 Subject: [PATCH 2/6] add an extra test case --- .../quickInfoMappedPropertyUnionUndefined2.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined2.ts diff --git a/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined2.ts b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined2.ts new file mode 100644 index 0000000000000..a5a68ba748dee --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined2.ts @@ -0,0 +1,26 @@ +/// + +// @strict: true + +//// type OptionalToUnionWithUndefined = { +//// [K in keyof T]: T extends Record ? T[K] : T[K] | undefined; +//// }; +//// +//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>; +//// type Literal/*2*/ = { a?: string | undefined }; +//// +//// type Res1/*3*/ = Required; +//// type Res2/*4*/ = Required; + +verify.quickInfoAt("1", `type Intermidiate = { + a?: string | undefined; +}`); +verify.quickInfoAt("2", `type Literal = { + a?: string | undefined; +}`); +verify.quickInfoAt("3", `type Res1 = { + a: string; +}`); +verify.quickInfoAt("4", `type Res2 = { + a: string; +}`); From 896e0eadc487bcbcdbe01f7b7daaea23635961ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 4 Nov 2024 19:28:10 +0100 Subject: [PATCH 3/6] add an extra test case --- .../quickInfoMappedPropertyUnionUndefined3.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined3.ts diff --git a/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined3.ts b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined3.ts new file mode 100644 index 0000000000000..0a23a8053eb37 --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined3.ts @@ -0,0 +1,13 @@ +/// + +// https://github.com/microsoft/TypeScript/issues/60411 + +// @strict: true + +//// type UnsetUndefinedToOblivion = { [P in keyof T]-?: T[P] | undefined }; +//// type SetUndefined = { [P in keyof T]: T[P] | undefined }; +//// type TheWhat/**/ = SetUndefined>; + +verify.quickInfoAt("", `type TheWhat = { + a: 1 | undefined; +}`); From 28a0160e6fcaac1395f5465be93b164635a45f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 20 Nov 2024 00:14:04 +0100 Subject: [PATCH 4/6] fix the fix --- src/compiler/checker.ts | 6 +++++- .../reference/mappedTypeIndexedAccessConstraint.types | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eb6ba5365b1c5..e9f6feeb8cd66 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6105,8 +6105,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.PropertySignature: case SyntaxKind.JSDocPropertyTag: symbol ??= getSymbolOfDeclaration(declaration); + if (!(symbol.flags & SymbolFlags.Property) || !(symbol as MappedSymbol).links?.mappedType) { + return false; + } const type = getTypeOfSymbol(symbol); - return !!(symbol.flags & SymbolFlags.Property && (symbol as MappedSymbol).links?.mappedType && containsNonMissingUndefinedType(type)); + const declaredType = getEffectiveTypeAnnotationNode(declaration); + return containsNonMissingUndefinedType(type) && !!declaredType && !containsUndefinedType(getTypeFromTypeNodeWithoutContext(declaredType)); case SyntaxKind.Parameter: case SyntaxKind.JSDocParameterTag: return requiresAddingImplicitUndefined(declaration, enclosingDeclaration); diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types index 7e40323959a29..7fb22d96e4459 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -114,7 +114,7 @@ type Obj = { const mapped: { [K in keyof Partial]: Obj[K] } = {}; >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ >{} : {} > : ^^ @@ -124,7 +124,7 @@ const resolveMapped = (key: K) => mapped[key].toS >(key: K) => mapped[key].toString() : (key: K) => string > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^ >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ >key : K > : ^ >mapped[key].toString() : string @@ -134,7 +134,7 @@ const resolveMapped = (key: K) => mapped[key].toS >mapped[key] : 1 | 2 | undefined > : ^^^^^^^^^^^^^^^^^ >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ >key : K > : ^ >toString : (radix?: number) => string From 6148904495cd7795e79fb9784554054fdd4dcd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 20 Nov 2024 09:44:12 +0100 Subject: [PATCH 5/6] tweak logic --- src/compiler/checker.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e9f6feeb8cd66..f613582e3c82a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6104,13 +6104,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.JSDocPropertyTag: + if (!isOptionalDeclaration(declaration)) { + return false; + } symbol ??= getSymbolOfDeclaration(declaration); if (!(symbol.flags & SymbolFlags.Property) || !(symbol as MappedSymbol).links?.mappedType) { return false; } const type = getTypeOfSymbol(symbol); - const declaredType = getEffectiveTypeAnnotationNode(declaration); - return containsNonMissingUndefinedType(type) && !!declaredType && !containsUndefinedType(getTypeFromTypeNodeWithoutContext(declaredType)); + if (!containsNonMissingUndefinedType(type)) { + return false; + } + const declaredType = getEffectiveTypeAnnotationNode(declaration) + return !!(declaredType && !containsUndefinedType(getTypeFromTypeNodeWithoutContext(declaredType))); case SyntaxKind.Parameter: case SyntaxKind.JSDocParameterTag: return requiresAddingImplicitUndefined(declaration, enclosingDeclaration); From 267bbe86cd94b59e4e350cab8228fa9ac823c730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 20 Nov 2024 10:18:23 +0100 Subject: [PATCH 6/6] update baseline --- src/compiler/checker.ts | 2 +- .../reference/mappedTypeIndexedAccessConstraint.types | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f613582e3c82a..c851b3afcd05c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6115,7 +6115,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!containsNonMissingUndefinedType(type)) { return false; } - const declaredType = getEffectiveTypeAnnotationNode(declaration) + const declaredType = getEffectiveTypeAnnotationNode(declaration); return !!(declaredType && !containsUndefinedType(getTypeFromTypeNodeWithoutContext(declaredType))); case SyntaxKind.Parameter: case SyntaxKind.JSDocParameterTag: diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types index 7fb22d96e4459..7e40323959a29 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -114,7 +114,7 @@ type Obj = { const mapped: { [K in keyof Partial]: Obj[K] } = {}; >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >{} : {} > : ^^ @@ -124,7 +124,7 @@ const resolveMapped = (key: K) => mapped[key].toS >(key: K) => mapped[key].toString() : (key: K) => string > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^ >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K > : ^ >mapped[key].toString() : string @@ -134,7 +134,7 @@ const resolveMapped = (key: K) => mapped[key].toS >mapped[key] : 1 | 2 | undefined > : ^^^^^^^^^^^^^^^^^ >mapped : { a?: 1 | undefined; b?: 2 | undefined; } -> : ^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K > : ^ >toString : (radix?: number) => string