mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-09 02:30:15 -06:00
Merge pull request #19655 from Microsoft/instantiate-this-in-type-parameter-constraints
Instantiate this when used only in type parameter constraints
This commit is contained in:
commit
8a7b8445de
@ -5104,10 +5104,14 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the interface given by the symbol is free of "this" references. Specifically, the result is
|
||||
// true if the interface itself contains no references to "this" in its body, if all base types are interfaces,
|
||||
// and if none of the base interfaces have a "this" type.
|
||||
function isIndependentInterface(symbol: Symbol): boolean {
|
||||
/**
|
||||
* Returns true if the interface given by the symbol is free of "this" references.
|
||||
*
|
||||
* Specifically, the result is true if the interface itself contains no references
|
||||
* to "this" in its body, if all base types are interfaces,
|
||||
* and if none of the base interfaces have a "this" type.
|
||||
*/
|
||||
function isThislessInterface(symbol: Symbol): boolean {
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
|
||||
if (declaration.flags & NodeFlags.ContainsThis) {
|
||||
@ -5141,7 +5145,7 @@ namespace ts {
|
||||
// property types inferred from initializers and method return types inferred from return statements are very hard
|
||||
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
|
||||
// "this" references.
|
||||
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isIndependentInterface(symbol)) {
|
||||
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) {
|
||||
type.objectFlags |= ObjectFlags.Reference;
|
||||
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
|
||||
type.outerTypeParameters = outerTypeParameters;
|
||||
@ -5323,22 +5327,12 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// A type reference is considered independent if each type argument is considered independent.
|
||||
function isIndependentTypeReference(node: TypeReferenceNode): boolean {
|
||||
if (node.typeArguments) {
|
||||
for (const typeNode of node.typeArguments) {
|
||||
if (!isIndependentType(typeNode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A type is considered independent if it the any, string, number, boolean, symbol, or void keyword, a string
|
||||
// literal type, an array with an element type that is considered independent, or a type reference that is
|
||||
// considered independent.
|
||||
function isIndependentType(node: TypeNode): boolean {
|
||||
/**
|
||||
* A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
|
||||
* literal type, an array with an element type that is free of this references, or a type reference that is
|
||||
* free of this references.
|
||||
*/
|
||||
function isThislessType(node: TypeNode): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.AnyKeyword:
|
||||
case SyntaxKind.StringKeyword:
|
||||
@ -5353,54 +5347,58 @@ namespace ts {
|
||||
case SyntaxKind.LiteralType:
|
||||
return true;
|
||||
case SyntaxKind.ArrayType:
|
||||
return isIndependentType((<ArrayTypeNode>node).elementType);
|
||||
return isThislessType((<ArrayTypeNode>node).elementType);
|
||||
case SyntaxKind.TypeReference:
|
||||
return isIndependentTypeReference(<TypeReferenceNode>node);
|
||||
return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// A variable-like declaration is considered independent (free of this references) if it has a type annotation
|
||||
// that specifies an independent type, or if it has no type annotation and no initializer (and thus of type any).
|
||||
function isIndependentVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
|
||||
/** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */
|
||||
function isThislessTypeParameter(node: TypeParameterDeclaration) {
|
||||
return !node.constraint || isThislessType(node.constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable-like declaration is free of this references if it has a type annotation
|
||||
* that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
|
||||
*/
|
||||
function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
|
||||
const typeNode = getEffectiveTypeAnnotationNode(node);
|
||||
return typeNode ? isIndependentType(typeNode) : !node.initializer;
|
||||
return typeNode ? isThislessType(typeNode) : !node.initializer;
|
||||
}
|
||||
|
||||
// A function-like declaration is considered independent (free of this references) if it has a return type
|
||||
// annotation that is considered independent and if each parameter is considered independent.
|
||||
function isIndependentFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
|
||||
if (node.kind !== SyntaxKind.Constructor) {
|
||||
const typeNode = getEffectiveReturnTypeNode(node);
|
||||
if (!typeNode || !isIndependentType(typeNode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const parameter of node.parameters) {
|
||||
if (!isIndependentVariableLikeDeclaration(parameter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* A function-like declaration is considered free of `this` references if it has a return type
|
||||
* annotation that is free of this references and if each parameter is thisless and if
|
||||
* each type parameter (if present) is thisless.
|
||||
*/
|
||||
function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
|
||||
const returnType = getEffectiveReturnTypeNode(node);
|
||||
return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
|
||||
node.parameters.every(isThislessVariableLikeDeclaration) &&
|
||||
(!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
|
||||
}
|
||||
|
||||
// Returns true if the class or interface member given by the symbol is free of "this" references. The
|
||||
// function may return false for symbols that are actually free of "this" references because it is not
|
||||
// feasible to perform a complete analysis in all cases. In particular, property members with types
|
||||
// inferred from their initializers and function members with inferred return types are conservatively
|
||||
// assumed not to be free of "this" references.
|
||||
function isIndependentMember(symbol: Symbol): boolean {
|
||||
/**
|
||||
* Returns true if the class or interface member given by the symbol is free of "this" references. The
|
||||
* function may return false for symbols that are actually free of "this" references because it is not
|
||||
* feasible to perform a complete analysis in all cases. In particular, property members with types
|
||||
* inferred from their initializers and function members with inferred return types are conservatively
|
||||
* assumed not to be free of "this" references.
|
||||
*/
|
||||
function isThisless(symbol: Symbol): boolean {
|
||||
if (symbol.declarations && symbol.declarations.length === 1) {
|
||||
const declaration = symbol.declarations[0];
|
||||
if (declaration) {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
return isIndependentVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
|
||||
return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.Constructor:
|
||||
return isIndependentFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
|
||||
return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5412,7 +5410,7 @@ namespace ts {
|
||||
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
|
||||
const result = createSymbolTable();
|
||||
for (const symbol of symbols) {
|
||||
result.set(symbol.escapedName, mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper));
|
||||
result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
33
tests/baselines/reference/thisTypeInFunctions3.js
Normal file
33
tests/baselines/reference/thisTypeInFunctions3.js
Normal file
@ -0,0 +1,33 @@
|
||||
//// [thisTypeInFunctions3.ts]
|
||||
declare class Base {
|
||||
check<TProp extends this>(prop: TProp): boolean;
|
||||
}
|
||||
|
||||
class Test extends Base {
|
||||
m() {
|
||||
this.check(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [thisTypeInFunctions3.js]
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var Test = /** @class */ (function (_super) {
|
||||
__extends(Test, _super);
|
||||
function Test() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
Test.prototype.m = function () {
|
||||
this.check(this);
|
||||
};
|
||||
return Test;
|
||||
}(Base));
|
||||
26
tests/baselines/reference/thisTypeInFunctions3.symbols
Normal file
26
tests/baselines/reference/thisTypeInFunctions3.symbols
Normal file
@ -0,0 +1,26 @@
|
||||
=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts ===
|
||||
declare class Base {
|
||||
>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0))
|
||||
|
||||
check<TProp extends this>(prop: TProp): boolean;
|
||||
>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
|
||||
>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10))
|
||||
>prop : Symbol(prop, Decl(thisTypeInFunctions3.ts, 1, 30))
|
||||
>TProp : Symbol(TProp, Decl(thisTypeInFunctions3.ts, 1, 10))
|
||||
}
|
||||
|
||||
class Test extends Base {
|
||||
>Test : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
|
||||
>Base : Symbol(Base, Decl(thisTypeInFunctions3.ts, 0, 0))
|
||||
|
||||
m() {
|
||||
>m : Symbol(Test.m, Decl(thisTypeInFunctions3.ts, 4, 25))
|
||||
|
||||
this.check(this);
|
||||
>this.check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
|
||||
>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
|
||||
>check : Symbol(Base.check, Decl(thisTypeInFunctions3.ts, 0, 20))
|
||||
>this : Symbol(Test, Decl(thisTypeInFunctions3.ts, 2, 1))
|
||||
}
|
||||
}
|
||||
|
||||
27
tests/baselines/reference/thisTypeInFunctions3.types
Normal file
27
tests/baselines/reference/thisTypeInFunctions3.types
Normal file
@ -0,0 +1,27 @@
|
||||
=== tests/cases/conformance/types/thisType/thisTypeInFunctions3.ts ===
|
||||
declare class Base {
|
||||
>Base : Base
|
||||
|
||||
check<TProp extends this>(prop: TProp): boolean;
|
||||
>check : <TProp extends this>(prop: TProp) => boolean
|
||||
>TProp : TProp
|
||||
>prop : TProp
|
||||
>TProp : TProp
|
||||
}
|
||||
|
||||
class Test extends Base {
|
||||
>Test : Test
|
||||
>Base : Base
|
||||
|
||||
m() {
|
||||
>m : () => void
|
||||
|
||||
this.check(this);
|
||||
>this.check(this) : boolean
|
||||
>this.check : <TProp extends this>(prop: TProp) => boolean
|
||||
>this : this
|
||||
>check : <TProp extends this>(prop: TProp) => boolean
|
||||
>this : this
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
declare class Base {
|
||||
check<TProp extends this>(prop: TProp): boolean;
|
||||
}
|
||||
|
||||
class Test extends Base {
|
||||
m() {
|
||||
this.check(this);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user