Skip to content
Draft
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: 6 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18533,6 +18533,12 @@ func (c *Checker) getWidenedTypeOfObjectLiteral(t *Type, context *WideningContex
}))
// Retain js literal flag through widening
result.objectFlags |= t.objectFlags & (ObjectFlagsJSLiteral | ObjectFlagsNonInferrableType)
// Remember that this type originates in an object literal so that later subtype checks (e.g. when
// computing a common supertype) don't treat it as though it were a "regular" (non-literal) type that
// requires target's optional properties to be present (see 'requireOptionalProperties' in relater.go).
if isObjectLiteralType(t) {
result.objectFlags |= ObjectFlagsWidenedObjectLiteral
}
// Only cache in child contexts since the root context never widens a particular object literal type more than once
if context != nil && context.parent != nil {
if context.widenedTypes == nil {
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -4261,7 +4261,7 @@ func (r *Relater) propertiesRelatedTo(source *Type, target *Type, reportErrors b
return TernaryFalse
}
}
requireOptionalProperties := (r.relation == r.c.subtypeRelation || r.relation == r.c.strictSubtypeRelation) && !isObjectLiteralType(source) && !r.c.isEmptyArrayLiteralType(source) && !isTupleType(source)
requireOptionalProperties := (r.relation == r.c.subtypeRelation || r.relation == r.c.strictSubtypeRelation) && !isObjectLiteralType(source) && source.objectFlags&ObjectFlagsWidenedObjectLiteral == 0 && !r.c.isEmptyArrayLiteralType(source) && !isTupleType(source)
unmatchedProperty := r.c.getUnmatchedProperty(source, target, requireOptionalProperties, false /*matchDiscriminantProperties*/)
if unmatchedProperty != nil {
if reportErrors && r.c.shouldReportUnmatchedPropertyError(source, target) {
Expand Down
1 change: 1 addition & 0 deletions tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ const (
ObjectFlagsCouldContainTypeVariablesComputed ObjectFlags = 1 << 19 // CouldContainTypeVariables flag has been computed
ObjectFlagsCouldContainTypeVariables ObjectFlags = 1 << 20 // Type could contain a type variable
ObjectFlagsMembersResolved ObjectFlags = 1 << 21 // Members have been resolved
ObjectFlagsWidenedObjectLiteral ObjectFlags = 1 << 31 // Originates in an object literal type that has since been widened

ObjectFlagsClassOrInterface = ObjectFlagsClass | ObjectFlagsInterface
ObjectFlagsRequiresWidening = ObjectFlagsContainsWideningType | ObjectFlagsContainsObjectOrArrayLiteral
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//// [tests/cases/compiler/discriminatedUnionFlatMap.ts] ////

=== discriminatedUnionFlatMap.ts ===
// https://github.com/microsoft/TypeScript/issues/63776

export type InputOp = { op: "add" } | { op: "remove"; value?: Array<unknown> };
>InputOp : Symbol(InputOp, Decl(discriminatedUnionFlatMap.ts, 0, 0))
>op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 2, 23))
>op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 2, 39))
>value : Symbol(value, Decl(discriminatedUnionFlatMap.ts, 2, 53))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 2 more)

export type OutputOp = { op: "add" | "remove" };
>OutputOp : Symbol(OutputOp, Decl(discriminatedUnionFlatMap.ts, 2, 79))
>op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 3, 24))

