Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ID-Prep] PR5 - Preserve type nodes from function like expression in declaration emit #57678

41 changes: 35 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6329,8 +6329,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

const firstIdentifier = getFirstIdentifier(entityName);
const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) {
return { accessibility: SymbolAccessibility.Accessible };
if (symbol) {
if (symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) {
return { accessibility: SymbolAccessibility.Accessible };
}

// Parameters or binding elements from parameters are always visible in their enclosing function declarations
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The checker complained about the parameter name not being accessible (since arrow functions are not themselves marked as visible by determineIfDeclarationIsVisible.

if (symbol.flags & SymbolFlags.FunctionScopedVariable && meaning & SymbolFlags.Value) {
let declaration: Node | undefined = symbol.valueDeclaration;
while (declaration) {
if (declaration.kind === SyntaxKind.Parameter) break;
if (declaration.kind === SyntaxKind.BindingElement) {
declaration = declaration.parent.parent;
}
break;
}
if (declaration?.kind === SyntaxKind.Parameter) {
return { accessibility: SymbolAccessibility.Accessible };
}
}
}
if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfDeclaration(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)), firstIdentifier, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) {
return { accessibility: SymbolAccessibility.Accessible };
Expand Down Expand Up @@ -48160,13 +48177,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier);
}

function isExpandoFunctionDeclaration(node: Declaration): boolean {
const declaration = getParseTreeNode(node, isFunctionDeclaration);
function isExpandoFunctionDeclaration(node: FunctionDeclaration | VariableDeclaration): boolean {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both function and variable declarations will now be tested. We can't reliably determine the type for an expando functions variable from just it's declaration so we fall back on the type checker.

const declaration = getParseTreeNode(node, isDeclaration);
if (!declaration) {
return false;
}
const symbol = getSymbolOfDeclaration(declaration);
if (!symbol || !(symbol.flags & SymbolFlags.Function)) {
let symbol: Symbol;
if (isVariableDeclaration(declaration)) {
if (declaration.type || !isVarConstLike(declaration)) {
return false;
}
if (!(declaration.initializer && isFunctionExpressionOrArrowFunction(declaration.initializer))) {
return false;
}
symbol = getSymbolOfDeclaration(declaration.initializer);
}
else {
symbol = getSymbolOfDeclaration(declaration);
}
if (!symbol || !(symbol.flags & SymbolFlags.Function | SymbolFlags.Variable)) {
return false;
}
return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && isExpandoPropertyDeclaration(p.valueDeclaration));
Expand Down
19 changes: 18 additions & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
append,
ArrayBindingElement,
arrayFrom,
ArrowFunction,
AsExpression,
BindingElement,
BindingName,
Expand Down Expand Up @@ -51,6 +52,7 @@ import {
flatten,
forEach,
FunctionDeclaration,
FunctionExpression,
FunctionTypeNode,
GeneratedIdentifierFlags,
GetAccessorDeclaration,
Expand Down Expand Up @@ -864,6 +866,9 @@ export function transformDeclarations(context: TransformationContext) {
}
function typeFromExpression(node: Expression, requiresUndefined?: boolean): TypeNode | undefined {
switch (node.kind) {
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
return typeFromFunctionLikeExpression(node as ArrowFunction | FunctionExpression);
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
const asExpression = node as AsExpression | TypeAssertion;
Expand All @@ -877,7 +882,19 @@ export function transformDeclarations(context: TransformationContext) {
}
return undefined;
}
function typeFromFunctionLikeExpression(fnNode: FunctionExpression | ArrowFunction) {
const oldEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = fnNode;

const returnType = fnNode.type ? visitTypeNode(fnNode.type) : inferReturnTypeOfSignatureSignature(fnNode);
const fnTypeNode = factory.createFunctionTypeNode(
visitNodes(fnNode.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration),
fnNode.parameters.map(p => ensureParameter(p)),
returnType,
);
enclosingDeclaration = oldEnclosingDeclaration;
return fnTypeNode;
}
function typeFromAccessor(node: AccessorDeclaration) {
const accessorDeclarations = resolver.getAllAccessorDeclarations(node);
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(node, accessorDeclarations);
Expand All @@ -892,7 +909,7 @@ export function transformDeclarations(context: TransformationContext) {
return visitNode(declaredType, visitDeclarationSubtree, isTypeNode)!;
}
let resultType;
if (node.initializer) {
if (node.initializer && !resolver.isExpandoFunctionDeclaration(node)) {
resultType = typeFromExpression(node.initializer);
}
return resultType ?? inferVariableLikeType(node);
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/transformers/declarations/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationD
Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1;
}

case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionType:
return symbolAccessibilityResult.errorModuleName ?
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5604,7 +5604,7 @@ export interface EmitResolver {
collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined;
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
requiresAddingImplicitUndefined(node: ParameterDeclaration): boolean;
isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean;
isExpandoFunctionDeclaration(node: VariableDeclaration | FunctionDeclaration): boolean;
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/commentsFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ b: number): void;
/** fooFunc
* comment
*/
declare var fooFunc: (b: string) => string;
declare var lambdaFoo: (a: number, b: number) => number;
declare var lambddaNoVarComment: (a: number, b: number) => number;
declare var fooFunc: (/** fooFunctionValue param */ b: string) => string;
declare var lambdaFoo: (/**param a*/ a: number, /**param b*/ b: number) => number;
declare var lambddaNoVarComment: (/**param a*/ a: number, /**param b*/ b: number) => number;
declare function blah(a: string): void;
declare function blah2(a: string): void;
declare function blah3(a: string): void;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/correlatedUnions.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ type SameKeys<T> = {
};
};
type MappedFromOriginal = SameKeys<Original>;
declare const getStringAndNumberFromOriginalAndMapped: <K extends keyof Original, N extends keyof Original[K]>(original: Original, mappedFromOriginal: MappedFromOriginal, key: K, nestedKey: N) => [Original[K][N], MappedFromOriginal[K][N]];
declare const getStringAndNumberFromOriginalAndMapped: <K extends KeyOfOriginal, N extends NestedKeyOfOriginalFor<K>>(original: Original, mappedFromOriginal: MappedFromOriginal, key: K, nestedKey: N) => [Original[K][N], MappedFromOriginal[K][N]];
interface Config {
string: string;
number: number;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/declFileTypeofFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ declare function b1(): typeof b1;
declare function foo(): typeof foo;
declare var foo1: typeof foo;
declare var foo2: typeof foo;
declare var foo3: () => any;
declare var x: () => any;
declare var foo3: () => () => any;
declare var x: () => () => any;
declare function foo5(x: number): (x: number) => number;
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export const createClient = <
| Record<string, new (...args: any[]) => Client> // or map of classes
>(
clientDef: D
): D extends new (...args: any[]) => infer C
) => {
return null! as D extends new (...args: any[]) => infer C
? UpdatedClient<C> // return instance
: {
[K in keyof D]: D[K] extends new (...args: any[]) => infer C // or map of instances respectively
? UpdatedClient<C>
: never
} => {
return null as any
}
}

//// [declarationEmitShadowingInferNotRenamed.js]
Expand All @@ -38,5 +38,5 @@ type Client = string;
type UpdatedClient<C> = C & {
foo: number;
};
export declare const createClient: <D extends Record<string, new (...args: any[]) => Client> | (new (...args: any[]) => Client)>(clientDef: D) => D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; };
export declare const createClient: <D extends (new (...args: any[]) => Client) | Record<string, new (...args: any[]) => Client>>(clientDef: D) => D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; };
export {};
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,29 @@ export const createClient = <
>clientDef : Symbol(clientDef, Decl(declarationEmitShadowingInferNotRenamed.ts, 10, 2))
>D : Symbol(D, Decl(declarationEmitShadowingInferNotRenamed.ts, 6, 29))

): D extends new (...args: any[]) => infer C
) => {
return null! as D extends new (...args: any[]) => infer C
>D : Symbol(D, Decl(declarationEmitShadowingInferNotRenamed.ts, 6, 29))
>args : Symbol(args, Decl(declarationEmitShadowingInferNotRenamed.ts, 12, 18))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 12, 42))
>args : Symbol(args, Decl(declarationEmitShadowingInferNotRenamed.ts, 13, 33))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 13, 57))

