diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4ec087c25..758ff3cfdc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2924,7 +2924,12 @@ namespace ts { if (func.kind === SyntaxKind.SetAccessor && !hasDynamicName(func)) { const getter = getDeclarationOfKind(declaration.parent.symbol, SyntaxKind.GetAccessor); if (getter) { - return getReturnTypeOfSignature(getSignatureFromDeclaration(getter)); + const signature = getSignatureFromDeclaration(getter); + const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); + if (thisParameter && declaration === thisParameter) { + return signature.thisType; + } + return getReturnTypeOfSignature(signature); } } // Use contextual parameter type if one is available @@ -3140,6 +3145,16 @@ namespace ts { return undefined; } + function getAnnotatedAccessorThisType(accessor: AccessorDeclaration): Type { + if (accessor) { + const parameter = getAccessorThisParameter(accessor); + if (parameter && parameter.type) { + return getTypeFromTypeNode(accessor.parameters[0].type); + } + } + return undefined; + } + function getTypeOfAccessors(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { @@ -4361,20 +4376,12 @@ namespace ts { function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { - const classType = declaration.kind === SyntaxKind.Constructor ? - getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) - : undefined; - const typeParameters = classType ? classType.localTypeParameters : - declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : - getTypeParametersFromJSDocTemplate(declaration); const parameters: Symbol[] = []; let hasStringLiterals = false; let minArgumentCount = -1; let thisType: Type = undefined; let hasThisParameter: boolean; const isJSConstructSignature = isJSDocConstructSignature(declaration); - let returnType: Type = undefined; - let typePredicate: TypePredicate = undefined; // If this is a JSDoc construct signature, then skip the first parameter in the // parameter list. The first parameter represents the return type of the construct @@ -4411,48 +4418,68 @@ namespace ts { } } + // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation + if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && + !hasDynamicName(declaration) && + (!hasThisParameter || thisType === unknownType)) { + const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const setter = getDeclarationOfKind(declaration.symbol, otherKind); + thisType = getAnnotatedAccessorThisType(setter); + } + if (minArgumentCount < 0) { minArgumentCount = declaration.parameters.length - (hasThisParameter ? 1 : 0); } - if (isJSConstructSignature) { minArgumentCount--; - returnType = getTypeFromTypeNode(declaration.parameters[0].type); } - else if (classType) { - returnType = classType; - } - else if (declaration.type) { - returnType = getTypeFromTypeNode(declaration.type); - if (declaration.type.kind === SyntaxKind.TypePredicate) { - typePredicate = createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode); - } - } - else { - if (declaration.flags & NodeFlags.JavaScriptFile) { - const type = getReturnTypeFromJSDocComment(declaration); - if (type && type !== unknownType) { - returnType = type; - } - } - // TypeScript 1.0 spec (April 2014): - // If only one accessor includes a type annotation, the other behaves as if it had the same type annotation. - if (declaration.kind === SyntaxKind.GetAccessor && !hasDynamicName(declaration)) { - const setter = getDeclarationOfKind(declaration.symbol, SyntaxKind.SetAccessor); - returnType = getAnnotatedAccessorType(setter); - } - - if (!returnType && nodeIsMissing((declaration).body)) { - returnType = anyType; - } - } + const classType = declaration.kind === SyntaxKind.Constructor ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : + declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : + getTypeParametersFromJSDocTemplate(declaration); + const returnType = getSignatureReturnTypeFromDeclaration(declaration, minArgumentCount, isJSConstructSignature, classType); + const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? + createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : + undefined; links.resolvedSignature = createSignature(declaration, typeParameters, thisType, parameters, returnType, typePredicate, minArgumentCount, hasRestParameter(declaration), hasStringLiterals); } return links.resolvedSignature; } + function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration, minArgumentCount: number, isJSConstructSignature: boolean, classType: Type) { + if (isJSConstructSignature) { + return getTypeFromTypeNode(declaration.parameters[0].type); + } + else if (classType) { + return classType; + } + else if (declaration.type) { + return getTypeFromTypeNode(declaration.type); + } + + if (declaration.flags & NodeFlags.JavaScriptFile) { + const type = getReturnTypeFromJSDocComment(declaration); + if (type && type !== unknownType) { + return type; + } + } + + // TypeScript 1.0 spec (April 2014): + // If only one accessor includes a type annotation, the other behaves as if it had the same type annotation. + if (declaration.kind === SyntaxKind.GetAccessor && !hasDynamicName(declaration)) { + const setter = getDeclarationOfKind(declaration.symbol, SyntaxKind.SetAccessor); + return getAnnotatedAccessorType(setter); + } + + if (nodeIsMissing((declaration).body)) { + return anyType; + } + } + function getSignaturesOfSymbol(symbol: Symbol): Signature[] { if (!symbol) return emptyArray; const result: Signature[] = []; @@ -12644,9 +12671,6 @@ namespace ts { if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); } - if (func.kind === SyntaxKind.SetAccessor) { - error(node, Diagnostics.A_setter_cannot_have_a_this_parameter); - } } // Only check rest parameter type if it's not a binding pattern. Since binding patterns are @@ -13033,15 +13057,10 @@ namespace ts { error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); } - const currentAccessorType = getAnnotatedAccessorType(node); - const otherAccessorType = getAnnotatedAccessorType(otherAccessor); // TypeScript 1.0 spec (April 2014): 4.5 // If both accessors include type annotations, the specified types must be identical. - if (currentAccessorType && otherAccessorType) { - if (!isTypeIdenticalTo(currentAccessorType, otherAccessorType)) { - error(node, Diagnostics.get_and_set_accessor_must_have_the_same_type); - } - } + checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type); + checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorThisType, Diagnostics.get_and_set_accessor_must_have_the_same_this_type); } } getTypeOfAccessors(getSymbolOfNode(node)); @@ -13054,6 +13073,14 @@ namespace ts { } } + function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type, message: DiagnosticMessage) { + const firstType = getAnnotatedType(first); + const secondType = getAnnotatedType(second); + if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) { + error(first, message); + } + } + function checkAccessorDeferred(node: AccessorDeclaration) { checkSourceElement(node.body); } @@ -18127,7 +18154,7 @@ namespace ts { return false; } - function checkGrammarAccessor(accessor: MethodDeclaration): boolean { + function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { const kind = accessor.kind; if (languageVersion < ScriptTarget.ES5) { return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); @@ -18141,16 +18168,16 @@ namespace ts { else if (accessor.typeParameters) { return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); } - else if (kind === SyntaxKind.GetAccessor && accessor.parameters.length) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_get_accessor_cannot_have_parameters); + else if (!doesAccessorHaveCorrectParameterCount(accessor)) { + return grammarErrorOnNode(accessor.name, + kind === SyntaxKind.GetAccessor ? + Diagnostics.A_get_accessor_cannot_have_parameters : + Diagnostics.A_set_accessor_must_have_exactly_one_parameter); } else if (kind === SyntaxKind.SetAccessor) { if (accessor.type) { return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); } - else if (accessor.parameters.length !== 1) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_must_have_exactly_one_parameter); - } else { const parameter = accessor.parameters[0]; if (parameter.dotDotDotToken) { @@ -18169,6 +18196,22 @@ namespace ts { } } + /** Does the accessor have the right number of parameters? + + A get accessor has no parameters or a single `this` parameter. + A set accessor has one parameter or a `this` parameter and one more parameter */ + function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { + return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + } + + function getAccessorThisParameter(accessor: AccessorDeclaration) { + if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2) && + accessor.parameters[0].name.kind === SyntaxKind.Identifier && + (accessor.parameters[0].name).originalKeywordKind === SyntaxKind.ThisKeyword) { + return accessor.parameters[0]; + } + } + function checkGrammarForNonSymbolComputedProperty(node: DeclarationName, message: DiagnosticMessage) { if (isDynamicName(node)) { return grammarErrorOnNode(node, message); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 4f5f173472..899b4292f0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1907,7 +1907,7 @@ "category": "Error", "code": 2681 }, - "A setter cannot have a 'this' parameter.": { + "'get' and 'set' accessor must have the same 'this' type.": { "category": "Error", "code": 2682 }, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b6c681e5f0..25e31677be 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2269,7 +2269,12 @@ namespace ts { } export function getSetAccessorTypeAnnotationNode(accessor: AccessorDeclaration): TypeNode { - return accessor && accessor.parameters.length > 0 && accessor.parameters[0].type; + if (accessor && accessor.parameters.length > 0) { + const hasThis = accessor.parameters.length === 2 && + accessor.parameters[0].name.kind === SyntaxKind.Identifier && + (accessor.parameters[0].name as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword; + return accessor.parameters[hasThis ? 1 : 0].type; + } } export function getAllAccessorDeclarations(declarations: NodeArray, accessor: AccessorDeclaration) { diff --git a/tests/baselines/reference/thisTypeInAccessors.js b/tests/baselines/reference/thisTypeInAccessors.js new file mode 100644 index 0000000000..ed339d0366 --- /dev/null +++ b/tests/baselines/reference/thisTypeInAccessors.js @@ -0,0 +1,83 @@ +//// [thisTypeInAccessors.ts] +interface Foo { + n: number; + x: number; +} + +const explicit = { + n: 12, + get x(this: Foo): number { return this.n; }, + set x(this: Foo, n: number) { this.n = n; } +} +const copiedFromGetter = { + n: 14, + get x(this: Foo): number { return this.n; }, + set x(n) { this.n = n; } +} +const copiedFromSetter = { + n: 15, + get x() { return this.n }, + set x(this: Foo, n: number) { this.n = n; } +} +const copiedFromGetterUnannotated = { + n: 16, + get x(this: Foo) { return this.n }, + set x(this, n) { this.n = n; } +} + +class Explicit { + n = 17; + get x(this: Foo): number { return this.n; } + set x(this: Foo, n: number) { this.n = n; } +} +class Contextual { + n = 21; + get x() { return this.n } // inside a class, so already correct +} + + +//// [thisTypeInAccessors.js] +var explicit = { + n: 12, + get x() { return this.n; }, + set x(n) { this.n = n; } +}; +var copiedFromGetter = { + n: 14, + get x() { return this.n; }, + set x(n) { this.n = n; } +}; +var copiedFromSetter = { + n: 15, + get x() { return this.n; }, + set x(n) { this.n = n; } +}; +var copiedFromGetterUnannotated = { + n: 16, + get x() { return this.n; }, + set x(n) { this.n = n; } +}; +var Explicit = (function () { + function Explicit() { + this.n = 17; + } + Object.defineProperty(Explicit.prototype, "x", { + get: function () { return this.n; }, + set: function (n) { this.n = n; }, + enumerable: true, + configurable: true + }); + return Explicit; +}()); +var Contextual = (function () { + function Contextual() { + this.n = 21; + } + Object.defineProperty(Contextual.prototype, "x", { + get: function () { return this.n; } // inside a class, so already correct + , + enumerable: true, + configurable: true + }); + return Contextual; +}()); diff --git a/tests/baselines/reference/thisTypeInAccessors.symbols b/tests/baselines/reference/thisTypeInAccessors.symbols new file mode 100644 index 0000000000..ef2201d380 --- /dev/null +++ b/tests/baselines/reference/thisTypeInAccessors.symbols @@ -0,0 +1,140 @@ +=== tests/cases/conformance/types/thisType/thisTypeInAccessors.ts === +interface Foo { +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) + + n: number; +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + x: number; +>x : Symbol(Foo.x, Decl(thisTypeInAccessors.ts, 1, 14)) +} + +const explicit = { +>explicit : Symbol(explicit, Decl(thisTypeInAccessors.ts, 5, 5)) + + n: 12, +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 5, 18)) + + get x(this: Foo): number { return this.n; }, +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 6, 10), Decl(thisTypeInAccessors.ts, 7, 48)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 7, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + set x(this: Foo, n: number) { this.n = n; } +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 6, 10), Decl(thisTypeInAccessors.ts, 7, 48)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 8, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 8, 20)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 8, 20)) +} +const copiedFromGetter = { +>copiedFromGetter : Symbol(copiedFromGetter, Decl(thisTypeInAccessors.ts, 10, 5)) + + n: 14, +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 10, 26)) + + get x(this: Foo): number { return this.n; }, +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 11, 10), Decl(thisTypeInAccessors.ts, 12, 48)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 12, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + set x(n) { this.n = n; } +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 11, 10), Decl(thisTypeInAccessors.ts, 12, 48)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 13, 10)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 13, 10)) +} +const copiedFromSetter = { +>copiedFromSetter : Symbol(copiedFromSetter, Decl(thisTypeInAccessors.ts, 15, 5)) + + n: 15, +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 15, 26)) + + get x() { return this.n }, +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 16, 10), Decl(thisTypeInAccessors.ts, 17, 30)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + set x(this: Foo, n: number) { this.n = n; } +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 16, 10), Decl(thisTypeInAccessors.ts, 17, 30)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 18, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 18, 20)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 18, 20)) +} +const copiedFromGetterUnannotated = { +>copiedFromGetterUnannotated : Symbol(copiedFromGetterUnannotated, Decl(thisTypeInAccessors.ts, 20, 5)) + + n: 16, +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 20, 37)) + + get x(this: Foo) { return this.n }, +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 21, 10), Decl(thisTypeInAccessors.ts, 22, 39)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 22, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + set x(this, n) { this.n = n; } +>x : Symbol(x, Decl(thisTypeInAccessors.ts, 21, 10), Decl(thisTypeInAccessors.ts, 22, 39)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 23, 10)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 23, 15)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 23, 15)) +} + +class Explicit { +>Explicit : Symbol(Explicit, Decl(thisTypeInAccessors.ts, 24, 1)) + + n = 17; +>n : Symbol(Explicit.n, Decl(thisTypeInAccessors.ts, 26, 16)) + + get x(this: Foo): number { return this.n; } +>x : Symbol(Explicit.x, Decl(thisTypeInAccessors.ts, 27, 11), Decl(thisTypeInAccessors.ts, 28, 47)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 28, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) + + set x(this: Foo, n: number) { this.n = n; } +>x : Symbol(Explicit.x, Decl(thisTypeInAccessors.ts, 27, 11), Decl(thisTypeInAccessors.ts, 28, 47)) +>this : Symbol(this, Decl(thisTypeInAccessors.ts, 29, 10)) +>Foo : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 29, 20)) +>this.n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>this : Symbol(Foo, Decl(thisTypeInAccessors.ts, 0, 0)) +>n : Symbol(Foo.n, Decl(thisTypeInAccessors.ts, 0, 15)) +>n : Symbol(n, Decl(thisTypeInAccessors.ts, 29, 20)) +} +class Contextual { +>Contextual : Symbol(Contextual, Decl(thisTypeInAccessors.ts, 30, 1)) + + n = 21; +>n : Symbol(Contextual.n, Decl(thisTypeInAccessors.ts, 31, 18)) + + get x() { return this.n } // inside a class, so already correct +>x : Symbol(Contextual.x, Decl(thisTypeInAccessors.ts, 32, 11)) +>this.n : Symbol(Contextual.n, Decl(thisTypeInAccessors.ts, 31, 18)) +>this : Symbol(Contextual, Decl(thisTypeInAccessors.ts, 30, 1)) +>n : Symbol(Contextual.n, Decl(thisTypeInAccessors.ts, 31, 18)) +} + diff --git a/tests/baselines/reference/thisTypeInAccessors.types b/tests/baselines/reference/thisTypeInAccessors.types new file mode 100644 index 0000000000..998658c5d5 --- /dev/null +++ b/tests/baselines/reference/thisTypeInAccessors.types @@ -0,0 +1,155 @@ +=== tests/cases/conformance/types/thisType/thisTypeInAccessors.ts === +interface Foo { +>Foo : Foo + + n: number; +>n : number + + x: number; +>x : number +} + +const explicit = { +>explicit : { n: number; x: number; } +>{ n: 12, get x(this: Foo): number { return this.n; }, set x(this: Foo, n: number) { this.n = n; }} : { n: number; x: number; } + + n: 12, +>n : number +>12 : number + + get x(this: Foo): number { return this.n; }, +>x : number +>this : Foo +>Foo : Foo +>this.n : number +>this : Foo +>n : number + + set x(this: Foo, n: number) { this.n = n; } +>x : number +>this : Foo +>Foo : Foo +>n : number +>this.n = n : number +>this.n : number +>this : Foo +>n : number +>n : number +} +const copiedFromGetter = { +>copiedFromGetter : { n: number; x: number; } +>{ n: 14, get x(this: Foo): number { return this.n; }, set x(n) { this.n = n; }} : { n: number; x: number; } + + n: 14, +>n : number +>14 : number + + get x(this: Foo): number { return this.n; }, +>x : number +>this : Foo +>Foo : Foo +>this.n : number +>this : Foo +>n : number + + set x(n) { this.n = n; } +>x : number +>n : number +>this.n = n : number +>this.n : number +>this : Foo +>n : number +>n : number +} +const copiedFromSetter = { +>copiedFromSetter : { n: number; x: number; } +>{ n: 15, get x() { return this.n }, set x(this: Foo, n: number) { this.n = n; }} : { n: number; x: number; } + + n: 15, +>n : number +>15 : number + + get x() { return this.n }, +>x : number +>this.n : number +>this : Foo +>n : number + + set x(this: Foo, n: number) { this.n = n; } +>x : number +>this : Foo +>Foo : Foo +>n : number +>this.n = n : number +>this.n : number +>this : Foo +>n : number +>n : number +} +const copiedFromGetterUnannotated = { +>copiedFromGetterUnannotated : { n: number; x: number; } +>{ n: 16, get x(this: Foo) { return this.n }, set x(this, n) { this.n = n; }} : { n: number; x: number; } + + n: 16, +>n : number +>16 : number + + get x(this: Foo) { return this.n }, +>x : number +>this : Foo +>Foo : Foo +>this.n : number +>this : Foo +>n : number + + set x(this, n) { this.n = n; } +>x : number +>this : Foo +>n : number +>this.n = n : number +>this.n : number +>this : Foo +>n : number +>n : number +} + +class Explicit { +>Explicit : Explicit + + n = 17; +>n : number +>17 : number + + get x(this: Foo): number { return this.n; } +>x : number +>this : Foo +>Foo : Foo +>this.n : number +>this : Foo +>n : number + + set x(this: Foo, n: number) { this.n = n; } +>x : number +>this : Foo +>Foo : Foo +>n : number +>this.n = n : number +>this.n : number +>this : Foo +>n : number +>n : number +} +class Contextual { +>Contextual : Contextual + + n = 21; +>n : number +>21 : number + + get x() { return this.n } // inside a class, so already correct +>x : number +>this.n : number +>this : this +>n : number +} + diff --git a/tests/baselines/reference/thisTypeInAccessorsNegative.errors.txt b/tests/baselines/reference/thisTypeInAccessorsNegative.errors.txt new file mode 100644 index 0000000000..5396f1f431 --- /dev/null +++ b/tests/baselines/reference/thisTypeInAccessorsNegative.errors.txt @@ -0,0 +1,30 @@ +tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts(10,9): error TS2682: 'get' and 'set' accessor must have the same 'this' type. +tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts(11,9): error TS2682: 'get' and 'set' accessor must have the same 'this' type. +tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts(16,22): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. + + +==== tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts (3 errors) ==== + interface Foo { + n: number; + x: number; + } + interface Bar { + wrong: "place" | "time" | "method" | "technique"; + } + const mismatch = { + n: 13, + get x(this: Foo) { return this.n; }, + ~ +!!! error TS2682: 'get' and 'set' accessor must have the same 'this' type. + set x(this: Bar, n) { this.wrong = "method"; } + ~ +!!! error TS2682: 'get' and 'set' accessor must have the same 'this' type. + } + const contextual: Foo = { + n: 16, + // there is no contextual this type from an Foo.x. + get x() { return this.n; } + ~~~~ +!!! error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. + } + \ No newline at end of file diff --git a/tests/baselines/reference/thisTypeInAccessorsNegative.js b/tests/baselines/reference/thisTypeInAccessorsNegative.js new file mode 100644 index 0000000000..4d0826232f --- /dev/null +++ b/tests/baselines/reference/thisTypeInAccessorsNegative.js @@ -0,0 +1,31 @@ +//// [thisTypeInAccessorsNegative.ts] +interface Foo { + n: number; + x: number; +} +interface Bar { + wrong: "place" | "time" | "method" | "technique"; +} +const mismatch = { + n: 13, + get x(this: Foo) { return this.n; }, + set x(this: Bar, n) { this.wrong = "method"; } +} +const contextual: Foo = { + n: 16, + // there is no contextual this type from an Foo.x. + get x() { return this.n; } +} + + +//// [thisTypeInAccessorsNegative.js] +var mismatch = { + n: 13, + get x() { return this.n; }, + set x(n) { this.wrong = "method"; } +}; +var contextual = { + n: 16, + // there is no contextual this type from an Foo.x. + get x() { return this.n; } +}; diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt b/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt index 017550dcec..564aa2ed92 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt @@ -75,32 +75,31 @@ tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(146,1): er tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(148,1): error TS2322: Type '(this: Base2) => number' is not assignable to type '(this: Base1) => number'. tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(154,16): error TS2679: A function that is called with the 'new' keyword cannot have a 'this' type that is 'void'. tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(158,17): error TS2681: A constructor cannot have a 'this' parameter. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(160,11): error TS2682: A setter cannot have a 'this' parameter. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(164,9): error TS2681: A constructor cannot have a 'this' parameter. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(166,31): error TS2681: A constructor cannot have a 'this' parameter. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(167,30): error TS2680: A 'this' parameter must be the first parameter. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(170,26): error TS1003: Identifier expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(170,30): error TS1005: ',' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(171,20): error TS2370: A rest parameter must be of an array type. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(171,23): error TS1003: Identifier expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(171,27): error TS1005: ',' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,23): error TS1005: ',' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,24): error TS1138: Parameter declaration expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(173,28): error TS1003: Identifier expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(173,32): error TS1005: ',' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,30): error TS1005: ',' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,32): error TS1138: Parameter declaration expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,39): error TS1005: ';' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,40): error TS1128: Declaration or statement expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,42): error TS2304: Cannot find name 'number'. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(174,49): error TS1005: ';' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,1): error TS7027: Unreachable code detected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,29): error TS2304: Cannot find name 'm'. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,32): error TS1005: ';' expected. -tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,35): error TS2304: Cannot find name 'm'. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(162,9): error TS2681: A constructor cannot have a 'this' parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(164,31): error TS2681: A constructor cannot have a 'this' parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(165,30): error TS2680: A 'this' parameter must be the first parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(168,26): error TS1003: Identifier expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(168,30): error TS1005: ',' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(169,20): error TS2370: A rest parameter must be of an array type. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(169,23): error TS1003: Identifier expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(169,27): error TS1005: ',' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(170,23): error TS1005: ',' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(170,24): error TS1138: Parameter declaration expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(171,28): error TS1003: Identifier expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(171,32): error TS1005: ',' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,30): error TS1005: ',' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,32): error TS1138: Parameter declaration expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,39): error TS1005: ';' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,40): error TS1128: Declaration or statement expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,42): error TS2304: Cannot find name 'number'. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,49): error TS1005: ';' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(175,1): error TS7027: Unreachable code detected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(175,29): error TS2304: Cannot find name 'm'. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(175,32): error TS1005: ';' expected. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(175,35): error TS2304: Cannot find name 'm'. -==== tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts (66 errors) ==== +==== tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts (65 errors) ==== class C { n: number; explicitThis(this: this, m: number): number { @@ -380,10 +379,6 @@ tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,35): e ~~~~~~~~~~~~~~~~~~~~~ !!! error TS2681: A constructor cannot have a 'this' parameter. } - set p(this: void) { - ~~~~~~~~~~ -!!! error TS2682: A setter cannot have a 'this' parameter. - } } interface ThisConstructorInterface { new(this: ThisConstructor, n: number); diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.js b/tests/baselines/reference/thisTypeInFunctionsNegative.js index 124a7c8801..602c6afefd 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.js +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.js @@ -158,8 +158,6 @@ let voidThis = new VoidThis(); class ThisConstructor { constructor(this: ThisConstructor, private n: number) { } - set p(this: void) { - } } interface ThisConstructorInterface { new(this: ThisConstructor, n: number); @@ -332,12 +330,6 @@ var ThisConstructor = (function () { function ThisConstructor(n) { this.n = n; } - Object.defineProperty(ThisConstructor.prototype, "p", { - set: function () { - }, - enumerable: true, - configurable: true - }); return ThisConstructor; }()); var thisConstructorType; diff --git a/tests/cases/conformance/types/thisType/thisTypeInAccessors.ts b/tests/cases/conformance/types/thisType/thisTypeInAccessors.ts new file mode 100644 index 0000000000..b090375976 --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeInAccessors.ts @@ -0,0 +1,38 @@ +// @noImplicitAny: true +// @noImplicitThis: true +// @target: es5 +interface Foo { + n: number; + x: number; +} + +const explicit = { + n: 12, + get x(this: Foo): number { return this.n; }, + set x(this: Foo, n: number) { this.n = n; } +} +const copiedFromGetter = { + n: 14, + get x(this: Foo): number { return this.n; }, + set x(n) { this.n = n; } +} +const copiedFromSetter = { + n: 15, + get x() { return this.n }, + set x(this: Foo, n: number) { this.n = n; } +} +const copiedFromGetterUnannotated = { + n: 16, + get x(this: Foo) { return this.n }, + set x(this, n) { this.n = n; } +} + +class Explicit { + n = 17; + get x(this: Foo): number { return this.n; } + set x(this: Foo, n: number) { this.n = n; } +} +class Contextual { + n = 21; + get x() { return this.n } // inside a class, so already correct +} diff --git a/tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts b/tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts new file mode 100644 index 0000000000..69ff4175a9 --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts @@ -0,0 +1,20 @@ +// @noImplicitAny: true +// @noImplicitThis: true +// @target: es5 +interface Foo { + n: number; + x: number; +} +interface Bar { + wrong: "place" | "time" | "method" | "technique"; +} +const mismatch = { + n: 13, + get x(this: Foo) { return this.n; }, + set x(this: Bar, n) { this.wrong = "method"; } +} +const contextual: Foo = { + n: 16, + // there is no contextual this type from an Foo.x. + get x() { return this.n; } +} diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts index 597fe5a836..aa2a48d5f4 100644 --- a/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts @@ -157,8 +157,6 @@ let voidThis = new VoidThis(); class ThisConstructor { constructor(this: ThisConstructor, private n: number) { } - set p(this: void) { - } } interface ThisConstructorInterface { new(this: ThisConstructor, n: number);