Skip to content

Commit 0d284e6

Browse files
authored
Add support for abstract constructor types (#36392)
* Add support for abstract constructor types * Add backwards-compatible overloads for creating/updating constructor types * Reverting use of 'abstract' in lib/es5.d.ts due to eslint issues * Update baseline due to reverting lib * Add error for failing to mark an mixin class as abstract * Fix declaration/quick info for abstract construct signatures
1 parent 3273dbc commit 0d284e6

40 files changed

+1510
-503
lines changed

src/compiler/checker.ts

+95-22
Large diffs are not rendered by default.

src/compiler/debug.ts

+9
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ namespace ts {
367367
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true);
368368
}
369369

370+
export function formatSignatureFlags(flags: SignatureFlags | undefined): string {
371+
return formatEnum(flags, (<any>ts).SignatureFlags, /*isFlags*/ true);
372+
}
373+
370374
export function formatObjectFlags(flags: ObjectFlags | undefined): string {
371375
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
372376
}
@@ -573,6 +577,11 @@ namespace ts {
573577
},
574578
});
575579

580+
Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, {
581+
__debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } },
582+
__debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } }
583+
});
584+
576585
const nodeConstructors = [
577586
objectAllocator.getNodeConstructor(),
578587
objectAllocator.getIdentifierConstructor(),

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3231,6 +3231,10 @@
32313231
"category": "Error",
32323232
"code": 2796
32333233
},
3234+
"A mixin class that extends from a type variable containing an abstract construct signature must also be declared 'abstract'.": {
3235+
"category": "Error",
3236+
"code": 2797
3237+
},
32343238

32353239
"Import declaration '{0}' is using private name '{1}'.": {
32363240
"category": "Error",

src/compiler/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2131,6 +2131,7 @@ namespace ts {
21312131

21322132
function emitConstructorType(node: ConstructorTypeNode) {
21332133
pushNameGenerationScope(node);
2134+
emitModifiers(node, node.modifiers);
21342135
writeKeyword("new");
21352136
writeSpace();
21362137
emitTypeParameters(node, node.typeParameters);

src/compiler/factory/nodeFactory.ts

+39-5
Original file line numberDiff line numberDiff line change
@@ -1700,15 +1700,22 @@ namespace ts {
17001700
}
17011701

17021702
// @api
1703-
function createConstructorTypeNode(
1703+
function createConstructorTypeNode(...args: Parameters<typeof createConstructorTypeNode1 | typeof createConstructorTypeNode2>) {
1704+
return args.length === 4 ? createConstructorTypeNode1(...args) :
1705+
args.length === 3 ? createConstructorTypeNode2(...args) :
1706+
Debug.fail("Incorrect number of arguments specified.");
1707+
}
1708+
1709+
function createConstructorTypeNode1(
1710+
modifiers: readonly Modifier[] | undefined,
17041711
typeParameters: readonly TypeParameterDeclaration[] | undefined,
17051712
parameters: readonly ParameterDeclaration[],
17061713
type: TypeNode | undefined
17071714
): ConstructorTypeNode {
17081715
const node = createBaseSignatureDeclaration<ConstructorTypeNode>(
17091716
SyntaxKind.ConstructorType,
17101717
/*decorators*/ undefined,
1711-
/*modifiers*/ undefined,
1718+
modifiers,
17121719
/*name*/ undefined,
17131720
typeParameters,
17141721
parameters,
@@ -1718,20 +1725,47 @@ namespace ts {
17181725
return node;
17191726
}
17201727

1728+
/** @deprecated */
1729+
function createConstructorTypeNode2(
1730+
typeParameters: readonly TypeParameterDeclaration[] | undefined,
1731+
parameters: readonly ParameterDeclaration[],
1732+
type: TypeNode | undefined
1733+
): ConstructorTypeNode {
1734+
return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type);
1735+
}
1736+
17211737
// @api
1722-
function updateConstructorTypeNode(
1738+
function updateConstructorTypeNode(...args: Parameters<typeof updateConstructorTypeNode1 | typeof updateConstructorTypeNode2>) {
1739+
return args.length === 5 ? updateConstructorTypeNode1(...args) :
1740+
args.length === 4 ? updateConstructorTypeNode2(...args) :
1741+
Debug.fail("Incorrect number of arguments specified.");
1742+
}
1743+
1744+
function updateConstructorTypeNode1(
17231745
node: ConstructorTypeNode,
1746+
modifiers: readonly Modifier[] | undefined,
17241747
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
17251748
parameters: NodeArray<ParameterDeclaration>,
17261749
type: TypeNode | undefined
17271750
) {
1728-
return node.typeParameters !== typeParameters
1751+
return node.modifiers !== modifiers
1752+
|| node.typeParameters !== typeParameters
17291753
|| node.parameters !== parameters
17301754
|| node.type !== type
1731-
? updateBaseSignatureDeclaration(createConstructorTypeNode(typeParameters, parameters, type), node)
1755+
? updateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node)
17321756
: node;
17331757
}
17341758

1759+
/** @deprecated */
1760+
function updateConstructorTypeNode2(
1761+
node: ConstructorTypeNode,
1762+
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
1763+
parameters: NodeArray<ParameterDeclaration>,
1764+
type: TypeNode | undefined
1765+
) {
1766+
return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type);
1767+
}
1768+
17351769
// @api
17361770
function createTypeQueryNode(exprName: EntityName) {
17371771
const node = createBaseNode<TypeQueryNode>(SyntaxKind.TypeQuery);

src/compiler/parser.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -3368,16 +3368,29 @@ namespace ts {
33683368
return finishNode(factory.createParenthesizedType(type), pos);
33693369
}
33703370

3371+
function parseModifiersForConstructorType(): NodeArray<Modifier> | undefined {
3372+
let modifiers: NodeArray<Modifier> | undefined;
3373+
if (token() === SyntaxKind.AbstractKeyword) {
3374+
const pos = getNodePos();
3375+
nextToken();
3376+
const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos);
3377+
modifiers = createNodeArray<Modifier>([modifier], pos);
3378+
}
3379+
return modifiers;
3380+
}
3381+
33713382
function parseFunctionOrConstructorType(): TypeNode {
33723383
const pos = getNodePos();
33733384
const hasJSDoc = hasPrecedingJSDocComment();
3385+
const modifiers = parseModifiersForConstructorType();
33743386
const isConstructorType = parseOptional(SyntaxKind.NewKeyword);
33753387
const typeParameters = parseTypeParameters();
33763388
const parameters = parseParameters(SignatureFlags.Type);
33773389
const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false);
33783390
const node = isConstructorType
3379-
? factory.createConstructorTypeNode(typeParameters, parameters, type)
3391+
? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type)
33803392
: factory.createFunctionTypeNode(typeParameters, parameters, type);
3393+
if (!isConstructorType) (node as Mutable<Node>).modifiers = modifiers;
33813394
return withJSDoc(finishNode(node, pos), hasJSDoc);
33823395
}
33833396

@@ -3678,14 +3691,20 @@ namespace ts {
36783691
return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode);
36793692
}
36803693

3694+
function nextTokenIsNewKeyword(): boolean {
3695+
nextToken();
3696+
return token() === SyntaxKind.NewKeyword;
3697+
}
3698+
36813699
function isStartOfFunctionTypeOrConstructorType(): boolean {
36823700
if (token() === SyntaxKind.LessThanToken) {
36833701
return true;
36843702
}
36853703
if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) {
36863704
return true;
36873705
}
3688-
return token() === SyntaxKind.NewKeyword;
3706+
return token() === SyntaxKind.NewKeyword ||
3707+
token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword);
36893708
}
36903709

