Skip to content

Commit 254e7bd

Browse files
committed
Initial basic support for higher kinded types.
Not close to finished but it does seem to work for simple happy path cases.
1 parent cee4289 commit 254e7bd

26 files changed

+2347
-8
lines changed

Diff for: src/compiler/binder.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2702,6 +2702,12 @@ namespace ts {
27022702
bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node));
27032703
}
27042704
}
2705+
else if (node.parent.kind === SyntaxKind.TypeParameter) {
2706+
if (!node.parent.locals) {
2707+
node.parent.locals = createSymbolTable();
2708+
}
2709+
declareSymbol(node.parent.locals, node.parent.symbol, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
2710+
}
27052711
else {
27062712
declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
27072713
}

Diff for: src/compiler/checker.ts

+127-6
Original file line numberDiff line numberDiff line change
@@ -5030,6 +5030,7 @@ namespace ts {
50305030
case SyntaxKind.JSDocTemplateTag:
50315031
case SyntaxKind.MappedType:
50325032
case SyntaxKind.ConditionalType:
5033+
case SyntaxKind.TypeParameter:
50335034
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
50345035
if (node.kind === SyntaxKind.MappedType) {
50355036
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
@@ -5058,8 +5059,8 @@ namespace ts {
50585059
let result: TypeParameter[];
50595060
for (const node of symbol.declarations) {
50605061
if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration ||
5061-
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) {
5062-
const declaration = <InterfaceDeclaration | TypeAliasDeclaration>node;
5062+
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || node.kind === SyntaxKind.TypeParameter) {
5063+
const declaration = <InterfaceDeclaration | TypeAliasDeclaration | TypeParameterDeclaration>node;
50635064
const typeParameters = getEffectiveTypeParameterDeclarations(declaration);
50645065
if (typeParameters) {
50655066
result = appendTypeParameters(result, typeParameters);
@@ -5484,6 +5485,13 @@ namespace ts {
54845485
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
54855486
type.symbol = symbol;
54865487
links.declaredType = type;
5488+
5489+
const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
5490+
if (typeParameters) {
5491+
links.instantiations = createMap<TypeParameter>();
5492+
links.instantiations.set(getTypeListId(typeParameters), type);
5493+
type.typeParameters = typeParameters;
5494+
}
54875495
}
54885496
return <TypeParameter>links.declaredType;
54895497
}
@@ -7445,8 +7453,14 @@ namespace ts {
74457453
}
74467454
else {
74477455
const constraintDeclaration = getConstraintDeclaration(typeParameter);
7448-
typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) :
7456+
let constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) :
74497457
getInferredTypeParameterConstraint(typeParameter) || noConstraintType;
7458+
if (constraint !== noConstraintType && typeParameter.typeParameters) {
7459+
const apparentMapper = createTypeMapper(typeParameter.typeParameters, map(typeParameter.typeParameters, getApparentType));
7460+
const argumentMapper = typeParameter.typeArguments ? createTypeMapper(typeParameter.typeParameters, typeParameter.typeArguments) : identityMapper;
7461+
constraint = instantiateType(constraint, combineTypeMappers(argumentMapper, apparentMapper));
7462+
}
7463+
typeParameter.constraint = constraint;
74507464
}
74517465
}
74527466
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
@@ -7528,6 +7542,9 @@ namespace ts {
75287542
const typeParameters = type.localTypeParameters;
75297543
if (typeParameters) {
75307544
const numTypeArguments = length(node.typeArguments);
7545+
if (numTypeArguments === 0 && isGenericTypeArgument(node)) {
7546+
return type;
7547+
}
75317548
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
75327549
const isJs = isInJavaScriptFile(node);
75337550
const isJsImplicitAny = !noImplicitAny && isJs;
@@ -7594,6 +7611,56 @@ namespace ts {
75947611
return checkNoTypeArguments(node, symbol) ? type : unknownType;
75957612
}
75967613

7614+
function getTypeFromTypeParameterReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type {
7615+
const type = <TypeParameter>getDeclaredTypeOfSymbol(symbol);
7616+
const typeParameters = type.typeParameters;
7617+
if (typeParameters) {
7618+
const numTypeArguments = length(typeArguments);
7619+
if (numTypeArguments === 0 && isGenericTypeArgument(node)) {
7620+
return type;
7621+
}
7622+
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
7623+
if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) {
7624+
error(node,
7625+
minTypeArgumentCount === typeParameters.length
7626+
? Diagnostics.Generic_type_0_requires_1_type_argument_s
7627+
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
7628+
symbolToString(symbol),
7629+
minTypeArgumentCount,
7630+
typeParameters.length);
7631+
return unknownType;
7632+
}
7633+
const id = getTypeListId(typeArguments);
7634+
const links = getSymbolLinks(symbol);
7635+
let reference = <TypeParameter>links.instantiations.get(id);
7636+
if (!reference) {
7637+
reference = getTypeParameterReference(type, typeArguments);
7638+
links.instantiations.set(id, reference);
7639+
}
7640+
return reference;
7641+
}
7642+
else if (!checkNoTypeArguments(node, symbol)) {
7643+
return unknownType;
7644+
}
7645+
return getConstrainedTypeVariable(type, node);
7646+
}
7647+
7648+
function getTypeParameterReference(genericTypeParameter: TypeParameter, typeArguments: Type[]): TypeParameter {
7649+
Debug.assert(genericTypeParameter.genericTarget === undefined && genericTypeParameter.typeParameters && genericTypeParameter.typeParameters.length === typeArguments.length);
7650+
const id = getTypeListId(typeArguments);
7651+
const links = getSymbolLinks(genericTypeParameter.symbol);
7652+
let reference = <TypeParameter>links.instantiations.get(id);
7653+
if (!reference) {
7654+
reference = <TypeParameter>createType(TypeFlags.TypeParameter);
7655+
reference.symbol = genericTypeParameter.symbol;
7656+
reference.typeParameters = genericTypeParameter.typeParameters;
7657+
reference.typeArguments = typeArguments;
7658+
reference.genericTarget = genericTypeParameter;
7659+
links.instantiations.set(id, reference);
7660+
}
7661+
return reference;
7662+
}
7663+
75977664
function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined {
75987665
switch (node.kind) {
75997666
case SyntaxKind.TypeReference:
@@ -7686,6 +7753,10 @@ namespace ts {
76867753
(symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
76877754
return getInferredClassType(symbol);
76887755
}
7756+
7757+
if (symbol.flags & SymbolFlags.TypeParameter) {
7758+
return getTypeFromTypeParameterReference(node, symbol, typeArguments);
7759+
}
76897760
}
76907761

76917762
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
@@ -9304,6 +9375,9 @@ namespace ts {
93049375
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
93059376
const result = <TypeParameter>createType(TypeFlags.TypeParameter);
93069377
result.symbol = typeParameter.symbol;
9378+
result.typeParameters = typeParameter.typeParameters;
9379+
result.typeArguments = typeParameter.typeArguments;
9380+
result.genericTarget = typeParameter.genericTarget;
93079381
result.target = typeParameter;
93089382
return result;
93099383
}
@@ -9538,7 +9612,27 @@ namespace ts {
95389612
function instantiateType(type: Type, mapper: TypeMapper): Type {
95399613
if (type && mapper && mapper !== identityMapper) {
95409614
if (type.flags & TypeFlags.TypeParameter) {
9541-
return mapper(<TypeParameter>type);
9615+
if ((<TypeParameter>type).typeParameters && (<TypeParameter>type).genericTarget) {
9616+
const newType = mapper((<TypeParameter>type).genericTarget);
9617+
if (newType.flags & TypeFlags.TypeParameter && (<TypeParameter>newType).typeParameters) {
9618+
// Mapper did not instantiate the generic type so just create another reference to it.
9619+
const newTypeArguments = instantiateTypes((<TypeParameter>type).typeArguments, mapper);
9620+
return getTypeParameterReference(<TypeParameter>newType, newTypeArguments);
9621+
}
9622+
const orginalNewTypeArguments = (<TypeReference>newType).typeArguments;
9623+
if (!orginalNewTypeArguments) {
9624+
// this means it was instantiated as anonymous type without type arguments.
9625+
return newType;
9626+
}
9627+
if (length(orginalNewTypeArguments) !== length((<TypeParameter>type).typeArguments)) {
9628+
return newType;
9629+
}
9630+
const newTypeArguments = instantiateTypes((<TypeParameter>type).typeArguments, mapper);
9631+
return createTypeReference((<TypeReference>newType).target, newTypeArguments);
9632+
}
9633+
else {
9634+
return mapper(<TypeParameter>type);
9635+
}
95429636
}
95439637
if (type.flags & TypeFlags.Object) {
95449638
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
@@ -9817,7 +9911,8 @@ namespace ts {
98179911
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
98189912
const related = callbacks ?
98199913
compareSignaturesRelated(targetSig, sourceSig, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
9820-
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
9914+
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) ||
9915+
compareTypes(targetType, sourceType, reportErrors);
98219916
if (!related) {
98229917
if (reportErrors) {
98239918
errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -10808,6 +10903,9 @@ namespace ts {
1080810903
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
1080910904
if (relation !== identityRelation) {
1081010905
source = getApparentType(source);
10906+
if (target.flags & TypeFlags.TypeParameter && (<TypeParameter>target).typeParameters) {
10907+
target = getApparentType(target);
10908+
}
1081110909
}
1081210910
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
1081310911
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
@@ -12260,8 +12358,13 @@ namespace ts {
1226012358
inference.topLevel = false;
1226112359
}
1226212360
}
12263-
return;
1226412361
}
12362+
if (target.flags & TypeFlags.TypeParameter && (<TypeParameter>target).typeArguments && forEach((<TypeParameter>target).typeArguments, couldContainTypeVariables) && getConstraintOfTypeParameter(<TypeParameter>target)) {
12363+
// This is a generic type parameter reference and it might contain other type parameters to infer
12364+
// so infer from the constraint of the type parameter (which is where the other type parameters would be if they are referenced)
12365+
inferFromTypes(source, getConstraintOfTypeParameter(<TypeParameter>target));
12366+
}
12367+
return;
1226512368
}
1226612369
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
1226712370
// If source and target are references to the same generic type, infer from type arguments
@@ -12378,6 +12481,7 @@ namespace ts {
1237812481

1237912482
function getInferenceInfoForType(type: Type) {
1238012483
if (type.flags & TypeFlags.TypeVariable) {
12484+
type = (<TypeParameter>type).genericTarget || type;
1238112485
for (const inference of inferences) {
1238212486
if (type === inference.typeParameter) {
1238312487
return inference;
@@ -20573,6 +20677,7 @@ namespace ts {
2057320677

2057420678
checkSourceElement(node.constraint);
2057520679
checkSourceElement(node.default);
20680+
checkTypeParameters(node.typeParameters);
2057620681
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
2057720682
if (!hasNonCircularBaseConstraint(typeParameter)) {
2057820683
error(node.constraint, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
@@ -21212,6 +21317,21 @@ namespace ts {
2121221317
return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters)));
2121321318
}
2121421319

21320+
function isGenericTypeArgument(node: NodeWithTypeArguments): boolean {
21321+
if (!isTypeReferenceType(node.parent)) {
21322+
return false;
21323+
}
21324+
const name = getTypeReferenceName(node.parent);
21325+
const identifier = getFirstIdentifier(name);
21326+
const symbol = resolveEntityName(identifier, SymbolFlags.Type, /*ignoreErrors*/ true);
21327+
const typeParameters = symbol && getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
21328+
if (!typeParameters) {
21329+
return false;
21330+
}
21331+
const typeParameter = typeParameters[node.parent.typeArguments.indexOf(node)!];
21332+
return !!length(typeParameter.typeParameters);
21333+
}
21334+
2121521335
function checkTypeQuery(node: TypeQueryNode) {
2121621336
getTypeFromTypeQueryNode(node);
2121721337
}
@@ -23822,6 +23942,7 @@ namespace ts {
2382223942
}
2382323943
}
2382423944

23945+
// TODO: Update to handle type parameters with type parameters
2382523946
function areTypeParametersIdentical(declarations: ReadonlyArray<ClassDeclaration | InterfaceDeclaration>, targetParameters: TypeParameter[]) {
2382623947
const maxTypeArgumentCount = length(targetParameters);
2382723948
const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters);

Diff for: src/compiler/parser.ts

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace ts {
7979
visitNode(cbNode, (<QualifiedName>node).right);
8080
case SyntaxKind.TypeParameter:
8181
return visitNode(cbNode, (<TypeParameterDeclaration>node).name) ||
82+
visitNodes(cbNode, cbNodes, (<TypeParameterDeclaration>node).typeParameters) ||
8283
visitNode(cbNode, (<TypeParameterDeclaration>node).constraint) ||
8384
visitNode(cbNode, (<TypeParameterDeclaration>node).default) ||
8485
visitNode(cbNode, (<TypeParameterDeclaration>node).expression);
@@ -2306,6 +2307,7 @@ namespace ts {
23062307
function parseTypeParameter(): TypeParameterDeclaration {
23072308
const node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
23082309
node.name = parseIdentifier();
2310+
node.typeParameters = parseTypeParameters();
23092311
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
23102312
// It's not uncommon for people to write improper constraints to a generic. If the
23112313
// user writes a constraint that is an expression and not an actual type, then parse

Diff for: src/compiler/types.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ namespace ts {
798798
kind: SyntaxKind.TypeParameter;
799799
parent?: DeclarationWithTypeParameters | InferTypeNode;
800800
name: Identifier;
801+
typeParameters?: NodeArray<TypeParameterDeclaration>;
801802
constraint?: TypeNode;
802803
default?: TypeNode;
803804

@@ -2051,7 +2052,7 @@ namespace ts {
20512052

20522053
export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
20532054

2054-
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
2055+
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | TypeParameterDeclaration;
20552056

20562057
export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
20572058
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
@@ -3901,6 +3902,12 @@ namespace ts {
39013902
isThisType?: boolean;
39023903
/* @internal */
39033904
resolvedDefaultType?: Type;
3905+
/* @internal */
3906+
typeParameters?: TypeParameter[];
3907+
/* @internal */
3908+
typeArguments?: TypeParameter[]; // Only set for references
3909+
/* @internal */
3910+
genericTarget?: TypeParameter; // This is the original generic type parameter a type parameter reference points to
39043911
}
39053912

39063913
// Indexed access types (TypeFlags.IndexedAccess)

Diff for: src/compiler/utilities.ts

+1
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ namespace ts {
527527
case SyntaxKind.SetAccessor:
528528
case SyntaxKind.FunctionExpression:
529529
case SyntaxKind.ArrowFunction:
530+
case SyntaxKind.TypeParameter:
530531
return true;
531532
default:
532533
assertTypeIsNever(node);

Diff for: tests/baselines/reference/api/typescript.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ declare namespace ts {
527527
kind: SyntaxKind.TypeParameter;
528528
parent?: DeclarationWithTypeParameters | InferTypeNode;
529529
name: Identifier;
530+
typeParameters?: NodeArray<TypeParameterDeclaration>;
530531
constraint?: TypeNode;
531532
default?: TypeNode;
532533
expression?: Expression;
@@ -1277,7 +1278,7 @@ declare namespace ts {
12771278
block: Block;
12781279
}
12791280
type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
1280-
type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
1281+
type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | TypeParameterDeclaration;
12811282
interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
12821283
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
12831284
name?: Identifier;

0 commit comments

Comments
 (0)