Skip to content

Commit

Permalink
Write non-missing undefined on mapped type results into output (#59208)
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham authored Jul 10, 2024
1 parent 2fd707d commit ed17a89
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 26 deletions.
16 changes: 12 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6113,11 +6113,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
) {
const originalType = type;
if (addUndefined) {
type = getOptionalType(type);
type = getOptionalType(type, !isParameter(host));
}
const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host);
if (clone) {
if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
// explicitly add `| undefined` if it's missing from the input type nodes and the type contains `undefined` (and not the missing type)
if (addUndefined && containsNonMissingUndefinedType(type) && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
}
return clone;
Expand Down Expand Up @@ -8252,7 +8253,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed
*/
function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) {
const addUndefined = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration);
const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration);
const enclosingDeclaration = context.enclosingDeclaration;
const oldFlags = context.flags;
if (declaration && hasInferredType(declaration) && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) {
Expand All @@ -8266,6 +8267,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));
const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined);
if (result) {
context.flags = oldFlags;
Expand All @@ -8283,7 +8286,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const decl = declaration ?? symbol.valueDeclaration ?? symbol.declarations?.[0];
const expr = decl && isDeclarationWithPossibleInnerTypeNodeReuse(decl) ? getPossibleTypeNodeReuseExpression(decl) : undefined;

const result = expressionOrTypeToTypeNode(context, expr, type, addUndefined);
const result = expressionOrTypeToTypeNode(context, expr, type, addUndefinedForParameter);
context.flags = oldFlags;
return result;
}
Expand Down Expand Up @@ -21425,6 +21428,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined);
}

function containsNonMissingUndefinedType(type: Type) {
const candidate = type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type;
return !!(candidate.flags & TypeFlags.Undefined) && candidate !== missingType;
}

function isStringIndexSignatureOnlyType(type: Type): boolean {
return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) ||
type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//// [tests/cases/compiler/declarationEmitExactOptionalPropertyTypesNodeNotReused.ts] ////

//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.ts]
type InexactOptionals<A> = {
[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
? A[K] | undefined
: A[K];
} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
};

type In = {
foo?: string;
bar: number;
baz: undefined;
}

type Out = InexactOptionals<In>

const foo = <A = {}>() => (x: Out & A) => null

export const baddts = foo()


//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.baddts = void 0;
var foo = function () { return function (x) { return null; }; };
exports.baddts = foo();


//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.d.ts]
export declare const baddts: (x: {
foo?: string | undefined;
baz?: undefined;
} & {
bar: number;
}) => null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//// [tests/cases/compiler/declarationEmitExactOptionalPropertyTypesNodeNotReused.ts] ////

=== declarationEmitExactOptionalPropertyTypesNodeNotReused.ts ===
type InexactOptionals<A> = {
>InexactOptionals : Symbol(InexactOptionals, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 0))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))

[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

? A[K] | undefined
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

: A[K];
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))

};

type In = {
>In : Symbol(In, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 6, 2))

foo?: string;
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 8, 11))

bar: number;
>bar : Symbol(bar, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 9, 17))

baz: undefined;
>baz : Symbol(baz, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 10, 16))
}

type Out = InexactOptionals<In>
>Out : Symbol(Out, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 12, 1))
>InexactOptionals : Symbol(InexactOptionals, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 0))
>In : Symbol(In, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 6, 2))

const foo = <A = {}>() => (x: Out & A) => null
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 13))
>x : Symbol(x, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 27))
>Out : Symbol(Out, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 12, 1))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 13))

export const baddts = foo()
>baddts : Symbol(baddts, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 18, 12))
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 5))

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

=== declarationEmitExactOptionalPropertyTypesNodeNotReused.ts ===
type InexactOptionals<A> = {
>InexactOptionals : InexactOptionals<A>
> : ^^^^^^^^^^^^^^^^^^^

[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
? A[K] | undefined
: A[K];
} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
};

type In = {
>In : In
> : ^^

foo?: string;
>foo : string | undefined
> : ^^^^^^^^^^^^^^^^^^

bar: number;
>bar : number
> : ^^^^^^

baz: undefined;
>baz : undefined
> : ^^^^^^^^^
}

