Skip to content

Commit a715c45

Browse files
committed
Merge pull request microsoft#8463 from Microsoft/this-types-for-accessors
This types for accessors
2 parents fad2574 + 3d3bcb4 commit a715c45

13 files changed

+624
-94
lines changed

src/compiler/checker.ts

Lines changed: 97 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2924,7 +2924,12 @@ namespace ts {
29242924
if (func.kind === SyntaxKind.SetAccessor && !hasDynamicName(func)) {
29252925
const getter = <AccessorDeclaration>getDeclarationOfKind(declaration.parent.symbol, SyntaxKind.GetAccessor);
29262926
if (getter) {
2927-
return getReturnTypeOfSignature(getSignatureFromDeclaration(getter));
2927+
const signature = getSignatureFromDeclaration(getter);
2928+
const thisParameter = getAccessorThisParameter(func as AccessorDeclaration);
2929+
if (thisParameter && declaration === thisParameter) {
2930+
return signature.thisType;
2931+
}
2932+
return getReturnTypeOfSignature(signature);
29282933
}
29292934
}
29302935
// Use contextual parameter type if one is available
@@ -3140,6 +3145,16 @@ namespace ts {
31403145
return undefined;
31413146
}
31423147

3148+
function getAnnotatedAccessorThisType(accessor: AccessorDeclaration): Type {
3149+
if (accessor) {
3150+
const parameter = getAccessorThisParameter(accessor);
3151+
if (parameter && parameter.type) {
3152+
return getTypeFromTypeNode(accessor.parameters[0].type);
3153+
}
3154+
}
3155+
return undefined;
3156+
}
3157+
31433158
function getTypeOfAccessors(symbol: Symbol): Type {
31443159
const links = getSymbolLinks(symbol);
31453160
if (!links.type) {
@@ -4361,20 +4376,12 @@ namespace ts {
43614376
function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature {
43624377
const links = getNodeLinks(declaration);
43634378
if (!links.resolvedSignature) {
4364-
const classType = declaration.kind === SyntaxKind.Constructor ?
4365-
getDeclaredTypeOfClassOrInterface(getMergedSymbol((<ClassDeclaration>declaration.parent).symbol))
4366-
: undefined;
4367-
const typeParameters = classType ? classType.localTypeParameters :
4368-
declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) :
4369-
getTypeParametersFromJSDocTemplate(declaration);
43704379
const parameters: Symbol[] = [];
43714380
let hasStringLiterals = false;
43724381
let minArgumentCount = -1;
43734382
let thisType: Type = undefined;
43744383
let hasThisParameter: boolean;
43754384
const isJSConstructSignature = isJSDocConstructSignature(declaration);
4376-
let returnType: Type = undefined;
4377-
let typePredicate: TypePredicate = undefined;
43784385

43794386
// If this is a JSDoc construct signature, then skip the first parameter in the
43804387
// parameter list. The first parameter represents the return type of the construct
@@ -4411,48 +4418,68 @@ namespace ts {
44114418
}
44124419
}
44134420

4421+
// If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation
4422+
if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) &&
4423+
!hasDynamicName(declaration) &&
4424+
(!hasThisParameter || thisType === unknownType)) {
4425+
const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
4426+
const setter = <AccessorDeclaration>getDeclarationOfKind(declaration.symbol, otherKind);
4427+
thisType = getAnnotatedAccessorThisType(setter);
4428+
}
4429+
44144430
if (minArgumentCount < 0) {
44154431
minArgumentCount = declaration.parameters.length - (hasThisParameter ? 1 : 0);
44164432
}
4417-
44184433
if (isJSConstructSignature) {
44194434
minArgumentCount--;
4420-
returnType = getTypeFromTypeNode(declaration.parameters[0].type);
44214435
}
4422-
else if (classType) {
4423-
returnType = classType;
4424-
}
4425-
else if (declaration.type) {
4426-
returnType = getTypeFromTypeNode(declaration.type);
4427-
if (declaration.type.kind === SyntaxKind.TypePredicate) {
4428-
typePredicate = createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode);
4429-
}
4430-
}
4431-
else {
4432-
if (declaration.flags & NodeFlags.JavaScriptFile) {
4433-
const type = getReturnTypeFromJSDocComment(declaration);
4434-
if (type && type !== unknownType) {
4435-
returnType = type;
4436-
}
4437-
}
4438-
4439-
// TypeScript 1.0 spec (April 2014):
4440-
// If only one accessor includes a type annotation, the other behaves as if it had the same type annotation.
4441-
if (declaration.kind === SyntaxKind.GetAccessor && !hasDynamicName(declaration)) {
4442-
const setter = <AccessorDeclaration>getDeclarationOfKind(declaration.symbol, SyntaxKind.SetAccessor);
4443-
returnType = getAnnotatedAccessorType(setter);
4444-
}
44454436

4446-
if (!returnType && nodeIsMissing((<FunctionLikeDeclaration>declaration).body)) {
4447-
returnType = anyType;
4448-
}
4449-
}
4437+
const classType = declaration.kind === SyntaxKind.Constructor ?
4438+
getDeclaredTypeOfClassOrInterface(getMergedSymbol((<ClassDeclaration>declaration.parent).symbol))
4439+
: undefined;
4440+
const typeParameters = classType ? classType.localTypeParameters :
4441+
declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) :
4442+
getTypeParametersFromJSDocTemplate(declaration);
4443+
const returnType = getSignatureReturnTypeFromDeclaration(declaration, minArgumentCount, isJSConstructSignature, classType);
4444+
const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ?
4445+
createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) :
4446+
undefined;
44504447

