Skip to content

Commit 910595d

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 7271ec1 commit 910595d

26 files changed

+2347
-8
lines changed

src/compiler/binder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,6 +2713,12 @@ namespace ts {
27132713
bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node));
27142714
}
27152715
}
2716+
else if (node.parent.kind === SyntaxKind.TypeParameter) {
2717+
if (!node.parent.locals) {
2718+
node.parent.locals = createSymbolTable();
2719+
}
2720+
declareSymbol(node.parent.locals, node.parent.symbol, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
2721+
}
27162722
else {
27172723
declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
27182724
}

src/compiler/checker.ts

Lines changed: 127 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5128,6 +5128,7 @@ namespace ts {
51285128
case SyntaxKind.JSDocTemplateTag:
51295129
case SyntaxKind.MappedType:
51305130
case SyntaxKind.ConditionalType:
5131+
case SyntaxKind.TypeParameter:
51315132
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
51325133
if (node.kind === SyntaxKind.MappedType) {
51335134
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
@@ -5156,8 +5157,8 @@ namespace ts {
51565157
let result: TypeParameter[];
51575158
for (const node of symbol.declarations) {
51585159
if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration ||
5159-
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) {
5160-
const declaration = <InterfaceDeclaration | TypeAliasDeclaration>node;
5160+
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || node.kind === SyntaxKind.TypeParameter) {
5161+
const declaration = <InterfaceDeclaration | TypeAliasDeclaration | TypeParameterDeclaration>node;
51615162
const typeParameters = getEffectiveTypeParameterDeclarations(declaration);
51625163
if (typeParameters) {
51635164
result = appendTypeParameters(result, typeParameters);
@@ -5582,6 +5583,13 @@ namespace ts {
55825583
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
55835584
type.symbol = symbol;
55845585
links.declaredType = type;
5586+
5587+
const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
5588+
if (typeParameters) {
5589+
links.instantiations = createMap<TypeParameter>();
5590+
links.instantiations.set(getTypeListId(typeParameters), type);
5591+
type.typeParameters = typeParameters;
5592+
}
55855593
}
55865594
return <TypeParameter>links.declaredType;
55875595
}
@@ -7553,8 +7561,14 @@ namespace ts {
75537561
}
75547562
else {
75557563
const constraintDeclaration = getConstraintDeclaration(typeParameter);
7556-
typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) :
7564+
let constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) :
75577565
getInferredTypeParameterConstraint(typeParameter) || noConstraintType;
7566+
if (constraint !== noConstraintType && typeParameter.typeParameters) {
7567+
const apparentMapper = createTypeMapper(typeParameter.typeParameters, map(typeParameter.typeParameters, getApparentType));
7568+
const argumentMapper = typeParameter.typeArguments ? createTypeMapper(typeParameter.typeParameters, typeParameter.typeArguments) : identityMapper;
7569+
constraint = instantiateType(constraint, combineTypeMappers(argumentMapper, apparentMapper));
7570+
}
7571+
typeParameter.constraint = constraint;
75587572
}
75597573
}
75607574
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
@@ -7636,6 +7650,9 @@ namespace ts {
76367650
const typeParameters = type.localTypeParameters;
76377651
if (typeParameters) {
76387652
const numTypeArguments = length(node.typeArguments);
7653+
if (numTypeArguments === 0 && isGenericTypeArgument(node)) {
7654+
return type;
7655+
}
76397656
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
76407657
const isJs = isInJavaScriptFile(node);
76417658
const isJsImplicitAny = !noImplicitAny && isJs;
@@ -7702,6 +7719,56 @@ namespace ts {
77027719
return checkNoTypeArguments(node, symbol) ? type : unknownType;
77037720
}
77047721

7722+
function getTypeFromTypeParameterReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type {
7723+
const type = <TypeParameter>getDeclaredTypeOfSymbol(symbol);
7724+
const typeParameters = type.typeParameters;
7725+
if (typeParameters) {
7726+
const numTypeArguments = length(typeArguments);
7727+
if (numTypeArguments === 0 && isGenericTypeArgument(node)) {
7728+
return type;
7729+
}
7730+
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
7731+
if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) {
7732+
error(node,
7733+
minTypeArgumentCount === typeParameters.length
7734+
? Diagnostics.Generic_type_0_requires_1_type_argument_s
7735+
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
7736+
symbolToString(symbol),
7737+
minTypeArgumentCount,
7738+
typeParameters.length);
7739+
return unknownType;
7740+
}
7741+
const id = getTypeListId(typeArguments);
7742+
const links = getSymbolLinks(symbol);
7743+
let reference = <TypeParameter>links.instantiations.get(id);
7744+
if (!reference) {
7745+
reference = getTypeParameterReference(type, typeArguments);
7746+
links.instantiations.set(id, reference);
7747+
}
7748+
return reference;
7749+
}
7750+
else if (!checkNoTypeArguments(node, symbol)) {
7751+
return unknownType;
7752+
}
7753+
return getConstrainedTypeVariable(type, node);
7754+
}
7755+
7756+
function getTypeParameterReference(genericTypeParameter: TypeParameter, typeArguments: Type[]): TypeParameter {
7757+
Debug.assert(genericTypeParameter.genericTarget === undefined && genericTypeParameter.typeParameters && genericTypeParameter.typeParameters.length === typeArguments.length);
7758+
const id = getTypeListId(typeArguments);
7759+
const links = getSymbolLinks(genericTypeParameter.symbol);
7760+
let reference = <TypeParameter>links.instantiations.get(id);
7761+
if (!reference) {
7762+
reference = <TypeParameter>createType(TypeFlags.TypeParameter);
7763+
reference.symbol = genericTypeParameter.symbol;
7764+
reference.typeParameters = genericTypeParameter.typeParameters;
7765+
reference.typeArguments = typeArguments;
7766+
reference.genericTarget = genericTypeParameter;
7767+
links.instantiations.set(id, reference);
7768+
}
7769+
return reference;
7770+
}
7771+
77057772
function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined {
77067773
switch (node.kind) {
77077774
case SyntaxKind.TypeReference:
@@ -7794,6 +7861,10 @@ namespace ts {
77947861
(symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
77957862
return getInferredClassType(symbol);
77967863
}
7864+
7865+
if (symbol.flags & SymbolFlags.TypeParameter) {
7866+
return getTypeFromTypeParameterReference(node, symbol, typeArguments);
7867+
}
77977868
}
77987869

77997870
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
@@ -9412,6 +9483,9 @@ namespace ts {
94129483
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
94139484
const result = <TypeParameter>createType(TypeFlags.TypeParameter);
94149485
result.symbol = typeParameter.symbol;
9486+
result.typeParameters = typeParameter.typeParameters;
9487+
result.typeArguments = typeParameter.typeArguments;
9488+
result.genericTarget = typeParameter.genericTarget;
94159489
result.target = typeParameter;
94169490
return result;
94179491
}
@@ -9646,7 +9720,27 @@ namespace ts {
96469720
function instantiateType(type: Type, mapper: TypeMapper): Type {
96479721
if (type && mapper && mapper !== identityMapper) {
96489722
if (type.flags & TypeFlags.TypeParameter) {
9649-
return mapper(<TypeParameter>type);
9723+
if ((<TypeParameter>type).typeParameters && (<TypeParameter>type).genericTarget) {
9724+
const newType = mapper((<TypeParameter>type).genericTarget);
9725+
if (newType.flags & TypeFlags.TypeParameter && (<TypeParameter>newType).typeParameters) {
9726+
// Mapper did not instantiate the generic type so just create another reference to it.
9727+
const newTypeArguments = instantiateTypes((<TypeParameter>type).typeArguments, mapper);
9728+
return getTypeParameterReference(<TypeParameter>newType, newTypeArguments);
9729+
}
9730+
const orginalNewTypeArguments = (<TypeReference>newType).typeArguments;
9731+
if (!orginalNewTypeArguments) {
9732+
// this means it was instantiated as anonymous type without type arguments.
9733+
return newType;
9734+
}
9735+
if (length(orginalNewTypeArguments) !== length((<TypeParameter>type).typeArguments)) {
9736+
return newType;
9737+
}
9738+
const newTypeArguments = instantiateTypes((<TypeParameter>type).typeArguments, mapper);
9739+
return createTypeReference((<TypeReference>newType).target, newTypeArguments);
9740+
}
9741+
else {
9742+
return mapper(<TypeParameter>type);
9743+
}
96509744
}
96519745
if (type.flags & TypeFlags.Object) {
96529746
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
@@ -9925,7 +10019,8 @@ namespace ts {
992510019
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
992610020
const related = callbacks ?
992710021
compareSignaturesRelated(targetSig, sourceSig, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
9928-
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
10022+
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) ||
10023+
compareTypes(targetType, sourceType, reportErrors);
992910024
if (!related) {
993010025
if (reportErrors) {
993110026
errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -10915,6 +11010,9 @@ namespace ts {
1091511010
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
1091611011
if (relation !== identityRelation) {
1091711012
source = getApparentType(source);
11013+
if (target.flags & TypeFlags.TypeParameter && (<TypeParameter>target).typeParameters) {
11014+
target = getApparentType(target);
11015+
}
1091811016
}
1091911017
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
1092011018
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
@@ -12367,8 +12465,13 @@ namespace ts {
1236712465
inference.topLevel = false;
1236812466
}
1236912467
}
12370-
return;
1237112468
}
12469+
if (target.flags & TypeFlags.TypeParameter && (<TypeParameter>target).typeArguments && forEach((<TypeParameter>target).typeArguments, couldContainTypeVariables) && getConstraintOfTypeParameter(<TypeParameter>target)) {
12470+
// This is a generic type parameter reference and it might contain other type parameters to infer
12471+
// so infer from the constraint of the type parameter (which is where the other type parameters would be if they are referenced)
12472+
inferFromTypes(source, getConstraintOfTypeParameter(<TypeParameter>target));
12473+
}
12474+
return;
1237212475
}
1237312476
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
1237412477
// If source and target are references to the same generic type, infer from type arguments
@@ -12485,6 +12588,7 @@ namespace ts {
1248512588

1248612589
function getInferenceInfoForType(type: Type) {
1248712590
if (type.flags & TypeFlags.TypeVariable) {
12591+
type = (<TypeParameter>type).genericTarget || type;
1248812592
for (const inference of inferences) {
1248912593
if (type === inference.typeParameter) {
1249012594
return inference;
@@ -20680,6 +20784,7 @@ namespace ts {
2068020784

2068120785
checkSourceElement(node.constraint);
2068220786
checkSourceElement(node.default);
20787+
checkTypeParameters(node.typeParameters);
2068320788
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
2068420789
if (!hasNonCircularBaseConstraint(typeParameter)) {
2068520790
error(node.constraint, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
@@ -21319,6 +21424,21 @@ namespace ts {
2131921424
return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters)));
2132021425
}
2132121426

21427+
function isGenericTypeArgument(node: NodeWithTypeArguments): boolean {
21428+
if (!isTypeReferenceType(node.parent)) {
21429+
return false;
21430+
}
21431+
const name = getTypeReferenceName(node.parent);
21432+
const identifier = getFirstIdentifier(name);
21433+
const symbol = resolveEntityName(identifier, SymbolFlags.Type, /*ignoreErrors*/ true);
21434+
const typeParameters = symbol && getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
21435+
if (!typeParameters) {
21436+
return false;
21437+
}
21438+
const typeParameter = typeParameters[node.parent.typeArguments.indexOf(node)!];
21439+
return !!length(typeParameter.typeParameters);
21440+
}
21441+
2132221442
function checkTypeQuery(node: TypeQueryNode) {
2132321443
getTypeFromTypeQueryNode(node);
2132421444
}
@@ -23946,6 +24066,7 @@ namespace ts {
2394624066
}
2394724067
}
2394824068

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

src/compiler/parser.ts

Lines changed: 2 additions & 0 deletions
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);
@@ -2353,6 +2354,7 @@ namespace ts {
23532354
function parseTypeParameter(): TypeParameterDeclaration {
23542355
const node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
23552356
node.name = parseIdentifier();
2357+
node.typeParameters = parseTypeParameters();
23562358
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
23572359
// It's not uncommon for people to write improper constraints to a generic. If the
23582360
// user writes a constraint that is an expression and not an actual type, then parse

src/compiler/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ namespace ts {
800800
kind: SyntaxKind.TypeParameter;
801801
parent?: DeclarationWithTypeParameters | InferTypeNode;
802802
name: Identifier;
803+
typeParameters?: NodeArray<TypeParameterDeclaration>;
803804
constraint?: TypeNode;
804805
default?: TypeNode;
805806

@@ -2053,7 +2054,7 @@ namespace ts {
20532054

20542055
export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
20552056

2056-
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
2057+
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | TypeParameterDeclaration;
20572058

20582059
export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
20592060
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
@@ -3960,6 +3961,12 @@ namespace ts {
39603961
isThisType?: boolean;
39613962
/* @internal */
39623963
resolvedDefaultType?: Type;
3964+
/* @internal */
3965+
typeParameters?: TypeParameter[];
3966+
/* @internal */
3967+
typeArguments?: TypeParameter[]; // Only set for references
3968+
/* @internal */
3969+
genericTarget?: TypeParameter; // This is the original generic type parameter a type parameter reference points to
39633970
}
39643971

39653972
// Indexed access types (TypeFlags.IndexedAccess)

src/compiler/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ namespace ts {
520520
case SyntaxKind.SetAccessor:
521521
case SyntaxKind.FunctionExpression:
522522
case SyntaxKind.ArrowFunction:
523+
case SyntaxKind.TypeParameter:
523524
return true;
524525
default:
525526
assertTypeIsNever(node);

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ declare namespace ts {
530530
kind: SyntaxKind.TypeParameter;
531531
parent?: DeclarationWithTypeParameters | InferTypeNode;
532532
name: Identifier;
533+
typeParameters?: NodeArray<TypeParameterDeclaration>;
533534
constraint?: TypeNode;
534535
default?: TypeNode;
535536
expression?: Expression;
@@ -1280,7 +1281,7 @@ declare namespace ts {
12801281
block: Block;
12811282
}
12821283
type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
1283-
type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
1284+
type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | TypeParameterDeclaration;
12841285
interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
12851286
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
12861287
name?: Identifier;

0 commit comments

Comments
 (0)