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

Avoid trying to emit anonymous classish/expando functions as assignments #55472

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8533,11 +8533,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
...oldcontext,
usedSymbolNames: new Set(oldcontext.usedSymbolNames),
weswigham marked this conversation as resolved.
Show resolved Hide resolved
remappedSymbolNames: new Map(),
remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()),
tracker: undefined!,
};
const tracker: SymbolTracker = {
...oldcontext.tracker.inner,
trackSymbol: (sym, decl, meaning) => {
if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible
const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ false);
if (accessibleResult.accessibility === SymbolAccessibility.Accessible) {
// Lookup the root symbol of the chain of refs we'll use to access it and serialize it
Expand Down Expand Up @@ -8790,9 +8792,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If it's a property: emit `export default _default` with a `_default` prop
// If it's a class/interface/function: emit a class/interface/function with a `default` modifier
// These forms can merge, eg (`export default 12; export default interface A {}`)
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void {
const symbolName = unescapeLeadingUnderscores(symbol.escapedName);
const isDefault = symbol.escapedName === InternalSymbolName.Default;
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean, escapedSymbolName = symbol.escapedName): void {
const symbolName = unescapeLeadingUnderscores(escapedSymbolName);
const isDefault = escapedSymbolName === InternalSymbolName.Default;
if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) {
// Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :(
context.encounteredError = true;
Expand All @@ -8811,7 +8813,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0);
const isConstMergedWithNS = symbol.flags & SymbolFlags.Module &&
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) &&
symbol.escapedName !== InternalSymbolName.ExportEquals;
escapedSymbolName !== InternalSymbolName.ExportEquals;
const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol);
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) {
serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
Expand All @@ -8823,7 +8825,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is.
if (
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property | SymbolFlags.Accessor)
&& symbol.escapedName !== InternalSymbolName.ExportEquals
&& escapedSymbolName !== InternalSymbolName.ExportEquals
&& !(symbol.flags & SymbolFlags.Prototype)
&& !(symbol.flags & SymbolFlags.Class)
&& !(symbol.flags & SymbolFlags.Method)
Expand All @@ -8839,7 +8841,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else {
const type = getTypeOfSymbol(symbol);
const localName = getInternalSymbolName(symbol, symbolName);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is to handle the not-anonymous-but-still-an-expression case where you could use that local name to refer to the type of the expando-thing as a whole. Because it'd be odd if just adding a name to the function expression made this transform stop working.

Copy link
Member

Choose a reason for hiding this comment

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

Is this is for cases like

const f = function g() { }
f.p = 1

Does remappedSymbolReferences record g? When would g show up in the declaration emit?

Edit: Looking at the test cases, it's actually g -> f so that when a constructor function g returns g, it serialises as f.

if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
if (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) {
// assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger,
// and the assignment form has to reference the unreachable anonymous type so will error.
// Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one.
if (!context.remappedSymbolReferences) {
context.remappedSymbolReferences = new Map();
}
context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol
serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName);
context.remappedSymbolReferences.delete(getSymbolId(type.symbol));
}
else if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
// If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns
serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags);
}
Expand Down Expand Up @@ -10159,6 +10172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`.
*/
function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string {
if (context?.remappedSymbolReferences?.has(getSymbolId(symbol))) {
symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!;
}
if (
context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) &&
// If it's not the first part of an entity name, it must print as `default`
Expand Down Expand Up @@ -49986,6 +50002,7 @@ interface NodeBuilderContext {
typeParameterNamesByTextNextNameCount?: Map<string, number>;
usedSymbolNames?: Set<string>;
remappedSymbolNames?: Map<SymbolId, string>;
remappedSymbolReferences?: Map<SymbolId, Symbol>;
reverseMappedStack?: ReverseMappedSymbol[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ type Input = {
timer: Timer;
hook: Hook;
};
/**
* Imports
*/
type Timer = import("./timer");
/**
* Imports
*/
type Hook = import("./hook");
/**
* Imports
*/
Expand All @@ -239,14 +247,6 @@ type State = {
timer: Timer;
hook: Hook;
};
/**
* Imports
*/
type Timer = import("./timer");
/**
* Imports
*/
type Hook = import("./hook");
//// [hook.d.ts]
export = Hook;
/**
Expand Down
36 changes: 36 additions & 0 deletions tests/baselines/reference/jsDeclarationsGlobalFileConstFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////

//// [file.js]
const SomeConstructor = function () {
this.x = 1;
};

const SomeConstructor2 = function () {
};
SomeConstructor2.staticMember = "str";

const SomeConstructor3 = function () {
this.x = 1;
};
SomeConstructor3.staticMember = "str";




//// [file.d.ts]
declare function SomeConstructor(): void;
declare class SomeConstructor {
x: number;
}
declare function SomeConstructor2(): void;
declare namespace SomeConstructor2 {
let staticMember: string;
}
declare function SomeConstructor3(): void;
declare namespace SomeConstructor3 {
let staticMember_1: string;
export { staticMember_1 as staticMember };
}
declare class SomeConstructor3 {
x: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////

=== file.js ===
const SomeConstructor = function () {
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))

this.x = 1;
>this.x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))
>this : Symbol(SomeConstructor, Decl(file.js, 0, 23))
>x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))

};

const SomeConstructor2 = function () {
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))

};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))

const SomeConstructor3 = function () {
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))

this.x = 1;
>this.x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))
>this : Symbol(SomeConstructor3, Decl(file.js, 8, 24))
>x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))

};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))

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

=== file.js ===
const SomeConstructor = function () {
>SomeConstructor : typeof SomeConstructor
>function () { this.x = 1;} : typeof SomeConstructor

this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1

};

const SomeConstructor2 = function () {
>SomeConstructor2 : { (): void; staticMember: string; }
>function () {} : { (): void; staticMember: string; }

};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember = "str" : "str"
>SomeConstructor2.staticMember : string
>SomeConstructor2 : { (): void; staticMember: string; }
>staticMember : string
>"str" : "str"

const SomeConstructor3 = function () {
>SomeConstructor3 : typeof SomeConstructor3
>function () { this.x = 1;} : typeof SomeConstructor3

this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1

};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember = "str" : "str"
>SomeConstructor3.staticMember : string
>SomeConstructor3 : typeof SomeConstructor3
>staticMember : string
>"str" : "str"

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

//// [file.js]
const SomeConstructor = function Named() {
this.x = 1;
};

const SomeConstructor2 = function Named() {
};
SomeConstructor2.staticMember = "str";

const SomeConstructor3 = function Named() {
this.x = 1;
};
SomeConstructor3.staticMember = "str";

const SelfReference = function Named() {
if (!(this instanceof Named)) return new Named();
this.x = 1;
}
SelfReference.staticMember = "str";




//// [file.d.ts]
declare function SomeConstructor(): void;
declare class SomeConstructor {
x: number;
}
declare function SomeConstructor2(): void;
declare namespace SomeConstructor2 {
let staticMember: string;
}
declare function SomeConstructor3(): void;
declare namespace SomeConstructor3 {
let staticMember_1: string;
export { staticMember_1 as staticMember };
}
declare class SomeConstructor3 {
x: number;
}
declare function SelfReference(): SelfReference;
declare namespace SelfReference {
let staticMember_2: string;
export { staticMember_2 as staticMember };
}
declare class SelfReference {
x: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////

=== file.js ===
const SomeConstructor = function Named() {
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))
>Named : Symbol(Named, Decl(file.js, 0, 23))

this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 0, 42))
>this : Symbol(Named, Decl(file.js, 0, 23))
>x : Symbol(Named.x, Decl(file.js, 0, 42))

};

const SomeConstructor2 = function Named() {
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>Named : Symbol(Named, Decl(file.js, 4, 24))

};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))

const SomeConstructor3 = function Named() {
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>Named : Symbol(Named, Decl(file.js, 8, 24))

this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 8, 43))
>this : Symbol(Named, Decl(file.js, 8, 24))
>x : Symbol(Named.x, Decl(file.js, 8, 43))

};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))

const SelfReference = function Named() {
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
>Named : Symbol(Named, Decl(file.js, 13, 21))

if (!(this instanceof Named)) return new Named();
>this : Symbol(Named, Decl(file.js, 13, 21))
>Named : Symbol(Named, Decl(file.js, 13, 21))
>Named : Symbol(Named, Decl(file.js, 13, 21))

this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 14, 53))
>this : Symbol(Named, Decl(file.js, 13, 21))
>x : Symbol(Named.x, Decl(file.js, 14, 53))
}
SelfReference.staticMember = "str";
>SelfReference.staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
>staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))

Loading
Loading