36913710
function skipParameterStart(): boolean {

src/compiler/transformers/declarations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ namespace ts {
10171017
return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
10181018
}
10191019
case SyntaxKind.ConstructorType: {
1020-
return cleanup(factory.updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
1020+
return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
10211021
}
10221022
case SyntaxKind.ImportType: {
10231023
if (!isLiteralImportTypeNode(input)) return cleanup(input);

src/compiler/types.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -5172,6 +5172,7 @@ namespace ts {
51725172
/* @internal */ constructSignatures?: readonly Signature[]; // Construct signatures of type
51735173
/* @internal */ stringIndexInfo?: IndexInfo; // String indexing info
51745174
/* @internal */ numberIndexInfo?: IndexInfo; // Numeric indexing info
5175+
/* @internal */ objectTypeWithoutAbstractConstructSignatures?: ObjectType;
51755176
}
51765177

51775178
/** Class and interface types (ObjectFlags.Class and ObjectFlags.Interface). */
@@ -5505,16 +5506,21 @@ namespace ts {
55055506
/* @internal */
55065507
export const enum SignatureFlags {
55075508
None = 0,
5509+
5510+
// Propagating flags
55085511
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
55095512
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
5510-
IsInnerCallChain = 1 << 2, // Indicates signature comes from a CallChain nested in an outer OptionalChain
5511-
IsOuterCallChain = 1 << 3, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
5512-
IsUntypedSignatureInJSFile = 1 << 4, // Indicates signature is from a js file and has no types
5513+
Abstract = 1 << 2, // Indicates signature comes from an abstract class, abstract construct signature, or abstract constructor type
5514+
5515+
// Non-propagating flags
5516+
IsInnerCallChain = 1 << 3, // Indicates signature comes from a CallChain nested in an outer OptionalChain
5517+
IsOuterCallChain = 1 << 4, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
5518+
IsUntypedSignatureInJSFile = 1 << 5, // Indicates signature is from a js file and has no types
55135519

5514-
// We do not propagate `IsInnerCallChain` to instantiated signatures, as that would result in us
5520+
// We do not propagate `IsInnerCallChain` or `IsOuterCallChain` to instantiated signatures, as that would result in us
55155521
// attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when
55165522
// instantiating the return type.
5517-
PropagatingFlags = HasRestParameter | HasLiteralTypes | IsUntypedSignatureInJSFile,
5523+
PropagatingFlags = HasRestParameter | HasLiteralTypes | Abstract | IsUntypedSignatureInJSFile,
55185524

55195525
CallChainFlags = IsInnerCallChain | IsOuterCallChain,
55205526
}
@@ -6879,7 +6885,11 @@ namespace ts {
68796885
updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray<TypeNode> | undefined): TypeReferenceNode;
68806886
createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): FunctionTypeNode;
68816887
updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): FunctionTypeNode;
6888+
createConstructorTypeNode(modifiers: readonly Modifier[] | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
6889+
/** @deprecated */
68826890
createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
6891+
updateConstructorTypeNode(node: ConstructorTypeNode, modifiers: readonly Modifier[] | undefined, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
6892+
/** @deprecated */
68836893
updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
68846894
createTypeQueryNode(exprName: EntityName): TypeQueryNode;
68856895
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode;

src/compiler/utilities.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -4751,7 +4751,7 @@ namespace ts {
47514751
return flags;
47524752
}
47534753

4754-
export function modifiersToFlags(modifiers: NodeArray<Modifier> | undefined) {
4754+
export function modifiersToFlags(modifiers: readonly Modifier[] | undefined) {
47554755
let flags = ModifierFlags.None;
47564756
if (modifiers) {
47574757
for (const modifier of modifiers) {
@@ -5452,11 +5452,6 @@ namespace ts {
54525452
});
54535453
}
54545454

5455-
// Return true if the given type is the constructor type for an abstract class
5456-
export function isAbstractConstructorType(type: Type): boolean {
5457-
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isAbstractConstructorSymbol(type.symbol);
5458-
}
5459-
54605455
export function isAbstractConstructorSymbol(symbol: Symbol): boolean {
54615456
if (symbol.flags & SymbolFlags.Class) {
54625457
const declaration = getClassLikeDeclarationOfSymbol(symbol);

src/compiler/visitorPublic.ts

+1
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ namespace ts {
485485

486486
case SyntaxKind.ConstructorType:
487487
return factory.updateConstructorTypeNode(<ConstructorTypeNode>node,
488+
nodesVisitor((<ConstructorTypeNode>node).modifiers, visitor, isModifier),
488489
nodesVisitor((<ConstructorTypeNode>node).typeParameters, visitor, isTypeParameterDeclaration),
489490
nodesVisitor((<ConstructorTypeNode>node).parameters, visitor, isParameterDeclaration),
490491
nodeVisitor((<ConstructorTypeNode>node).type, visitor, isTypeNode));

src/deprecatedCompat/deprecations.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,23 @@ namespace ts {
164164
export const updateFunctionTypeNode = Debug.deprecate(factory.updateFunctionTypeNode, factoryDeprecation);
165165

166166
/** @deprecated Use `factory.createConstructorTypeNode` or the factory supplied by your transformation context instead. */
167-
export const createConstructorTypeNode = Debug.deprecate(factory.createConstructorTypeNode, factoryDeprecation);
167+
export const createConstructorTypeNode = Debug.deprecate((
168+
typeParameters: readonly TypeParameterDeclaration[] | undefined,
169+
parameters: readonly ParameterDeclaration[],
170+
type: TypeNode
171+
) => {
172+
return factory.createConstructorTypeNode(/*modifiers*/ undefined, typeParameters, parameters, type);
173+
}, factoryDeprecation);
168174

169175
/** @deprecated Use `factory.updateConstructorTypeNode` or the factory supplied by your transformation context instead. */
170-
export const updateConstructorTypeNode = Debug.deprecate(factory.updateConstructorTypeNode, factoryDeprecation);
176+
export const updateConstructorTypeNode = Debug.deprecate((
177+
node: ConstructorTypeNode,
178+
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
179+
parameters: NodeArray<ParameterDeclaration>,
180+
type: TypeNode
181+
) => {
182+
return factory.updateConstructorTypeNode(node, node.modifiers, typeParameters, parameters, type);
183+
}, factoryDeprecation);
171184

172185
/** @deprecated Use `factory.createTypeQueryNode` or the factory supplied by your transformation context instead. */
173186
export const createTypeQueryNode = Debug.deprecate(factory.createTypeQueryNode, factoryDeprecation);

src/services/symbolDisplay.ts

+8
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ namespace ts.SymbolDisplay {
220220
pushSymbolKind(symbolKind);
221221
displayParts.push(spacePart());
222222
if (useConstructSignatures) {
223+
if (signature.flags & SignatureFlags.Abstract) {
224+
displayParts.push(keywordPart(SyntaxKind.AbstractKeyword));
225+
displayParts.push(spacePart());
226+
}
223227
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
224228
displayParts.push(spacePart());
225229
}
@@ -245,6 +249,10 @@ namespace ts.SymbolDisplay {
245249
displayParts.push(lineBreakPart());
246250
}
247251
if (useConstructSignatures) {
252+
if (signature.flags & SignatureFlags.Abstract) {
253+
displayParts.push(keywordPart(SyntaxKind.AbstractKeyword));
254+
displayParts.push(spacePart());
255+
}
248256
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
249257
displayParts.push(spacePart());
250258
}

0 commit comments

Comments
 (0)