Skip to content

Commit

Permalink
Allow for partial inference by fixing inferences for type parameters …
Browse files Browse the repository at this point in the history
…preferring inferring

Co-authored-by: Wesley Wigham <[email protected]>
  • Loading branch information
Andarist and weswigham committed Jan 14, 2023
1 parent 80ce1df commit c06ba17
Show file tree
Hide file tree
Showing 12 changed files with 1,677 additions and 472 deletions.
56 changes: 50 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const markerSubTypeForCheck = createTypeParameter();
markerSubTypeForCheck.constraint = markerSuperTypeForCheck;

const preferInferType = createTypeParameter();

const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<<unresolved>>", 0, anyType);

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

function isTypeVariablePreferringInference(typeParameter: TypeParameter): boolean {
return some(typeParameter.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.PreferInfer));
}

function getConstraintOfIndexedAccess(type: IndexedAccessType) {
return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined;
}
Expand Down Expand Up @@ -14095,7 +14101,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let minTypeArgumentCount = 0;
if (typeParameters) {
for (let i = 0; i < typeParameters.length; i++) {
if (!hasTypeParameterDefault(typeParameters[i])) {
if (!hasTypeParameterDefault(typeParameters[i]) && !isTypeVariablePreferringInference(typeParameters[i])) {
minTypeArgumentCount = i + 1;
}
}
Expand Down Expand Up @@ -14127,7 +14133,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
for (let i = numTypeArguments; i < numTypeParameters; i++) {
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
const typeParam = typeParameters![i];
if (isTypeVariablePreferringInference(typeParam)) {
result[i] = preferInferType;
continue;
}
let defaultType = getDefaultFromTypeParameter(typeParam);
if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) {
defaultType = anyType;
}
Expand Down Expand Up @@ -31865,9 +31876,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
return checkTypeArgumentsTypes(signature, typeArgumentNodes, map(typeArgumentNodes, getTypeFromTypeNode), reportErrors, headMessage);
}

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

if (candidate.typeParameters) {
const isJavascript = isInJSFile(candidate.declaration);
let typeArgumentTypes: Type[] | undefined;
if (some(typeArguments)) {
typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false);
if (!typeArgumentTypes) {
candidateForTypeArgumentError = candidate;
continue;
}
if (some(typeArgumentTypes, t => t === preferInferType)) {
// There are implied inferences we must make, despite having type arguments
const originalParams = candidate.typeParameters;
const withOriginalArgs = map(typeArgumentTypes, (r, i) => r === preferInferType ? originalParams[i] : r);
const uninferedInstantiation = getSignatureInstantiation(candidate, withOriginalArgs, isJavascript);
inferenceContext = createInferenceContext(originalParams, uninferedInstantiation, isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
for (let i = 0; i < inferenceContext.inferences.length; i++) {
const correspondingArgument = typeArgumentTypes[i];
if (correspondingArgument !== preferInferType) {
const inference = inferenceContext.inferences[i];
inference.inferredType = correspondingArgument;
inference.isFixed = true;
inference.priority = InferencePriority.None;
}
}
typeArgumentTypes = inferTypeArguments(node, uninferedInstantiation, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext)
// TODO: implement plumbing for error reporting
// if (!checkTypeArgumentsTypes(candidate, typeArguments, typeArgumentTypes, /*reportErrors*/ false)) {
// continue;
// }
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
}
}
else {
inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
}
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isJavascript, inferenceContext && inferenceContext.inferredTypeParameters);
// If the original signature has a generic rest type, instantiation may produce a
// signature with different arity and we need to perform another arity check.
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
Expand Down Expand Up @@ -45649,15 +45692,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind));
}
}
if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) {
if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword && modifier.kind !== SyntaxKind.PreferInferKeyword) {
if (node.kind === SyntaxKind.TypeParameter) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind));
}
}
switch (modifier.kind) {
case SyntaxKind.ConstKeyword:
case SyntaxKind.PreferInferKeyword:
if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) {
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(modifier.kind));
}
const parent = node.parent;
if (node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) ||
Expand Down
Loading

0 comments on commit c06ba17

Please sign in to comment.