Skip to content

Commit d656065

Browse files
committed
Infer instance type arguments as constraint
Signed-off-by: Michael Molisani <[email protected]>
1 parent c1e9afd commit d656065

File tree

40 files changed

+853
-255
lines changed

40 files changed

+853
-255
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ namespace ts {
364364
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
365365
const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
366366
const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables");
367+
const inferInstanceTypeArgumentsAsConstraint = getStrictOptionValue(compilerOptions, "inferInstanceTypeArgumentsAsConstraint");
367368
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
368369
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
369370
const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes;
@@ -8839,13 +8840,24 @@ namespace ts {
88398840
})!.parent;
88408841
}
88418842

8843+
function getInstanceTypeOfClassSymbol(classSymbol: Symbol): Type {
8844+
const classType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType;
8845+
if (!classType.typeParameters) {
8846+
return classType;
8847+
}
8848+
const typeArguments = map(classType.typeParameters, typeParameter => {
8849+
return (inferInstanceTypeArgumentsAsConstraint) ? getBaseConstraintOfType(typeParameter) || unknownType : anyType;
8850+
});
8851+
return createTypeReference(classType as GenericType, typeArguments);
8852+
}
8853+
88428854
function getTypeOfPrototypeProperty(prototype: Symbol): Type {
88438855
// TypeScript 1.0 spec (April 2014): 8.4
88448856
// Every class automatically contains a static property member named 'prototype',
8845-
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
8857+
// the type of which is an instantiation of the class type.
88468858
// It is an error to explicitly declare a static property member with the name 'prototype'.
8847-
const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType;
8848-
return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType;
8859+
const classSymbol = getParentOfSymbol(prototype)!;
8860+
return getInstanceTypeOfClassSymbol(classSymbol);
88498861
}
88508862

88518863
// Return the type of the given property in the given type, or undefined if no such property exists
@@ -25460,10 +25472,10 @@ namespace ts {
2546025472
if (symbol === undefined) {
2546125473
return type;
2546225474
}
25463-
const classSymbol = symbol.parent!;
25475+
const classSymbol = getParentOfSymbol(symbol)!;
2546425476
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
2546525477
? getTypeOfSymbol(classSymbol) as InterfaceType
25466-
: getDeclaredTypeOfSymbol(classSymbol);
25478+
: getInstanceTypeOfClassSymbol(classSymbol);
2546725479
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
2546825480
}
2546925481

src/compiler/commandLineParser.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,16 @@ namespace ts {
740740
description: Diagnostics.Default_catch_clause_variables_as_unknown_instead_of_any,
741741
defaultValueDescription: false,
742742
},
743+
{
744+
name: "inferInstanceTypeArgumentsAsConstraint",
745+
type: "boolean",
746+
affectsSemanticDiagnostics: true,
747+
affectsMultiFileEmitBuildInfo: true,
748+
strictFlag: true,
749+
category: Diagnostics.Type_Checking,
750+
description: Diagnostics.Default_type_arguments_to_parameter_constraint_or_unknown_instead_of_any,
751+
defaultValueDescription: false,
752+
},
743753
{
744754
name: "alwaysStrict",
745755
type: "boolean",

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5919,6 +5919,10 @@
59195919
"category": "Message",
59205920
"code": 6803
59215921
},
5922+
"Default type arguments to parameter constraint or 'unknown' instead of 'any'.": {
5923+
"category": "Message",
5924+
"code": 6804
5925+
},
59225926

59235927
"one of:": {
59245928
"category": "Message",

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6581,6 +6581,7 @@ namespace ts {
65816581
/*@internal*/help?: boolean;
65826582
importHelpers?: boolean;
65836583
importsNotUsedAsValues?: ImportsNotUsedAsValues;
6584+
inferInstanceTypeArgumentsAsConstraint?: boolean;
65846585
/*@internal*/init?: boolean;
65856586
inlineSourceMap?: boolean;
65866587
inlineSources?: boolean;

src/compiler/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6466,6 +6466,7 @@ namespace ts {
64666466
| "strictPropertyInitialization"
64676467
| "alwaysStrict"
64686468
| "useUnknownInCatchVariables"
6469+
| "inferInstanceTypeArgumentsAsConstraint"
64696470
;
64706471

64716472
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {

tests/baselines/reference/accessorsOverrideProperty9.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
6868
}
6969

7070
return MixedClass;
71-
>MixedClass : ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<any>.MixedClass; }) & TBaseClass
71+
>MixedClass : ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<IApiItemConstructor>.MixedClass; }) & TBaseClass
7272
}
7373

