Skip to content

Commit 50f3910

Browse files
authored
Merge pull request microsoft#17096 from Microsoft/improve-return-type-parse-error
Improve return type parse error
2 parents 38f6db5 + 1b1f257 commit 50f3910

File tree

4 files changed

+134
-41
lines changed

4 files changed

+134
-41
lines changed

src/compiler/parser.ts

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
/// <reference path="scanner.ts"/>
33

44
namespace ts {
5+
const enum SignatureFlags {
6+
None = 0,
7+
Yield = 1 << 0,
8+
Await = 1 << 1,
9+
Type = 1 << 2,
10+
RequireCompleteParameterList = 1 << 3,
11+
IgnoreMissingOpenBrace = 1 << 4,
12+
}
13+
514
let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
615
let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
716
let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
@@ -2218,25 +2227,32 @@ namespace ts {
22182227

22192228
function fillSignature(
22202229
returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken,
2221-
yieldContext: boolean,
2222-
awaitContext: boolean,
2223-
requireCompleteParameterList: boolean,
2230+
flags: SignatureFlags,
22242231
signature: SignatureDeclaration): void {
2225-
2226-
const returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken;
22272232
signature.typeParameters = parseTypeParameters();
2228-
signature.parameters = parseParameterList(yieldContext, awaitContext, requireCompleteParameterList);
2233+
signature.parameters = parseParameterList(flags);
22292234

2235+
const returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken;
22302236
if (returnTokenRequired) {
22312237
parseExpected(returnToken);
22322238
signature.type = parseTypeOrTypePredicate();
22332239
}
22342240
else if (parseOptional(returnToken)) {
22352241
signature.type = parseTypeOrTypePredicate();
22362242
}
2243+
else if (flags & SignatureFlags.Type) {
2244+
const start = scanner.getTokenPos();
2245+
const length = scanner.getTextPos() - start;
2246+
const backwardToken = parseOptional(returnToken === SyntaxKind.ColonToken ? SyntaxKind.EqualsGreaterThanToken : SyntaxKind.ColonToken);
2247+
if (backwardToken) {
2248+
// This is easy to get backward, especially in type contexts, so parse the type anyway
2249+
signature.type = parseTypeOrTypePredicate();
2250+
parseErrorAtPosition(start, length, Diagnostics._0_expected, tokenToString(returnToken));
2251+
}
2252+
}
22372253
}
22382254

2239-
function parseParameterList(yieldContext: boolean, awaitContext: boolean, requireCompleteParameterList: boolean) {
2255+
function parseParameterList(flags: SignatureFlags) {
22402256
// FormalParameters [Yield,Await]: (modified)
22412257
// [empty]
22422258
// FormalParameterList[?Yield,Await]
@@ -2254,15 +2270,15 @@ namespace ts {
22542270
const savedYieldContext = inYieldContext();
22552271
const savedAwaitContext = inAwaitContext();
22562272

2257-
setYieldContext(yieldContext);
2258-
setAwaitContext(awaitContext);
2273+
setYieldContext(!!(flags & SignatureFlags.Yield));
2274+
setAwaitContext(!!(flags & SignatureFlags.Await));
22592275

22602276
const result = parseDelimitedList(ParsingContext.Parameters, parseParameter);
22612277

22622278
setYieldContext(savedYieldContext);
22632279
setAwaitContext(savedAwaitContext);
22642280

2265-
if (!parseExpected(SyntaxKind.CloseParenToken) && requireCompleteParameterList) {
2281+
if (!parseExpected(SyntaxKind.CloseParenToken) && (flags & SignatureFlags.RequireCompleteParameterList)) {
22662282
// Caller insisted that we had to end with a ) We didn't. So just return
22672283
// undefined here.
22682284
return undefined;
@@ -2274,7 +2290,7 @@ namespace ts {
22742290
// We didn't even have an open paren. If the caller requires a complete parameter list,
22752291
// we definitely can't provide that. However, if they're ok with an incomplete one,
22762292
// then just return an empty set of parameters.
2277-
return requireCompleteParameterList ? undefined : createMissingList<ParameterDeclaration>();
2293+
return (flags & SignatureFlags.RequireCompleteParameterList) ? undefined : createMissingList<ParameterDeclaration>();
22782294
}
22792295

22802296
function parseTypeMemberSemicolon() {
@@ -2293,7 +2309,7 @@ namespace ts {
22932309
if (kind === SyntaxKind.ConstructSignature) {
22942310
parseExpected(SyntaxKind.NewKeyword);
22952311
}
2296-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
2312+
fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, node);
22972313
parseTypeMemberSemicolon();
22982314
return addJSDocComment(finishNode(node));
22992315
}
@@ -2383,7 +2399,7 @@ namespace ts {
23832399

23842400
// Method signatures don't exist in expression contexts. So they have neither
23852401
// [Yield] nor [Await]
2386-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, method);
2402+
fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, method);
23872403
parseTypeMemberSemicolon();
23882404
return addJSDocComment(finishNode(method));
23892405
}
@@ -2527,7 +2543,7 @@ namespace ts {
25272543
if (kind === SyntaxKind.ConstructorType) {
25282544
parseExpected(SyntaxKind.NewKeyword);
25292545
}
2530-
fillSignature(SyntaxKind.EqualsGreaterThanToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
2546+
fillSignature(SyntaxKind.EqualsGreaterThanToken, SignatureFlags.Type, node);
25312547
return finishNode(node);
25322548
}
25332549

@@ -3254,7 +3270,7 @@ namespace ts {
32543270
function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction {
32553271
const node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
32563272
node.modifiers = parseModifiersForArrowFunction();
3257-
const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async);
3273+
const isAsync = (getModifierFlags(node) & ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
32583274

32593275
// Arrow functions are never generators.
32603276
//
@@ -3263,7 +3279,7 @@ namespace ts {
32633279
// a => (b => c)
32643280
// And think that "(b =>" was actually a parenthesized arrow function with a missing
32653281
// close paren.
3266-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ !allowAmbiguity, node);
3282+
fillSignature(SyntaxKind.ColonToken, isAsync | (allowAmbiguity ? SignatureFlags.None : SignatureFlags.RequireCompleteParameterList), node);
32673283

32683284
// If we couldn't get parameters, we definitely could not parse out an arrow function.
32693285
if (!node.parameters) {
@@ -3288,7 +3304,7 @@ namespace ts {
32883304

32893305
function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression {
32903306
if (token() === SyntaxKind.OpenBraceToken) {
3291-
return parseFunctionBlock(/*allowYield*/ false, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false);
3307+
return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None);
32923308
}
32933309

32943310
if (token() !== SyntaxKind.SemicolonToken &&
@@ -3309,8 +3325,8 @@ namespace ts {
33093325
// try to recover better. If we don't do this, then the next close curly we see may end
33103326
// up preemptively closing the containing construct.
33113327
//
3312-
// Note: even when 'ignoreMissingOpenBrace' is passed as true, parseBody will still error.
3313-
return parseFunctionBlock(/*allowYield*/ false, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ true);
3328+
// Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error.
3329+
return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None));
33143330
}
33153331

33163332
return isAsync
@@ -4386,16 +4402,16 @@ namespace ts {
43864402
parseExpected(SyntaxKind.FunctionKeyword);
43874403
node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
43884404

4389-
const isGenerator = !!node.asteriskToken;
4390-
const isAsync = !!(getModifierFlags(node) & ModifierFlags.Async);
4405+
const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None;
4406+
const isAsync = (getModifierFlags(node) & ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
43914407
node.name =
43924408
isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) :
43934409
isGenerator ? doInYieldContext(parseOptionalIdentifier) :
43944410
isAsync ? doInAwaitContext(parseOptionalIdentifier) :
43954411
parseOptionalIdentifier();
43964412

4397-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node);
4398-
node.body = parseFunctionBlock(/*allowYield*/ isGenerator, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false);
4413+
fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node);
4414+
node.body = parseFunctionBlock(isGenerator | isAsync);
43994415

44004416
if (saveDecoratorContext) {
44014417
setDecoratorContext(/*val*/ true);
@@ -4444,12 +4460,12 @@ namespace ts {
44444460
return finishNode(node);
44454461
}
44464462

4447-
function parseFunctionBlock(allowYield: boolean, allowAwait: boolean, ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block {
4463+
function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block {
44484464
const savedYieldContext = inYieldContext();
4449-
setYieldContext(allowYield);
4465+
setYieldContext(!!(flags & SignatureFlags.Yield));
44504466

44514467
const savedAwaitContext = inAwaitContext();
4452-
setAwaitContext(allowAwait);
4468+
setAwaitContext(!!(flags & SignatureFlags.Await));
44534469

44544470
// We may be in a [Decorator] context when parsing a function expression or
44554471
// arrow function. The body of the function is not in [Decorator] context.
@@ -4458,7 +4474,7 @@ namespace ts {
44584474
setDecoratorContext(/*val*/ false);
44594475
}
44604476

4461-
const block = parseBlock(ignoreMissingOpenBrace, diagnosticMessage);
4477+
const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage);
44624478

44634479
if (saveDecoratorContext) {
44644480
setDecoratorContext(/*val*/ true);
@@ -5005,13 +5021,13 @@ namespace ts {
50055021
return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral);
50065022
}
50075023

5008-
function parseFunctionBlockOrSemicolon(isGenerator: boolean, isAsync: boolean, diagnosticMessage?: DiagnosticMessage): Block {
5024+
function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block {
50095025
if (token() !== SyntaxKind.OpenBraceToken && canParseSemicolon()) {
50105026
parseSemicolon();
50115027
return;
50125028
}
50135029

5014-
return parseFunctionBlock(isGenerator, isAsync, /*ignoreMissingOpenBrace*/ false, diagnosticMessage);
5030+
return parseFunctionBlock(flags, diagnosticMessage);
50155031
}
50165032

50175033
// DECLARATIONS
@@ -5146,10 +5162,10 @@ namespace ts {
51465162
parseExpected(SyntaxKind.FunctionKeyword);
51475163
node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
51485164
node.name = hasModifier(node, ModifierFlags.Default) ? parseOptionalIdentifier() : parseIdentifier();
5149-
const isGenerator = !!node.asteriskToken;
5150-
const isAsync = hasModifier(node, ModifierFlags.Async);
5151-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node);
5152-
node.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, Diagnostics.or_expected);
5165+
const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None;
5166+
const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
5167+
fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node);
5168+
node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected);
51535169
return addJSDocComment(finishNode(node));
51545170
}
51555171

@@ -5158,8 +5174,8 @@ namespace ts {
51585174
node.decorators = decorators;
51595175
node.modifiers = modifiers;
51605176
parseExpected(SyntaxKind.ConstructorKeyword);
5161-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
5162-
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false, Diagnostics.or_expected);
5177+
fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node);
5178+
node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected);
51635179
return addJSDocComment(finishNode(node));
51645180
}
51655181

@@ -5170,10 +5186,10 @@ namespace ts {
51705186
method.asteriskToken = asteriskToken;
51715187
method.name = name;
51725188
method.questionToken = questionToken;
5173-
const isGenerator = !!asteriskToken;
5174-
const isAsync = hasModifier(method, ModifierFlags.Async);
5175-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, method);
5176-
method.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, diagnosticMessage);
5189+
const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None;
5190+
const isAsync = hasModifier(method, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
5191+
fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, method);
5192+
method.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage);
51775193
return addJSDocComment(finishNode(method));
51785194
}
51795195

@@ -5226,8 +5242,8 @@ namespace ts {
52265242
node.decorators = decorators;
52275243
node.modifiers = modifiers;
52285244
node.name = parsePropertyName();
5229-
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
5230-
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false);
5245+
fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node);
5246+
node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None);
52315247
return addJSDocComment(finishNode(node));
52325248
}
52335249

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(2,17): error TS1005: ':' expected.
2+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(4,22): error TS1005: '=>' expected.
3+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(4,24): error TS2693: 'string' only refers to a type, but is being used as a value here.
4+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,18): error TS1005: '{' expected.
5+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,21): error TS2693: 'string' only refers to a type, but is being used as a value here.
6+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(9,28): error TS1005: ';' expected.
7+
tests/cases/compiler/parseErrorIncorrectReturnToken.ts(12,1): error TS1128: Declaration or statement expected.
8+
9+
10+
==== tests/cases/compiler/parseErrorIncorrectReturnToken.ts (7 errors) ====
11+
type F1 = {
12+
(n: number) => string; // should be : not =>
13+
~~
14+
!!! error TS1005: ':' expected.
15+
}
16+
type F2 = (n: number): string; // should be => not :
17+
~
18+
!!! error TS1005: '=>' expected.
19+
~~~~~~
20+
!!! error TS2693: 'string' only refers to a type, but is being used as a value here.
21+
22+
// doesn't work in non-type contexts, where the return type is optional
23+
let f = (n: number) => string => n.toString();
24+
let o = {
25+
m(n: number) => string {
26+
~~
27+
!!! error TS1005: '{' expected.
28+
~~~~~~
29+
!!! error TS2693: 'string' only refers to a type, but is being used as a value here.
30+
~
31+
!!! error TS1005: ';' expected.
32+
return n.toString();
33+
}
34+
};
35+
~
36+
!!! error TS1128: Declaration or statement expected.
37+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [parseErrorIncorrectReturnToken.ts]
2+
type F1 = {
3+
(n: number) => string; // should be : not =>
4+
}
5+
type F2 = (n: number): string; // should be => not :
6+
7+
// doesn't work in non-type contexts, where the return type is optional
8+
let f = (n: number) => string => n.toString();
9+
let o = {
10+
m(n: number) => string {
11+
return n.toString();
12+
}
13+
};
14+
15+
16+
//// [parseErrorIncorrectReturnToken.js]
17+
string; // should be => not :
18+
// doesn't work in non-type contexts, where the return type is optional
19+
var f = function (n) { return function (string) { return n.toString(); }; };
20+
var o = {
21+
m: function (n) { }
22+
};
23+
string;
24+
{
25+
return n.toString();
26+
}
27+
;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
type F1 = {
3+
(n: number) => string; // should be : not =>
4+
}
5+
type F2 = (n: number): string; // should be => not :
6+
7+
// doesn't work in non-type contexts, where the return type is optional
8+
let f = (n: number) => string => n.toString();
9+
let o = {
10+
m(n: number) => string {
11+
return n.toString();
12+
}
13+
};

0 commit comments

Comments
 (0)