? UpdatedClient<C> // return instance
>UpdatedClient : Symbol(UpdatedClient, Decl(declarationEmitShadowingInferNotRenamed.ts, 1, 20))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 12, 42))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 13, 57))

: {
[K in keyof D]: D[K] extends new (...args: any[]) => infer C // or map of instances respectively
>K : Symbol(K, Decl(declarationEmitShadowingInferNotRenamed.ts, 15, 7))
>K : Symbol(K, Decl(declarationEmitShadowingInferNotRenamed.ts, 16, 7))
>D : Symbol(D, Decl(declarationEmitShadowingInferNotRenamed.ts, 6, 29))
>D : Symbol(D, Decl(declarationEmitShadowingInferNotRenamed.ts, 6, 29))
>K : Symbol(K, Decl(declarationEmitShadowingInferNotRenamed.ts, 15, 7))
>args : Symbol(args, Decl(declarationEmitShadowingInferNotRenamed.ts, 15, 40))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 15, 64))
>K : Symbol(K, Decl(declarationEmitShadowingInferNotRenamed.ts, 16, 7))
>args : Symbol(args, Decl(declarationEmitShadowingInferNotRenamed.ts, 16, 40))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 16, 64))

? UpdatedClient<C>
>UpdatedClient : Symbol(UpdatedClient, Decl(declarationEmitShadowingInferNotRenamed.ts, 1, 20))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 15, 64))
>C : Symbol(C, Decl(declarationEmitShadowingInferNotRenamed.ts, 16, 64))

