Skip to content

preferinfer modifier for type parameters #52241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 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 @@ -32608,20 +32627,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 +45691,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
2 changes: 2 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
case SyntaxKind.InKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.OverrideKeyword:
case SyntaxKind.PreferInferKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.BooleanKeyword:
case SyntaxKind.SymbolKeyword:
Expand Down Expand Up @@ -1417,6 +1418,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword));
if (flags & ModifierFlags.In) result.push(createModifier(SyntaxKind.InKeyword));
if (flags & ModifierFlags.Out) result.push(createModifier(SyntaxKind.OutKeyword));
if (flags & ModifierFlags.PreferInfer) result.push(createModifier(SyntaxKind.PreferInferKeyword));
return result.length ? result : undefined;
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3016,6 +3016,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
case SyntaxKind.OverrideKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.PreferInferKeyword:
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind)));
break;

Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
number: SyntaxKind.NumberKeyword,
object: SyntaxKind.ObjectKeyword,
package: SyntaxKind.PackageKeyword,
preferinfer: SyntaxKind.PreferInferKeyword,
private: SyntaxKind.PrivateKeyword,
protected: SyntaxKind.ProtectedKeyword,
public: SyntaxKind.PublicKeyword,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ export function transformTypeScript(context: TransformationContext) {
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.PreferInferKeyword:
// TypeScript accessibility and readonly modifiers are elided
// falls through
case SyntaxKind.ArrayType:
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export const enum SyntaxKind {
NamespaceKeyword,
NeverKeyword,
OutKeyword,
PreferInferKeyword,
ReadonlyKeyword,
RequireKeyword,
NumberKeyword,
Expand Down Expand Up @@ -625,6 +626,7 @@ export type KeywordSyntaxKind =
| SyntaxKind.ObjectKeyword
| SyntaxKind.OfKeyword
| SyntaxKind.PackageKeyword
| SyntaxKind.PreferInferKeyword
| SyntaxKind.PrivateKeyword
| SyntaxKind.ProtectedKeyword
| SyntaxKind.PublicKeyword
Expand Down Expand Up @@ -665,6 +667,7 @@ export type ModifierSyntaxKind =
| SyntaxKind.DefaultKeyword
| SyntaxKind.ExportKeyword
| SyntaxKind.InKeyword
| SyntaxKind.PreferInferKeyword
| SyntaxKind.PrivateKeyword
| SyntaxKind.ProtectedKeyword
| SyntaxKind.PublicKeyword
Expand Down Expand Up @@ -858,16 +861,17 @@ export const enum ModifierFlags {
In = 1 << 15, // Contravariance modifier
Out = 1 << 16, // Covariance modifier
Decorator = 1 << 17, // Contains a decorator.
PreferInfer = 1 << 18, // preferinfer modifier on type params to allow partial inference
HasComputedFlags = 1 << 29, // Modifier flags have been computed

AccessibilityModifier = Public | Private | Protected,
// Accessibility modifiers and 'readonly' can be attached to a parameter in a constructor to make it a property.
ParameterPropertyModifier = AccessibilityModifier | Readonly | Override,
NonPublicAccessibilityModifier = Private | Protected,

TypeScriptModifier = Ambient | Public | Private | Protected | Readonly | Abstract | Const | Override | In | Out,
TypeScriptModifier = Ambient | Public | Private | Protected | Readonly | Abstract | Const | Override | In | Out | PreferInfer,
ExportDefault = Export | Default,
All = Export | Ambient | Public | Private | Protected | Static | Readonly | Abstract | Accessor | Async | Default | Const | Deprecated | Override | In | Out | Decorator,
All = ExportDefault | Static | Accessor | Async | Deprecated | Decorator | TypeScriptModifier,
Modifier = All & ~Decorator
}

Expand Down Expand Up @@ -1606,6 +1610,7 @@ export type DeclareKeyword = ModifierToken<SyntaxKind.DeclareKeyword>;
export type DefaultKeyword = ModifierToken<SyntaxKind.DefaultKeyword>;
export type ExportKeyword = ModifierToken<SyntaxKind.ExportKeyword>;
export type InKeyword = ModifierToken<SyntaxKind.InKeyword>;
export type PreferInferKeyword = ModifierToken<SyntaxKind.PreferInferKeyword>;
export type PrivateKeyword = ModifierToken<SyntaxKind.PrivateKeyword>;
export type ProtectedKeyword = ModifierToken<SyntaxKind.ProtectedKeyword>;
export type PublicKeyword = ModifierToken<SyntaxKind.PublicKeyword>;
Expand All @@ -1626,6 +1631,7 @@ export type Modifier =
| DefaultKeyword
| ExportKeyword
| InKeyword
| PreferInferKeyword
| PrivateKeyword
| ProtectedKeyword
| PublicKeyword
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6238,6 +6238,7 @@ export function modifierToFlag(token: SyntaxKind): ModifierFlags {
case SyntaxKind.OverrideKeyword: return ModifierFlags.Override;
case SyntaxKind.InKeyword: return ModifierFlags.In;
case SyntaxKind.OutKeyword: return ModifierFlags.Out;
case SyntaxKind.PreferInferKeyword: return ModifierFlags.PreferInfer;
case SyntaxKind.Decorator: return ModifierFlags.Decorator;
}
return ModifierFlags.None;
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,7 @@ export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] {
case SyntaxKind.ExportKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.PreferInferKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
Expand Down
Loading