Skip to content

Commit

Permalink
Preserve types from initializers that contain function expressions an…
Browse files Browse the repository at this point in the history
…d arrow functions.
  • Loading branch information
dragomirtitian committed Mar 7, 2024
1 parent 85ee2c0 commit cd7636d
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 74 deletions.
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
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 {
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 @@ -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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -188,58 +188,62 @@ exports.createClient = createClient;
type P = {
name: string;
};
export declare let vLet: (p: P, p2: P) => P;
export declare const vConst: (p: P, p2: P) => P;
export declare function fn(p?: (p: P, p2: P) => P): void;
export declare let vLet: (/* param */ p: P, p2: typeof p) => P;
export declare const vConst: (/* param */ p: P, p2: typeof p) => P;
export declare function fn(p?: (/* param */ p: P, p2: typeof p) => P): void;
export declare function fnTypeFromBinding({ foo }: {
foo: number;
}, p?: (p: P, p2: P, p3: typeof foo) => P): void;
}, p?: (/* param */ p: P, p2: typeof p, p3: typeof foo) => P): void;
/** p wil be resolved by the checker (requires | undefined) */
export declare function fnWithRequiredDefaultParam(p: (p: P, p2: P) => P, req: number): void;
export declare function fnWithRequiredDefaultParam(p: (/* param */ p: P, p2: typeof p) => P, req: number): void;
/** p wil be resolved by the checker (requires | undefined) */
export declare const exprWithRequiredDefaultParam: (p: (p: P, p2: P) => P, req: number) => void;
export declare const exprWithRequiredDefaultParam: (p: (/* param */ p: P, p2: typeof p) => P, req: number) => void;
export declare class C {
ctorField: (p: P, p2: P) => P;
field: (p: P, p2: P) => P;
readonly roFiled: (p: P, p2: P) => P;
method(p?: (p: P, p2: P) => P): void;
ctorField: (/* param */ p: P, p2: typeof p) => P;
field: (/* param */ p: P, p2: typeof p) => P;
readonly roFiled: (/* param */ p: P, p2: typeof p) => P;
method(p?: (/* param */ p: P, p2: typeof p) => P): void;
/** p wil be resolved by the checker (requires | undefined) */
methodWithRequiredDefault(p: (p: P, p2: P) => P, req: number): void;
methodWithRequiredDefault(p: (/* param */ p: P, p2: typeof p) => P, req: number): void;
thisType: () => this;
constructor(ctorField?: (p: P, p2: P) => P);
constructor(ctorField?: (/* param */ p: P, p2: typeof p) => P);
}
declare const _default: (p: P, p2: P) => P;
declare const _default: (/* param */ p: P, p2: typeof p) => P;
export default _default;
//// [functionExpressionPlacement.d.ts]
type P = {
name: string;
};
export declare let vLet: (p: P, p2: P) => P;
export declare const vConst: (p: P, p2: P) => P;
export declare function fn(p?: (p: P, p2: P) => P): void;
export declare let vLet: (/* param */ p: P, p2: typeof p) => P;
export declare const vConst: (/* param */ p: P, p2: typeof p) => P;
export declare function fn(p?: (/* param */ p: P, p2: typeof p) => P): void;
/** p wil be resolved by the checker (requires | undefined) */
export declare function fnWithRequiredDefaultParam(p: (p: P, p2: P) => P, req: number): void;
export declare function fnWithRequiredDefaultParam(p: (/* param */ p: P, p2: typeof p) => P, req: number): void;
export declare class C {
ctorField: (p: P, p2: P) => P;
field: (p: P, p2: P) => P;
readonly roFiled: (p: P, p2: P) => P;
method(p?: (p: P, p2: P) => P): void;
ctorField: (/* param */ p: P, p2: typeof p) => P;
field: (/* param */ p: P, p2: typeof p) => P;
readonly roFiled: (/* param */ p: P, p2: typeof p) => P;
method(p?: (/* param */ p: P, p2: typeof p) => P): void;
/** p wil be resolved by the checker (requires | undefined) */
methodWithRequiredDefault(p: (p: P, p2: P) => P, req: number): void;
constructor(ctorField?: (p: P, p2: P) => P);
methodWithRequiredDefault(p: (/* param */ p: P, p2: typeof p) => P, req: number): void;
constructor(ctorField?: (/* param */ p: P, p2: typeof p) => P);
}
export default function (/* param */ p: P, p2: typeof p): P;
export {};
//// [returnTypes.d.ts]
type P = {
name: string;
};
export declare let fromAnnotation: (p: P) => string;
export declare let fromAnnotation: (p: P) => typeof p[keyof typeof p];
export declare let fromInference: (p: P) => string;
export {};
//// [genericFunctions.d.ts]
export declare let g1: <T extends "name">(p: T) => void;
export declare let g2: <G2 extends "name">() => G2;
export declare const createClient: <D>(clientDef: D) => D extends new (...args: any[]) => infer D_1 ? D_1 extends {
d: infer D_2;
} ? D_2 : never : never;
type G1 = {
name: string;
};
export declare let g1: <T extends keyof G1>(/* param */ p: T) => void;
export declare let g2: <G2 extends keyof G1>() => G2;
export declare const createClient: <D>(clientDef: D) => D extends new (...args: any[]) => infer D ? (D extends {
d: infer D;
} ? D : never) : never;
export {};
Loading

0 comments on commit cd7636d

Please sign in to comment.