Skip to content
Open
12 changes: 11 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6249,9 +6249,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);
return !!(symbol.flags & SymbolFlags.Property && symbol.flags & SymbolFlags.Optional && isOptionalDeclaration(declaration) && (symbol as MappedSymbol).links?.mappedType && containsNonMissingUndefinedType(type));
if (!containsNonMissingUndefinedType(type)) {
return false;
}
const declaredType = getEffectiveTypeAnnotationNode(declaration);
return !!(declaredType && !containsUndefinedType(getTypeFromTypeNodeWithoutContext(declaredType)));
case SyntaxKind.Parameter:
case SyntaxKind.JSDocParameterTag:
return requiresAddingImplicitUndefined(declaration, enclosingDeclaration);
Expand Down
29 changes: 29 additions & 0 deletions tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference path="./fourslash.ts"/>

// @strict: true
// @exactOptionalPropertyTypes: true

// https://github.com/microsoft/TypeScript/issues/59948

//// type OptionalToUnionWithUndefined<T> = {
//// [K in keyof T]: T extends Record<K, T[K]> ? T[K] : T[K] | undefined;
//// };
////
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;

verify.quickInfoAt("1", `type Intermidiate = {
Comment on lines +12 to +18
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +18
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +18
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
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;
}`);
26 changes: 26 additions & 0 deletions tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// <reference path="./fourslash.ts"/>

// @strict: true

//// type OptionalToUnionWithUndefined<T> = {
//// [K in keyof T]: T extends Record<K, T[K]> ? T[K] : T[K] | undefined;
//// };
////
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;

verify.quickInfoAt("1", `type Intermidiate = {
Comment on lines +9 to +15
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

The type name 'Intermidiate' is misspelled. It should be 'Intermediate'.

Suggested change
//// type Intermidiate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermidiate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermidiate = {
//// type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
//// type Literal/*2*/ = { a?: string | undefined };
////
//// type Res1/*3*/ = Required<Intermediate>;
//// type Res2/*4*/ = Required<Literal>;
verify.quickInfoAt("1", `type Intermediate = {

Copilot uses AI. Check for mistakes.
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;
}`);
13 changes: 13 additions & 0 deletions tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path="./fourslash.ts"/>

// https://github.com/microsoft/TypeScript/issues/60411

// @strict: true

//// type UnsetUndefinedToOblivion<T> = { [P in keyof T]-?: T[P] | undefined };
//// type SetUndefined<T> = { [P in keyof T]: T[P] | undefined };
//// type TheWhat/**/ = SetUndefined<UnsetUndefinedToOblivion<{ a?: 1 }>>;

verify.quickInfoAt("", `type TheWhat = {
a: 1 | undefined;
}`);
11 changes: 11 additions & 0 deletions tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @strict: true

//// type A/*1*/ = { [K in keyof { a?: string }]-?: string }
//// type B/*2*/ = { [K in keyof A]: string | undefined }

verify.quickInfoAt("1", `type A = {
a: string;
}`);
verify.quickInfoAt("2", `type B = {
a: string | undefined;
}`);
23 changes: 23 additions & 0 deletions tests/cases/fourslash/quickInfoMappedPropertyUnionUndefined5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @strict: true

// https://github.com/microsoft/TypeScript/issues/62325

//// type RequiredKeys<T extends object> = {
//// [K in keyof Required<T>]: T[K];
//// };
////
//// type Foo = {
//// a?: string;
//// b?: number;
//// c: string;
//// d: boolean | undefined;
//// };
////
//// type Bar/*1*/ = RequiredKeys<Foo>;

verify.quickInfoAt("1", `type Bar = {
a: string | undefined;
b: number | undefined;
c: string;
d: boolean | undefined;
}`);
Loading