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 'extends' clause to 'infer' type #48112

Merged
merged 7 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35593,6 +35593,22 @@ namespace ts {
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
}
checkSourceElement(node.typeParameter);
const symbol = getSymbolOfNode(node.typeParameter);
if (symbol.declarations && symbol.declarations.length > 1) {
const links = getSymbolLinks(symbol);
if (!links.typeParametersChecked) {
links.typeParametersChecked = true;
const typeParameter = getDeclaredTypeOfTypeParameter(symbol);
const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter);
if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) {
// Report an error on every conflicting declaration.
const name = symbolToString(symbol);
for (const declaration of declarations) {
error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name);
}
}
}
}
registerForUnusedIdentifiersCheck(node);
}

Expand Down Expand Up @@ -39080,7 +39096,7 @@ namespace ts {
}

const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) {
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) {
// Report an error on every conflicting declaration.
const name = symbolToString(symbol);
for (const declaration of declarations) {
Expand All @@ -39090,13 +39106,13 @@ namespace ts {
}
}

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

for (const declaration of declarations) {
// If this declaration has too few or too many type parameters, we report an error
const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
const sourceParameters = getTypeParameterDeclarations(declaration);
const numTypeParameters = sourceParameters.length;
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3429,6 +3429,10 @@
"category": "Error",
"code": 2837
},
"All declarations of '{0}' must have identical constraints.": {
"category": "Error",
"code": 2838
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
68 changes: 49 additions & 19 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,9 @@ namespace ts {
let currentParenthesizerRule: ((node: Node) => Node) | undefined;
const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment");
const parenthesizer = factory.parenthesizer;
const typeArgumentParenthesizerRuleSelector: OrdinalParentheizerRuleSelector<Node> = {
select: index => index === 0 ? parenthesizer.parenthesizeLeadingTypeArgument : undefined
};
const emitBinaryExpression = createEmitBinaryExpression();

reset();
Expand Down Expand Up @@ -2241,7 +2244,7 @@ namespace ts {
}

function emitArrayType(node: ArrayTypeNode) {
emit(node.elementType, parenthesizer.parenthesizeElementTypeOfArrayType);
emit(node.elementType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType);
writePunctuation("[");
writePunctuation("]");
}
Expand All @@ -2254,7 +2257,7 @@ namespace ts {
function emitTupleType(node: TupleTypeNode) {
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node);
const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements;
emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty);
emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty, parenthesizer.parenthesizeElementTypeOfTupleType);
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node);
}

Expand All @@ -2268,24 +2271,24 @@ namespace ts {
}

function emitOptionalType(node: OptionalTypeNode) {
emit(node.type, parenthesizer.parenthesizeElementTypeOfArrayType);
emit(node.type, parenthesizer.parenthesizeTypeOfOptionalType);
writePunctuation("?");
}

function emitUnionType(node: UnionTypeNode) {
emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType);
emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfUnionType);
}

function emitIntersectionType(node: IntersectionTypeNode) {
emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType);
emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfIntersectionType);
}

function emitConditionalType(node: ConditionalTypeNode) {
emit(node.checkType, parenthesizer.parenthesizeMemberOfConditionalType);
emit(node.checkType, parenthesizer.parenthesizeCheckTypeOfConditionalType);
writeSpace();
writeKeyword("extends");
writeSpace();
emit(node.extendsType, parenthesizer.parenthesizeMemberOfConditionalType);
emit(node.extendsType, parenthesizer.parenthesizeExtendsTypeOfConditionalType);
writeSpace();
writePunctuation("?");
writeSpace();
Expand Down Expand Up @@ -2315,11 +2318,15 @@ namespace ts {
function emitTypeOperator(node: TypeOperatorNode) {
writeTokenText(node.operator, writeKeyword);
writeSpace();
emit(node.type, parenthesizer.parenthesizeMemberOfElementType);

const parenthesizerRule = node.operator === SyntaxKind.ReadonlyKeyword ?
parenthesizer.parenthesizeOperandOfReadonlyTypeOperator :
parenthesizer.parenthesizeOperandOfTypeOperator;
emit(node.type, parenthesizerRule);
}

function emitIndexedAccessType(node: IndexedAccessTypeNode) {
emit(node.objectType, parenthesizer.parenthesizeMemberOfElementType);
emit(node.objectType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType);
writePunctuation("[");
emit(node.indexType);
writePunctuation("]");
Expand Down Expand Up @@ -4255,7 +4262,7 @@ namespace ts {
}

function emitTypeArguments(parentNode: Node, typeArguments: NodeArray<TypeNode> | undefined) {
emitList(parentNode, typeArguments, ListFormat.TypeArguments, parenthesizer.parenthesizeMemberOfElementType);
emitList(parentNode, typeArguments, ListFormat.TypeArguments, typeArgumentParenthesizerRuleSelector);
}

function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray<TypeParameterDeclaration> | undefined) {
Expand Down Expand Up @@ -4323,15 +4330,15 @@ namespace ts {
}
}

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

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

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) {
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) {
const isUndefined = children === undefined;
if (isUndefined && format & ListFormat.OptionalIfUndefined) {
return;
Expand Down Expand Up @@ -4387,6 +4394,8 @@ namespace ts {
increaseIndent();
}

const emitListItem = getEmitListItem(emit, parenthesizerRule);

// Emit each child.
let previousSibling: Node | undefined;
let previousSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>;
Expand Down Expand Up @@ -4442,12 +4451,7 @@ namespace ts {
}

nextListElementPos = child.pos;
if (emit.length === 1) {
emit(child);
}
else {
emit(child, parenthesizerRule);
}
emitListItem(child, emit, parenthesizerRule, i);

if (shouldDecreaseIndentAfterEmit) {
decreaseIndent();
Expand Down Expand Up @@ -5889,4 +5893,30 @@ namespace ts {
CountMask = 0x0FFFFFFF, // Temp variable counter
_i = 0x10000000, // Use/preference flag for '_i'
}

interface OrdinalParentheizerRuleSelector<T extends Node> {
select(index: number): ((node: T) => T) | undefined;
}

type ParenthesizerRule<T extends Node> = (node: T) => T;

type ParenthesizerRuleOrSelector<T extends Node> = OrdinalParentheizerRuleSelector<T> | ParenthesizerRule<T>;

function emitListItemNoParenthesizer(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, _parenthesizerRule: ParenthesizerRuleOrSelector<Node> | undefined, _index: number) {
emit(node);
}

function emitListItemWithParenthesizerRuleSelector(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRuleSelector: OrdinalParentheizerRuleSelector<Node>, index: number) {
emit(node, parenthesizerRuleSelector.select(index));
}

function emitListItemWithParenthesizerRule(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: ParenthesizerRule<Node> | undefined, _index: number) {
emit(node, parenthesizerRule);
}

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 {
return emit.length === 1 ? emitListItemNoParenthesizer :
typeof parenthesizerRule === "object" ? emitListItemWithParenthesizerRuleSelector :
emitListItemWithParenthesizerRule;
}
}
Loading