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

Add support for abstract constructor types #36392

Merged
merged 9 commits into from
Jan 8, 2021
72 changes: 51 additions & 21 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ namespace ts {
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true);
}

export function formatSignatureFlags(flags: SignatureFlags | undefined): string {
return formatEnum(flags, (<any>ts).SignatureFlags, /*isFlags*/ true);
}

export function formatObjectFlags(flags: ObjectFlags | undefined): string {
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
}
Expand Down Expand Up @@ -573,6 +577,11 @@ namespace ts {
},
});

Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, {
__debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } },
__debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } }
});

const nodeConstructors = [
objectAllocator.getNodeConstructor(),
objectAllocator.getIdentifierConstructor(),
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,10 @@
"category": "Error",
"code": 2796
},
"A mixin class that extends from a type variable containing an abstract construct signature must also be declared 'abstract'.": {
"category": "Error",
"code": 2797
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,7 @@ namespace ts {

function emitConstructorType(node: ConstructorTypeNode) {
pushNameGenerationScope(node);
emitModifiers(node, node.modifiers);
writeKeyword("new");
writeSpace();
emitTypeParameters(node, node.typeParameters);
Expand Down
44 changes: 39 additions & 5 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1700,15 +1700,22 @@ namespace ts {
}

// @api
function createConstructorTypeNode(
function createConstructorTypeNode(...args: Parameters<typeof createConstructorTypeNode1 | typeof createConstructorTypeNode2>) {
return args.length === 4 ? createConstructorTypeNode1(...args) :
args.length === 3 ? createConstructorTypeNode2(...args) :
Debug.fail("Incorrect number of arguments specified.");
}

function createConstructorTypeNode1(
modifiers: readonly Modifier[] | undefined,
typeParameters: readonly TypeParameterDeclaration[] | undefined,
parameters: readonly ParameterDeclaration[],
type: TypeNode | undefined
): ConstructorTypeNode {
const node = createBaseSignatureDeclaration<ConstructorTypeNode>(
SyntaxKind.ConstructorType,
/*decorators*/ undefined,
/*modifiers*/ undefined,
modifiers,
/*name*/ undefined,
typeParameters,
parameters,
Expand All @@ -1718,20 +1725,47 @@ namespace ts {
return node;
}

/** @deprecated */
function createConstructorTypeNode2(
typeParameters: readonly TypeParameterDeclaration[] | undefined,
parameters: readonly ParameterDeclaration[],
type: TypeNode | undefined
): ConstructorTypeNode {
return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type);
}

// @api
function updateConstructorTypeNode(
function updateConstructorTypeNode(...args: Parameters<typeof updateConstructorTypeNode1 | typeof updateConstructorTypeNode2>) {
return args.length === 5 ? updateConstructorTypeNode1(...args) :
args.length === 4 ? updateConstructorTypeNode2(...args) :
Debug.fail("Incorrect number of arguments specified.");
}

function updateConstructorTypeNode1(
node: ConstructorTypeNode,
modifiers: readonly Modifier[] | undefined,
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
parameters: NodeArray<ParameterDeclaration>,
type: TypeNode | undefined
) {
return node.typeParameters !== typeParameters
return node.modifiers !== modifiers
|| node.typeParameters !== typeParameters
|| node.parameters !== parameters
|| node.type !== type
? updateBaseSignatureDeclaration(createConstructorTypeNode(typeParameters, parameters, type), node)
? updateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node)
: node;
}

/** @deprecated */
function updateConstructorTypeNode2(
node: ConstructorTypeNode,
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
parameters: NodeArray<ParameterDeclaration>,
type: TypeNode | undefined
) {
return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type);
}

// @api
function createTypeQueryNode(exprName: EntityName) {
const node = createBaseNode<TypeQueryNode>(SyntaxKind.TypeQuery);
Expand Down
23 changes: 21 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3368,16 +3368,29 @@ namespace ts {
return finishNode(factory.createParenthesizedType(type), pos);
}

function parseModifiersForConstructorType(): NodeArray<Modifier> | undefined {
let modifiers: NodeArray<Modifier> | undefined;
if (token() === SyntaxKind.AbstractKeyword) {
const pos = getNodePos();
nextToken();
const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos);
modifiers = createNodeArray<Modifier>([modifier], pos);
}
return modifiers;
}

function parseFunctionOrConstructorType(): TypeNode {
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
const modifiers = parseModifiersForConstructorType();
const isConstructorType = parseOptional(SyntaxKind.NewKeyword);
const typeParameters = parseTypeParameters();
const parameters = parseParameters(SignatureFlags.Type);
const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false);
const node = isConstructorType
? factory.createConstructorTypeNode(typeParameters, parameters, type)
? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type)
: factory.createFunctionTypeNode(typeParameters, parameters, type);
if (!isConstructorType) (node as Mutable<Node>).modifiers = modifiers;
return withJSDoc(finishNode(node, pos), hasJSDoc);
}

Expand Down Expand Up @@ -3678,14 +3691,20 @@ namespace ts {
return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode);
}

function nextTokenIsNewKeyword(): boolean {
nextToken();
return token() === SyntaxKind.NewKeyword;
}

function isStartOfFunctionTypeOrConstructorType(): boolean {
if (token() === SyntaxKind.LessThanToken) {
return true;
}
if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) {
return true;
}
return token() === SyntaxKind.NewKeyword;
return token() === SyntaxKind.NewKeyword ||
token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword);
}

