Skip to content

Commit f373993

Browse files
committed
Allow infer types in type argument positions
1 parent a138985 commit f373993

14 files changed

+298
-44
lines changed

src/compiler/binder.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,8 @@ namespace ts {
14341434
return ContainerFlags.IsContainer | ContainerFlags.HasLocals;
14351435

14361436
case SyntaxKind.ConditionalType:
1437+
case SyntaxKind.CallExpression:
1438+
case SyntaxKind.NewExpression:
14371439
return ContainerFlags.IsInferenceContainer;
14381440

14391441
case SyntaxKind.SourceFile:

src/compiler/checker.ts

+66-11
Original file line numberDiff line numberDiff line change
@@ -3021,6 +3021,11 @@ namespace ts {
30213021
if (type.flags & TypeFlags.Substitution) {
30223022
return typeToTypeNodeHelper((<SubstitutionType>type).typeParameter, context);
30233023
}
3024+
if (type.flags & TypeFlags.InferType) {
3025+
// Infer types only parse as identifiers, so the target should always be a TypeParameter that becomes a TypeReferenceNode
3026+
const ref = typeToTypeNodeHelper((<InferType>type).target, context) as TypeReferenceNode;
3027+
return createInferTypeNode(createTypeParameterDeclaration(ref.typeName as Identifier));
3028+
}
30243029

30253030
Debug.fail("Should be unreachable.");
30263031

@@ -3517,7 +3522,7 @@ namespace ts {
35173522
const params = getTypeParametersOfClassOrInterface(
35183523
parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol
35193524
);
3520-
typeParameterNodes = mapToTypeNodes(map(params, (nextSymbol as TransientSymbol).mapper), context);
3525+
typeParameterNodes = mapToTypeNodes(mapIndexless(params, (nextSymbol as TransientSymbol).mapper), context);
35213526
}
35223527
else {
35233528
typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context);
@@ -4736,12 +4741,14 @@ namespace ts {
47364741
case SyntaxKind.JSDocTemplateTag:
47374742
case SyntaxKind.MappedType:
47384743
case SyntaxKind.ConditionalType:
4744+
case SyntaxKind.CallExpression:
4745+
case SyntaxKind.NewExpression:
47394746
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
47404747
if (node.kind === SyntaxKind.MappedType) {
47414748
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
47424749
}
4743-
else if (node.kind === SyntaxKind.ConditionalType) {
4744-
return concatenate(outerTypeParameters, getInferTypeParameters(<ConditionalTypeNode>node));
4750+
else if (node.kind === SyntaxKind.ConditionalType || node.kind === SyntaxKind.NewExpression || node.kind === SyntaxKind.CallExpression) {
4751+
return concatenate(outerTypeParameters, getInferTypeParameters(<ConditionalTypeNode | CallLikeExpression>node));
47454752
}
47464753
const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>node) || emptyArray);
47474754
const thisType = includeThisTypes &&
@@ -8334,7 +8341,7 @@ namespace ts {
83348341
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
83358342
}
83368343

8337-
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] {
8344+
function getInferTypeParameters(node: ConditionalTypeNode | CallLikeExpression): TypeParameter[] {
83388345
let result: TypeParameter[];
83398346
if (node.locals) {
83408347
node.locals.forEach(symbol => {
@@ -8375,10 +8382,16 @@ namespace ts {
83758382
return links.resolvedType;
83768383
}
83778384

8385+
function createInferType(target: TypeParameter): InferType {
8386+
const type = createType(TypeFlags.InferType) as InferType;
8387+
type.target = target;
8388+
return type;
8389+
}
8390+
83788391
function getTypeFromInferTypeNode(node: InferTypeNode): Type {
83798392
const links = getNodeLinks(node);
83808393
if (!links.resolvedType) {
8381-
links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter));
8394+
links.resolvedType = createInferType(getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)));
83828395
}
83838396
return links.resolvedType;
83848397
}
@@ -8882,7 +8895,7 @@ namespace ts {
88828895
// mapper to the type parameters to produce the effective list of type arguments, and compute the
88838896
// instantiation cache key from the type IDs of the type arguments.
88848897
const combinedMapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper;
8885-
const typeArguments = map(typeParameters, combinedMapper);
8898+
const typeArguments = mapIndexless(typeParameters, combinedMapper);
88868899
const id = getTypeListId(typeArguments);
88878900
let result = links.instantiations.get(id);
88888901
if (!result) {
@@ -8965,7 +8978,7 @@ namespace ts {
89658978
// We are instantiating a conditional type that has one or more type parameters in scope. Apply the
89668979
// mapper to the type parameters to produce the effective list of type arguments, and compute the
89678980
// instantiation cache key from the type IDs of the type arguments.
8968-
const typeArguments = map(root.outerTypeParameters, mapper);
8981+
const typeArguments = mapIndexless(root.outerTypeParameters, mapper);
89698982
const id = getTypeListId(typeArguments);
89708983
let result = root.instantiations.get(id);
89718984
if (!result) {
@@ -9036,6 +9049,9 @@ namespace ts {
90369049
if (type.flags & TypeFlags.Substitution) {
90379050
return mapper((<SubstitutionType>type).typeParameter);
90389051
}
9052+
if (type.flags & TypeFlags.InferType) {
9053+
return mapper((<InferType>type).target, /*isInferDeclaration*/ true);
9054+
}
90399055
}
90409056
return type;
90419057
}
@@ -9642,9 +9658,15 @@ namespace ts {
96429658
if (source.flags & TypeFlags.Substitution) {
96439659
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeParameter : (<SubstitutionType>source).substitute;
96449660
}
9661+
if (source.flags & TypeFlags.InferType) {
9662+
source = (<InferType>source).target;
9663+
}
96459664
if (target.flags & TypeFlags.Substitution) {
96469665
target = (<SubstitutionType>target).typeParameter;
96479666
}
9667+
if (target.flags & TypeFlags.InferType) {
9668+
target = (<InferType>target).target;
9669+
}
96489670

96499671
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
96509672
if (source === target) return Ternary.True;
@@ -11587,6 +11609,12 @@ namespace ts {
1158711609
if (!couldContainTypeVariables(target)) {
1158811610
return;
1158911611
}
11612+
if (source.flags & TypeFlags.InferType) {
11613+
source = (source as InferType).target;
11614+
}
11615+
if (target.flags & TypeFlags.InferType) {
11616+
target = (target as InferType).target;
11617+
}
1159011618
if (source.flags & TypeFlags.Any) {
1159111619
// We are inferring from an 'any' type. We want to infer this type for every type parameter
1159211620
// referenced in the target type, so we record it as the propagation type and infer from the
@@ -17529,10 +17557,29 @@ namespace ts {
1752917557
candidate = originalCandidate;
1753017558
if (candidate.typeParameters) {
1753117559
let typeArgumentTypes: Type[];
17560+
const isJavascript = isInJavaScriptFile(candidate.declaration);
1753217561
if (typeArguments) {
1753317562
const typeArgumentResult = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false);
1753417563
if (typeArgumentResult) {
17535-
typeArgumentTypes = typeArgumentResult;
17564+
if (node.locals) {
17565+
// Call has `infer` arguments that still need to be inferred and instantiated
17566+
const inferParams = getInferTypeParameters(node);
17567+
// Mapper replaces references to infered type parameters with emptyObjectType
17568+
// Causing the original location to be the _only_ inference site
17569+
const preprocessMapper = (p: TypeParameter, isInferDecl: boolean) => {
17570+
if (!isInferDecl && contains(inferParams, p)) return emptyObjectType;
17571+
return p;
17572+
};
17573+
const resultsWithNonInferInferredVarsDefaulted = map(typeArgumentResult, t => instantiateType(t, preprocessMapper));
17574+
const partialCandidate = getSignatureInstantiation(candidate, resultsWithNonInferInferredVarsDefaulted, isJavascript);
17575+
const context = createInferenceContext(inferParams, partialCandidate, InferenceFlags.None);
17576+
const inferences = inferTypeArguments(node, partialCandidate, args, excludeArgument, context);
17577+
const mapper = createTypeMapper(inferParams, inferences);
17578+
typeArgumentTypes = map(typeArgumentResult, t => instantiateType(t, mapper));
17579+
}
17580+
else {
17581+
typeArgumentTypes = typeArgumentResult;
17582+
}
1753617583
}
1753717584
else {
1753817585
candidateForTypeArgumentError = originalCandidate;
@@ -17542,7 +17589,6 @@ namespace ts {
1754217589
else {
1754317590
typeArgumentTypes = inferTypeArguments(node, candidate, args, excludeArgument, inferenceContext);
1754417591
}
17545-
const isJavascript = isInJavaScriptFile(candidate.declaration);
1754617592
candidate = getSignatureInstantiation(candidate, typeArgumentTypes, isJavascript);
1754717593
}
1754817594
if (!checkApplicableSignature(node, args, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
@@ -20544,9 +20590,18 @@ namespace ts {
2054420590
forEachChild(node, checkSourceElement);
2054520591
}
2054620592

20593+
function isConditionalTypeExtendsClause(n: Node) {
20594+
return n.parent && n.parent.kind === SyntaxKind.ConditionalType && (<ConditionalTypeNode>n.parent).extendsType === n;
20595+
}
20596+
20597+
function isCallOrNewExpressionTypeArgument(n: Node) {
20598+
return n.parent && (n.parent.kind === SyntaxKind.CallExpression || n.parent.kind === SyntaxKind.NewExpression)
20599+
&& contains((<CallExpression | NewExpression>n.parent).typeArguments, n);
20600+
}
20601+
2054720602
function checkInferType(node: InferTypeNode) {
20548-
if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (<ConditionalTypeNode>n.parent).extendsType === n)) {
20549-
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
20603+
if (!findAncestor(node, n => isConditionalTypeExtendsClause(n) || isCallOrNewExpressionTypeArgument(n))) {
20604+
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type_or_in_call_or_new_expression_type_argument_lists);
2055020605
}
2055120606
checkSourceElement(node.typeParameter);
2055220607
}

src/compiler/core.ts

+11
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,17 @@ namespace ts {
414414
array.length = 0;
415415
}
416416

417+
export function mapIndexless<T, U>(array: ReadonlyArray<T>, f: (x: T) => U): U[] {
418+
let result: U[];
419+
if (array) {
420+
result = [];
421+
for (const elem of array) {
422+
result.push(f(elem));
423+
}
424+
}
425+
return result;
426+
}
427+
417428
export function map<T, U>(array: ReadonlyArray<T>, f: (x: T, i: number) => U): U[] {
418429
let result: U[];
419430
if (array) {

src/compiler/diagnosticMessages.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@
947947
"category": "Error",
948948
"code": 1337
949949
},
950-
"'infer' declarations are only permitted in the 'extends' clause of a conditional type.": {
950+
"'infer' declarations are only permitted in the 'extends' clause of a conditional type or in call or new expression type argument lists.": {
951951
"category": "Error",
952952
"code": 1338
953953
},

src/compiler/types.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -3538,6 +3538,7 @@ namespace ts {
35383538
/* @internal */
35393539
ContainsAnyFunctionType = 1 << 26, // Type is or contains the anyFunctionType
35403540
NonPrimitive = 1 << 27, // intrinsic object type
3541+
InferType = 1 << 28, // A type whose concrete value upon instantiation will be inferred at a given site
35413542
/* @internal */
35423543
GenericMappedType = 1 << 29, // Flag used by maybeTypeOfKind
35433544

@@ -3562,7 +3563,7 @@ namespace ts {
35623563
ESSymbolLike = ESSymbol | UniqueESSymbol,
35633564
UnionOrIntersection = Union | Intersection,
35643565
StructuredType = Object | Union | Intersection,
3565-
TypeVariable = TypeParameter | IndexedAccess,
3566+
TypeVariable = TypeParameter | IndexedAccess | InferType,
35663567
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
35673568
InstantiablePrimitive = Index,
35683569
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
@@ -3817,6 +3818,11 @@ namespace ts {
38173818
resolvedDefaultType?: Type;
38183819
}
38193820

3821+
// Infer Types (TypeFlags.InferType)
3822+
export interface InferType extends Type {
3823+
target: TypeParameter;
3824+
}
3825+
38203826
// Indexed access types (TypeFlags.IndexedAccess)
38213827
// Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable
38223828
export interface IndexedAccessType extends InstantiableType {
@@ -3919,7 +3925,7 @@ namespace ts {
39193925
}
39203926

39213927
/* @internal */
3922-
export type TypeMapper = (t: TypeParameter) => Type;
3928+
export type TypeMapper = (t: TypeParameter, isInferDeclaration?: boolean) => Type;
39233929

39243930
export const enum InferencePriority {
39253931
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -2074,6 +2074,7 @@ declare namespace ts {
20742074
Conditional = 2097152,
20752075
Substitution = 4194304,
20762076
NonPrimitive = 134217728,
2077+
InferType = 268435456,
20772078
Literal = 224,
20782079
Unit = 13536,
20792080
StringOrNumberLiteral = 96,
@@ -2085,12 +2086,12 @@ declare namespace ts {
20852086
ESSymbolLike = 1536,
20862087
UnionOrIntersection = 393216,
20872088
StructuredType = 458752,
2088-
TypeVariable = 1081344,
2089-
InstantiableNonPrimitive = 7372800,
2089+
TypeVariable = 269516800,
2090+
InstantiableNonPrimitive = 275808256,
20902091
InstantiablePrimitive = 524288,
2091-
Instantiable = 7897088,
2092-
StructuredOrInstantiable = 8355840,
2093-
Narrowable = 142575359,
2092+
Instantiable = 276332544,
2093+
StructuredOrInstantiable = 276791296,
2094+
Narrowable = 411010815,
20942095
NotUnionOrUnit = 134283777,
20952096
}
20962097
type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
@@ -2184,6 +2185,9 @@ declare namespace ts {
21842185
}
21852186
interface TypeParameter extends InstantiableType {
21862187
}
2188+
interface InferType extends Type {
2189+
target: TypeParameter;
2190+
}
21872191
interface IndexedAccessType extends InstantiableType {
21882192
objectType: Type;
21892193
indexType: Type;

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -2074,6 +2074,7 @@ declare namespace ts {
20742074
Conditional = 2097152,
20752075
Substitution = 4194304,
20762076
NonPrimitive = 134217728,
2077+
InferType = 268435456,
20772078
Literal = 224,
20782079
Unit = 13536,
20792080
StringOrNumberLiteral = 96,
@@ -2085,12 +2086,12 @@ declare namespace ts {
20852086
ESSymbolLike = 1536,
20862087
UnionOrIntersection = 393216,
20872088
StructuredType = 458752,
2088-
TypeVariable = 1081344,
2089-
InstantiableNonPrimitive = 7372800,
2089+
TypeVariable = 269516800,
2090+
InstantiableNonPrimitive = 275808256,
20902091
InstantiablePrimitive = 524288,
2091-
Instantiable = 7897088,
2092-
StructuredOrInstantiable = 8355840,
2093-
Narrowable = 142575359,
2092+
Instantiable = 276332544,
2093+
StructuredOrInstantiable = 276791296,
2094+
Narrowable = 411010815,
20942095
NotUnionOrUnit = 134283777,
20952096
}
20962097
type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
@@ -2184,6 +2185,9 @@ declare namespace ts {
21842185
}
21852186
interface TypeParameter extends InstantiableType {
21862187
}
2188+
interface InferType extends Type {
2189+
target: TypeParameter;
2190+
}
21872191
interface IndexedAccessType extends InstantiableType {
21882192
objectType: Type;
21892193
indexType: Type;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tests/cases/conformance/types/typeParameters/typeArgumentLists/inferTypeArgumentKeyword.ts(7,51): error TS2345: Argument of type '{ z: number; }' is not assignable to parameter of type '{ z: { y: number; }; }'.
2+
Types of property 'z' are incompatible.
3+
Type 'number' is not assignable to type '{ y: number; }'.
4+
tests/cases/conformance/types/typeParameters/typeArgumentLists/inferTypeArgumentKeyword.ts(10,30): error TS2345: Argument of type '{ y: number; }' is not assignable to parameter of type 'number'.
5+
6+
7+
==== tests/cases/conformance/types/typeParameters/typeArgumentLists/inferTypeArgumentKeyword.ts (2 errors) ====
8+
declare function foo<A, B, C>(x: A, y: B, z: { z: C }): A & B & C;
9+
10+
// good
11+
foo<infer A, {x: string}, A>({y: 12}, {x: "yes"}, {z: {y: 12}});
12+
13+
// error on 3rd arg
14+
foo<infer A, {x: string}, A>({y: 12}, {x: "yes"}, {z: 12});
15+
~~~~~~~
16+
!!! error TS2345: Argument of type '{ z: number; }' is not assignable to parameter of type '{ z: { y: number; }; }'.
17+
!!! error TS2345: Types of property 'z' are incompatible.
18+
!!! error TS2345: Type 'number' is not assignable to type '{ y: number; }'.
19+
20+
// error on first arg
21+
foo<A, {x: string}, infer A>({y: 12}, {x: "yes"}, {z: 12});
22+
~~~~~~~
23+
!!! error TS2345: Argument of type '{ y: number; }' is not assignable to parameter of type 'number'.
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [inferTypeArgumentKeyword.ts]
2+
declare function foo<A, B, C>(x: A, y: B, z: { z: C }): A & B & C;
3+
4+
// good
5+
foo<infer A, {x: string}, A>({y: 12}, {x: "yes"}, {z: {y: 12}});
6+
7+
// error on 3rd arg
8+
foo<infer A, {x: string}, A>({y: 12}, {x: "yes"}, {z: 12});
9+
10+
// error on first arg
11+
foo<A, {x: string}, infer A>({y: 12}, {x: "yes"}, {z: 12});
12+
13+
14+
//// [inferTypeArgumentKeyword.js]
15+
// good
16+
foo({ y: 12 }, { x: "yes" }, { z: { y: 12 } });
17+
// error on 3rd arg
18+
foo({ y: 12 }, { x: "yes" }, { z: 12 });
19+
// error on first arg
20+
foo({ y: 12 }, { x: "yes" }, { z: 12 });

0 commit comments

Comments
 (0)