Skip to content

Commit 957043a

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

File tree

40 files changed

+622
-17
lines changed

40 files changed

+622
-17
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 4 deletions
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,25 @@ 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',
87068718
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
8719+
// FIXME: this needs to be updated to address new behavior with inferInstanceTypeArgumentsAsConstraint
87078720
// 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;
8721+
const classSymbol = getParentOfSymbol(prototype)!;
8722+
return getInstanceTypeOfClassSymbol(classSymbol);
87108723
}
87118724

87128725
// Return the type of the given property in the given type, or undefined if no such property exists
@@ -25135,10 +25148,10 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2513525148
if (symbol === undefined) {
2513625149
return type;
2513725150
}
25138-
const classSymbol = symbol.parent!;
25151+
const classSymbol = getParentOfSymbol(symbol)!;
2513925152
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
2514025153
? getTypeOfSymbol(classSymbol) as InterfaceType
25141-
: getDeclaredTypeOfSymbol(classSymbol);
25154+
: getInstanceTypeOfClassSymbol(classSymbol);
2514225155
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
2514325156
}
2514425157

src/compiler/commandLineParser.ts

Lines changed: 10 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

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
@@ -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

Lines changed: 1 addition & 0 deletions
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;
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)