Skip to content

Commit b6f09cc

Browse files
uhyoDanielRosenwassersandersn
authored
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>
1 parent 37e6e27 commit b6f09cc

12 files changed

+616
-5
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,22 @@
11641164
"category": "Error",
11651165
"code": 1384
11661166
},
1167+
"Function type notation must be parenthesized when used in a union type.": {
1168+
"category": "Error",
1169+
"code": 1385
1170+
},
1171+
"Constructor type notation must be parenthesized when used in a union type.": {
1172+
"category": "Error",
1173+
"code": 1386
1174+
},
1175+
"Function type notation must be parenthesized when used in an intersection type.": {
1176+
"category": "Error",
1177+
"code": 1387
1178+
},
1179+
"Constructor type notation must be parenthesized when used in an intersection type.": {
1180+
"category": "Error",
1181+
"code": 1388
1182+
},
11671183

11681184
"The types of '{0}' are incompatible between these types.": {
11691185
"category": "Error",

src/compiler/parser.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3569,18 +3569,46 @@ namespace ts {
35693569
return parsePostfixTypeOrHigher();
35703570
}
35713571

3572+
function parseFunctionOrConstructorTypeToError(
3573+
isInUnionType: boolean
3574+
): TypeNode | undefined {
3575+
// the function type and constructor type shorthand notation
3576+
// are not allowed directly in unions and intersections, but we'll
3577+
// try to parse them gracefully and issue a helpful message.
3578+
if (isStartOfFunctionTypeOrConstructorType()) {
3579+
const type = parseFunctionOrConstructorType();
3580+
let diagnostic: DiagnosticMessage;
3581+
if (isFunctionTypeNode(type)) {
3582+
diagnostic = isInUnionType
3583+
? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type
3584+
: Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type;
3585+
}
3586+
else {
3587+
diagnostic = isInUnionType
3588+
? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type
3589+
: Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type;
3590+
3591+
}
3592+
parseErrorAtRange(type, diagnostic);
3593+
return type;
3594+
}
3595+
return undefined;
3596+
}
3597+
35723598
function parseUnionOrIntersectionType(
35733599
operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken,
35743600
parseConstituentType: () => TypeNode,
35753601
createTypeNode: (types: NodeArray<TypeNode>) => UnionOrIntersectionTypeNode
35763602
): TypeNode {
35773603
const pos = getNodePos();
3604+
const isUnionType = operator === SyntaxKind.BarToken;
35783605
const hasLeadingOperator = parseOptional(operator);
3579-
let type = parseConstituentType();
3606+
let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType)
3607+
|| parseConstituentType();
35803608
if (token() === operator || hasLeadingOperator) {
35813609
const types = [type];
35823610
while (parseOptional(operator)) {
3583-
types.push(parseConstituentType());
3611+
types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType());
35843612
}
35853613
type = finishNode(createTypeNode(createNodeArray(types, pos)), pos);
35863614
}
@@ -3595,11 +3623,14 @@ namespace ts {
35953623
return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode);
35963624
}
35973625

3598-
function isStartOfFunctionType(): boolean {
3626+
function isStartOfFunctionTypeOrConstructorType(): boolean {
35993627
if (token() === SyntaxKind.LessThanToken) {
36003628
return true;
36013629
}
3602-
return token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType);
3630+
if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) {
3631+
return true;
3632+
}
3633+
return token() === SyntaxKind.NewKeyword;
36033634
}
36043635

