Skip to content

Commit ea45dc2

Browse files
Andaristweswigham
andcommitted
Allow for partial inference by fixing inferences for type parameters preferring inferring
Co-authored-by: Wesley Wigham <[email protected]>
1 parent 80ce1df commit ea45dc2

12 files changed

+1677
-472
lines changed

src/compiler/checker.ts

+50-6
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
19401940
const markerSubTypeForCheck = createTypeParameter();
19411941
markerSubTypeForCheck.constraint = markerSuperTypeForCheck;
19421942

1943+
const preferInferType = createTypeParameter();
1944+
19431945
const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<<unresolved>>", 0, anyType);
19441946

19451947
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
@@ -13260,6 +13262,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1326013262
type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType));
1326113263
}
1326213264

13265+
function isTypeVariablePreferringInference(typeParameter: TypeParameter): boolean {
13266+
return some(typeParameter.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.PreferInfer));
13267+
}
13268+
1326313269
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
1326413270
return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined;
1326513271
}
@@ -14095,7 +14101,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1409514101
let minTypeArgumentCount = 0;
1409614102
if (typeParameters) {
1409714103
for (let i = 0; i < typeParameters.length; i++) {
14098-
if (!hasTypeParameterDefault(typeParameters[i])) {
14104+
if (!hasTypeParameterDefault(typeParameters[i]) && !isTypeVariablePreferringInference(typeParameters[i])) {
1409914105
minTypeArgumentCount = i + 1;
1410014106
}
1410114107
}
@@ -14127,7 +14133,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1412714133
}
1412814134
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
1412914135
for (let i = numTypeArguments; i < numTypeParameters; i++) {
14130-
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
14136+
const typeParam = typeParameters![i];
14137+
if (isTypeVariablePreferringInference(typeParam)) {
14138+
result[i] = preferInferType;
14139+
continue;
14140+
}
14141+
let defaultType = getDefaultFromTypeParameter(typeParam);
1413114142
if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) {
1413214143
defaultType = anyType;
1413314144
}
@@ -31865,9 +31876,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3186531876
}
3186631877

3186731878
function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
31879+
return checkTypeArgumentsTypes(signature, typeArgumentNodes, map(typeArgumentNodes, getTypeFromTypeNode), reportErrors, headMessage);
31880+
}
31881+
31882+
function checkTypeArgumentsTypes(signature: Signature, typeArgumentNodes: readonly TypeNode[], typeArguments: readonly Type[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
3186831883
const isJavascript = isInJSFile(signature.declaration);
3186931884
const typeParameters = signature.typeParameters!;
31870-
const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript);
31885+
const typeArgumentTypes = fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript);
31886+
if (some(typeArgumentTypes, t => t === preferInferType)) {
31887+
// Do validation once partial inference is complete
31888+
return typeArgumentTypes;
31889+
}
3187131890
let mapper: TypeMapper | undefined;
3187231891
for (let i = 0; i < typeArgumentNodes.length; i++) {
3187331892
Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments");
@@ -32542,6 +32561,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3254232561
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args));
3254332562
}
3254432563
else if (candidateForTypeArgumentError) {
32564+
// andarist, we could error here - or using a new candidates~ array (?)
3254532565
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError);
3254632566
}
3254732567
else {
@@ -32608,20 +32628,43 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3260832628
let inferenceContext: InferenceContext | undefined;
3260932629

3261032630
if (candidate.typeParameters) {
32631+
const isJavascript = isInJSFile(candidate.declaration);
3261132632
let typeArgumentTypes: Type[] | undefined;
3261232633
if (some(typeArguments)) {
3261332634
typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false);
3261432635
if (!typeArgumentTypes) {
3261532636
candidateForTypeArgumentError = candidate;
3261632637
continue;
3261732638
}
32639+
if (some(typeArgumentTypes, t => t === preferInferType)) {
32640+
// There are implied inferences we must make, despite having type arguments
32641+
const originalParams = candidate.typeParameters;
32642+
const withOriginalArgs = map(typeArgumentTypes, (r, i) => r === preferInferType ? originalParams[i] : r);
32643+
const uninferedInstantiation = getSignatureInstantiation(candidate, withOriginalArgs, isJavascript);
32644+
inferenceContext = createInferenceContext(originalParams, uninferedInstantiation, isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
32645+
for (let i = 0; i < inferenceContext.inferences.length; i++) {
32646+
const correspondingArgument = typeArgumentTypes[i];
32647+
if (correspondingArgument !== preferInferType) {
32648+
const inference = inferenceContext.inferences[i];
32649+
inference.inferredType = correspondingArgument;
32650+
inference.isFixed = true;
32651+
inference.priority = InferencePriority.None;
32652+
}
32653+
}
32654+
typeArgumentTypes = inferTypeArguments(node, uninferedInstantiation, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
32655+
// TODO: implement plumbing for error reporting
32656+
// if (!checkTypeArgumentsTypes(candidate, typeArguments, typeArgumentTypes, /*reportErrors*/ false)) {
32657+
// continue;
32658+
// }
32659+
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
32660+
}
3261832661
}
3261932662
else {
3262032663
inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
3262132664
typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
3262232665
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
3262332666
}
32624-
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
32667+
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isJavascript, inferenceContext && inferenceContext.inferredTypeParameters);
3262532668
// If the original signature has a generic rest type, instantiation may produce a
3262632669
// signature with different arity and we need to perform another arity check.
3262732670
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
@@ -45649,15 +45692,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4564945692
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind));
4565045693
}
4565145694
}
45652-
if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) {
45695+
if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword && modifier.kind !== SyntaxKind.PreferInferKeyword) {
4565345696
if (node.kind === SyntaxKind.TypeParameter) {
4565445697
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind));
4565545698
}
4565645699
}
4565745700
switch (modifier.kind) {
4565845701
case SyntaxKind.ConstKeyword:
45702+
case SyntaxKind.PreferInferKeyword:
4565945703
if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) {
45660-
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
45704+
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(modifier.kind));
4566145705
}
4566245706
const parent = node.parent;
4566345707
if (node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) ||

0 commit comments

Comments
 (0)