Skip to content

Commit 7da80d7

Browse files
authored
Add 'extends' clause to 'infer' type (#48112)
* Add 'extends' clause to 'infer' type * Revise parse for infer..extends, improve parenthesizer * More aggressive parens to match existing DT tests * tests 'infer' constraint using outer type parameter * Adds a test showing 'infer' cannot reference other 'infer' in same 'extends' * Emit extends clause for synthetic infer typesduring declaration emit
1 parent 6e0447f commit 7da80d7

File tree

56 files changed

+1946
-240
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1946
-240
lines changed

src/compiler/checker.ts

+35-6
Original file line numberDiff line numberDiff line change
@@ -5009,7 +5009,20 @@ namespace ts {
50095009
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
50105010
if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) {
50115011
context.approximateLength += (symbolName(type.symbol).length + 6);
5012-
return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined));
5012+
let constraintNode: TypeNode | undefined;
5013+
const constraint = getConstraintOfTypeParameter(type as TypeParameter);
5014+
if (constraint) {
5015+
// If the infer type has a constraint that is not the same as the constraint
5016+
// we would have normally inferred based on context, we emit the constraint
5017+
// using `infer T extends ?`. We omit inferred constraints from type references
5018+
// as they may be elided.
5019+
const inferredConstraint = getInferredTypeParameterConstraint(type as TypeParameter, /*omitTypeReferences*/ true);
5020+
if (!(inferredConstraint && isTypeIdenticalTo(constraint, inferredConstraint))) {
5021+
context.approximateLength += 9;
5022+
constraintNode = constraint && typeToTypeNodeHelper(constraint, context);
5023+
}
5024+
}
5025+
return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, constraintNode));
50135026
}
50145027
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
50155028
type.flags & TypeFlags.TypeParameter &&
@@ -13257,7 +13270,7 @@ namespace ts {
1325713270
return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0];
1325813271
}
1325913272