function skipParameterStart(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ namespace ts {
return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
}
case SyntaxKind.ConstructorType: {
return cleanup(factory.updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
}
case SyntaxKind.ImportType: {
if (!isLiteralImportTypeNode(input)) return cleanup(input);
Expand Down
19 changes: 14 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5502,16 +5502,21 @@ namespace ts {
/* @internal */
export const enum SignatureFlags {
None = 0,

// Propagating flags
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
IsInnerCallChain = 1 << 2, // Indicates signature comes from a CallChain nested in an outer OptionalChain
IsOuterCallChain = 1 << 3, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
IsUntypedSignatureInJSFile = 1 << 4, // Indicates signature is from a js file and has no types
Abstract = 1 << 2, // Indicates signature comes from an abstract class, abstract construct signature, or abstract constructor type

// Non-propagating flags
IsInnerCallChain = 1 << 3, // Indicates signature comes from a CallChain nested in an outer OptionalChain
IsOuterCallChain = 1 << 4, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
IsUntypedSignatureInJSFile = 1 << 5, // Indicates signature is from a js file and has no types

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

CallChainFlags = IsInnerCallChain | IsOuterCallChain,
}
Expand Down Expand Up @@ -6878,7 +6883,11 @@ namespace ts {
updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray<TypeNode> | undefined): TypeReferenceNode;
createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): FunctionTypeNode;
updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): FunctionTypeNode;
createConstructorTypeNode(modifiers: readonly Modifier[] | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
/** @deprecated */
createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): ConstructorTypeNode;
updateConstructorTypeNode(node: ConstructorTypeNode, modifiers: readonly Modifier[] | undefined, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
/** @deprecated */
updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode): ConstructorTypeNode;
createTypeQueryNode(exprName: EntityName): TypeQueryNode;
updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode;
Expand Down
7 changes: 1 addition & 6 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4751,7 +4751,7 @@ namespace ts {
return flags;
}

export function modifiersToFlags(modifiers: NodeArray<Modifier> | undefined) {
export function modifiersToFlags(modifiers: readonly Modifier[] | undefined) {
let flags = ModifierFlags.None;
if (modifiers) {
for (const modifier of modifiers) {
Expand Down Expand Up @@ -5452,11 +5452,6 @@ namespace ts {
});
}

// Return true if the given type is the constructor type for an abstract class
export function isAbstractConstructorType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isAbstractConstructorSymbol(type.symbol);
}

export function isAbstractConstructorSymbol(symbol: Symbol): boolean {
if (symbol.flags & SymbolFlags.Class) {
const declaration = getClassLikeDeclarationOfSymbol(symbol);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ namespace ts {

case SyntaxKind.ConstructorType:
return factory.updateConstructorTypeNode(<ConstructorTypeNode>node,
nodesVisitor((<ConstructorTypeNode>node).modifiers, visitor, isModifier),
nodesVisitor((<ConstructorTypeNode>node).typeParameters, visitor, isTypeParameterDeclaration),
nodesVisitor((<ConstructorTypeNode>node).parameters, visitor, isParameterDeclaration),
nodeVisitor((<ConstructorTypeNode>node).type, visitor, isTypeNode));
Expand Down
17 changes: 15 additions & 2 deletions src/deprecatedCompat/deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,23 @@ namespace ts {
export const updateFunctionTypeNode = Debug.deprecate(factory.updateFunctionTypeNode, factoryDeprecation);

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

/** @deprecated Use `factory.updateConstructorTypeNode` or the factory supplied by your transformation context instead. */
export const updateConstructorTypeNode = Debug.deprecate(factory.updateConstructorTypeNode, factoryDeprecation);
export const updateConstructorTypeNode = Debug.deprecate((
node: ConstructorTypeNode,
typeParameters: NodeArray<TypeParameterDeclaration> | undefined,
parameters: NodeArray<ParameterDeclaration>,
type: TypeNode
) => {
return factory.updateConstructorTypeNode(node, node.modifiers, typeParameters, parameters, type);
}, factoryDeprecation);

/** @deprecated Use `factory.createTypeQueryNode` or the factory supplied by your transformation context instead. */
export const createTypeQueryNode = Debug.deprecate(factory.createTypeQueryNode, factoryDeprecation);
Expand Down
8 changes: 8 additions & 0 deletions src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ namespace ts.SymbolDisplay {
pushSymbolKind(symbolKind);
displayParts.push(spacePart());
if (useConstructSignatures) {
if (signature.flags & SignatureFlags.Abstract) {
displayParts.push(keywordPart(SyntaxKind.AbstractKeyword));
displayParts.push(spacePart());
}
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
Expand All @@ -245,6 +249,10 @@ namespace ts.SymbolDisplay {
displayParts.push(lineBreakPart());
}
if (useConstructSignatures) {
if (signature.flags & SignatureFlags.Abstract) {
displayParts.push(keywordPart(SyntaxKind.AbstractKeyword));
displayParts.push(spacePart());
}
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
Expand Down
Loading