export function f(operations: InputOp[]): OutputOp[] {
>f : Symbol(f, Decl(discriminatedUnionFlatMap.ts, 3, 48))
>operations : Symbol(operations, Decl(discriminatedUnionFlatMap.ts, 5, 18))
>InputOp : Symbol(InputOp, Decl(discriminatedUnionFlatMap.ts, 0, 0))
>OutputOp : Symbol(OutputOp, Decl(discriminatedUnionFlatMap.ts, 2, 79))

return operations.flatMap((operation) => {
>operations.flatMap : Symbol(Array.flatMap, Decl(lib.es2019.array.d.ts, --, --))
>operations : Symbol(operations, Decl(discriminatedUnionFlatMap.ts, 5, 18))
>flatMap : Symbol(Array.flatMap, Decl(lib.es2019.array.d.ts, --, --))
>operation : Symbol(operation, Decl(discriminatedUnionFlatMap.ts, 6, 31))

if (operation.op === "remove" && operation.value) {
>operation.op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 2, 23), Decl(discriminatedUnionFlatMap.ts, 2, 39))
>operation : Symbol(operation, Decl(discriminatedUnionFlatMap.ts, 6, 31))
>op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 2, 23), Decl(discriminatedUnionFlatMap.ts, 2, 39))
>operation.value : Symbol(value, Decl(discriminatedUnionFlatMap.ts, 2, 53))
>operation : Symbol(operation, Decl(discriminatedUnionFlatMap.ts, 6, 31))
>value : Symbol(value, Decl(discriminatedUnionFlatMap.ts, 2, 53))

return [].map(() => ({ op: "remove" }));
>[].map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>op : Symbol(op, Decl(discriminatedUnionFlatMap.ts, 8, 34))

} else {
return [operation];
>operation : Symbol(operation, Decl(discriminatedUnionFlatMap.ts, 6, 31))
}
});
}

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

=== discriminatedUnionFlatMap.ts ===
// https://github.com/microsoft/TypeScript/issues/63776

export type InputOp = { op: "add" } | { op: "remove"; value?: Array<unknown> };
>InputOp : InputOp
>op : "add"
>op : "remove"
>value : unknown[] | undefined

export type OutputOp = { op: "add" | "remove" };
>OutputOp : OutputOp
>op : "add" | "remove"

export function f(operations: InputOp[]): OutputOp[] {
>f : (operations: InputOp[]) => OutputOp[]
>operations : InputOp[]

return operations.flatMap((operation) => {
>operations.flatMap((operation) => { if (operation.op === "remove" && operation.value) { return [].map(() => ({ op: "remove" })); } else { return [operation]; } }) : InputOp[]
>operations.flatMap : <U, This = undefined>(callback: (this: This, value: InputOp, index: number, array: InputOp[]) => U | readonly U[], thisArg?: This | undefined) => U[]
>operations : InputOp[]
>flatMap : <U, This = undefined>(callback: (this: This, value: InputOp, index: number, array: InputOp[]) => U | readonly U[], thisArg?: This | undefined) => U[]
>(operation) => { if (operation.op === "remove" && operation.value) { return [].map(() => ({ op: "remove" })); } else { return [operation]; } } : (this: undefined, operation: InputOp) => InputOp[]
>operation : InputOp

if (operation.op === "remove" && operation.value) {
>operation.op === "remove" && operation.value : false | unknown[] | undefined
>operation.op === "remove" : boolean
>operation.op : "add" | "remove"
>operation : InputOp
>op : "add" | "remove"
>"remove" : "remove"
>operation.value : unknown[] | undefined
>operation : { op: "remove"; value?: Array<unknown>; }
>value : unknown[] | undefined

return [].map(() => ({ op: "remove" }));
>[].map(() => ({ op: "remove" })) : { op: "remove"; }[]
>[].map : <U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[]
>[] : never[]
>map : <U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[]
>() => ({ op: "remove" }) : () => { op: "remove"; }
>({ op: "remove" }) : { op: "remove"; }
>{ op: "remove" } : { op: "remove"; }
>op : "remove"
>"remove" : "remove"

} else {
return [operation];
>[operation] : InputOp[]
>operation : InputOp
}
});
}

18 changes: 18 additions & 0 deletions tsc/testdata/tests/cases/compiler/discriminatedUnionFlatMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @strict: true
// @noEmit: true
// @lib: es2019

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

export type InputOp = { op: "add" } | { op: "remove"; value?: Array<unknown> };
export type OutputOp = { op: "add" | "remove" };

export function f(operations: InputOp[]): OutputOp[] {
return operations.flatMap((operation) => {
if (operation.op === "remove" && operation.value) {
return [].map(() => ({ op: "remove" }));
} else {
return [operation];
}
});
}