13260-
function getInferredTypeParameterConstraint(typeParameter: TypeParameter) {
13273+
function getInferredTypeParameterConstraint(typeParameter: TypeParameter, omitTypeReferences?: boolean) {
1326113274
let inferences: Type[] | undefined;
1326213275
if (typeParameter.symbol?.declarations) {
1326313276
for (const declaration of typeParameter.symbol.declarations) {
@@ -13267,7 +13280,7 @@ namespace ts {
1326713280
// corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are
1326813281
// present, we form an intersection of the inferred constraint types.
1326913282
const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent);
13270-
if (grandParent.kind === SyntaxKind.TypeReference) {
13283+
if (grandParent.kind === SyntaxKind.TypeReference && !omitTypeReferences) {
1327113284
const typeReference = grandParent as TypeReferenceNode;
1327213285
const typeParameters = getTypeParametersForTypeReference(typeReference);
1327313286
if (typeParameters) {
@@ -35614,6 +35627,22 @@ namespace ts {
3561435627
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
3561535628
}
3561635629
checkSourceElement(node.typeParameter);
35630+
const symbol = getSymbolOfNode(node.typeParameter);
35631+
if (symbol.declarations && symbol.declarations.length > 1) {
35632+
const links = getSymbolLinks(symbol);
35633+
if (!links.typeParametersChecked) {
35634+
links.typeParametersChecked = true;
35635+
const typeParameter = getDeclaredTypeOfTypeParameter(symbol);
35636+
const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter);
35637+
if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) {
35638+
// Report an error on every conflicting declaration.
35639+
const name = symbolToString(symbol);
35640+
for (const declaration of declarations) {
35641+
error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name);
35642+
}
35643+
}
35644+
}
35645+
}
3561735646
registerForUnusedIdentifiersCheck(node);
3561835647
}
3561935648

@@ -39124,7 +39153,7 @@ namespace ts {
3912439153
}
3912539154

3912639155
const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
39127-
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) {
39156+
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) {
3912839157
// Report an error on every conflicting declaration.
3912939158
const name = symbolToString(symbol);
3913039159
for (const declaration of declarations) {
@@ -39134,13 +39163,13 @@ namespace ts {
3913439163
}
3913539164
}
3913639165

39137-
function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) {
39166+
function areTypeParametersIdentical<T extends DeclarationWithTypeParameters | TypeParameterDeclaration>(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) {
3913839167
const maxTypeArgumentCount = length(targetParameters);
3913939168
const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters);
3914039169

3914139170
for (const declaration of declarations) {
3914239171
// If this declaration has too few or too many type parameters, we report an error
39143-
const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
39172+
const sourceParameters = getTypeParameterDeclarations(declaration);
3914439173
const numTypeParameters = sourceParameters.length;
3914539174
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
3914639175
return false;

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3433,6 +3433,10 @@
34333433
"category": "Error",
34343434
"code": 2837
34353435
},
3436+
"All declarations of '{0}' must have identical constraints.": {
3437+
"category": "Error",
3438+
"code": 2838
3439+
},
34363440

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

src/compiler/emitter.ts

+49-19
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,9 @@ namespace ts {
909909
let currentParenthesizerRule: ((node: Node) => Node) | undefined;
910910
const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment");
911911
const parenthesizer = factory.parenthesizer;
912+
const typeArgumentParenthesizerRuleSelector: OrdinalParentheizerRuleSelector<Node> = {
913+
select: index => index === 0 ? parenthesizer.parenthesizeLeadingTypeArgument : undefined
914+
};
912915
const emitBinaryExpression = createEmitBinaryExpression();
913916

914917
reset();
@@ -2241,7 +2244,7 @@ namespace ts {
22412244
}
22422245

22432246
function emitArrayType(node: ArrayTypeNode) {
2244-
emit(node.elementType, parenthesizer.parenthesizeElementTypeOfArrayType);
2247+
emit(node.elementType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType);
22452248
writePunctuation("[");
22462249
writePunctuation("]");
22472250
}
@@ -2254,7 +2257,7 @@ namespace ts {
22542257
function emitTupleType(node: TupleTypeNode) {
22552258
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node);
22562259
const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements;
2257-
emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty);
2260+
emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty, parenthesizer.parenthesizeElementTypeOfTupleType);
22582261
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node);
22592262
}
22602263

@@ -2268,24 +2271,24 @@ namespace ts {
22682271
}
22692272

22702273
function emitOptionalType(node: OptionalTypeNode) {
2271-
emit(node.type, parenthesizer.parenthesizeElementTypeOfArrayType);
2274+
emit(node.type, parenthesizer.parenthesizeTypeOfOptionalType);
22722275
writePunctuation("?");
22732276
}
22742277

22752278
function emitUnionType(node: UnionTypeNode) {
2276-
emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType);
2279+
emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfUnionType);
22772280
}
22782281

22792282
function emitIntersectionType(node: IntersectionTypeNode) {
2280-
emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType);
2283+
emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfIntersectionType);
22812284
}
22822285

22832286
function emitConditionalType(node: ConditionalTypeNode) {
2284-
emit(node.checkType, parenthesizer.parenthesizeMemberOfConditionalType);
2287+
emit(node.checkType, parenthesizer.parenthesizeCheckTypeOfConditionalType);
22852288
writeSpace();
22862289
writeKeyword("extends");
22872290
writeSpace();
2288-
emit(node.extendsType, parenthesizer.parenthesizeMemberOfConditionalType);
2291+
emit(node.extendsType, parenthesizer.parenthesizeExtendsTypeOfConditionalType);
22892292
writeSpace();
22902293
writePunctuation("?");
22912294
writeSpace();
@@ -2315,11 +2318,15 @@ namespace ts {
23152318
function emitTypeOperator(node: TypeOperatorNode) {
23162319
writeTokenText(node.operator, writeKeyword);
23172320
writeSpace();
2318-
emit(node.type, parenthesizer.parenthesizeMemberOfElementType);
2321+
2322+
const parenthesizerRule = node.operator === SyntaxKind.ReadonlyKeyword ?
2323+
parenthesizer.parenthesizeOperandOfReadonlyTypeOperator :
2324+
parenthesizer.parenthesizeOperandOfTypeOperator;
2325+
emit(node.type, parenthesizerRule);
23192326
}
23202327

23212328
function emitIndexedAccessType(node: IndexedAccessTypeNode) {
2322-
emit(node.objectType, parenthesizer.parenthesizeMemberOfElementType);
2329+
emit(node.objectType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType);
23232330
writePunctuation("[");
23242331
emit(node.indexType);
23252332
writePunctuation("]");
@@ -4256,7 +4263,7 @@ namespace ts {
42564263
}
42574264

42584265
function emitTypeArguments(parentNode: Node, typeArguments: NodeArray<TypeNode> | undefined) {
4259-
emitList(parentNode, typeArguments, ListFormat.TypeArguments, parenthesizer.parenthesizeMemberOfElementType);
4266+
emitList(parentNode, typeArguments, ListFormat.TypeArguments, typeArgumentParenthesizerRuleSelector);
42604267
}
42614268

42624269
function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray<TypeParameterDeclaration> | undefined) {
@@ -4324,15 +4331,15 @@ namespace ts {
43244331
}
43254332
}
43264333

4327-
function emitList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: (node: Node) => Node, start?: number, count?: number) {
4334+
function emitList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector<Node>, start?: number, count?: number) {
43284335
emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count);
43294336
}
43304337

