diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 0f549d889..e843b6909 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -496,10 +496,18 @@ function localClassResultInstance( const viaConstructorType = retTs.getSymbol()?.valueDeclaration !== resultClass; const returnType = withInstanceScope(lowerer, info, inst, () => { if (!viaConstructorType) return lowerer.mapTypeOf(retTs); - const classSymbol = resultClass.name - ? lowerer.checker.getSymbolAtLocation(resultClass.name) - : undefined; - return classSymbol ? lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(classSymbol)) : null; + // The local class's own constructor type: a class expression's type is + // its constructor type, named or not (`return class { ... } as Struct`). + const classSymbol = + !ts.isClassExpression(resultClass) && resultClass.name + ? lowerer.checker.getSymbolAtLocation(resultClass.name) + : undefined; + const classType = ts.isClassExpression(resultClass) + ? lowerer.checker.getTypeAtLocation(resultClass) + : classSymbol + ? lowerer.checker.getTypeOfSymbol(classSymbol) + : undefined; + return classType ? lowerer.mapTypeOf(classType) : null; }); if (!returnType) { fenceGenericSignatureResult(lowerer, expr, retTs); @@ -646,11 +654,19 @@ function returnedLocalClass(lowerer: Lowerer, info: GenericFnInfo): ts.ClassLike if (ts.isClassExpression(expr)) return declaredInside(expr, info.decl) ? expr : null; if (!ts.isIdentifier(expr)) return null; const declaration = lowerer.checker.getSymbolAtLocation(expr)?.valueDeclaration; - return declaration && - ts.isClassDeclaration(declaration) && - declaredInside(declaration, info.decl) - ? declaration - : null; + if (!declaration || !declaredInside(declaration, info.decl)) return null; + if (ts.isClassDeclaration(declaration)) return declaration; + // A `const` bound to a class expression (`const C = class { ... }; return C`). + if ( + ts.isVariableDeclaration(declaration) && + ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const && + declaration.initializer + ) { + let init: ts.Expression = declaration.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (ts.isClassExpression(init)) return init; + } + return null; }; if (!ts.isBlock(body)) return classOf(body as ts.Expression); let found: ts.ClassLikeDeclaration | null = null; diff --git a/tests/corpus/6912-extend-factory-local-class.ts b/tests/corpus/6912-extend-factory-local-class.ts index 8848280e2..e33a59c77 100644 --- a/tests/corpus/6912-extend-factory-local-class.ts +++ b/tests/corpus/6912-extend-factory-local-class.ts @@ -65,3 +65,35 @@ function twice(n: number): number { return K.double() + K.quad(); } console.log(twice(3), twice(5)); + +// The factory returns an anonymous class expression, directly or through a +// `const` binding. +function anonymous(value: S): Struct { + return class { + static decode(_input: unknown): S { + return value; + } + } as unknown as Struct; +} +class Anonymous extends anonymous({ tag: "anon" }) {} +console.log(Anonymous.decode(null).tag); + +type Holder = (new (held: T) => { held: T }) & { readonly seed: () => T }; +function holder(initial: T): Holder { + const Hold = class { + held: T; + constructor(held: T) { + this.held = held; + } + static seed(): T { + return initial; + } + }; + return Hold; +} +class Boxed extends holder(7) { + twice(): number { + return this.held * 2; + } +} +console.log(new Boxed(4).twice(), Boxed.seed());