Skip to content

Fixed declaration emit for expando properties on function declarations declared using element access expressions #55183

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

Merged
7 changes: 6 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ import {
isEnumDeclaration,
isEnumMember,
isExclusivelyTypeOnlyImportOrExport,
isExpandoPropertyDeclaration,
isExportAssignment,
isExportDeclaration,
isExportsIdentifier,
Expand Down Expand Up @@ -8165,6 +8166,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const type = checkExpression(name.expression);
return !!(type.flags & TypeFlags.StringLike);
}
if (isElementAccessExpression(name)) {
const type = checkExpression(name.argumentExpression);
return !!(type.flags & TypeFlags.StringLike);
}
return isStringLiteral(name);
}

Expand Down Expand Up @@ -47091,7 +47096,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!symbol || !(symbol.flags & SymbolFlags.Function)) {
return false;
}
return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration));
return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && isExpandoPropertyDeclaration(p.valueDeclaration));
}

function getPropertiesOfContainerFunction(node: Declaration): Symbol[] {
Expand Down
6 changes: 2 additions & 4 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,14 @@ import {
isAnyImportSyntax,
isArray,
isArrayBindingElement,
isBinaryExpression,
isBindingElement,
isBindingPattern,
isClassDeclaration,
isClassElement,
isDeclaration,
isElementAccessExpression,
isEntityName,
isEntityNameExpression,
isExpandoPropertyDeclaration,
isExportAssignment,
isExportDeclaration,
isExpressionWithTypeArguments,
Expand Down Expand Up @@ -133,7 +132,6 @@ import {
isNightly,
isOmittedExpression,
isPrivateIdentifier,
isPropertyAccessExpression,
isPropertySignature,
isSemicolonClassElement,
isSetAccessorDeclaration,
Expand Down Expand Up @@ -1537,7 +1535,7 @@ export function transformDeclarations(context: TransformationContext) {
fakespace.symbol = props[0].parent!;
const exportMappings: [Identifier, string][] = [];
let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => {
if (!p.valueDeclaration || !(isPropertyAccessExpression(p.valueDeclaration) || isElementAccessExpression(p.valueDeclaration) || isBinaryExpression(p.valueDeclaration))) {
if (!isExpandoPropertyDeclaration(p.valueDeclaration)) {
return undefined;
}
const nameStr = unescapeLeadingUnderscores(p.escapedName);
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10434,3 +10434,8 @@ export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralT
}
return Debug.fail();
}

/** @internal */
export function isExpandoPropertyDeclaration(declaration: Declaration | undefined): declaration is PropertyAccessExpression | ElementAccessExpression | BinaryExpression {
return !!declaration && (isPropertyAccessExpression(declaration) || isElementAccessExpression(declaration) || isBinaryExpression(declaration));
}
186 changes: 186 additions & 0 deletions tests/baselines/reference/declarationEmitLateBoundAssignments2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//// [tests/cases/compiler/declarationEmitLateBoundAssignments2.ts] ////

//// [declarationEmitLateBoundAssignments2.ts]
// https://github.com/microsoft/TypeScript/issues/54811

const c = "C"
const num = 1
const numStr = "10"
const withWhitespace = "foo bar"
const emoji = "🤷‍♂️"

export function decl() {}
decl["B"] = 'foo'

export function decl2() {}
decl2[c] = 0

export function decl3() {}
decl3[77] = 0

export function decl4() {}
decl4[num] = 0

export function decl5() {}
decl5["101"] = 0

export function decl6() {}
decl6[numStr] = 0

export function decl7() {}
decl7["qwe rty"] = 0

export function decl8() {}
decl8[withWhitespace] = 0

export function decl9() {}
decl9["🤪"] = 0

export function decl10() {}
decl10[emoji] = 0

export const arrow = () => {}
arrow["B"] = 'bar'

export const arrow2 = () => {}
arrow2[c] = 100

export const arrow3 = () => {}
arrow3[77] = 0

export const arrow4 = () => {}
arrow4[num] = 0

export const arrow5 = () => {}
arrow5["101"] = 0

export const arrow6 = () => {}
arrow6[numStr] = 0

export const arrow7 = () => {}
arrow7["qwe rty"] = 0

export const arrow8 = () => {}
arrow8[withWhitespace] = 0

export const arrow9 = () => {}
arrow9["🤪"] = 0

export const arrow10 = () => {}
arrow10[emoji] = 0


//// [declarationEmitLateBoundAssignments2.js]
// https://github.com/microsoft/TypeScript/issues/54811
const c = "C";
const num = 1;
const numStr = "10";
const withWhitespace = "foo bar";
const emoji = "🤷‍♂️";
export function decl() { }
decl["B"] = 'foo';
export function decl2() { }
decl2[c] = 0;
export function decl3() { }
decl3[77] = 0;
export function decl4() { }
decl4[num] = 0;
export function decl5() { }
decl5["101"] = 0;
export function decl6() { }
decl6[numStr] = 0;
export function decl7() { }
decl7["qwe rty"] = 0;
export function decl8() { }
decl8[withWhitespace] = 0;
export function decl9() { }
decl9["🤪"] = 0;
export function decl10() { }
decl10[emoji] = 0;
export const arrow = () => { };
arrow["B"] = 'bar';
export const arrow2 = () => { };
arrow2[c] = 100;
export const arrow3 = () => { };
arrow3[77] = 0;
export const arrow4 = () => { };
arrow4[num] = 0;
export const arrow5 = () => { };
arrow5["101"] = 0;
export const arrow6 = () => { };
arrow6[numStr] = 0;
export const arrow7 = () => { };
arrow7["qwe rty"] = 0;
export const arrow8 = () => { };
arrow8[withWhitespace] = 0;
export const arrow9 = () => { };
arrow9["🤪"] = 0;
export const arrow10 = () => { };
arrow10[emoji] = 0;


//// [declarationEmitLateBoundAssignments2.d.ts]
export declare function decl(): void;
export declare namespace decl {
var B: string;
}
export declare function decl2(): void;
export declare namespace decl2 {
var C: number;
}
export declare function decl3(): void;
export declare namespace decl3 { }
export declare function decl4(): void;
export declare namespace decl4 { }
export declare function decl5(): void;
export declare namespace decl5 { }
export declare function decl6(): void;
export declare namespace decl6 { }
export declare function decl7(): void;
export declare namespace decl7 { }
export declare function decl8(): void;
export declare namespace decl8 { }
export declare function decl9(): void;
export declare namespace decl9 { }
export declare function decl10(): void;
export declare namespace decl10 { }
Comment on lines +131 to +146
Copy link
Contributor Author

Choose a reason for hiding this comment

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

those don't work because function declarations are emitted as namespaces and non-identifiers can't be exported from namespaces, see: #55190

I could make an effort to avoid those empty namespaces. The issue itself stands though. I think it's a slight problem that function declarations and function expressions have such a subtle and silent (!) difference in their capabilities to emit expando members

export declare const arrow: {
(): void;
B: string;
};
export declare const arrow2: {
(): void;
C: number;
};
export declare const arrow3: {
(): void;
77: number;
};
export declare const arrow4: {
(): void;
1: number;
};
export declare const arrow5: {
(): void;
"101": number;
};
export declare const arrow6: {
(): void;
"10": number;
};
export declare const arrow7: {
(): void;
"qwe rty": number;
};
export declare const arrow8: {
(): void;
"foo bar": number;
};
export declare const arrow9: {
(): void;
"\uD83E\uDD2A": number;
};
export declare const arrow10: {
(): void;
"\uD83E\uDD37\u200D\u2642\uFE0F": number;
};
Loading