Skip to content
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

Typed 'this' in object literal methods #14141

Merged
merged 28 commits into from
Mar 6, 2017
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f6a3a3f
Use '__this__' property in contextual type to indicate type of 'this'
ahejlsberg Feb 14, 2017
8cd6c5d
Introduce ThisType<T> marker interface
ahejlsberg Feb 17, 2017
2ca6164
Default contextual 'this' type is containing object literal
ahejlsberg Feb 17, 2017
e512376
Update tests
ahejlsberg Feb 17, 2017
d7e153d
Accept new baselines
ahejlsberg Feb 17, 2017
fe32bb7
Merge branch 'master' into contextualThisType
ahejlsberg Feb 17, 2017
27346b1
Accept new baselines
ahejlsberg Feb 17, 2017
e3a0687
Contextual this in 'obj.xxx = function(...)' or 'obj[xxx] = function(…
ahejlsberg Feb 17, 2017
168d367
Contextually type 'this' in accessors of object literals
ahejlsberg Feb 23, 2017
c2d8a59
Accept new baselines
ahejlsberg Feb 23, 2017
ec292c9
Update test
ahejlsberg Feb 23, 2017
9b6b6cc
Fix linting error
ahejlsberg Feb 23, 2017
9dc2bae
Use contextual type of object literal as 'this' in methods
ahejlsberg Feb 25, 2017
16f4030
Accept new baselines
ahejlsberg Feb 25, 2017
20b4523
Rename applyToContextualType to mapType and remove old mapType
ahejlsberg Feb 25, 2017
6fdd929
Update test
ahejlsberg Feb 25, 2017
cd87d90
Update comment
ahejlsberg Feb 27, 2017
5bda48b
Add tests
ahejlsberg Feb 27, 2017
993397b
Introduce CheckMode enum and getContextualMapper() function
ahejlsberg Feb 28, 2017
ff2cfd2
Update test
ahejlsberg Feb 28, 2017
c87c124
Accept new baselines
ahejlsberg Feb 28, 2017
ee7b93c
Merge branch 'master' into contextualThisType
ahejlsberg Feb 28, 2017
21c4300
Enable new behavior only in --noImplicitThis mode
ahejlsberg Mar 1, 2017
25738a8
Update tests
ahejlsberg Mar 1, 2017
f77cd8e
Accept new baselines
ahejlsberg Mar 1, 2017
9d1b325
Update another test
ahejlsberg Mar 1, 2017
7561cdf
Add ThisType<any> to Object.{create|defineProperty|defineProperties}
ahejlsberg Mar 2, 2017
258bb4f
Accept new baselines
ahejlsberg Mar 2, 2017
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
98 changes: 77 additions & 21 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ namespace ts {
let globalNumberType: ObjectType;
let globalBooleanType: ObjectType;
let globalRegExpType: ObjectType;
let globalThisType: GenericType;
let anyArrayType: Type;
let autoArrayType: Type;
let anyReadonlyArrayType: Type;
Expand Down Expand Up @@ -6040,6 +6041,11 @@ namespace ts {
return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType("IterableIterator", /*arity*/ 1, reportErrors)) || emptyGenericType;
}

function getGlobalTypeOrUndefined(name: string, arity = 0): ObjectType {
const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined);
return symbol && <GenericType>getTypeOfGlobalSymbol(symbol, arity);
}

