Skip to content

Infer type arguments on instance check #58028

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
65 changes: 59 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables");
var strictInstanceOfTypeParameters = getStrictOptionValue(compilerOptions, "strictInstanceOfTypeParameters");
var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes;

var checkBinaryExpression = createCheckBinaryExpression();
Expand Down Expand Up @@ -10534,13 +10535,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
})!.parent;
}

function getInstanceTypeOfClassSymbol(classSymbol: Symbol): Type {
const classType = getDeclaredTypeOfSymbol(classSymbol) as GenericType;
const objectFlags = getObjectFlags(classType);
if (!(objectFlags & ObjectFlags.ClassOrInterface) || !classType.typeParameters) {
return classType;
}
const variances = getVariances(classType);
const isJs = some(classSymbol.declarations, isInJSFile);
const inferredTypes = calculateInferredTypeArguments(classType.typeParameters, isJs);
const typeArguments = map(inferredTypes, (inferredType, i) => {
if (!strictInstanceOfTypeParameters) {
return anyType;
}
const variance = variances[i];
switch (variance & VarianceFlags.VarianceMask) {
case VarianceFlags.Contravariant:
case VarianceFlags.Bivariant:
return neverType;
}
return inferredType || unknownType;
});
return createTypeReference(classType, typeArguments);
}

function getTypeOfPrototypeProperty(prototype: Symbol): Type {
// TypeScript 1.0 spec (April 2014): 8.4
// Every class automatically contains a static property member named 'prototype',
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
// the type of which is an instantiation of the class type.
// Type parameters on this class are instantiated with a type based on their constraint and variance.
// It is an error to explicitly declare a static property member with the name 'prototype'.
const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType;
return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType;
const classSymbol = getParentOfSymbol(prototype)!;
return getInstanceTypeOfClassSymbol(classSymbol);
}

// Return the type of the given property in the given type, or undefined if no such property exists
Expand Down Expand Up @@ -15061,6 +15086,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return typeArguments && typeArguments.slice();
}

/**
* Similar to `fillMissingTypeArguments` returns an array of type arguments that correspond to
* the input array of type parameters.
* However, this function infers the type of each argument to be the constraint of the type
* parameter; instantiating them with the other inferred type arguments.
*/
function calculateInferredTypeArguments(typeParameters: readonly TypeParameter[] | undefined, isJavaScriptImplicitAny: boolean) {
const numTypeParameters = length(typeParameters);
if (!numTypeParameters) {
return [];
}
const result = [];
// Map invalid forward references in default types to the error type
for (let i = 0; i < numTypeParameters; i++) {
result[i] = errorType;
}
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
for (let i = 0; i < numTypeParameters; i++) {
let inferredType = getBaseConstraintOfType(typeParameters![i]);
if (isJavaScriptImplicitAny && inferredType && (isTypeIdenticalTo(inferredType, unknownType) || isTypeIdenticalTo(inferredType, emptyObjectType))) {
inferredType = anyType;
}
result[i] = inferredType ? instantiateType(inferredType, createTypeMapper(typeParameters!, result)) : baseDefaultType;
}
result.length = typeParameters!.length;
return result;
}

function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature {
const links = getNodeLinks(declaration);
if (!links.resolvedSignature) {
Expand Down Expand Up @@ -28411,10 +28464,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (symbol === undefined) {
return type;
}
const classSymbol = symbol.parent!;
const classSymbol = getParentOfSymbol(symbol)!;
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
? getTypeOfSymbol(classSymbol) as InterfaceType
: getDeclaredTypeOfSymbol(classSymbol);
: getInstanceTypeOfClassSymbol(classSymbol);
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
}

Expand Down
10 changes: 10 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,16 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
description: Diagnostics.Check_for_class_properties_that_are_declared_but_not_set_in_the_constructor,
defaultValueDescription: Diagnostics.false_unless_strict_is_set,
},
{
name: "strictInstanceOfTypeParameters",
type: "boolean",
affectsSemanticDiagnostics: true,
affectsBuildInfo: true,
strictFlag: true,
category: Diagnostics.Type_Checking,
description: Diagnostics.Default_type_arguments_to_parameter_s_constraint_or_unknown_instead_of_any_for_instance_checks,
defaultValueDescription: false,
},
{
name: "noImplicitThis",
type: "boolean",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6392,6 +6392,10 @@
"category": "Message",
"code": 6805
},
"Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks.": {
"category": "Message",
"code": 6806
},

"one of:": {
"category": "Message",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7351,6 +7351,7 @@ export interface CompilerOptions {
strictBindCallApply?: boolean; // Always combine with strict property
strictNullChecks?: boolean; // Always combine with strict property
strictPropertyInitialization?: boolean; // Always combine with strict property
strictInstanceOfTypeParameters?: boolean; // Always combine with strict property
stripInternal?: boolean;
/** @deprecated */
suppressExcessPropertyErrors?: boolean;
Expand Down
9 changes: 8 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9016,6 +9016,12 @@ export const computedOptions = createComputedCompilerOptions({
return getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables");
},
},
strictInstanceOfTypeParameters: {
dependencies: ["strict"],
computeValue: compilerOptions => {
return getStrictOptionValue(compilerOptions, "strictInstanceOfTypeParameters");
},
},
});

