Better error message for unparenthesized function/constructor type notation in union/intersection types (#39570)

* add graceful error message for unparenthesized function types in union and intersection

* add unparenthesizedFunctionTypeInUnionOrIntersection test

* add unparenthesizedConstructorTypeInUnionOrIntersection test

* Update src/compiler/parser.ts

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>

* pass isInUnionType to parseFunctionOrConstructorTypeToError

* Apply suggestions from code review

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>

* syntax fix

* refactor isStartOfFunctionType into isStartOfFunctionTypeOrConstructorType

* Update src/compiler/parser.ts

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>

* hoist isUnionType

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
uhyo
2020-07-16 06:43:56 +09:00
committed by GitHub
parent 37e6e2761c
commit b6f09ccf06
12 changed files with 616 additions and 5 deletions

View File

@@ -1164,6 +1164,22 @@
"category": "Error",
"code": 1384
},
"Function type notation must be parenthesized when used in a union type.": {
"category": "Error",
"code": 1385
},
"Constructor type notation must be parenthesized when used in a union type.": {
"category": "Error",
"code": 1386
},
"Function type notation must be parenthesized when used in an intersection type.": {
"category": "Error",
"code": 1387
},
"Constructor type notation must be parenthesized when used in an intersection type.": {
"category": "Error",
"code": 1388
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",

View File

@@ -3569,18 +3569,46 @@ namespace ts {
return parsePostfixTypeOrHigher();
}
function parseFunctionOrConstructorTypeToError(
isInUnionType: boolean
): TypeNode | undefined {
// the function type and constructor type shorthand notation
// are not allowed directly in unions and intersections, but we'll
// try to parse them gracefully and issue a helpful message.
if (isStartOfFunctionTypeOrConstructorType()) {
const type = parseFunctionOrConstructorType();
let diagnostic: DiagnosticMessage;
if (isFunctionTypeNode(type)) {
diagnostic = isInUnionType
? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type
: Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type;
}
else {
diagnostic = isInUnionType
? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type
: Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type;
}
parseErrorAtRange(type, diagnostic);
return type;
}
return undefined;
}
function parseUnionOrIntersectionType(
operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken,
parseConstituentType: () => TypeNode,
createTypeNode: (types: NodeArray<TypeNode>) => UnionOrIntersectionTypeNode
): TypeNode {
const pos = getNodePos();
const isUnionType = operator === SyntaxKind.BarToken;
const hasLeadingOperator = parseOptional(operator);
let type = parseConstituentType();
let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType)
|| parseConstituentType();
if (token() === operator || hasLeadingOperator) {
const types = [type];
while (parseOptional(operator)) {
types.push(parseConstituentType());
types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType());
}
type = finishNode(createTypeNode(createNodeArray(types, pos)), pos);
}
@@ -3595,11 +3623,14 @@ namespace ts {
return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode);
}
function isStartOfFunctionType(): boolean {
function isStartOfFunctionTypeOrConstructorType(): boolean {
if (token() === SyntaxKind.LessThanToken) {
return true;
}
return token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType);
if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) {
return true;
}
return token() === SyntaxKind.NewKeyword;
}
function skipParameterStart(): boolean {
@@ -3684,7 +3715,7 @@ namespace ts {
}
function parseTypeWorker(noConditionalTypes?: boolean): TypeNode {
if (isStartOfFunctionType() || token() === SyntaxKind.NewKeyword) {
if (isStartOfFunctionTypeOrConstructorType()) {
return parseFunctionOrConstructorType();
}
const pos = getNodePos();