44514448
links.resolvedSignature = createSignature(declaration, typeParameters, thisType, parameters, returnType, typePredicate, minArgumentCount, hasRestParameter(declaration), hasStringLiterals);
44524449
}
44534450
return links.resolvedSignature;
44544451
}
44554452

4453+
function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration, minArgumentCount: number, isJSConstructSignature: boolean, classType: Type) {
4454+
if (isJSConstructSignature) {
4455+
return getTypeFromTypeNode(declaration.parameters[0].type);
4456+
}
4457+
else if (classType) {
4458+
return classType;
4459+
}
4460+
else if (declaration.type) {
4461+
return getTypeFromTypeNode(declaration.type);
4462+
}
4463+
4464+
if (declaration.flags & NodeFlags.JavaScriptFile) {
4465+
const type = getReturnTypeFromJSDocComment(declaration);
4466+
if (type && type !== unknownType) {
4467+
return type;
4468+
}
4469+
}
4470+
4471+
// TypeScript 1.0 spec (April 2014):
4472+
// If only one accessor includes a type annotation, the other behaves as if it had the same type annotation.
4473+
if (declaration.kind === SyntaxKind.GetAccessor && !hasDynamicName(declaration)) {
4474+
const setter = <AccessorDeclaration>getDeclarationOfKind(declaration.symbol, SyntaxKind.SetAccessor);
4475+
return getAnnotatedAccessorType(setter);
4476+
}
4477+
4478+
if (nodeIsMissing((<FunctionLikeDeclaration>declaration).body)) {
4479+
return anyType;
4480+
}
4481+
}
4482+
44564483
function getSignaturesOfSymbol(symbol: Symbol): Signature[] {
44574484
if (!symbol) return emptyArray;
44584485
const result: Signature[] = [];
@@ -12644,9 +12671,6 @@ namespace ts {
1264412671
if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) {
1264512672
error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter);
1264612673
}
12647-
if (func.kind === SyntaxKind.SetAccessor) {
12648-
error(node, Diagnostics.A_setter_cannot_have_a_this_parameter);
12649-
}
1265012674
}
1265112675