/**
* Returns a type that is inside a namespace at the global scope, e.g.
* getExportedTypeFromNamespace('JSX', 'Element') returns the JSX.Element type
Expand Down Expand Up @@ -8977,11 +8983,19 @@ namespace ts {
return regularNew;
}

function getWidenedProperty(prop: Symbol): Symbol {
const original = getTypeOfSymbol(prop);
const widened = getWidenedType(original);
return widened === original ? prop : createSymbolWithType(prop, widened);
}

function getWidenedTypeOfObjectLiteral(type: Type): Type {
const members = transformTypeOfMembers(type, prop => {
const widened = getWidenedType(prop);
return prop === widened ? prop : widened;
});
const members = createMap<Symbol>();
for (const prop of getPropertiesOfObjectType(type)) {
// Since get accessors already widen their return value there is no need to
// widen accessor based properties here.
members.set(prop.name, prop.flags & SymbolFlags.Property ? getWidenedProperty(prop) : prop);
};
const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String);
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);
return createAnonymousType(type.symbol, members, emptyArray, emptyArray,
Expand Down Expand Up @@ -11464,8 +11478,29 @@ namespace ts {
}
}

function getContainingObjectLiteral(func: FunctionLikeDeclaration) {
return (func.kind === SyntaxKind.MethodDeclaration ||
func.kind === SyntaxKind.GetAccessor ||
func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? <ObjectLiteralExpression>func.parent :
Copy link
Member

@sandersn sandersn Feb 17, 2017

Choose a reason for hiding this comment

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

shouldn't applyToContextualType just be named mapType and replace mapType? It doesn't do anything specific to contextual types that I could see, and it's better than the current mapType because it skips return values of undefined. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that makes sense.

func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? <ObjectLiteralExpression>func.parent.parent :
undefined;
}

function getThisTypeArgument(type: Type): Type {
return getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target === globalThisType ? (<TypeReference>type).typeArguments[0] : undefined;
}

function getThisTypeFromContextualType(type: Type): Type {
return applyToContextualType(type, t => {
return t.flags & TypeFlags.Intersection ? forEach((<IntersectionType>t).types, getThisTypeArgument) : getThisTypeArgument(t);
});
}

function getContextualThisParameterType(func: FunctionLikeDeclaration): Type {
if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
if (func.kind === SyntaxKind.ArrowFunction) {
return undefined;
}
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
const thisParameter = contextualSignature.thisParameter;
Expand All @@ -11474,6 +11509,37 @@ namespace ts {
}
}
}
const containingLiteral = getContainingObjectLiteral(func);
if (containingLiteral) {
// We have an object literal method. Check if the containing object literal has a contextual type
// and if that contextual type is or includes a ThisType<T>. If so, T is the contextual type for
// 'this'. We continue looking in any directly enclosing object literals.
let objectLiteral = containingLiteral;
while (true) {
const type = getApparentTypeOfContextualType(objectLiteral);
if (type) {
const thisType = getThisTypeFromContextualType(type);
if (thisType) {
return thisType;
}
}
if (objectLiteral.parent.kind !== SyntaxKind.PropertyAssignment) {
break;
}
objectLiteral = <ObjectLiteralExpression>objectLiteral.parent.parent;
}
// There was no contextual ThisType<T> for the containing object literal, so the contextual type
// for 'this' is the type of the object literal itself.
return checkExpressionCached(containingLiteral);
}
// In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the
// contextual type for 'this' is 'obj'.
if (func.parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>func.parent).operatorToken.kind === SyntaxKind.EqualsToken) {
const target = (<BinaryExpression>func.parent).left;
if (target.kind === SyntaxKind.PropertyAccessExpression || target.kind === SyntaxKind.ElementAccessExpression) {
return checkExpressionCached((<PropertyAccessExpression | ElementAccessExpression>target).expression);
}
}
return undefined;
}

Expand Down Expand Up @@ -12234,7 +12300,7 @@ namespace ts {
// A set accessor declaration is processed in the same manner
// as an ordinary function declaration with a single parameter and a Void return type.
Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor);
checkAccessorDeclaration(<AccessorDeclaration>memberDecl);
checkNodeDeferred(memberDecl);
}

if (hasDynamicName(memberDecl)) {
Expand Down Expand Up @@ -16889,13 +16955,8 @@ namespace ts {
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType);
}
}
if (node.parent.kind !== SyntaxKind.ObjectLiteralExpression) {
checkSourceElement(node.body);
registerForUnusedIdentifiersCheck(node);
}
else {
checkNodeDeferred(node);
}
checkSourceElement(node.body);
registerForUnusedIdentifiersCheck(node);
}

function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type, message: DiagnosticMessage) {
Expand All @@ -16906,11 +16967,6 @@ namespace ts {
}
}

function checkAccessorDeferred(node: AccessorDeclaration) {
checkSourceElement(node.body);
registerForUnusedIdentifiersCheck(node);
}

function checkMissingDeclaration(node: Node) {
checkDecorators(node);
}
Expand Down Expand Up @@ -20577,7 +20633,7 @@ namespace ts {
break;
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
checkAccessorDeferred(<AccessorDeclaration>node);
checkAccessorDeclaration(<AccessorDeclaration>node);
break;
case SyntaxKind.ClassExpression:
checkClassExpressionDeferred(<ClassExpression>node);
Expand Down Expand Up @@ -21921,9 +21977,9 @@ namespace ts {
anyArrayType = createArrayType(anyType);
autoArrayType = createArrayType(autoType);

const symbol = getGlobalSymbol("ReadonlyArray", SymbolFlags.Type, /*diagnostic*/ undefined);
globalReadonlyArrayType = symbol && <GenericType>getTypeOfGlobalSymbol(symbol, /*arity*/ 1);
globalReadonlyArrayType = <GenericType>getGlobalTypeOrUndefined("ReadonlyArray", /*arity*/ 1);
anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType;
globalThisType = <GenericType>getGlobalTypeOrUndefined("ThisType", /*arity*/ 1);
}

