Skip to content

Commit 8a7b844

Browse files
authored
Merge pull request #19655 from Microsoft/instantiate-this-in-type-parameter-constraints
Instantiate this when used only in type parameter constraints
2 parents 592ee00 + 0c77b77 commit 8a7b844

File tree

5 files changed

+144
-51
lines changed

5 files changed

+144
-51
lines changed

src/compiler/checker.ts

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5104,10 +5104,14 @@ namespace ts {
51045104
}
51055105
}
51065106

5107-
// Returns true if the interface given by the symbol is free of "this" references. Specifically, the result is
5108-
// true if the interface itself contains no references to "this" in its body, if all base types are interfaces,
5109-
// and if none of the base interfaces have a "this" type.
5110-
function isIndependentInterface(symbol: Symbol): boolean {
5107+
/**
5108+
* Returns true if the interface given by the symbol is free of "this" references.
5109+
*
5110+
* Specifically, the result is true if the interface itself contains no references
5111+
* to "this" in its body, if all base types are interfaces,
5112+
* and if none of the base interfaces have a "this" type.
5113+
*/
5114+
function isThislessInterface(symbol: Symbol): boolean {
51115115
for (const declaration of symbol.declarations) {
51125116
if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
51135117
if (declaration.flags & NodeFlags.ContainsThis) {
@@ -5141,7 +5145,7 @@ namespace ts {
51415145
// property types inferred from initializers and method return types inferred from return statements are very hard
51425146
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
51435147
// "this" references.
5144-
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isIndependentInterface(symbol)) {
5148+
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) {
51455149
type.objectFlags |= ObjectFlags.Reference;
51465150
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
51475151
type.outerTypeParameters = outerTypeParameters;
@@ -5323,22 +5327,12 @@ namespace ts {
53235327
return undefined;
53245328
}
53255329

5326-
// A type reference is considered independent if each type argument is considered independent.
5327-
function isIndependentTypeReference(node: TypeReferenceNode): boolean {
5328-
if (node.typeArguments) {
5329-
for (const typeNode of node.typeArguments) {
5330-
if (!isIndependentType(typeNode)) {
5331-
return false;
5332-
}
5333-
}
5334-
}
5335-
return true;
5336-
}
5337-
5338-
// A type is considered independent if it the any, string, number, boolean, symbol, or void keyword, a string
5339-
// literal type, an array with an element type that is considered independent, or a type reference that is
5340-
// considered independent.
5341-
function isIndependentType(node: TypeNode): boolean {
5330+
/**
5331+
* A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
5332+
* literal type, an array with an element type that is free of this references, or a type reference that is
5333+
* free of this references.
5334+
*/
5335+
function isThislessType(node: TypeNode): boolean {
53425336
switch (node.kind) {
53435337
case SyntaxKind.AnyKeyword:
53445338
case SyntaxKind.StringKeyword:
@@ -5353,54 +5347,58 @@ namespace ts {
53535347
case SyntaxKind.LiteralType:
53545348
return true;
53555349
case SyntaxKind.ArrayType:
5356-
return isIndependentType((<ArrayTypeNode>node).elementType);
5350+
return isThislessType((<ArrayTypeNode>node).elementType);
53575351
case SyntaxKind.TypeReference:
5358-
return isIndependentTypeReference(<TypeReferenceNode>node);
5352+
return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType);
53595353
}
53605354
return false;
53615355
}
53625356

5363-
// A variable-like declaration is considered independent (free of this references) if it has a type annotation
5364-
// that specifies an independent type, or if it has no type annotation and no initializer (and thus of type any).
5365-
function isIndependentVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
5357+
/** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */
5358+
function isThislessTypeParameter(node: TypeParameterDeclaration) {
5359+
return !node.constraint || isThislessType(node.constraint);
5360+
}
5361+
5362+
/**
5363+
* A variable-like declaration is free of this references if it has a type annotation
5364+
* that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
5365+
*/
5366+
function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
53665367
const typeNode = getEffectiveTypeAnnotationNode(node);
5367-
return typeNode ? isIndependentType(typeNode) : !node.initializer;
5368+
return typeNode ? isThislessType(typeNode) : !node.initializer;
53685369
}
53695370

5370-
// A function-like declaration is considered independent (free of this references) if it has a return type
5371-
// annotation that is considered independent and if each parameter is considered independent.
5372-
function isIndependentFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
5373-
if (node.kind !== SyntaxKind.Constructor) {
5374-
const typeNode = getEffectiveReturnTypeNode(node);
5375-
if (!typeNode || !isIndependentType(typeNode)) {
5376-
return false;
5377-
}
5378-
}
5379-
for (const parameter of node.parameters) {
5380-
if (!isIndependentVariableLikeDeclaration(parameter)) {
5381-
return false;
5382-
}
5383-
}
5384-
return true;
5371+
/**
5372+
* A function-like declaration is considered free of `this` references if it has a return type
5373+
* annotation that is free of this references and if each parameter is thisless and if
5374+
* each type parameter (if present) is thisless.
5375+
*/
5376+
function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
5377+
const returnType = getEffectiveReturnTypeNode(node);
5378+
return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
5379+
node.parameters.every(isThislessVariableLikeDeclaration) &&
5380+
(!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
53855381
}
53865382

5387-
// Returns true if the class or interface member given by the symbol is free of "this" references. The
5388-
// function may return false for symbols that are actually free of "this" references because it is not
5389-
// feasible to perform a complete analysis in all cases. In particular, property members with types
5390-
// inferred from their initializers and function members with inferred return types are conservatively
5391-
// assumed not to be free of "this" references.
5392-
function isIndependentMember(symbol: Symbol): boolean {
5383+
/**
5384+
* Returns true if the class or interface member given by the symbol is free of "this" references. The
5385+
* function may return false for symbols that are actually free of "this" references because it is not
5386+
* feasible to perform a complete analysis in all cases. In particular, property members with types
5387+
* inferred from their initializers and function members with inferred return types are conservatively
5388+
* assumed not to be free of "this" references.
5389+
*/
5390+
function isThisless(symbol: Symbol): boolean {
53935391
if (symbol.declarations && symbol.declarations.length === 1) {
53945392
const declaration = symbol.declarations[0];
53955393
if (declaration) {
53965394
switch (declaration.kind) {
53975395
case SyntaxKind.PropertyDeclaration:
53985396
case SyntaxKind.PropertySignature:
5399-
return isIndependentVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
5397+
return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
54005398
case SyntaxKind.MethodDeclaration:
54015399
case SyntaxKind.MethodSignature:
54025400
case SyntaxKind.Constructor:
5403-
return isIndependentFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
5401+
return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
54045402
}
54055403
}
54065404
}
@@ -5412,7 +5410,7 @@ namespace ts {
54125410
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
54135411
const result = createSymbolTable();
54145412
for (const symbol of symbols) {
5415-
result.set(symbol.escapedName, mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper));
5413+
result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper));
54165414
}
54175415
return result;
54185416
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [thisTypeInFunctions3.ts]
2+
declare class Base {
3+
check<TProp extends this>(prop: TProp): boolean;
4+
}
5+
6+
class Test extends Base {
7+
m() {
8+
this.check(this);
9+
}
10+
}
11+
12+
13+
//// [thisTypeInFunctions3.js]
14+
var __extends = (this && this.__extends) || (function () {
15+
var extendStatics = Object.setPrototypeOf ||
16+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
17+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
18+
return function (d, b) {
19+
extendStatics(d, b);
20+
function __() { this.constructor = d; }
21+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
22+
};
23+
})();
24+
var Test = /** @class */ (function (_super) {
25+
__extends(Test, _super);
26+
function Test() {
27+
return _super !== null && _super.apply(this, arguments) || this;
28+
}
29+
Test.prototype.m = function () {
30+
this.check(this);
31+
};
32+
return Test;
33+
}(Base));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts ===
2+
declare class Base {
3+
>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0))
4+
5+
check<TProp extends this>(prop: TProp): boolean;
6+
>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
7+
>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10))
8+
>prop : Symbol(prop, Decl(thisTypeInFunctions3.ts, 1, 30))
9+
>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10))
10+
}
11+
12+
class Test extends Base {
13+
>Test : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
14+
>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0))
15+
16+
m() {
17+
>m : Symbol(Test.m, Decl(thisTypeInFunctions3.ts, 4, 25))
18+
19+
this.check(this);
20+
>this.check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
21+
>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
22+
>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
23+
>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
24+
}
25+
}
26+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts ===
2+
declare class Base {
3+
>Base : Base
4+
5+
check<TProp extends this>(prop: TProp): boolean;
6+
>check : <TProp extends this>(prop: TProp) => boolean
7+
>TProp : TProp
8+
>prop : TProp
9+
>TProp : TProp
10+
}
11+
12+
class Test extends Base {
13+
>Test : Test
14+
>Base : Base
15+
16+
m() {
17+
>m : () => void
18+
19+
this.check(this);
20+
>this.check(this) : boolean
21+
>this.check : <TProp extends this>(prop: TProp) => boolean
22+
>this : this
23+
>check : <TProp extends this>(prop: TProp) => boolean
24+
>this : this
25+
}
26+
}
27+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare class Base {
2+
check<TProp extends this>(prop: TProp): boolean;
3+
}
4+
5+
class Test extends Base {
6+
m() {
7+
this.check(this);
8+
}
9+
}

0 commit comments

Comments
 (0)