36053636
function skipParameterStart(): boolean {
@@ -3684,7 +3715,7 @@ namespace ts {
36843715
}
36853716

36863717
function parseTypeWorker(noConditionalTypes?: boolean): TypeNode {
3687-
if (isStartOfFunctionType() || token() === SyntaxKind.NewKeyword) {
3718+
if (isStartOfFunctionTypeOrConstructorType()) {
36883719
return parseFunctionOrConstructorType();
36893720
}
36903721
const pos = getNodePos();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(1,19): error TS1386: Constructor type notation must be parenthesized when used in a union type.
2+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(2,19): error TS1386: Constructor type notation must be parenthesized when used in a union type.
3+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(3,12): error TS1386: Constructor type notation must be parenthesized when used in a union type.
4+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(4,12): error TS1386: Constructor type notation must be parenthesized when used in a union type.
5+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(5,19): error TS1386: Constructor type notation must be parenthesized when used in a union type.
6+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(8,4): error TS1386: Constructor type notation must be parenthesized when used in a union type.
7+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(11,19): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
8+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(12,19): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
9+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(13,12): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
10+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(14,12): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
11+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(15,19): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
12+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(18,4): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
13+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(20,37): error TS1386: Constructor type notation must be parenthesized when used in a union type.
14+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(21,31): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
15+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(22,16): error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
16+
tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts(22,41): error TS1386: Constructor type notation must be parenthesized when used in a union type.
17+
18+
19+
==== tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts (16 errors) ====
20+
type U1 = string | new () => void;
21+
~~~~~~~~~~~~~~~
22+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
23+
type U2 = string | new (foo: number) => void
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
26+
type U3 = | new () => number
27+
~~~~~~~~~~~~~~~~~
28+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
29+
type U4 = | new (foo?: number) => void;
30+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
31+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
32+
type U5 = string | new (number: number, foo?: string) => void | number;
33+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
35+
type U6 =
36+
| string
37+
| new (...args: any[]) => void
38+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39+
| number;
40+
~~~~~~~~~~
41+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
42+
43+
type I1 = string & new () => void;
44+
~~~~~~~~~~~~~~~
45+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
46+
type I2 = string & new (...foo: number[]) => void;
47+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
49+
type I3 = & new () => boolean
50+
~~~~~~~~~~~~~~~~~~
51+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
52+
type I4 = & new () => boolean & null;
53+
~~~~~~~~~~~~~~~~~~~~~~~~~
54+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
55+
type I5 = string & new (any: any, any2: any) => any & any;
56+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
58+
type I6 =
59+
& string
60+
& new (foo: any) => void;
61+
~~~~~~~~~~~~~~~~~~~~~~~
62+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
63+
64+
type M1 = string | number & string | new () => number;
65+
~~~~~~~~~~~~~~~~~
66+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
67+
type M2 = any & string | any & new () => void;
68+
~~~~~~~~~~~~~~~
69+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
70+
type M3 = any & new (foo: any) => void | new () => void & any;
71+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72+
!!! error TS1388: Constructor type notation must be parenthesized when used in an intersection type.
73+
~~~~~~~~~~~~~~~~~~~~~
74+
!!! error TS1386: Constructor type notation must be parenthesized when used in a union type.
75+
76+
type OK1 = string | (new ()=> void);
77+
type OK2 = string | (new ()=> string | number);
78+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [unparenthesizedConstructorTypeInUnionOrIntersection.ts]
2+
type U1 = string | new () => void;
3+
type U2 = string | new (foo: number) => void
4+
type U3 = | new () => number
5+
type U4 = | new (foo?: number) => void;
6+
type U5 = string | new (number: number, foo?: string) => void | number;
7+
type U6 =
8+
| string
9+
| new (...args: any[]) => void
10+
| number;
11+
12+
type I1 = string & new () => void;
13+
type I2 = string & new (...foo: number[]) => void;
14+
type I3 = & new () => boolean
15+
type I4 = & new () => boolean & null;
16+
type I5 = string & new (any: any, any2: any) => any & any;
17+
type I6 =
18+
& string
19+
& new (foo: any) => void;
20+
21+
type M1 = string | number & string | new () => number;
22+
type M2 = any & string | any & new () => void;
23+
type M3 = any & new (foo: any) => void | new () => void & any;
24+
25+
type OK1 = string | (new ()=> void);
26+
type OK2 = string | (new ()=> string | number);
27+
28+
29+
//// [unparenthesizedConstructorTypeInUnionOrIntersection.js]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts ===
2+
type U1 = string | new () => void;
3+
>U1 : Symbol(U1, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 0, 0))
4+
5+
type U2 = string | new (foo: number) => void
6+
>U2 : Symbol(U2, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 0, 34))
7+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 1, 24))
8+
9+
type U3 = | new () => number
10+
>U3 : Symbol(U3, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 1, 44))
11+
12+
type U4 = | new (foo?: number) => void;
13+
>U4 : Symbol(U4, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 2, 28))
14+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 3, 17))
15+
16+
type U5 = string | new (number: number, foo?: string) => void | number;
17+
>U5 : Symbol(U5, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 3, 39))
18+
>number : Symbol(number, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 4, 24))
19+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 4, 39))
20+
21+
type U6 =
22+
>U6 : Symbol(U6, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 4, 71))
23+
24+
| string
25+
| new (...args: any[]) => void
26+
>args : Symbol(args, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 7, 9))
27+
28+
| number;
29+
30+
type I1 = string & new () => void;
31+
>I1 : Symbol(I1, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 8, 11))
32+
33+
type I2 = string & new (...foo: number[]) => void;
34+
>I2 : Symbol(I2, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 10, 34))
35+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 11, 24))
36+
37+
type I3 = & new () => boolean
38+
>I3 : Symbol(I3, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 11, 50))
39+
40+
type I4 = & new () => boolean & null;
41+
>I4 : Symbol(I4, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 12, 29))
42+
43+
type I5 = string & new (any: any, any2: any) => any & any;
44+
>I5 : Symbol(I5, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 13, 37))
45+
>any : Symbol(any, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 14, 24))
46+
>any2 : Symbol(any2, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 14, 33))
47+
48+
type I6 =
49+
>I6 : Symbol(I6, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 14, 58))
50+
51+
& string
52+
& new (foo: any) => void;
53+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 17, 9))
54+
55+
type M1 = string | number & string | new () => number;
56+
>M1 : Symbol(M1, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 17, 27))
57+
58+
type M2 = any & string | any & new () => void;
59+
>M2 : Symbol(M2, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 19, 54))
60+
61+
type M3 = any & new (foo: any) => void | new () => void & any;
62+
>M3 : Symbol(M3, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 20, 46))
63+
>foo : Symbol(foo, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 21, 21))
64+
65+
type OK1 = string | (new ()=> void);
66+
>OK1 : Symbol(OK1, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 21, 62))
67+
68+
type OK2 = string | (new ()=> string | number);
69+
>OK2 : Symbol(OK2, Decl(unparenthesizedConstructorTypeInUnionOrIntersection.ts, 23, 36))
70+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
=== tests/cases/compiler/unparenthesizedConstructorTypeInUnionOrIntersection.ts ===
2+
type U1 = string | new () => void;
3+
>U1 : U1
4+
5+
type U2 = string | new (foo: number) => void
6+
>U2 : U2
7+
>foo : number
8+
9+
type U3 = | new () => number
10+
>U3 : new () => number
11+
12+
type U4 = | new (foo?: number) => void;
13+
>U4 : new (foo?: number) => void
14+
>foo : number
15+
16+
type U5 = string | new (number: number, foo?: string) => void | number;
17+
>U5 : U5
18+
>number : number
19+
>foo : string
20+
21+
type U6 =
22+
>U6 : U6
23+
24+
| string
25+
| new (...args: any[]) => void
26+
>args : any[]
27+
28+
| number;
29+
30+
type I1 = string & new () => void;
31+
>I1 : I1
32+
33+
type I2 = string & new (...foo: number[]) => void;
34+
>I2 : I2
35+
>foo : number[]
36+
37+
type I3 = & new () => boolean
38+
>I3 : new () => boolean
39+
40+
type I4 = & new () => boolean & null;
41+
>I4 : new () => boolean & null
42+
>null : null
43+
44+
type I5 = string & new (any: any, any2: any) => any & any;
45+
>I5 : I5
46+
>any : any
47+
>any2 : any
48+
49+
type I6 =
50+
>I6 : I6
51+
52+
& string
53+
& new (foo: any) => void;
54+
>foo : any
55+
56+
type M1 = string | number & string | new () => number;
57+
>M1 : M1
58+
59+
type M2 = any & string | any & new () => void;
60+
>M2 : any
61+
62+
type M3 = any & new (foo: any) => void | new () => void & any;
63+
>M3 : any
64+
>foo : any
65+
66+
type OK1 = string | (new ()=> void);
67+
>OK1 : OK1
68+
69+
type OK2 = string | (new ()=> string | number);
70+
>OK2 : OK2
71+

0 commit comments

Comments
 (0)