function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,11 @@ type Record<K extends string, T> = {
[P in K]: T;
}

/**
* Marker for contextual 'this' type
*/
interface ThisType<T> { }

/**
* Represents a raw buffer of binary data, which is used to store data for the
* different typed arrays. ArrayBuffers cannot be read from or written to directly,
Expand Down
6 changes: 6 additions & 0 deletions tests/baselines/reference/castTest.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ var p_cast = <Point> ({

return new Point(this.x + dx, this.y + dy);
>Point : Symbol(Point, Decl(castTest.ts, 11, 37))
>this.x : Symbol(x, Decl(castTest.ts, 22, 23))
>this : Symbol(__object, Decl(castTest.ts, 22, 22))
>x : Symbol(x, Decl(castTest.ts, 22, 23))
>dx : Symbol(dx, Decl(castTest.ts, 25, 18))
>this.y : Symbol(y, Decl(castTest.ts, 23, 9))
>this : Symbol(__object, Decl(castTest.ts, 22, 22))
>y : Symbol(y, Decl(castTest.ts, 23, 9))
>dy : Symbol(dy, Decl(castTest.ts, 25, 21))

},
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/castTest.types
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ var p_cast = <Point> ({
return new Point(this.x + dx, this.y + dy);
>new Point(this.x + dx, this.y + dy) : Point
>Point : typeof Point
>this.x + dx : any
>this.x : any
>this : any
>x : any
>this.x + dx : number
>this.x : number
>this : { x: number; y: number; add: (dx: number, dy: number) => Point; mult: (p: Point) => Point; }
>x : number
>dx : number
>this.y + dy : any
>this.y : any
>this : any
>y : any
>this.y + dy : number
>this.y : number
>this : { x: number; y: number; add: (dx: number, dy: number) => Point; mult: (p: Point) => Point; }
>y : number
>dy : number

},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
tests/cases/compiler/commentsOnObjectLiteral2.ts(1,14): error TS2304: Cannot find name 'makeClass'.
tests/cases/compiler/commentsOnObjectLiteral2.ts(9,17): error TS2339: Property 'name' does not exist on type '{ initialize: (name: any) => void; }'.


==== tests/cases/compiler/commentsOnObjectLiteral2.ts (1 errors) ====
==== tests/cases/compiler/commentsOnObjectLiteral2.ts (2 errors) ====
var Person = makeClass(
~~~~~~~~~
!!! error TS2304: Cannot find name 'makeClass'.
Expand All @@ -13,6 +14,8 @@ tests/cases/compiler/commentsOnObjectLiteral2.ts(1,14): error TS2304: Cannot fin
*/
initialize: function(name) {
this.name = name;
~~~~
!!! error TS2339: Property 'name' does not exist on type '{ initialize: (name: any) => void; }'.
} /* trailing comment 1*/,
}
);
7 changes: 7 additions & 0 deletions tests/baselines/reference/commentsOnObjectLiteral3.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@ var v = {
>a : Symbol(a, Decl(commentsOnObjectLiteral3.ts, 8, 13), Decl(commentsOnObjectLiteral3.ts, 12, 18))

return this.prop;
>this.prop : Symbol(prop, Decl(commentsOnObjectLiteral3.ts, 1, 9))
>this : Symbol(v, Decl(commentsOnObjectLiteral3.ts, 1, 7))
>prop : Symbol(prop, Decl(commentsOnObjectLiteral3.ts, 1, 9))

} /*trailing 1*/,
//setter
set a(value) {
>a : Symbol(a, Decl(commentsOnObjectLiteral3.ts, 8, 13), Decl(commentsOnObjectLiteral3.ts, 12, 18))
>value : Symbol(value, Decl(commentsOnObjectLiteral3.ts, 14, 7))

this.prop = value;
>this.prop : Symbol(prop, Decl(commentsOnObjectLiteral3.ts, 1, 9))
>this : Symbol(v, Decl(commentsOnObjectLiteral3.ts, 1, 7))
>prop : Symbol(prop, Decl(commentsOnObjectLiteral3.ts, 1, 9))
>value : Symbol(value, Decl(commentsOnObjectLiteral3.ts, 14, 7))

} // trailing 2
Expand Down
26 changes: 13 additions & 13 deletions tests/baselines/reference/commentsOnObjectLiteral3.types
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
=== tests/cases/compiler/commentsOnObjectLiteral3.ts ===