4331-
function emitExpressionList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: (node: Expression) => Expression, start?: number, count?: number) {
4338+
function emitExpressionList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector<Expression>, start?: number, count?: number) {
43324339
emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count);
43334340
}
43344341

4335-
function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule: ((node: Node) => Node) | undefined, start = 0, count = children ? children.length - start : 0) {
4342+
function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector<Node> | undefined, start = 0, count = children ? children.length - start : 0) {
43364343
const isUndefined = children === undefined;
43374344
if (isUndefined && format & ListFormat.OptionalIfUndefined) {
43384345
return;
@@ -4388,6 +4395,8 @@ namespace ts {
43884395
increaseIndent();
43894396
}
43904397

4398+
const emitListItem = getEmitListItem(emit, parenthesizerRule);
4399+
43914400
// Emit each child.
43924401
let previousSibling: Node | undefined;
43934402
let previousSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>;
@@ -4443,12 +4452,7 @@ namespace ts {
44434452
}
44444453

44454454
nextListElementPos = child.pos;
4446-
if (emit.length === 1) {
4447-
emit(child);
4448-
}
4449-
else {
4450-
emit(child, parenthesizerRule);
4451-
}
4455+
emitListItem(child, emit, parenthesizerRule, i);
44524456

44534457
if (shouldDecreaseIndentAfterEmit) {
44544458
decreaseIndent();
@@ -5890,4 +5894,30 @@ namespace ts {
58905894
CountMask = 0x0FFFFFFF, // Temp variable counter
58915895
_i = 0x10000000, // Use/preference flag for '_i'
58925896
}
5897+
5898+
interface OrdinalParentheizerRuleSelector<T extends Node> {
5899+
select(index: number): ((node: T) => T) | undefined;
5900+
}
5901+
5902+
type ParenthesizerRule<T extends Node> = (node: T) => T;
5903+
5904+
type ParenthesizerRuleOrSelector<T extends Node> = OrdinalParentheizerRuleSelector<T> | ParenthesizerRule<T>;
5905+
5906+
function emitListItemNoParenthesizer(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, _parenthesizerRule: ParenthesizerRuleOrSelector<Node> | undefined, _index: number) {
5907+
emit(node);
5908+
}
5909+
5910+
function emitListItemWithParenthesizerRuleSelector(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRuleSelector: OrdinalParentheizerRuleSelector<Node>, index: number) {
5911+
emit(node, parenthesizerRuleSelector.select(index));
5912+
}
5913+
5914+
function emitListItemWithParenthesizerRule(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: ParenthesizerRule<Node> | undefined, _index: number) {
5915+
emit(node, parenthesizerRule);
5916+
}
5917+
5918+
function getEmitListItem<T extends Node, R extends ParenthesizerRuleOrSelector<T> | undefined>(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R): (node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R, index: number) => void {
5919+
return emit.length === 1 ? emitListItemNoParenthesizer :
5920+
typeof parenthesizerRule === "object" ? emitListItemWithParenthesizerRuleSelector :
5921+
emitListItemWithParenthesizerRule;
5922+
}
58935923
}

0 commit comments

Comments
 (0)