: never
} => {
return null as any
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type UpdatedClient<C> = C & {foo: number}

export const createClient = <
>createClient : <D extends Record<string, new (...args: any[]) => Client> | (new (...args: any[]) => Client)>(clientDef: D) => D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; }
>< D extends | (new (...args: any[]) => Client) // accept class | Record<string, new (...args: any[]) => Client> // or map of classes>( clientDef: D): D extends new (...args: any[]) => infer C ? UpdatedClient<C> // return instance : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C // or map of instances respectively ? UpdatedClient<C> : never } => { return null as any} : <D extends Record<string, new (...args: any[]) => Client> | (new (...args: any[]) => Client)>(clientDef: D) => D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; }
>< D extends | (new (...args: any[]) => Client) // accept class | Record<string, new (...args: any[]) => Client> // or map of classes>( clientDef: D) => { return null! as D extends new (...args: any[]) => infer C ? UpdatedClient<C> // return instance : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C // or map of instances respectively ? UpdatedClient<C> : never }} : <D extends Record<string, new (...args: any[]) => Client> | (new (...args: any[]) => Client)>(clientDef: D) => D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; }

D extends
| (new (...args: any[]) => Client) // accept class
Expand All @@ -25,7 +25,10 @@ export const createClient = <
clientDef: D
>clientDef : D

): D extends new (...args: any[]) => infer C
) => {
return null! as D extends new (...args: any[]) => infer C
>null! as D extends new (...args: any[]) => infer C ? UpdatedClient<C> // return instance : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C // or map of instances respectively ? UpdatedClient<C> : never } : D extends new (...args: any[]) => infer C ? UpdatedClient<C> : { [K in keyof D]: D[K] extends new (...args: any[]) => infer C_1 ? UpdatedClient<C_1> : never; }
>null! : null
>args : any[]

? UpdatedClient<C> // return instance
Expand All @@ -35,7 +38,5 @@ export const createClient = <

? UpdatedClient<C>
: never
} => {
return null as any
>null as any : any
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ var x = function somefn() { return somefn; };


//// [functionExpressionReturningItself.d.ts]
declare var x: () => any;
declare var x: () => () => any;
8 changes: 6 additions & 2 deletions tests/baselines/reference/keyofAndIndexedAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -1431,7 +1431,11 @@ type DictDict<V extends string, T extends string> = {
};
declare function ff1<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2: T): number;
declare function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2: T): number;
declare const cf1: <T extends { [P in K]: string; } & {
declare const cf1: <T extends {
[P in K]: string;
} & {
cool: string;
}, K extends keyof T>(t: T, k: K) => void;
declare const cf2: <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) => void;
declare const cf2: <T extends {
[P in K | "cool"]: string;
}, K extends keyof T>(t: T, k: K) => void;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export function test2<T = Record<string, never>>(schema: {


//// [mappedTypeGenericInstantiationPreservesInlineForm.d.ts]
export declare const test1: <T = Record<string, never>>(schema: { [K in keyof Required<T>]: T[K]; }) => void;
export declare const test1: <T = Record<string, never>>(schema: {
[K in keyof Required<T>]: T[K];
}) => void;
export declare function test2<T = Record<string, never>>(schema: {
[K in keyof Required<T>]: T[K];
}): void;
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ interface I {
declare function f1({ a: string }: O): void;
declare const f2: ({ a: string }: O) => void;
declare const f3: ({ a: string, b, c }: O) => void;
declare const f4: ({ a: string }: O) => string;
declare const f5: ({ a: string, b, c }: O) => string;
declare const f4: ({ a: string }: O) => typeof string;
declare const f5: ({ a: string, b, c }: O) => typeof string;
declare const obj1: {
method({ a: string }: O): void;
};
Expand Down
Loading
Loading