/** @internal */
Expand Down Expand Up @@ -9092,7 +9098,8 @@ export type StrictOptionName =
| "strictBindCallApply"
| "strictPropertyInitialization"
| "alwaysStrict"
| "useUnknownInCatchVariables";
| "useUnknownInCatchVariables"
| "strictInstanceOfTypeParameters";

/** @internal */
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/accessorsOverrideProperty9.types
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
}

return MixedClass;
>MixedClass : ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<any>.MixedClass; }) & TBaseClass
> : ^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>MixedClass : ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<IApiItemConstructor>.MixedClass; }) & TBaseClass
> : ^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

// Subclass inheriting from mixin
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
aliasInstantiationExpressionGenericIntersectionNoCrash1.ts(10,1): error TS2352: Conversion of type '{ new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)' to type '{ new (): ErrImpl<string>; prototype: ErrImpl<any>; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type '{ new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)' is not comparable to type '{ new (): ErrImpl<string>; prototype: ErrImpl<any>; }'.
aliasInstantiationExpressionGenericIntersectionNoCrash1.ts(10,1): error TS2352: Conversion of type '{ new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)' to type '{ new (): ErrImpl<string>; prototype: ErrImpl<unknown>; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type '{ new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)' is not comparable to type '{ new (): ErrImpl<string>; prototype: ErrImpl<unknown>; }'.
Type 'ErrImpl<number>' is not comparable to type 'ErrImpl<string>'.
Type 'number' is not comparable to type 'string'.

Expand All @@ -16,8 +16,8 @@ aliasInstantiationExpressionGenericIntersectionNoCrash1.ts(10,1): error TS2352:
declare const e: ErrAlias<number>;
e as ErrAlias<string>;
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2352: Conversion of type '{ new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)' to type '{ new (): ErrImpl<string>; prototype: ErrImpl<any>; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
!!! error TS2352: Type '{ new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)' is not comparable to type '{ new (): ErrImpl<string>; prototype: ErrImpl<any>; }'.
!!! error TS2352: Conversion of type '{ new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)' to type '{ new (): ErrImpl<string>; prototype: ErrImpl<unknown>; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
!!! error TS2352: Type '{ new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)' is not comparable to type '{ new (): ErrImpl<string>; prototype: ErrImpl<unknown>; }'.
!!! error TS2352: Type 'ErrImpl<number>' is not comparable to type 'ErrImpl<string>'.
!!! error TS2352: Type 'number' is not comparable to type 'string'.

Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ declare const Err: typeof ErrImpl & (<T>() => T);
> : ^^^^^^^^^^^^^^

type ErrAlias<U> = typeof Err<U>;
>ErrAlias : { new (): ErrImpl<U>; prototype: ErrImpl<any>; } & (() => U)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>ErrAlias : { new (): ErrImpl<U>; prototype: ErrImpl<unknown>; } & (() => U)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Err : typeof ErrImpl & (<T>() => T)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

declare const e: ErrAlias<number>;
>e : { new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>e : { new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

e as ErrAlias<string>;
>e as ErrAlias<string> : { new (): ErrImpl<string>; prototype: ErrImpl<any>; } & (() => string)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>e : { new (): ErrImpl<number>; prototype: ErrImpl<any>; } & (() => number)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>e as ErrAlias<string> : { new (): ErrImpl<string>; prototype: ErrImpl<unknown>; } & (() => string)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>e : { new (): ErrImpl<number>; prototype: ErrImpl<unknown>; } & (() => number)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
aliasInstantiationExpressionGenericIntersectionNoCrash2.ts(15,1): error TS2352: Conversion of type 'Wat<number>' to type 'Wat<string>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'Wat<number>' is not comparable to type '{ new (): Class<string>; prototype: Class<any>; }'.
Type 'Wat<number>' is not comparable to type '{ new (): Class<string>; prototype: Class<unknown>; }'.
Type 'Class<number>' is not comparable to type 'Class<string>'.
Type 'number' is not comparable to type 'string'.

Expand All @@ -22,7 +22,7 @@ aliasInstantiationExpressionGenericIntersectionNoCrash2.ts(15,1): error TS2352:
wat as Wat<string>;
~~~~~~~~~~~~~~~~~~
!!! error TS2352: Conversion of type 'Wat<number>' to type 'Wat<string>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
!!! error TS2352: Type 'Wat<number>' is not comparable to type '{ new (): Class<string>; prototype: Class<any>; }'.
!!! error TS2352: Type 'Wat<number>' is not comparable to type '{ new (): Class<string>; prototype: Class<unknown>; }'.
!!! error TS2352: Type 'Class<number>' is not comparable to type 'Class<string>'.
!!! error TS2352: Type 'number' is not comparable to type 'string'.

1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6967,6 +6967,7 @@ declare namespace ts {
strictBindCallApply?: boolean;
strictNullChecks?: boolean;
strictPropertyInitialization?: boolean;
strictInstanceOfTypeParameters?: boolean;
stripInternal?: boolean;
/** @deprecated */
suppressExcessPropertyErrors?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictInstanceOfTypeParameters": true, /* Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictInstanceOfTypeParameters": true, /* Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictInstanceOfTypeParameters": true, /* Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictInstanceOfTypeParameters": true, /* Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictInstanceOfTypeParameters": true, /* Default type arguments to parameter's constraint or 'unknown' instead of 'any' for instance checks. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
Expand Down
Loading