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:
Nathan Shively-Sanders
2017-11-14 16:22:37 -08:00
committed by GitHub
5 changed files with 144 additions and 51 deletions

View File

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