var v = {
>v : { prop: number; func: () => void; func1(): void; a: any; }
>{ //property prop: 1 /* multiple trailing comments */ /*trailing comments*/, //property func: function () { }, //PropertyName + CallSignature func1() { }, //getter get a() { return this.prop; } /*trailing 1*/, //setter set a(value) { this.prop = value; } // trailing 2} : { prop: number; func: () => void; func1(): void; a: any; }
>v : { prop: number; func: () => void; func1(): void; a: number; }
>{ //property prop: 1 /* multiple trailing comments */ /*trailing comments*/, //property func: function () { }, //PropertyName + CallSignature func1() { }, //getter get a() { return this.prop; } /*trailing 1*/, //setter set a(value) { this.prop = value; } // trailing 2} : { prop: number; func: () => void; func1(): void; a: number; }

//property
prop: 1 /* multiple trailing comments */ /*trailing comments*/,
Expand All @@ -21,25 +21,25 @@ var v = {

//getter
get a() {
>a : any
>a : number

return this.prop;
>this.prop : any
>this : any
>prop : any
>this.prop : number
>this : { prop: number; func: () => void; func1(): void; a: number; }
>prop : number

} /*trailing 1*/,
//setter
set a(value) {
>a : any
>value : any
>a : number
>value : number

this.prop = value;
>this.prop = value : any
>this.prop : any
>this : any
>prop : any
>value : any
>this.prop = value : number
>this.prop : number
>this : { prop: number; func: () => void; func1(): void; a: number; }
>prop : number
>value : number

} // trailing 2
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ function /*1*/makePoint(x: number) {
set x(a: number) { this.b = a; }
>x : Symbol(x, Decl(declFileObjectLiteralWithAccessors.ts, 3, 14), Decl(declFileObjectLiteralWithAccessors.ts, 4, 30))
>a : Symbol(a, Decl(declFileObjectLiteralWithAccessors.ts, 5, 14))
>this.b : Symbol(b, Decl(declFileObjectLiteralWithAccessors.ts, 2, 12))
>this : Symbol(__object, Decl(declFileObjectLiteralWithAccessors.ts, 2, 10))
>b : Symbol(b, Decl(declFileObjectLiteralWithAccessors.ts, 2, 12))
>a : Symbol(a, Decl(declFileObjectLiteralWithAccessors.ts, 5, 14))

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ function /*1*/makePoint(x: number) {
>x : number
>a : number
>this.b = a : number
>this.b : any
>this : any
>b : any
>this.b : number
>this : { b: number; x: number; }
>b : number
>a : number

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ function /*1*/makePoint(x: number) {
set x(a: number) { this.b = a; }
>x : Symbol(x, Decl(declFileObjectLiteralWithOnlySetter.ts, 3, 14))
>a : Symbol(a, Decl(declFileObjectLiteralWithOnlySetter.ts, 4, 14))
>this.b : Symbol(b, Decl(declFileObjectLiteralWithOnlySetter.ts, 2, 12))
>this : Symbol(__object, Decl(declFileObjectLiteralWithOnlySetter.ts, 2, 10))
>b : Symbol(b, Decl(declFileObjectLiteralWithOnlySetter.ts, 2, 12))
>a : Symbol(a, Decl(declFileObjectLiteralWithOnlySetter.ts, 4, 14))

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function /*1*/makePoint(x: number) {
>x : number
>a : number
>this.b = a : number
>this.b : any
>this : any
>b : any
>this.b : number
>this : { b: number; x: number; }
>b : number
>a : number

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ tests/cases/conformance/declarationEmit/typePredicates/declarationEmitThisPredic
m(): this is Foo {
~~~~
!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface.
let dis = this as Foo;
let dis = this as {} as Foo;
return dis.a != null && dis.b != null && dis.c != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface Foo {

export const obj = {
m(): this is Foo {
let dis = this as Foo;
let dis = this as {} as Foo;
return dis.a != null && dis.b != null && dis.c != null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tests/cases/conformance/declarationEmit/typePredicates/declarationEmitThisPredic
m(): this is Foo {
~~~~
!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface.
let dis = this as Foo;
let dis = this as {} as Foo;
return dis.a != null && dis.b != null && dis.c != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface Foo {

export const obj = {
m(): this is Foo {
let dis = this as Foo;
let dis = this as {} as Foo;
return dis.a != null && dis.b != null && dis.c != null;
}
}
Expand Down
Loading