1265212676
// Only check rest parameter type if it's not a binding pattern. Since binding patterns are
@@ -13033,15 +13057,10 @@ namespace ts {
1303313057
error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract);
1303413058
}
1303513059

13036-
const currentAccessorType = getAnnotatedAccessorType(node);
13037-
const otherAccessorType = getAnnotatedAccessorType(otherAccessor);
1303813060
// TypeScript 1.0 spec (April 2014): 4.5
1303913061
// If both accessors include type annotations, the specified types must be identical.
13040-
if (currentAccessorType && otherAccessorType) {
13041-
if (!isTypeIdenticalTo(currentAccessorType, otherAccessorType)) {
13042-
error(node, Diagnostics.get_and_set_accessor_must_have_the_same_type);
13043-
}
13044-
}
13062+
checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type);
13063+
checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorThisType, Diagnostics.get_and_set_accessor_must_have_the_same_this_type);
1304513064
}
1304613065
}
1304713066
getTypeOfAccessors(getSymbolOfNode(node));
@@ -13054,6 +13073,14 @@ namespace ts {
1305413073
}
1305513074
}
1305613075

13076+
function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type, message: DiagnosticMessage) {
13077+
const firstType = getAnnotatedType(first);
13078+
const secondType = getAnnotatedType(second);
13079+
if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) {
13080+
error(first, message);
13081+
}
13082+
}
13083+
1305713084
function checkAccessorDeferred(node: AccessorDeclaration) {
1305813085
checkSourceElement(node.body);
1305913086
}
@@ -18127,7 +18154,7 @@ namespace ts {
1812718154
return false;
1812818155
}
1812918156

