Skip to content
Open
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 @@ -20135,7 +20135,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
const flags = SymbolFlags.Property | SymbolFlags.Optional;
const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0));
result.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true);
let propType = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
if (exactOptionalPropertyTypes && !isSetonlyAccessor && !(prop.flags & SymbolFlags.Optional) && containsMissingType(propType)) {
propType = getUnionType([removeMissingOrUndefinedType(propType), undefinedType]);
}
result.links.type = isSetonlyAccessor ? undefinedType : addOptionality(propType, /*isProperty*/ true);
result.declarations = prop.declarations;
result.links.nameType = getSymbolLinks(prop).nameType;
result.links.syntheticOrigin = prop;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
exactOptionalPropertyTypes_spreadTernary.ts(13,1): error TS2375: Type '{ parentId: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'parentId' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
exactOptionalPropertyTypes_spreadTernary.ts(16,1): error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'parentId' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
exactOptionalPropertyTypes_spreadTernary.ts(20,1): error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'parentId' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
exactOptionalPropertyTypes_spreadTernary.ts(24,1): error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'parentId' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.


==== exactOptionalPropertyTypes_spreadTernary.ts (4 errors) ====
// Repro from https://github.com/microsoft/TypeScript/issues/63240
// exactOptionalPropertyTypes should flag optional-property values spread via ternary

type Foo = {
parentId?: string;
};

declare const requestBody: Foo;
declare const cond: boolean;
let target: Foo;

// Direct assignment — correctly flagged
target = { parentId: requestBody.parentId }; // Error
~~~~~~
!!! error TS2375: Type '{ parentId: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'parentId' are incompatible.
!!! error TS2375: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2375: Type 'undefined' is not assignable to type 'string'.

// Spread + ternary with optional property access — must also be flagged
target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // Error
~~~~~~
!!! error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'parentId' are incompatible.
!!! error TS2375: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2375: Type 'undefined' is not assignable to type 'string'.

// Destructured optional property — must also be flagged
const { parentId } = requestBody;
target = { ...(cond ? { parentId } : {}) }; // Error
~~~~~~
!!! error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'parentId' are incompatible.
!!! error TS2375: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2375: Type 'undefined' is not assignable to type 'string'.

// Explicit `string | undefined` value — must be flagged (was already working)
const parentId2 = '' as string | undefined;
target = { ...(cond ? { parentId: parentId2 } : {}) }; // Error
~~~~~~
!!! error TS2375: Type '{ parentId?: string | undefined; }' is not assignable to type 'Foo' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'parentId' are incompatible.
!!! error TS2375: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2375: Type 'undefined' is not assignable to type 'string'.

// ------------------------------------------------------------------
// Valid cases — must NOT produce errors
// ------------------------------------------------------------------

// Spreading the whole Foo object is fine (its optional properties are already correctly typed)
target = { ...(cond ? requestBody : {}) }; // OK

// A required string value is fine
const parentId3 = 'hello';
target = { ...(cond ? { parentId: parentId3 } : {}) }; // OK

