Skip to content

Instantiate this when used only in type parameter constraints #19655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 49 additions & 51 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
}
}
Expand All @@ -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;
}
Expand Down
33 changes: 33 additions & 0 deletions tests/baselines/reference/thisTypeInFunctions3.js
Original file line number Diff line number Diff line change
@@ -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 changes: 26 additions & 0 deletions tests/baselines/reference/thisTypeInFunctions3.symbols
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions tests/baselines/reference/thisTypeInFunctions3.types
Original file line number Diff line number Diff line change
@@ -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
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare class Base {
check<TProp extends this>(prop: TProp): boolean;
}

class Test extends Base {
m() {
this.check(this);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously the second this reference was not assignable to prop: TProp.

}
}