18130-
function checkGrammarAccessor(accessor: MethodDeclaration): boolean {
18157+
function checkGrammarAccessor(accessor: AccessorDeclaration): boolean {
1813118158
const kind = accessor.kind;
1813218159
if (languageVersion < ScriptTarget.ES5) {
1813318160
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
@@ -18141,16 +18168,16 @@ namespace ts {
1814118168
else if (accessor.typeParameters) {
1814218169
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
1814318170
}
18144-
else if (kind === SyntaxKind.GetAccessor && accessor.parameters.length) {
18145-
return grammarErrorOnNode(accessor.name, Diagnostics.A_get_accessor_cannot_have_parameters);
18171+
else if (!doesAccessorHaveCorrectParameterCount(accessor)) {
18172+
return grammarErrorOnNode(accessor.name,
18173+
kind === SyntaxKind.GetAccessor ?
18174+
Diagnostics.A_get_accessor_cannot_have_parameters :
18175+
Diagnostics.A_set_accessor_must_have_exactly_one_parameter);
1814618176
}
1814718177
else if (kind === SyntaxKind.SetAccessor) {
1814818178
if (accessor.type) {
1814918179
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation);
1815018180
}
18151-
else if (accessor.parameters.length !== 1) {
18152-
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_must_have_exactly_one_parameter);
18153-
}
1815418181
else {
1815518182
const parameter = accessor.parameters[0];
1815618183
if (parameter.dotDotDotToken) {
@@ -18169,6 +18196,22 @@ namespace ts {
1816918196
}
1817018197
}
1817118198

18199+
/** Does the accessor have the right number of parameters?
18200+
18201+
A get accessor has no parameters or a single `this` parameter.
18202+
A set accessor has one parameter or a `this` parameter and one more parameter */
18203+
function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) {
18204+
return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1);
18205+
}
18206+
18207+
function getAccessorThisParameter(accessor: AccessorDeclaration) {
18208+
if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2) &&
18209+
accessor.parameters[0].name.kind === SyntaxKind.Identifier &&
18210+
(<Identifier>accessor.parameters[0].name).originalKeywordKind === SyntaxKind.ThisKeyword) {
18211+
return accessor.parameters[0];
18212+
}
18213+
}
18214+
1817218215
function checkGrammarForNonSymbolComputedProperty(node: DeclarationName, message: DiagnosticMessage) {
1817318216
if (isDynamicName(node)) {
1817418217
return grammarErrorOnNode(node, message);

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1907,7 +1907,7 @@
19071907
"category": "Error",
19081908
"code": 2681
19091909
},
1910-
"A setter cannot have a 'this' parameter.": {
1910+
"'get' and 'set' accessor must have the same 'this' type.": {
19111911
"category": "Error",
19121912
"code": 2682
19131913
},

src/compiler/utilities.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2269,7 +2269,12 @@ namespace ts {
22692269
}
22702270

22712271
export function getSetAccessorTypeAnnotationNode(accessor: AccessorDeclaration): TypeNode {
2272-
return accessor && accessor.parameters.length > 0 && accessor.parameters[0].type;
2272+
if (accessor && accessor.parameters.length > 0) {
2273+
const hasThis = accessor.parameters.length === 2 &&
2274+
accessor.parameters[0].name.kind === SyntaxKind.Identifier &&
2275+
(accessor.parameters[0].name as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword;
2276+
return accessor.parameters[hasThis ? 1 : 0].type;
2277+
}
22732278
}
22742279

22752280
export function getAllAccessorDeclarations(declarations: NodeArray<Declaration>, accessor: AccessorDeclaration) {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//// [thisTypeInAccessors.ts]
2+
interface Foo {
3+
n: number;
4+
x: number;
5+
}
6+
7+
const explicit = {
8+
n: 12,
9+
get x(this: Foo): number { return this.n; },
10+
set x(this: Foo, n: number) { this.n = n; }
11+
}
12+
const copiedFromGetter = {
13+
n: 14,
14+
get x(this: Foo): number { return this.n; },
15+
set x(n) { this.n = n; }
16+
}
17+
const copiedFromSetter = {
18+
n: 15,
19+
get x() { return this.n },
20+
set x(this: Foo, n: number) { this.n = n; }
21+
}
22+
const copiedFromGetterUnannotated = {
23+
n: 16,
24+
get x(this: Foo) { return this.n },
25+
set x(this, n) { this.n = n; }
26+
}
27+
28+
class Explicit {
29+
n = 17;
30+
get x(this: Foo): number { return this.n; }
31+
set x(this: Foo, n: number) { this.n = n; }
32+
}
33+
class Contextual {
34+
n = 21;
35+
get x() { return this.n } // inside a class, so already correct
36+
}
37+
38+
39+
//// [thisTypeInAccessors.js]
40+
var explicit = {
41+
n: 12,
42+
get x() { return this.n; },
43+
set x(n) { this.n = n; }
44+
};
45+
var copiedFromGetter = {
46+
n: 14,
47+
get x() { return this.n; },
48+
set x(n) { this.n = n; }
49+
};
50+
var copiedFromSetter = {
51+
n: 15,
52+
get x() { return this.n; },
53+
set x(n) { this.n = n; }
54+
};
55+
var copiedFromGetterUnannotated = {
56+
n: 16,
57+
get x() { return this.n; },
58+
set x(n) { this.n = n; }
59+
};
60+
var Explicit = (function () {
61+
function Explicit() {
62+
this.n = 17;
63+
}
64+
Object.defineProperty(Explicit.prototype, "x", {
65+
get: function () { return this.n; },
66+
set: function (n) { this.n = n; },
67+
enumerable: true,
68+
configurable: true
69+
});
70+
return Explicit;
71+
}());
72+
var Contextual = (function () {
73+
function Contextual() {
74+
this.n = 21;
75+
}
76+
Object.defineProperty(Contextual.prototype, "x", {
77+
get: function () { return this.n; } // inside a class, so already correct
78+
,
79+
enumerable: true,
80+
configurable: true
81+
});
82+
return Contextual;
83+
}());

0 commit comments

Comments
 (0)