// Spreading an object where the property is narrowed to string
if (cond && requestBody.parentId !== undefined) {
target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // OK — narrowed to string
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//// [tests/cases/compiler/exactOptionalPropertyTypes_spreadTernary.ts] ////

//// [exactOptionalPropertyTypes_spreadTernary.ts]
type Foo = {
parentId?: string;
};

declare const requestBody: Foo;
declare const cond: boolean;
let target: Foo;

// Direct assignment — correctly flagged
target = { parentId: requestBody.parentId }; // Error

// Spread + ternary with optional property access — must also be flagged
target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // Error

// Destructured optional property — must also be flagged
const { parentId } = requestBody;
target = { ...(cond ? { parentId } : {}) }; // Error

// Explicit `string | undefined` value — must be flagged (was already working)
const parentId2 = '' as string | undefined;
target = { ...(cond ? { parentId: parentId2 } : {}) }; // Error

// ------------------------------------------------------------------
// Valid cases — must NOT produce errors
// ------------------------------------------------------------------

// Spreading the whole Foo object is fine (its optional properties are already correctly typed)
target = { ...(cond ? requestBody : {}) }; // OK

// A required string value is fine
const parentId3 = 'hello';
target = { ...(cond ? { parentId: parentId3 } : {}) }; // OK

// Spreading an object where the property is narrowed to string
if (cond && requestBody.parentId !== undefined) {
target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // OK — narrowed to string
}


//// [exactOptionalPropertyTypes_spreadTernary.js]
"use strict";
// Repro from https://github.com/microsoft/TypeScript/issues/63240
// exactOptionalPropertyTypes should flag optional-property values spread via ternary
let target;
// Direct assignment — correctly flagged
target = { parentId: requestBody.parentId }; // Error
// Spread + ternary with optional property access — must also be flagged
target = Object.assign({}, (cond ? { parentId: requestBody.parentId } : {})); // Error
// Destructured optional property — must also be flagged
const { parentId } = requestBody;
target = Object.assign({}, (cond ? { parentId } : {})); // Error
// Explicit `string | undefined` value — must be flagged (was already working)
const parentId2 = '';
target = Object.assign({}, (cond ? { parentId: parentId2 } : {})); // Error
// ------------------------------------------------------------------
// Valid cases — must NOT produce errors
// ------------------------------------------------------------------
// Spreading the whole Foo object is fine (its optional properties are already correctly typed)
target = Object.assign({}, (cond ? requestBody : {})); // OK
// A required string value is fine
const parentId3 = 'hello';
target = Object.assign({}, (cond ? { parentId: parentId3 } : {})); // OK
// Spreading an object where the property is narrowed to string
if (cond && requestBody.parentId !== undefined) {
target = Object.assign({}, (cond ? { parentId: requestBody.parentId } : {})); // OK — narrowed to string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//// [tests/cases/compiler/exactOptionalPropertyTypes_spreadTernary.ts] ////

=== exactOptionalPropertyTypes_spreadTernary.ts ===
// Repro from https://github.com/microsoft/TypeScript/issues/63240
// exactOptionalPropertyTypes should flag optional-property values spread via ternary

type Foo = {
>Foo : Symbol(Foo, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 0, 0))

parentId?: string;
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))

};

declare const requestBody: Foo;
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))
>Foo : Symbol(Foo, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 0, 0))

declare const cond: boolean;
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))

let target: Foo;
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>Foo : Symbol(Foo, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 0, 0))

// Direct assignment — correctly flagged
target = { parentId: requestBody.parentId }; // Error
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 12, 10))
>requestBody.parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))

// Spread + ternary with optional property access — must also be flagged
target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // Error
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 15, 23))
>requestBody.parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))

// Destructured optional property — must also be flagged
const { parentId } = requestBody;
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 18, 7))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))

target = { ...(cond ? { parentId } : {}) }; // Error
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 19, 23))

// Explicit `string | undefined` value — must be flagged (was already working)
const parentId2 = '' as string | undefined;
>parentId2 : Symbol(parentId2, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 22, 5))

target = { ...(cond ? { parentId: parentId2 } : {}) }; // Error
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 23, 23))
>parentId2 : Symbol(parentId2, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 22, 5))

// ------------------------------------------------------------------
// Valid cases — must NOT produce errors
// ------------------------------------------------------------------

// Spreading the whole Foo object is fine (its optional properties are already correctly typed)
target = { ...(cond ? requestBody : {}) }; // OK
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))

// A required string value is fine
const parentId3 = 'hello';
>parentId3 : Symbol(parentId3, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 33, 5))

target = { ...(cond ? { parentId: parentId3 } : {}) }; // OK
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 34, 23))
>parentId3 : Symbol(parentId3, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 33, 5))

// Spreading an object where the property is narrowed to string
if (cond && requestBody.parentId !== undefined) {
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>requestBody.parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
>undefined : Symbol(undefined)

target = { ...(cond ? { parentId: requestBody.parentId } : {}) }; // OK — narrowed to string
>target : Symbol(target, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 9, 3))
>cond : Symbol(cond, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 8, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 38, 27))
>requestBody.parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
>requestBody : Symbol(requestBody, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 7, 13))
>parentId : Symbol(parentId, Decl(exactOptionalPropertyTypes_spreadTernary.ts, 3, 12))
}

Loading