mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Declaration emit for inlined mapped types preserves modifier-preserving behavior (#55054)
This commit is contained in:
parent
34c144d493
commit
970733653c
@ -6641,6 +6641,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined;
|
||||
let appropriateConstraintTypeNode: TypeNode;
|
||||
let newTypeVariable: TypeReferenceNode | undefined;
|
||||
// If the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do
|
||||
const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type)
|
||||
&& !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown)
|
||||
&& context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams
|
||||
&& !(getConstraintTypeFromMappedType(type).flags & TypeFlags.TypeParameter && getConstraintOfTypeParameter(getConstraintTypeFromMappedType(type))?.flags! & TypeFlags.Index);
|
||||
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
|
||||
// We have a { [P in keyof T]: X }
|
||||
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
|
||||
@ -6651,6 +6656,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
|
||||
}
|
||||
else if (needsModifierPreservingWrapper) {
|
||||
// So, step 1: new type variable
|
||||
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
|
||||
const name = typeParameterToName(newParam, context);
|
||||
newTypeVariable = factory.createTypeReferenceNode(name);
|
||||
// step 2: make that new type variable itself the constraint node, making the mapped type `{[K in T_1]: Template}`
|
||||
appropriateConstraintTypeNode = newTypeVariable;
|
||||
}
|
||||
else {
|
||||
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
|
||||
}
|
||||
@ -6672,6 +6685,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
|
||||
);
|
||||
}
|
||||
else if (needsModifierPreservingWrapper) {
|
||||
// and step 3: once the mapped type is reconstructed, create a `ConstraintType extends infer T_1 extends keyof ModifiersType ? {[K in T_1]: Template} : never`
|
||||
// subtly different from the `keyof` constraint case, by including the `keyof` constraint on the `infer` type parameter, it doesn't rely on the constraint type being itself
|
||||
// constrained to a `keyof` type to preserve its modifier-preserving behavior. This is all basically because we preserve modifiers for a wider set of mapped types than
|
||||
// just homomorphic ones.
|
||||
return factory.createConditionalTypeNode(
|
||||
typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context),
|
||||
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)))),
|
||||
result,
|
||||
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
//// [tests/cases/compiler/inlineMappedTypeModifierDeclarationEmit.ts] ////
|
||||
|
||||
//// [index.ts]
|
||||
import { test1, test2 } from "./other";
|
||||
|
||||
export function wrappedTest1<T, K extends string>(obj: T, k: K) {
|
||||
return test1(obj, k);
|
||||
}
|
||||
|
||||
export function wrappedTest2<T, K extends string>(obj: T, k: K) {
|
||||
return test2(obj, k);
|
||||
}
|
||||
|
||||
export type Obj = {
|
||||
a: number;
|
||||
readonly foo: string;
|
||||
};
|
||||
|
||||
export const processedInternally1 = wrappedTest1({} as Obj, "a");
|
||||
export const processedInternally2 = wrappedTest2({} as Obj, "a");
|
||||
//// [other.ts]
|
||||
// how Omit from lib is defined
|
||||
type OmitReal<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
// what we see when we hover it
|
||||
type OmitUnveiled<T, K extends string | number | symbol> = {
|
||||
[P in Exclude<keyof T, K>]: T[P];
|
||||
};
|
||||
|
||||
export function test1<T, K extends string>(obj: T, k: K): OmitReal<T, K> {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
export function test2<T, K extends string>(obj: T, k: K): OmitUnveiled<T, K> {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
//// [other.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.test2 = exports.test1 = void 0;
|
||||
function test1(obj, k) {
|
||||
return {};
|
||||
}
|
||||
exports.test1 = test1;
|
||||
function test2(obj, k) {
|
||||
return {};
|
||||
}
|
||||
exports.test2 = test2;
|
||||
//// [index.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.processedInternally2 = exports.processedInternally1 = exports.wrappedTest2 = exports.wrappedTest1 = void 0;
|
||||
var other_1 = require("./other");
|
||||
function wrappedTest1(obj, k) {
|
||||
return (0, other_1.test1)(obj, k);
|
||||
}
|
||||
exports.wrappedTest1 = wrappedTest1;
|
||||
function wrappedTest2(obj, k) {
|
||||
return (0, other_1.test2)(obj, k);
|
||||
}
|
||||
exports.wrappedTest2 = wrappedTest2;
|
||||
exports.processedInternally1 = wrappedTest1({}, "a");
|
||||
exports.processedInternally2 = wrappedTest2({}, "a");
|
||||
|
||||
|
||||
//// [other.d.ts]
|
||||
type OmitReal<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
type OmitUnveiled<T, K extends string | number | symbol> = {
|
||||
[P in Exclude<keyof T, K>]: T[P];
|
||||
};
|
||||
export declare function test1<T, K extends string>(obj: T, k: K): OmitReal<T, K>;
|
||||
export declare function test2<T, K extends string>(obj: T, k: K): OmitUnveiled<T, K>;
|
||||
export {};
|
||||
//// [index.d.ts]
|
||||
export declare function wrappedTest1<T, K extends string>(obj: T, k: K): Exclude<keyof T, K> extends infer T_1 extends keyof T ? { [P in T_1]: T[P]; } : never;
|
||||
export declare function wrappedTest2<T, K extends string>(obj: T, k: K): { [P in Exclude<keyof T, K>]: T[P]; };
|
||||
export type Obj = {
|
||||
a: number;
|
||||
readonly foo: string;
|
||||
};
|
||||
export declare const processedInternally1: {
|
||||
readonly foo: string;
|
||||
};
|
||||
export declare const processedInternally2: {
|
||||
foo: string;
|
||||
};
|
||||
@ -0,0 +1,115 @@
|
||||
//// [tests/cases/compiler/inlineMappedTypeModifierDeclarationEmit.ts] ////
|
||||
|
||||
=== index.ts ===
|
||||
import { test1, test2 } from "./other";
|
||||
>test1 : Symbol(test1, Decl(index.ts, 0, 8))
|
||||
>test2 : Symbol(test2, Decl(index.ts, 0, 15))
|
||||
|
||||
export function wrappedTest1<T, K extends string>(obj: T, k: K) {
|
||||
>wrappedTest1 : Symbol(wrappedTest1, Decl(index.ts, 0, 39))
|
||||
>T : Symbol(T, Decl(index.ts, 2, 29))
|
||||
>K : Symbol(K, Decl(index.ts, 2, 31))
|
||||
>obj : Symbol(obj, Decl(index.ts, 2, 50))
|
||||
>T : Symbol(T, Decl(index.ts, 2, 29))
|
||||
>k : Symbol(k, Decl(index.ts, 2, 57))
|
||||
>K : Symbol(K, Decl(index.ts, 2, 31))
|
||||
|
||||
return test1(obj, k);
|
||||
>test1 : Symbol(test1, Decl(index.ts, 0, 8))
|
||||
>obj : Symbol(obj, Decl(index.ts, 2, 50))
|
||||
>k : Symbol(k, Decl(index.ts, 2, 57))
|
||||
}
|
||||
|
||||
export function wrappedTest2<T, K extends string>(obj: T, k: K) {
|
||||
>wrappedTest2 : Symbol(wrappedTest2, Decl(index.ts, 4, 1))
|
||||
>T : Symbol(T, Decl(index.ts, 6, 29))
|
||||
>K : Symbol(K, Decl(index.ts, 6, 31))
|
||||
>obj : Symbol(obj, Decl(index.ts, 6, 50))
|
||||
>T : Symbol(T, Decl(index.ts, 6, 29))
|
||||
>k : Symbol(k, Decl(index.ts, 6, 57))
|
||||
>K : Symbol(K, Decl(index.ts, 6, 31))
|
||||
|
||||
return test2(obj, k);
|
||||
>test2 : Symbol(test2, Decl(index.ts, 0, 15))
|
||||
>obj : Symbol(obj, Decl(index.ts, 6, 50))
|
||||
>k : Symbol(k, Decl(index.ts, 6, 57))
|
||||
}
|
||||
|
||||
export type Obj = {
|
||||
>Obj : Symbol(Obj, Decl(index.ts, 8, 1))
|
||||
|
||||
a: number;
|
||||
>a : Symbol(a, Decl(index.ts, 10, 19))
|
||||
|
||||
readonly foo: string;
|
||||
>foo : Symbol(foo, Decl(index.ts, 11, 12))
|
||||
|
||||
};
|
||||
|
||||
export const processedInternally1 = wrappedTest1({} as Obj, "a");
|
||||
>processedInternally1 : Symbol(processedInternally1, Decl(index.ts, 15, 12))
|
||||
>wrappedTest1 : Symbol(wrappedTest1, Decl(index.ts, 0, 39))
|
||||
>Obj : Symbol(Obj, Decl(index.ts, 8, 1))
|
||||
|
||||
export const processedInternally2 = wrappedTest2({} as Obj, "a");
|
||||
>processedInternally2 : Symbol(processedInternally2, Decl(index.ts, 16, 12))
|
||||
>wrappedTest2 : Symbol(wrappedTest2, Decl(index.ts, 4, 1))
|
||||
>Obj : Symbol(Obj, Decl(index.ts, 8, 1))
|
||||
|
||||
=== other.ts ===
|
||||
// how Omit from lib is defined
|
||||
type OmitReal<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
>OmitReal : Symbol(OmitReal, Decl(other.ts, 0, 0))
|
||||
>T : Symbol(T, Decl(other.ts, 1, 14))
|
||||
>K : Symbol(K, Decl(other.ts, 1, 16))
|
||||
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
|
||||
>T : Symbol(T, Decl(other.ts, 1, 14))
|
||||
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
|
||||
>T : Symbol(T, Decl(other.ts, 1, 14))
|
||||
>K : Symbol(K, Decl(other.ts, 1, 16))
|
||||
|
||||
// what we see when we hover it
|
||||
type OmitUnveiled<T, K extends string | number | symbol> = {
|
||||
>OmitUnveiled : Symbol(OmitUnveiled, Decl(other.ts, 1, 69))
|
||||
>T : Symbol(T, Decl(other.ts, 3, 18))
|
||||
>K : Symbol(K, Decl(other.ts, 3, 20))
|
||||
|
||||
[P in Exclude<keyof T, K>]: T[P];
|
||||
>P : Symbol(P, Decl(other.ts, 4, 3))
|
||||
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
|
||||
>T : Symbol(T, Decl(other.ts, 3, 18))
|
||||
>K : Symbol(K, Decl(other.ts, 3, 20))
|
||||
>T : Symbol(T, Decl(other.ts, 3, 18))
|
||||
>P : Symbol(P, Decl(other.ts, 4, 3))
|
||||
|
||||
};
|
||||
|
||||
export function test1<T, K extends string>(obj: T, k: K): OmitReal<T, K> {
|
||||
>test1 : Symbol(test1, Decl(other.ts, 5, 2))
|
||||
>T : Symbol(T, Decl(other.ts, 7, 22))
|
||||
>K : Symbol(K, Decl(other.ts, 7, 24))
|
||||
>obj : Symbol(obj, Decl(other.ts, 7, 43))
|
||||
>T : Symbol(T, Decl(other.ts, 7, 22))
|
||||
>k : Symbol(k, Decl(other.ts, 7, 50))
|
||||
>K : Symbol(K, Decl(other.ts, 7, 24))
|
||||
>OmitReal : Symbol(OmitReal, Decl(other.ts, 0, 0))
|
||||
>T : Symbol(T, Decl(other.ts, 7, 22))
|
||||
>K : Symbol(K, Decl(other.ts, 7, 24))
|
||||
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
export function test2<T, K extends string>(obj: T, k: K): OmitUnveiled<T, K> {
|
||||
>test2 : Symbol(test2, Decl(other.ts, 9, 1))
|
||||
>T : Symbol(T, Decl(other.ts, 11, 22))
|
||||
>K : Symbol(K, Decl(other.ts, 11, 24))
|
||||
>obj : Symbol(obj, Decl(other.ts, 11, 43))
|
||||
>T : Symbol(T, Decl(other.ts, 11, 22))
|
||||
>k : Symbol(k, Decl(other.ts, 11, 50))
|
||||
>K : Symbol(K, Decl(other.ts, 11, 24))
|
||||
>OmitUnveiled : Symbol(OmitUnveiled, Decl(other.ts, 1, 69))
|
||||
>T : Symbol(T, Decl(other.ts, 11, 22))
|
||||
>K : Symbol(K, Decl(other.ts, 11, 24))
|
||||
|
||||
return {} as any;
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
//// [tests/cases/compiler/inlineMappedTypeModifierDeclarationEmit.ts] ////
|
||||
|
||||
=== index.ts ===
|
||||
import { test1, test2 } from "./other";
|
||||
>test1 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>test2 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
|
||||
export function wrappedTest1<T, K extends string>(obj: T, k: K) {
|
||||
>wrappedTest1 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>obj : T
|
||||
>k : K
|
||||
|
||||
return test1(obj, k);
|
||||
>test1(obj, k) : { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>test1 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>obj : T
|
||||
>k : K
|
||||
}
|
||||
|
||||
export function wrappedTest2<T, K extends string>(obj: T, k: K) {
|
||||
>wrappedTest2 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>obj : T
|
||||
>k : K
|
||||
|
||||
return test2(obj, k);
|
||||
>test2(obj, k) : { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>test2 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>obj : T
|
||||
>k : K
|
||||
}
|
||||
|
||||
export type Obj = {
|
||||
>Obj : { a: number; readonly foo: string; }
|
||||
|
||||
a: number;
|
||||
>a : number
|
||||
|
||||
readonly foo: string;
|
||||
>foo : string
|
||||
|
||||
};
|
||||
|
||||
export const processedInternally1 = wrappedTest1({} as Obj, "a");
|
||||
>processedInternally1 : { readonly foo: string; }
|
||||
>wrappedTest1({} as Obj, "a") : { readonly foo: string; }
|
||||
>wrappedTest1 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>{} as Obj : Obj
|
||||
>{} : {}
|
||||
>"a" : "a"
|
||||
|
||||
export const processedInternally2 = wrappedTest2({} as Obj, "a");
|
||||
>processedInternally2 : { foo: string; }
|
||||
>wrappedTest2({} as Obj, "a") : { foo: string; }
|
||||
>wrappedTest2 : <T, K extends string>(obj: T, k: K) => { [P in Exclude<keyof T, K>]: T[P]; }
|
||||
>{} as Obj : Obj
|
||||
>{} : {}
|
||||
>"a" : "a"
|
||||
|
||||
=== other.ts ===
|
||||
// how Omit from lib is defined
|
||||
type OmitReal<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
>OmitReal : OmitReal<T, K>
|
||||
|
||||
// what we see when we hover it
|
||||
type OmitUnveiled<T, K extends string | number | symbol> = {
|
||||
>OmitUnveiled : OmitUnveiled<T, K>
|
||||
|
||||
[P in Exclude<keyof T, K>]: T[P];
|
||||
};
|
||||
|
||||
export function test1<T, K extends string>(obj: T, k: K): OmitReal<T, K> {
|
||||
>test1 : <T, K extends string>(obj: T, k: K) => OmitReal<T, K>
|
||||
>obj : T
|
||||
>k : K
|
||||
|
||||
return {} as any;
|
||||
>{} as any : any
|
||||
>{} : {}
|
||||
}
|
||||
|
||||
export function test2<T, K extends string>(obj: T, k: K): OmitUnveiled<T, K> {
|
||||
>test2 : <T, K extends string>(obj: T, k: K) => OmitUnveiled<T, K>
|
||||
>obj : T
|
||||
>k : K
|
||||
|
||||
return {} as any;
|
||||
>{} as any : any
|
||||
>{} : {}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
// @declaration: true
|
||||
// @filename: index.ts
|
||||
import { test1, test2 } from "./other";
|
||||
|
||||
export function wrappedTest1<T, K extends string>(obj: T, k: K) {
|
||||
return test1(obj, k);
|
||||
}
|
||||
|
||||
export function wrappedTest2<T, K extends string>(obj: T, k: K) {
|
||||
return test2(obj, k);
|
||||
}
|
||||
|
||||
export type Obj = {
|
||||
a: number;
|
||||
readonly foo: string;
|
||||
};
|
||||
|
||||
export const processedInternally1 = wrappedTest1({} as Obj, "a");
|
||||
export const processedInternally2 = wrappedTest2({} as Obj, "a");
|
||||
// @filename: other.ts
|
||||
// how Omit from lib is defined
|
||||
type OmitReal<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
// what we see when we hover it
|
||||
type OmitUnveiled<T, K extends string | number | symbol> = {
|
||||
[P in Exclude<keyof T, K>]: T[P];
|
||||
};
|
||||
|
||||
export function test1<T, K extends string>(obj: T, k: K): OmitReal<T, K> {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
export function test2<T, K extends string>(obj: T, k: K): OmitUnveiled<T, K> {
|
||||
return {} as any;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user