From f72193eedc220a9dab0dd7f557e805c6f161fef5 Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Mon, 23 Jul 2018 10:42:56 -0400 Subject: [PATCH] Report a semantic error for an arrow function with a "this" parameter. Fixes #9744. --- src/compiler/checker.ts | 3 + src/compiler/diagnosticMessages.json | 4 + src/compiler/parser.ts | 5 +- .../thisTypeInFunctionsNegative.errors.txt | 26 ++- .../reference/thisTypeInFunctionsNegative.js | 172 ++++++++---------- .../thisTypeInFunctionsNegative.symbols | 26 +++ .../thisTypeInFunctionsNegative.types | 49 ++++- .../thisType/thisTypeInFunctionsNegative.ts | 5 + 8 files changed, 171 insertions(+), 119 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cdcf1448d2d..8a71311ecd7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21857,6 +21857,9 @@ 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.ArrowFunction) { + error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + } } // Only check rest parameter type if it's not a binding pattern. Since binding patterns are diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ef310cf2717..a9118945b05 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2417,6 +2417,10 @@ "category": "Error", "code": 2729 }, + "An arrow function cannot have a 'this' parameter.": { + "category": "Error", + "code": 2730 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ba266d6b217..070e774d82f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3537,8 +3537,9 @@ namespace ts { } // If we had "(" followed by something that's not an identifier, - // then this definitely doesn't look like a lambda. - if (!isIdentifier()) { + // then this definitely doesn't look like a lambda. "this" is not + // valid, but we want to parse it and then give a semantic error. + if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { return Tristate.False; } diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt b/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt index 797c6877e21..e77addd5ab1 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.errors.txt @@ -93,12 +93,13 @@ tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,39): e 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 TS2693: 'number' only refers to a type, but is being used as a value here. tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(172,49): error TS1005: ';' expected. -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(175,23): error TS2730: An arrow function cannot have a 'this' parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(176,16): error TS2730: An arrow function cannot have a 'this' parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(177,19): error TS2730: An arrow function cannot have a 'this' parameter. +tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(178,22): error TS2730: An arrow function cannot have a 'this' parameter. -==== tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts (64 errors) ==== +==== tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts (65 errors) ==== class C { n: number; explicitThis(this: this, m: number): number { @@ -430,10 +431,15 @@ tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts(175,35): e // can't name parameters 'this' in a lambda. c.explicitProperty = (this, m) => m + this.n; - ~ -!!! error TS2304: Cannot find name 'm'. - ~~ -!!! error TS1005: ';' expected. - ~ -!!! error TS2304: Cannot find name 'm'. + ~~~~ +!!! error TS2730: An arrow function cannot have a 'this' parameter. + const f2 = (this: {n: number}, m: number) => m + this.n; + ~~~~~~~~~~~~~~~~~ +!!! error TS2730: An arrow function cannot have a 'this' parameter. + const f3 = async (this: {n: number}, m: number) => m + this.n; + ~~~~~~~~~~~~~~~~~ +!!! error TS2730: An arrow function cannot have a 'this' parameter. + const f4 = async (this: {n: number}, m: number) => m + this.n; + ~~~~~~~~~~~~~~~~~ +!!! error TS2730: An arrow function cannot have a 'this' parameter. \ No newline at end of file diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.js b/tests/baselines/reference/thisTypeInFunctionsNegative.js index 086cf3284a3..9150536f4e7 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.js +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.js @@ -174,69 +174,60 @@ function initializer(this: C = new C()): number { return this.n; } // can't name parameters 'this' in a lambda. c.explicitProperty = (this, m) => m + this.n; +const f2 = (this: {n: number}, m: number) => m + this.n; +const f3 = async (this: {n: number}, m: number) => m + this.n; +const f4 = async (this: {n: number}, m: number) => m + this.n; //// [thisTypeInFunctionsNegative.js] -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +class C { + explicitThis(m) { + return this.n + m; } - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var _this = this; -var C = /** @class */ (function () { - function C() { + implicitThis(m) { + return this.n + m; } - C.prototype.explicitThis = function (m) { + explicitC(m) { return this.n + m; - }; - C.prototype.implicitThis = function (m) { + } + explicitProperty(m) { return this.n + m; - }; - C.prototype.explicitC = function (m) { - return this.n + m; - }; - C.prototype.explicitProperty = function (m) { - return this.n + m; - }; - C.prototype.explicitVoid = function (m) { + } + explicitVoid(m) { return this.n + m; // 'n' doesn't exist on type 'void'. - }; - return C; -}()); -var D = /** @class */ (function () { - function D() { } - D.prototype.explicitThis = function (m) { +} +class D { + explicitThis(m) { return this.x + m; - }; - D.prototype.explicitD = function (m) { + } + explicitD(m) { return this.x + m; - }; - return D; -}()); -var impl = { + } +} +let impl = { a: 12, - explicitVoid1: function () { + explicitVoid1() { return this.a; // error, no 'a' in 'void' }, - explicitVoid2: function () { return _this.a; }, - explicitStructural: function () { return 12; }, - explicitInterface: function () { return 12; }, - explicitThis: function () { + explicitVoid2: () => this.a, + explicitStructural: () => 12, + explicitInterface: () => 12, + explicitThis() { return this.a; - } + }, }; -var implExplicitStructural = impl.explicitStructural; +let implExplicitStructural = impl.explicitStructural; implExplicitStructural(); // error, no 'a' in 'void' -var implExplicitInterface = impl.explicitInterface; +let implExplicitInterface = impl.explicitInterface; implExplicitInterface(); // error, no 'a' in 'void' function explicitStructural(x) { return x + this.y; @@ -247,15 +238,15 @@ function propertyName(x) { function voidThisSpecified(x) { return x + this.notSpecified; } -var ok = { y: 12, explicitStructural: explicitStructural }; -var wrongPropertyType = { y: 'foo', explicitStructural: explicitStructural }; -var wrongPropertyName = { wrongName: 12, explicitStructural: explicitStructural }; +let ok = { y: 12, explicitStructural }; +let wrongPropertyType = { y: 'foo', explicitStructural }; +let wrongPropertyName = { wrongName: 12, explicitStructural }; ok.f(); // not enough arguments ok.f('wrong type'); ok.f(13, 'too many arguments'); wrongPropertyType.f(13); wrongPropertyName.f(13); -var c = new C(); +let c = new C(); c.explicitC(); // not enough arguments c.explicitC('wrong type'); c.explicitC(13, 'too many arguments'); @@ -269,8 +260,8 @@ c.explicitProperty(); // not enough arguments c.explicitProperty('wrong type 3'); c.explicitProperty(15, 'too many arguments 3'); // oops, this triggers contextual typing, which needs to be updated to understand that =>'s `this` is void. -var specifiedToVoid = explicitStructural; -var reconstructed = { +let specifiedToVoid = explicitStructural; +let reconstructed = { n: 12, explicitThis: c.explicitThis, explicitC: c.explicitC, @@ -279,8 +270,8 @@ var reconstructed = { }; ; // lambdas have this: void for assignability purposes (and this unbound (free) for body checking) -var d = new D(); -var explicitXProperty; +let d = new D(); +let explicitXProperty; // from differing object types c.explicitC = function (m) { return this.x + m; }; c.explicitProperty = explicitXProperty; @@ -293,64 +284,41 @@ c.explicitThis = d.explicitThis; c.explicitVoid = d.explicitD; c.explicitVoid = d.explicitThis; /// class-based polymorphic assignability (with inheritance!) /// -var Base1 = /** @class */ (function () { - function Base1() { - } - Base1.prototype.polymorphic = function () { return this.x; }; - Base1.prototype.explicit = function () { return this.x; }; - Base1.explicitStatic = function () { return this.x; }; - return Base1; -}()); -var Derived1 = /** @class */ (function (_super) { - __extends(Derived1, _super); - function Derived1() { - return _super !== null && _super.apply(this, arguments) || this; - } - return Derived1; -}(Base1)); -var Base2 = /** @class */ (function () { - function Base2() { - } - Base2.prototype.polymorphic = function () { return this.y; }; - Base2.prototype.explicit = function () { return this.x; }; - return Base2; -}()); -var Derived2 = /** @class */ (function (_super) { - __extends(Derived2, _super); - function Derived2() { - return _super !== null && _super.apply(this, arguments) || this; - } - return Derived2; -}(Base2)); -var b1 = new Base1(); -var d1 = new Derived1(); -var b2 = new Base2(); -var d2 = new Derived2(); +class Base1 { + polymorphic() { return this.x; } + explicit() { return this.x; } + static explicitStatic() { return this.x; } +} +class Derived1 extends Base1 { +} +class Base2 { + polymorphic() { return this.y; } + explicit() { return this.x; } +} +class Derived2 extends Base2 { +} +let b1 = new Base1(); +let d1 = new Derived1(); +let b2 = new Base2(); +let d2 = new Derived2(); b1.polymorphic = b2.polymorphic; // error, 'this.y' not in Base1: { x } b1.explicit = b2.polymorphic; // error, 'y' not in Base1: { x } d1.explicit = b2.polymorphic; // error, 'y' not in Base1: { x } ////// use this-type for construction with new //// function VoidThis() { } -var voidThis = new VoidThis(); +let voidThis = new VoidThis(); ///// syntax-ish errors ///// -var ThisConstructor = /** @class */ (function () { - function ThisConstructor(n) { +class ThisConstructor { + constructor(n) { this.n = n; } - return ThisConstructor; -}()); +} var thisConstructorType; function notFirst(a) { return this.n; } ///// parse errors ///// function modifiers() { return this.n; } -function restParam() { - var = []; - for (var _i = 0; _i < arguments.length; _i++) { - [_i] = arguments[_i]; - } - return this.n; -} +function restParam(...) { return this.n; } function optional() { return this.n; } function decorated() { return this.n; } function initializer(, C) { } @@ -360,5 +328,7 @@ number; return this.n; } // can't name parameters 'this' in a lambda. -c.explicitProperty = (this, m); -m + this.n; +c.explicitProperty = (m) => m + this.n; +const f2 = (m) => m + this.n; +const f3 = (m) => __awaiter(this, void 0, void 0, function* () { return m + this.n; }); +const f4 = (m) => __awaiter(this, void 0, void 0, function* () { return m + this.n; }); diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.symbols b/tests/baselines/reference/thisTypeInFunctionsNegative.symbols index 61b4274d99f..024ae663a9d 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.symbols +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.symbols @@ -661,4 +661,30 @@ c.explicitProperty = (this, m) => m + this.n; >c.explicitProperty : Symbol(C.explicitProperty, Decl(thisTypeInFunctionsNegative.ts, 10, 5)) >c : Symbol(c, Decl(thisTypeInFunctionsNegative.ts, 70, 3)) >explicitProperty : Symbol(C.explicitProperty, Decl(thisTypeInFunctionsNegative.ts, 10, 5)) +>this : Symbol(this, Decl(thisTypeInFunctionsNegative.ts, 174, 22)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 174, 27)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 174, 27)) + +const f2 = (this: {n: number}, m: number) => m + this.n; +>f2 : Symbol(f2, Decl(thisTypeInFunctionsNegative.ts, 175, 5)) +>T : Symbol(T, Decl(thisTypeInFunctionsNegative.ts, 175, 12)) +>this : Symbol(this, Decl(thisTypeInFunctionsNegative.ts, 175, 15)) +>n : Symbol(n, Decl(thisTypeInFunctionsNegative.ts, 175, 22)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 175, 33)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 175, 33)) + +const f3 = async (this: {n: number}, m: number) => m + this.n; +>f3 : Symbol(f3, Decl(thisTypeInFunctionsNegative.ts, 176, 5)) +>this : Symbol(this, Decl(thisTypeInFunctionsNegative.ts, 176, 18)) +>n : Symbol(n, Decl(thisTypeInFunctionsNegative.ts, 176, 25)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 176, 36)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 176, 36)) + +const f4 = async (this: {n: number}, m: number) => m + this.n; +>f4 : Symbol(f4, Decl(thisTypeInFunctionsNegative.ts, 177, 5)) +>T : Symbol(T, Decl(thisTypeInFunctionsNegative.ts, 177, 18)) +>this : Symbol(this, Decl(thisTypeInFunctionsNegative.ts, 177, 21)) +>n : Symbol(n, Decl(thisTypeInFunctionsNegative.ts, 177, 28)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 177, 39)) +>m : Symbol(m, Decl(thisTypeInFunctionsNegative.ts, 177, 39)) diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.types b/tests/baselines/reference/thisTypeInFunctionsNegative.types index 1625497fb16..cfc60b04e32 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.types +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.types @@ -777,16 +777,53 @@ function initializer(this: C = new C()): number { return this.n; } // can't name parameters 'this' in a lambda. c.explicitProperty = (this, m) => m + this.n; ->c.explicitProperty = (this, m) : any +>c.explicitProperty = (this, m) => m + this.n : (this: { n: number; }, m: number) => any >c.explicitProperty : (this: { n: number; }, m: number) => number >c : C >explicitProperty : (this: { n: number; }, m: number) => number ->(this, m) : any ->this, m : any ->this : any ->m : any +>(this, m) => m + this.n : (this: { n: number; }, m: number) => any +>this : { n: number; } +>m : number >m + this.n : any ->m : any +>m : number +>this.n : any +>this : any +>n : any + +const f2 = (this: {n: number}, m: number) => m + this.n; +>f2 : (this: { n: number; }, m: number) => any +>(this: {n: number}, m: number) => m + this.n : (this: { n: number; }, m: number) => any +>T : T +>this : { n: number; } +>n : number +>m : number +>m + this.n : any +>m : number +>this.n : any +>this : any +>n : any + +const f3 = async (this: {n: number}, m: number) => m + this.n; +>f3 : (this: { n: number; }, m: number) => Promise +>async (this: {n: number}, m: number) => m + this.n : (this: { n: number; }, m: number) => Promise +>this : { n: number; } +>n : number +>m : number +>m + this.n : any +>m : number +>this.n : any +>this : any +>n : any + +const f4 = async (this: {n: number}, m: number) => m + this.n; +>f4 : (this: { n: number; }, m: number) => Promise +>async (this: {n: number}, m: number) => m + this.n : (this: { n: number; }, m: number) => Promise +>T : T +>this : { n: number; } +>n : number +>m : number +>m + this.n : any +>m : number >this.n : any >this : any >n : any diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts index aa2a48d5f47..217fbb93622 100644 --- a/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctionsNegative.ts @@ -1,3 +1,5 @@ +// @target: es6 + class C { n: number; explicitThis(this: this, m: number): number { @@ -173,3 +175,6 @@ function initializer(this: C = new C()): number { return this.n; } // can't name parameters 'this' in a lambda. c.explicitProperty = (this, m) => m + this.n; +const f2 = (this: {n: number}, m: number) => m + this.n; +const f3 = async (this: {n: number}, m: number) => m + this.n; +const f4 = async (this: {n: number}, m: number) => m + this.n;