Skip to content

Commit 0af9933

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

File tree

40 files changed

+622
-18
lines changed

40 files changed

+622
-18
lines changed

src/compiler/checker.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
383383
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
384384
const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
385385
const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables");
386+
const inferInstanceTypeArgumentsAsConstraint = getStrictOptionValue(compilerOptions, "inferInstanceTypeArgumentsAsConstraint");
386387
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
387388
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
388389
const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes;
@@ -8700,13 +8701,24 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
87008701
})!.parent;
87018702
}
87028703

8704+
function getInstanceTypeOfClassSymbol(classSymbol: Symbol): Type {
8705+
const classType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType;
8706+
if (!classType.typeParameters) {
8707+
return classType;
8708+
}
8709+
const typeArguments = map(classType.typeParameters, typeParameter => {
8710+
return (inferInstanceTypeArgumentsAsConstraint) ? getBaseConstraintOfType(typeParameter) || unknownType : anyType;
8711+
});
8712+
return createTypeReference(classType as GenericType, typeArguments);
8713+
}
8714+
87038715
function getTypeOfPrototypeProperty(prototype: Symbol): Type {
87048716
// TypeScript 1.0 spec (April 2014): 8.4
87058717
// Every class automatically contains a static property member named 'prototype',
8706-
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
8718+
// the type of which is an instantiation of the class type.
87078719
// It is an error to explicitly declare a static property member with the name 'prototype'.
8708-
const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType;
8709-
return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType;
8720+
const classSymbol = getParentOfSymbol(prototype)!;
8721+
return getInstanceTypeOfClassSymbol(classSymbol);
87108722
}
87118723

87128724
// Return the type of the given property in the given type, or undefined if no such property exists
@@ -25135,10 +25147,10 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2513525147
if (symbol === undefined) {
2513625148
return type;
2513725149
}
25138-
const classSymbol = symbol.parent!;
25150+
const classSymbol = getParentOfSymbol(symbol)!;
2513925151
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
2514025152
? getTypeOfSymbol(classSymbol) as InterfaceType
25141-
: getDeclaredTypeOfSymbol(classSymbol);
25153+
: getInstanceTypeOfClassSymbol(classSymbol);
2514225154
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
2514325155
}
2514425156

src/compiler/commandLineParser.ts

+10
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,16 @@ namespace ts {
739739
description: Diagnostics.Default_catch_clause_variables_as_unknown_instead_of_any,
740740
defaultValueDescription: false,
741741
},
742+
{
743+
name: "inferInstanceTypeArgumentsAsConstraint",
744+
type: "boolean",
745+
affectsSemanticDiagnostics: true,
746+
affectsMultiFileEmitBuildInfo: true,
747+
strictFlag: true,
748+
category: Diagnostics.Type_Checking,
749+
description: Diagnostics.Default_type_arguments_to_parameter_constraint_or_unknown_instead_of_any,
750+
defaultValueDescription: false,
751+
},
742752
{
743753
name: "alwaysStrict",
744754
type: "boolean",

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -5843,6 +5843,10 @@
58435843
"category": "Message",
58445844
"code": 6803
58455845
},
5846+
"Default type arguments to parameter constraint or 'unknown' instead of 'any'.": {
5847+
"category": "Message",
5848+
"code": 6804
5849+
},
58465850