type Out = InexactOptionals<In>
>Out : Out
> : ^^^

const foo = <A = {}>() => (x: Out & A) => null
>foo : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^
><A = {}>() => (x: Out & A) => null : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^
>(x: Out & A) => null : (x: Out & A) => null
> : ^ ^^ ^^^^^^^^^
>x : { foo?: string | undefined; baz?: undefined; } & { bar: number; } & A
> : ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^

export const baddts = foo()
>baddts : (x: { foo?: string | undefined; baz?: undefined; } & { bar: number; }) => null
> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^
>foo() : (x: { foo?: string | undefined; baz?: undefined; } & { bar: number; }) => null
> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^
>foo : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^

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

//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.ts]
type InexactOptionals<A> = {
[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
? A[K] | undefined
: A[K];
} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
};

type In = {
foo?: string;
bar: number;
baz: undefined;
}

type Out = InexactOptionals<In>

const foo = <A = {}>() => (x: Out & A) => null

export const baddts = foo()


//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.baddts = void 0;
var foo = function () { return function (x) { return null; }; };
exports.baddts = foo();


//// [declarationEmitExactOptionalPropertyTypesNodeNotReused.d.ts]
export declare const baddts: (x: {
foo?: string | undefined;
baz?: undefined;
} & {
bar: number;
}) => null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//// [tests/cases/compiler/declarationEmitExactOptionalPropertyTypesNodeNotReused.ts] ////

=== declarationEmitExactOptionalPropertyTypesNodeNotReused.ts ===
type InexactOptionals<A> = {
>InexactOptionals : Symbol(InexactOptionals, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 0))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))

[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

? A[K] | undefined
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

: A[K];
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 1, 5))

} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 22))
>K : Symbol(K, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 5, 5))

};

type In = {
>In : Symbol(In, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 6, 2))

foo?: string;
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 8, 11))

bar: number;
>bar : Symbol(bar, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 9, 17))

baz: undefined;
>baz : Symbol(baz, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 10, 16))
}

type Out = InexactOptionals<In>
>Out : Symbol(Out, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 12, 1))
>InexactOptionals : Symbol(InexactOptionals, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 0, 0))
>In : Symbol(In, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 6, 2))

const foo = <A = {}>() => (x: Out & A) => null
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 5))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 13))
>x : Symbol(x, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 27))
>Out : Symbol(Out, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 12, 1))
>A : Symbol(A, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 13))

export const baddts = foo()
>baddts : Symbol(baddts, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 18, 12))
>foo : Symbol(foo, Decl(declarationEmitExactOptionalPropertyTypesNodeNotReused.ts, 16, 5))

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

=== declarationEmitExactOptionalPropertyTypesNodeNotReused.ts ===
type InexactOptionals<A> = {
>InexactOptionals : InexactOptionals<A>
> : ^^^^^^^^^^^^^^^^^^^

[K in keyof A as undefined extends A[K] ? K : never]?: undefined extends A[K]
? A[K] | undefined
: A[K];
} & {
[K in keyof A as undefined extends A[K] ? never : K]: A[K];
};

type In = {
>In : In
> : ^^

foo?: string;
>foo : string | undefined
> : ^^^^^^^^^^^^^^^^^^

bar: number;
>bar : number
> : ^^^^^^

baz: undefined;
>baz : undefined
> : ^^^^^^^^^
}

type Out = InexactOptionals<In>
>Out : Out
> : ^^^

const foo = <A = {}>() => (x: Out & A) => null
>foo : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^
><A = {}>() => (x: Out & A) => null : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^
>(x: Out & A) => null : (x: Out & A) => null
> : ^ ^^ ^^^^^^^^^
>x : { foo?: string | undefined; baz?: undefined; } & { bar: number; } & A
> : ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^

export const baddts = foo()
>baddts : (x: { foo?: string | undefined; baz?: undefined; } & { bar: number; }) => null
> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^
>foo() : (x: { foo?: string | undefined; baz?: undefined; } & { bar: number; }) => null
> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^
>foo : <A = {}>() => (x: Out & A) => null
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^

Loading

0 comments on commit ed17a89

Please sign in to comment.