Skip to content

Change "isThisless" predicates to "mayReferenceThis" predicates #20036

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
3 commits merged into from
Nov 15, 2017
Merged
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
125 changes: 51 additions & 74 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5111,26 +5111,18 @@ namespace ts {
* 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) {
return false;
}
const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration);
if (baseTypeNodes) {
for (const node of baseTypeNodes) {
if (isEntityNameExpression(node.expression)) {
const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true);
if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) {
return false;
}
}
}
}
}
function interfaceReferencesThis(symbol: Symbol): boolean {
return some(symbol.declarations, declaration =>
isInterfaceDeclaration(declaration) && (
!!(declaration.flags & NodeFlags.ContainsThis)
|| some(getInterfaceBaseTypeNodes(declaration), baseTypeReferencesThis)));
}
function baseTypeReferencesThis({ expression }: ExpressionWithTypeArguments): boolean {
if (!isEntityNameExpression(expression)) {
return false;
}
return true;
const baseSymbol = resolveEntityName(expression, SymbolFlags.Type, /*ignoreErrors*/ true);
return !baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || !!getDeclaredTypeOfClassOrInterface(baseSymbol).thisType;
}

function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
Expand All @@ -5145,7 +5137,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 || !isThislessInterface(symbol)) {
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || interfaceReferencesThis(symbol)) {
type.objectFlags |= ObjectFlags.Reference;
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
type.outerTypeParameters = outerTypeParameters;
Expand Down Expand Up @@ -5327,13 +5319,9 @@ namespace ts {
return undefined;
}

/**
* 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) {
/** A type may reference `this` unless it's one of a few special types. */
function typeReferencesThis(node: TypeNode | undefined): boolean {
switch (node && node.kind) {
case SyntaxKind.AnyKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.NumberKeyword:
Expand All @@ -5345,72 +5333,61 @@ namespace ts {
case SyntaxKind.NullKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.LiteralType:
return true;
return false;
case SyntaxKind.ArrayType:
return isThislessType((<ArrayTypeNode>node).elementType);
return typeReferencesThis((<ArrayTypeNode>node).elementType);
case SyntaxKind.TypeReference:
return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments.every(isThislessType);
return some((node as TypeReferenceNode).typeArguments, typeReferencesThis);
}
return false;
return true; // TODO: GH#20034
}

/** 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 {
/** A variable-like declaration may reference `this` if its type does or if it has no declared type and an initializer (which may infer a `this` type). */
function variableLikeDeclarationReferencesThis(node: VariableLikeDeclaration): boolean {
const typeNode = getEffectiveTypeAnnotationNode(node);
return typeNode ? isThislessType(typeNode) : !node.initializer;
}

/**
* 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));
return typeNode ? typeReferencesThis(typeNode) : !!node.initializer;
}

/**
* 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.
* Returns true if the class/interface member may reference `this`.
* May return true for symbols that don't actually reference `this` because it would be slow to do a complete analysis.
* For example, property members with types inferred from initializers or function members with inferred return types are
* conservatively assumed to reference `this`.
*/
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 isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration>declaration);
}
function symbolReferencesThis(symbol: Symbol): boolean {
const declaration = singleOrUndefined(symbol.declarations);
if (!declaration) return true;
switch (declaration.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return variableLikeDeclarationReferencesThis(<PropertyDeclaration | PropertySignature>declaration);
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor: {
// A function-like declaration references `this` if its return type does or some parameter / type parameter does.
const fn = declaration as MethodDeclaration | MethodSignature | ConstructorDeclaration;
return typeReferencesThis(getEffectiveReturnTypeNode(fn))
|| fn.parameters.some(variableLikeDeclarationReferencesThis)
// A type parameter references `this` if its constraint does.
|| some(fn.typeParameters, tp => typeReferencesThis(tp.constraint));
}
case SyntaxKind.Parameter:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.BinaryExpression:
case SyntaxKind.PropertyAccessExpression: // See `tests/cases/fourslash/renameJsThisProperty05` and 06
return true; // TODO: GH#20034
default:
throw Debug.failBadSyntaxKind(declaration);
}
return false;
}

// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
const result = createSymbolTable();
for (const symbol of symbols) {
result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper));
result.set(symbol.escapedName, mappingThisOnly && !symbolReferencesThis(symbol) ? symbol : instantiateSymbol(symbol, mapper));
}
return result;
}
Expand Down