-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Avoid trying to emit anonymous classish/expando functions as assignments #55472
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8533,11 +8533,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
...oldcontext, | ||
usedSymbolNames: new Set(oldcontext.usedSymbolNames), | ||
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 | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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) | ||
|
@@ -8839,7 +8841,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
else { | ||
const type = getTypeOfSymbol(symbol); | ||
const localName = getInternalSymbolName(symbol, symbolName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Edit: Looking at the test cases, it's actually |
||
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); | ||
} | ||
|
@@ -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` | ||
|
@@ -49986,6 +50002,7 @@ interface NodeBuilderContext { | |
typeParameterNamesByTextNextNameCount?: Map<string, number>; | ||
usedSymbolNames?: Set<string>; | ||
remappedSymbolNames?: Map<SymbolId, string>; | ||
remappedSymbolReferences?: Map<SymbolId, Symbol>; | ||
reverseMappedStack?: ReverseMappedSymbol[]; | ||
} | ||
|
||
|
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)) | ||
|
Uh oh!
There was an error while loading. Please reload this page.