Skip to content

this in object literals intersects contextual type and literal type #8356

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 3 commits into from
Apr 28, 2016
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
91 changes: 48 additions & 43 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5508,6 +5508,10 @@ namespace ts {
return !node.typeParameters && areAllParametersUntyped && !isNullaryArrow;
}

function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
}

function getTypeWithoutSignatures(type: Type): Type {
if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand Down Expand Up @@ -8179,6 +8183,21 @@ namespace ts {
captureLexicalThis(node, container);
}
if (isFunctionLike(container)) {
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
isInJavaScriptFile(container.parent) &&
getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
return getInferredClassType(classSymbol);
}
}
const type = getContextuallyTypedThisType(container);
if (type) {
return type;
Expand Down Expand Up @@ -8207,22 +8226,6 @@ namespace ts {
if (type && type !== unknownType) {
return type;
}

// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression) {
if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
return getInferredClassType(classSymbol);
}
}
}
}

if (compilerOptions.noImplicitThis) {
Expand Down Expand Up @@ -8447,12 +8450,13 @@ namespace ts {
}

function getContextuallyTypedThisType(func: FunctionLikeDeclaration): Type {
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitive(func) &&
func.kind !== SyntaxKind.ArrowFunction) {
if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
return contextualSignature.thisType;
return contextualSignature.thisType || anyType;
}
else if (getContextualTypeForFunctionLikeDeclaration(func) === anyType) {
return anyType;
}
}

Expand All @@ -8462,34 +8466,31 @@ namespace ts {
// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type {
const func = parameter.parent;
if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) {
if (isContextSensitive(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {

const funcHasRestParameters = hasRestParameter(func);
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
const indexOfParameter = indexOf(func.parameters, parameter);
if (indexOfParameter < len) {
return getTypeAtPosition(contextualSignature, indexOfParameter);
}
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
const funcHasRestParameters = hasRestParameter(func);
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
const indexOfParameter = indexOf(func.parameters, parameter);
if (indexOfParameter < len) {
return getTypeAtPosition(contextualSignature, indexOfParameter);
}

// If last parameter is contextually rest parameter get its type
if (funcHasRestParameters &&
indexOfParameter === (func.parameters.length - 1) &&
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
}
// If last parameter is contextually rest parameter get its type
if (funcHasRestParameters &&
indexOfParameter === (func.parameters.length - 1) &&
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
}
}
}
return undefined;
}

// In a variable, parameter or property declaration with a type annotation,
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// Otherwise, in a variable or parameter declaration with a binding pattern name,
// the contextual type of an initializer expression is the type implied by the binding pattern.
// Otherwise, in a binding pattern inside a variable or parameter declaration,
Expand Down Expand Up @@ -8843,16 +8844,20 @@ namespace ts {
: undefined;
}

function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | MethodDeclaration) {
return isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(node)
: getApparentTypeOfContextualType(node);
}

// Return the contextual signature for a given expression node. A contextual type provides a
// contextual signature if it has a single call signature and if that call signature is non-generic.
// If the contextual type is a union type, get the signature from each type possible and if they are
// all identical ignoring their return type, the result is same signature but with return type as
// union type of return types from these signatures
function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
const type = isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(node)
: getApparentTypeOfContextualType(node);
const type = getContextualTypeForFunctionLikeDeclaration(node);
if (!type) {
return undefined;
}
Expand Down
91 changes: 91 additions & 0 deletions tests/baselines/reference/thisTypeInFunctions2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//// [thisTypeInFunctions2.ts]
interface IndexedWithThis {
// this is a workaround for React
init?: (this: this) => void;
willDestroy?: (this: any) => void;
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
}
interface IndexedWithoutThis {
// this is what React would like to write (and what they write today)
init?: () => void;
willDestroy?: () => void;
[propName: string]: any;
}
interface SimpleInterface {
foo(n: string);
bar(): number;
}
declare function extend1(args: IndexedWithThis): void;
declare function extend2(args: IndexedWithoutThis): void;
declare function simple(arg: SimpleInterface): void;

extend1({
init() {
this // this: IndexedWithThis because of contextual typing.
// this.mine
this.willDestroy
},
mine: 12,
foo() {
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
init() {
this // this: any because the contextual signature of init doesn't specify this' type
this.mine
this.willDestroy
},
mine: 13,
foo() {
this // this: any because of the string indexer
this.mine
this.willDestroy
}
});

simple({
foo(n) {
return n.length + this.bar();
},
bar() {
return 14;
}
})


//// [thisTypeInFunctions2.js]
extend1({
init: function () {
this; // this: IndexedWithThis because of contextual typing.
// this.mine
this.willDestroy;
},
mine: 12,
foo: function () {
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
init: function () {
this; // this: any because the contextual signature of init doesn't specify this' type
this.mine;
this.willDestroy;
},
mine: 13,
foo: function () {
this; // this: any because of the string indexer
this.mine;
this.willDestroy;
}
});
simple({
foo: function (n) {
return n.length + this.bar();
},
bar: function () {
return 14;
}
});
124 changes: 124 additions & 0 deletions tests/baselines/reference/thisTypeInFunctions2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
interface IndexedWithThis {
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))

// this is a workaround for React
init?: (this: this) => void;
>init : Symbol(IndexedWithThis.init, Decl(thisTypeInFunctions2.ts, 0, 27))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 12))

willDestroy?: (this: any) => void;
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 19))

[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 4, 5))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 4, 87))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 4, 97))
}
interface IndexedWithoutThis {
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))

// this is what React would like to write (and what they write today)
init?: () => void;
>init : Symbol(IndexedWithoutThis.init, Decl(thisTypeInFunctions2.ts, 6, 30))

willDestroy?: () => void;
>willDestroy : Symbol(IndexedWithoutThis.willDestroy, Decl(thisTypeInFunctions2.ts, 8, 22))

[propName: string]: any;
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 10, 5))
}
interface SimpleInterface {
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))

foo(n: string);
>foo : Symbol(SimpleInterface.foo, Decl(thisTypeInFunctions2.ts, 12, 27))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 13, 8))

bar(): number;
>bar : Symbol(SimpleInterface.bar, Decl(thisTypeInFunctions2.ts, 13, 19))
}
declare function extend1(args: IndexedWithThis): void;
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 16, 25))
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))

declare function extend2(args: IndexedWithoutThis): void;
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 17, 25))
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))

declare function simple(arg: SimpleInterface): void;
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))
>arg : Symbol(arg, Decl(thisTypeInFunctions2.ts, 18, 24))
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))

extend1({
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))

init() {
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 20, 9))

this // this: IndexedWithThis because of contextual typing.
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))

// this.mine
this.willDestroy
>this.willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))

},
mine: 12,
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 25, 6))

foo() {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 26, 13))

this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))

init() {
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 32, 9))

this // this: any because the contextual signature of init doesn't specify this' type
this.mine
this.willDestroy
},
mine: 13,
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 37, 6))

foo() {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 38, 13))

this // this: any because of the string indexer
this.mine
this.willDestroy
}
});

simple({
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))

foo(n) {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 46, 8))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))

return n.length + this.bar();
>n.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

},
bar() {
>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 49, 6))

return 14;
}
})

Loading