7474
// Subclass inheriting from mixin

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3017,6 +3017,7 @@ declare namespace ts {
30173017
forceConsistentCasingInFileNames?: boolean;
30183018
importHelpers?: boolean;
30193019
importsNotUsedAsValues?: ImportsNotUsedAsValues;
3020+
inferInstanceTypeArgumentsAsConstraint?: boolean;
30203021
inlineSourceMap?: boolean;
30213022
inlineSources?: boolean;
30223023
isolatedModules?: boolean;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3017,6 +3017,7 @@ declare namespace ts {
30173017
forceConsistentCasingInFileNames?: boolean;
30183018
importHelpers?: boolean;
30193019
importsNotUsedAsValues?: ImportsNotUsedAsValues;
3020+
inferInstanceTypeArgumentsAsConstraint?: boolean;
30203021
inlineSourceMap?: boolean;
30213022
inlineSources?: boolean;
30223023
isolatedModules?: boolean;

tests/baselines/reference/config/initTSConfig/Default initialized TSConfig/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with --help/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with advanced options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with enum value compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with files options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with list compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
8585
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
8686
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87+
// "inferInstanceTypeArgumentsAsConstraint": true, /* Default type arguments to parameter constraint or 'unknown' instead of 'any'. */
8788
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8889
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8990
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts(8,13): error TS2339: Property 'toUpperCase' does not exist on type 'unknown'.
2+
tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts(9,5): error TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type.
3+
tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts(10,7): error TS2349: This expression is not callable.
4+
Type '{}' has no call signatures.
5+
6+
7+
==== tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts (3 errors) ====
8+
class Unconstrained<T> {
9+
value: T;
10+
}
11+
12+
declare const x: unknown;
13+
14+
if (x instanceof Unconstrained) {
15+
x.value.toUpperCase();
16+
~~~~~~~~~~~
17+
!!! error TS2339: Property 'toUpperCase' does not exist on type 'unknown'.
18+
x.value++;
19+
~~~~~~~
20+
!!! error TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type.
21+
x.value();
22+
~~~~~
23+
!!! error TS2349: This expression is not callable.
24+
!!! error TS2349: Type '{}' has no call signatures.
25+
26+
if (typeof x.value === "string") {
27+
x.value.toUpperCase();
28+
}
29+
if (typeof x.value === "number") {
30+
x.value++;
31+
}
32+
}
33+
34+
class Constrained<T extends number> {
35+
value: T;
36+
}
37+
38+
declare const y: unknown;
39+
40+
if (y instanceof Constrained) {
41+
y.value++;
42+
}
43+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [inferInstanceTypeArgumentsAsConstraint.ts]
2+
class Unconstrained<T> {
3+
value: T;
4+
}
5+
6+
declare const x: unknown;
7+
8+
if (x instanceof Unconstrained) {
9+
x.value.toUpperCase();
10+
x.value++;
11+
x.value();
12+
13+
if (typeof x.value === "string") {
14+
x.value.toUpperCase();
15+
}
16+
if (typeof x.value === "number") {
17+
x.value++;
18+
}
19+
}
20+
21+
class Constrained<T extends number> {
22+
value: T;
23+
}
24+
25+
declare const y: unknown;
26+
27+
if (y instanceof Constrained) {
28+
y.value++;
29+
}
30+
31+
32+
//// [inferInstanceTypeArgumentsAsConstraint.js]
33+
var Unconstrained = /** @class */ (function () {
34+
function Unconstrained() {
35+
}
36+
return Unconstrained;
37+
}());
38+
if (x instanceof Unconstrained) {
39+
x.value.toUpperCase();
40+
x.value++;
41+
x.value();
42+
if (typeof x.value === "string") {
43+
x.value.toUpperCase();
44+
}
45+
if (typeof x.value === "number") {
46+
x.value++;
47+
}
48+
}
49+
var Constrained = /** @class */ (function () {
50+
function Constrained() {
51+
}
52+
return Constrained;
53+
}());
54+
if (y instanceof Constrained) {
55+
y.value++;
56+
}

0 commit comments

Comments
 (0)