Skip to content

Commit bf436ad

Browse files
author
Josh Goldberg
committed
Allowed AsExpression-like type assertions on object literal shorthands
This PR adds a `type?: TypeNode` property to the `ShorthandPropertyAssignment` interface, and adds it to the allowed tokens for shorthand property assignments in parsing. In checking, if a member type is declared on a shorthand assignment, it is used instead of the name. Fixes microsoft#13035
1 parent 57d94b9 commit bf436ad

10 files changed

+438
-2
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14829,7 +14829,12 @@ namespace ts {
1482914829
}
1483014830
else {
1483114831
Debug.assert(memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment);
14832-
type = checkExpressionForMutableLocation((<ShorthandPropertyAssignment>memberDecl).name, checkMode);
14832+
if (memberDecl.type) {
14833+
type = getTypeFromTypeNode(memberDecl.type);
14834+
}
14835+
else {
14836+
type = checkExpressionForMutableLocation(memberDecl.name, checkMode);
14837+
}
1483314838
}
1483414839

1483514840
if (jsdocType) {

src/compiler/parser.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4572,14 +4572,26 @@ namespace ts {
45724572
// IdentifierReference[?Yield] Initializer[In, ?Yield]
45734573
// this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern
45744574
const isShorthandPropertyAssignment =
4575-
tokenIsIdentifier && (token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EqualsToken);
4575+
tokenIsIdentifier && (
4576+
token() === SyntaxKind.CommaToken
4577+
|| token() === SyntaxKind.CloseBraceToken
4578+
|| token() === SyntaxKind.EqualsToken
4579+
|| token() === SyntaxKind.AsKeyword);
45764580
if (isShorthandPropertyAssignment) {
45774581
node.kind = SyntaxKind.ShorthandPropertyAssignment;
45784582
const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken);
45794583
if (equalsToken) {
45804584
(<ShorthandPropertyAssignment>node).equalsToken = equalsToken;
45814585
(<ShorthandPropertyAssignment>node).objectAssignmentInitializer = allowInAnd(parseAssignmentExpressionOrHigher);
45824586
}
4587+
else {
4588+
const asKeyword = parseOptionalToken(118);
4589+
if (asKeyword) {
4590+
const asType = parseType();
4591+
(<ShorthandPropertyAssignment>node).type = asType;
4592+
asType.parent = node;
4593+
}
4594+
}
45834595
}
45844596
else {
45854597
node.kind = SyntaxKind.PropertyAssignment;

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,8 @@ namespace ts {
900900
// it is grammar error to appear in actual object initializer
901901
equalsToken?: Token<SyntaxKind.EqualsToken>;
902902
objectAssignmentInitializer?: Expression;
903+
// Type assertion, as `<type>name` if before the name or `name as type` if after
904+
type?: TypeNode;
903905
}
904906

905907
export interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ declare namespace ts {
605605
questionToken?: QuestionToken;
606606
equalsToken?: Token<SyntaxKind.EqualsToken>;
607607
objectAssignmentInitializer?: Expression;
608+
type?: TypeNode;
608609
}
609610
interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer {
610611
parent: ObjectLiteralExpression;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ declare namespace ts {
605605
questionToken?: QuestionToken;
606606
equalsToken?: Token<SyntaxKind.EqualsToken>;
607607
objectAssignmentInitializer?: Expression;
608+
type?: TypeNode;
608609
}
609610
interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer {
610611
parent: ObjectLiteralExpression;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(40,12): error TS2352: Type 'number' cannot be converted to type 'Valid'.
2+
tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(44,2): error TS2322: Type '{ extra: Valid; }' is not assignable to type 'ContainsValid'.
3+
Object literal may only specify known properties, and 'extra' does not exist in type 'ContainsValid'.
4+
tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(44,9): error TS2693: 'number' only refers to a type, but is being used as a value here.
5+
6+
7+
==== tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts (3 errors) ====
8+
interface Valid {
9+
general: number;
10+
specific: 0;
11+
optional?: 1;
12+
}
13+
14+
const general = 2;
15+
let specific = 0;
16+
17+
const valid = { general, specific };
18+
19+
specific = 2;
20+
21+
const expressionValid = {
22+
general,
23+
specific as 0,
24+
};
25+
26+
const invalid = { general, specific };
27+
28+
const optional = 3;
29+
30+
const veryInvalid = {
31+
general,
32+
specific,
33+
optional as number | undefined,
34+
};
35+
36+
interface ContainsValid {
37+
required: Valid;
38+
optional?: Valid;
39+
}
40+
41+
const fullContains: ContainsValid = {
42+
required: {} as Valid,
43+
optional: {} as Valid,
44+
};
45+
46+
const invalidContains: ContainsValid = {
47+
required: 7 as Valid,
48+
~~~~~~~~~~
49+
!!! error TS2352: Type 'number' cannot be converted to type 'Valid'.
50+
};
51+
52+
const extraContains: ContainsValid = {
53+
extra: number as Valid,
54+
~~~~~~~~~~~~~~~~~~~~~~
55+
!!! error TS2322: Type '{ extra: Valid; }' is not assignable to type 'ContainsValid'.
56+
!!! error TS2322: Object literal may only specify known properties, and 'extra' does not exist in type 'ContainsValid'.
57+
~~~~~~
58+
!!! error TS2693: 'number' only refers to a type, but is being used as a value here.
59+
};
60+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//// [shorthandPropertyAssignmentsAsExpression.ts]
2+
interface Valid {
3+
general: number;
4+
specific: 0;
5+
optional?: 1;
6+
}
7+
8+
const general = 2;
9+
let specific = 0;
10+
11+
const valid = { general, specific };
12+
13+
specific = 2;
14+
15+
const expressionValid = {
16+
general,
17+
specific as 0,
18+
};
19+
20+
const invalid = { general, specific };
21+
22+
const optional = 3;
23+
24+
const veryInvalid = {
25+
general,
26+
specific,
27+
optional as number | undefined,
28+
};
29+
30+
interface ContainsValid {
31+
required: Valid;
32+
optional?: Valid;
33+
}
34+
35+
const fullContains: ContainsValid = {
36+
required: {} as Valid,
37+
optional: {} as Valid,
38+
};
39+
40+
const invalidContains: ContainsValid = {
41+
required: 7 as Valid,
42+
};
43+
44+
const extraContains: ContainsValid = {
45+
extra: number as Valid,
46+
};
47+
48+
49+
//// [shorthandPropertyAssignmentsAsExpression.js]
50+
var general = 2;
51+
var specific = 0;
52+
var valid = { general: general, specific: specific };
53+
specific = 2;
54+
var expressionValid = {
55+
general: general,
56+
specific: specific,
57+
};
58+
var invalid = { general: general, specific: specific };
59+
var optional = 3;
60+
var veryInvalid = {
61+
general: general,
62+
specific: specific,
63+
optional: optional,
64+
};
65+
var fullContains = {
66+
required: {},
67+
optional: {},
68+
};
69+
var invalidContains = {
70+
required: 7,
71+
};
72+
var extraContains = {
73+
extra: number,
74+
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
=== tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts ===
2+
interface Valid {
3+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
4+
5+
general: number;
6+
>general : Symbol(Valid.general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 17))
7+
8+
specific: 0;
9+
>specific : Symbol(Valid.specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 1, 17))
10+
11+
optional?: 1;
12+
>optional : Symbol(Valid.optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 2, 13))
13+
}
14+
15+
const general = 2;
16+
>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 6, 5))
17+
18+
let specific = 0;
19+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 7, 3))
20+
21+
const valid = { general, specific };
22+
>valid : Symbol(valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 5))
23+
>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 15))
24+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 24))
25+
26+
specific = 2;
27+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 7, 3))
28+
29+
const expressionValid = {
30+
>expressionValid : Symbol(expressionValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 13, 5))
31+
32+
general,
33+
>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 13, 25))
34+
35+
specific as 0,
36+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 14, 9))
37+
38+
};
39+
40+
const invalid = { general, specific };
41+
>invalid : Symbol(invalid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 5))
42+
>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 17))
43+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 26))
44+
45+
const optional = 3;
46+
>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 20, 5))
47+
48+
const veryInvalid = {
49+
>veryInvalid : Symbol(veryInvalid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 22, 5))
50+
51+
general,
52+
>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 22, 21))
53+
54+
specific,
55+
>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 23, 9))
56+
57+
optional as number | undefined,
58+
>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 24, 10))
59+
60+
};
61+
62+
interface ContainsValid {
63+
>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2))
64+
65+
required: Valid;
66+
>required : Symbol(ContainsValid.required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 28, 25))
67+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
68+
69+
optional?: Valid;
70+
>optional : Symbol(ContainsValid.optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 29, 17))
71+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
72+
}
73+
74+
const fullContains: ContainsValid = {
75+
>fullContains : Symbol(fullContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 33, 5))
76+
>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2))
77+
78+
required: {} as Valid,
79+
>required : Symbol(required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 33, 37))
80+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
81+
82+
optional: {} as Valid,
83+
>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 34, 23))
84+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
85+
86+
};
87+
88+
const invalidContains: ContainsValid = {
89+
>invalidContains : Symbol(invalidContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 38, 5))
90+
>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2))
91+
92+
required: 7 as Valid,
93+
>required : Symbol(required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 38, 40))
94+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
95+
96+
};
97+
98+
const extraContains: ContainsValid = {
99+
>extraContains : Symbol(extraContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 42, 5))
100+
>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2))
101+
102+
extra: number as Valid,
103+
>extra : Symbol(extra, Decl(shorthandPropertyAssignmentsAsExpression.ts, 42, 38))
104+
>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0))
105+
106+
};
107+

0 commit comments

Comments
 (0)