58475851
"one of:": {
58485852
"category": "Message",

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6342,6 +6342,7 @@ namespace ts {
63426342
/*@internal*/help?: boolean;
63436343
importHelpers?: boolean;
63446344
importsNotUsedAsValues?: ImportsNotUsedAsValues;
6345+
inferInstanceTypeArgumentsAsConstraint?: boolean;
63456346
/*@internal*/init?: boolean;
63466347
inlineSourceMap?: boolean;
63476348
inlineSources?: boolean;

src/compiler/utilities.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6459,6 +6459,7 @@ namespace ts {
64596459
| "strictPropertyInitialization"
64606460
| "alwaysStrict"
64616461
| "useUnknownInCatchVariables"
6462+
| "inferInstanceTypeArgumentsAsConstraint"
64626463
;
64636464

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

tests/baselines/reference/accessorsOverrideProperty9.types

+1-1
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

+1
Original file line numberDiff line numberDiff line change
@@ -3002,6 +3002,7 @@ declare namespace ts {
30023002
forceConsistentCasingInFileNames?: boolean;
30033003
importHelpers?: boolean;
30043004
importsNotUsedAsValues?: ImportsNotUsedAsValues;
3005+
inferInstanceTypeArgumentsAsConstraint?: boolean;
30053006
inlineSourceMap?: boolean;
30063007
inlineSources?: boolean;
30073008
isolatedModules?: boolean;

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

+1
Original file line numberDiff line numberDiff line change
@@ -3002,6 +3002,7 @@ declare namespace ts {
30023002
forceConsistentCasingInFileNames?: boolean;
30033003
importHelpers?: boolean;
30043004
importsNotUsedAsValues?: ImportsNotUsedAsValues;
3005+
inferInstanceTypeArgumentsAsConstraint?: boolean;
30053006
inlineSourceMap?: boolean;
30063007
inlineSources?: boolean;
30073008
isolatedModules?: boolean;
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+
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
=== tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts ===
2+
class Unconstrained<T> {
3+
>Unconstrained : Symbol(Unconstrained, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 0))
4+
>T : Symbol(T, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 20))
5+
6+
value: T;
7+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
8+
>T : Symbol(T, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 20))
9+
}
10+
11+
declare const x: unknown;
12+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
13+
14+
if (x instanceof Unconstrained) {
15+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
16+
>Unconstrained : Symbol(Unconstrained, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 0))
17+
18+
x.value.toUpperCase();
19+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
20+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
21+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
22+
23+
x.value++;
24+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
25+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
26+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
27+
28+
x.value();
29+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
30+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
31+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
32+
33+
if (typeof x.value === "string") {
34+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
35+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
36+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
37+
38+
x.value.toUpperCase();
39+
>x.value.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
40+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
41+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
42+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
43+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
44+
}
45+
if (typeof x.value === "number") {
46+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
47+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
48+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
49+
50+
x.value++;
51+
>x.value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
52+
>x : Symbol(x, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 4, 13))
53+
>value : Symbol(Unconstrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 0, 24))
54+
}
55+
}
56+
57+
class Constrained<T extends number> {
58+
>Constrained : Symbol(Constrained, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 17, 1))
59+
>T : Symbol(T, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 19, 18))
60+
61+
value: T;
62+
>value : Symbol(Constrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 19, 37))
63+
>T : Symbol(T, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 19, 18))
64+
}
65+
66+
declare const y: unknown;
67+
>y : Symbol(y, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 23, 13))
68+
69+
if (y instanceof Constrained) {
70+
>y : Symbol(y, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 23, 13))
71+
>Constrained : Symbol(Constrained, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 17, 1))
72+
73+
y.value++;
74+
>y.value : Symbol(Constrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 19, 37))
75+
>y : Symbol(y, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 23, 13))
76+
>value : Symbol(Constrained.value, Decl(inferInstanceTypeArgumentsAsConstraint.ts, 19, 37))
77+
}
78+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
=== tests/cases/compiler/inferInstanceTypeArgumentsAsConstraint.ts ===
2+
class Unconstrained<T> {
3+
>Unconstrained : Unconstrained<T>
4+
5+
value: T;
6+
>value : T
7+
}
8+
9+
declare const x: unknown;
10+
>x : unknown
11+
12+
if (x instanceof Unconstrained) {
13+
>x instanceof Unconstrained : boolean
14+
>x : unknown
15+
>Unconstrained : typeof Unconstrained
16+
17+
x.value.toUpperCase();
18+
>x.value.toUpperCase() : any
19+
>x.value.toUpperCase : any
20+
>x.value : unknown
21+
>x : Unconstrained<unknown>
22+
>value : unknown
23+
>toUpperCase : any
24+
25+
x.value++;
26+
>x.value++ : number
27+
>x.value : unknown
28+
>x : Unconstrained<unknown>
29+
>value : unknown
30+
31+
x.value();
32+
>x.value() : any
33+
>x.value : unknown
34+
>x : Unconstrained<unknown>
35+
>value : unknown
36+
37+
if (typeof x.value === "string") {
38+
>typeof x.value === "string" : boolean
39+
>typeof x.value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
40+
>x.value : unknown
41+
>x : Unconstrained<unknown>
42+
>value : unknown
43+
>"string" : "string"
44+
45+
x.value.toUpperCase();
46+
>x.value.toUpperCase() : string
47+
>x.value.toUpperCase : () => string
48+
>x.value : string
49+
>x : Unconstrained<unknown>
50+
>value : string
51+
>toUpperCase : () => string
52+
}
53+
if (typeof x.value === "number") {
54+
>typeof x.value === "number" : boolean
55+
>typeof x.value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
56+
>x.value : unknown
57+
>x : Unconstrained<unknown>
58+
>value : unknown
59+
>"number" : "number"
60+
61+
x.value++;
62+
>x.value++ : number
63+
>x.value : number
64+
>x : Unconstrained<unknown>
65+
>value : number
66+
}
67+
}
68+
69+
class Constrained<T extends number> {
70+
>Constrained : Constrained<T>
71+
72+
value: T;
73+
>value : T
74+
}
75+
76+
declare const y: unknown;
77+
>y : unknown
78+
79+
if (y instanceof Constrained) {
80+
>y instanceof Constrained : boolean
81+
>y : unknown
82+
>Constrained : typeof Constrained
83+
84+
y.value++;
85+
>y.value++ : number
86+
>y.value : number
87+
>y : Constrained<number>
88+
>value : number
89+
}
90+

0 commit comments

Comments
 (0)