diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1e90d5a309ba5..e0a75ef63554b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14975,7 +14975,12 @@ namespace ts { } else { Debug.assert(memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment); - type = checkExpressionForMutableLocation(memberDecl.name, checkMode); + if (memberDecl.type) { + type = getTypeFromTypeNode(memberDecl.type); + } + else { + type = checkExpressionForMutableLocation(memberDecl.name, checkMode); + } } if (jsdocType) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 137d250300c9b..c852822cb3daa 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -82,6 +82,7 @@ namespace ts { return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).type) || visitNode(cbNode, (node).questionToken) || visitNode(cbNode, (node).equalsToken) || visitNode(cbNode, (node).objectAssignmentInitializer); @@ -4591,7 +4592,11 @@ namespace ts { // IdentifierReference[?Yield] Initializer[In, ?Yield] // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern const isShorthandPropertyAssignment = - tokenIsIdentifier && (token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EqualsToken); + tokenIsIdentifier && ( + token() === SyntaxKind.CommaToken + || token() === SyntaxKind.CloseBraceToken + || token() === SyntaxKind.EqualsToken + || token() === SyntaxKind.AsKeyword); if (isShorthandPropertyAssignment) { node.kind = SyntaxKind.ShorthandPropertyAssignment; const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); @@ -4599,6 +4604,13 @@ namespace ts { (node).equalsToken = equalsToken; (node).objectAssignmentInitializer = allowInAnd(parseAssignmentExpressionOrHigher); } + else { + const asKeyword = parseOptionalToken(118); + if (asKeyword) { + const asType = parseType(); + (node).type = asType; + } + } } else { node.kind = SyntaxKind.PropertyAssignment; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b933d6853afb4..c22a2e0804576 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -902,6 +902,8 @@ namespace ts { // it is grammar error to appear in actual object initializer equalsToken?: Token; objectAssignmentInitializer?: Expression; + // Type assertion, as `name` if before the name or `name as type` if after + type?: TypeNode; } export interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 90ed78047e6a6..13d70c72a9952 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -607,6 +607,7 @@ declare namespace ts { questionToken?: QuestionToken; equalsToken?: Token; objectAssignmentInitializer?: Expression; + type?: TypeNode; } interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { parent: ObjectLiteralExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c1f088c058bf9..225e74a03b741 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -607,6 +607,7 @@ declare namespace ts { questionToken?: QuestionToken; equalsToken?: Token; objectAssignmentInitializer?: Expression; + type?: TypeNode; } interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { parent: ObjectLiteralExpression; diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.errors.txt b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.errors.txt new file mode 100644 index 0000000000000..91f1febff5b11 --- /dev/null +++ b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.errors.txt @@ -0,0 +1,60 @@ +tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(40,12): error TS2352: Type 'number' cannot be converted to type 'Valid'. +tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(44,2): error TS2322: Type '{ extra: Valid; }' is not assignable to type 'ContainsValid'. + Object literal may only specify known properties, and 'extra' does not exist in type 'ContainsValid'. +tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts(44,9): error TS2693: 'number' only refers to a type, but is being used as a value here. + + +==== tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts (3 errors) ==== + interface Valid { + general: number; + specific: 0; + optional?: 1; + } + + const general = 2; + let specific = 0; + + const valid = { general, specific }; + + specific = 2; + + const expressionValid = { + general, + specific as 0, + }; + + const invalid = { general, specific }; + + const optional = 3; + + const veryInvalid = { + general, + specific, + optional as number | undefined, + }; + + interface ContainsValid { + required: Valid; + optional?: Valid; + } + + const fullContains: ContainsValid = { + required: {} as Valid, + optional: {} as Valid, + }; + + const invalidContains: ContainsValid = { + required: 7 as Valid, + ~~~~~~~~~~ +!!! error TS2352: Type 'number' cannot be converted to type 'Valid'. + }; + + const extraContains: ContainsValid = { + extra: number as Valid, + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ extra: Valid; }' is not assignable to type 'ContainsValid'. +!!! error TS2322: Object literal may only specify known properties, and 'extra' does not exist in type 'ContainsValid'. + ~~~~~~ +!!! error TS2693: 'number' only refers to a type, but is being used as a value here. + }; + \ No newline at end of file diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.js b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.js new file mode 100644 index 0000000000000..4adf2c6ade695 --- /dev/null +++ b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.js @@ -0,0 +1,74 @@ +//// [shorthandPropertyAssignmentsAsExpression.ts] +interface Valid { + general: number; + specific: 0; + optional?: 1; +} + +const general = 2; +let specific = 0; + +const valid = { general, specific }; + +specific = 2; + +const expressionValid = { + general, + specific as 0, +}; + +const invalid = { general, specific }; + +const optional = 3; + +const veryInvalid = { + general, + specific, + optional as number | undefined, +}; + +interface ContainsValid { + required: Valid; + optional?: Valid; +} + +const fullContains: ContainsValid = { + required: {} as Valid, + optional: {} as Valid, +}; + +const invalidContains: ContainsValid = { + required: 7 as Valid, +}; + +const extraContains: ContainsValid = { + extra: number as Valid, +}; + + +//// [shorthandPropertyAssignmentsAsExpression.js] +var general = 2; +var specific = 0; +var valid = { general: general, specific: specific }; +specific = 2; +var expressionValid = { + general: general, + specific: specific, +}; +var invalid = { general: general, specific: specific }; +var optional = 3; +var veryInvalid = { + general: general, + specific: specific, + optional: optional, +}; +var fullContains = { + required: {}, + optional: {}, +}; +var invalidContains = { + required: 7, +}; +var extraContains = { + extra: number, +}; diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.symbols b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.symbols new file mode 100644 index 0000000000000..acd141702594e --- /dev/null +++ b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.symbols @@ -0,0 +1,107 @@ +=== tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts === +interface Valid { +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + + general: number; +>general : Symbol(Valid.general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 17)) + + specific: 0; +>specific : Symbol(Valid.specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 1, 17)) + + optional?: 1; +>optional : Symbol(Valid.optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 2, 13)) +} + +const general = 2; +>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 6, 5)) + +let specific = 0; +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 7, 3)) + +const valid = { general, specific }; +>valid : Symbol(valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 5)) +>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 15)) +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 9, 24)) + +specific = 2; +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 7, 3)) + +const expressionValid = { +>expressionValid : Symbol(expressionValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 13, 5)) + + general, +>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 13, 25)) + + specific as 0, +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 14, 9)) + +}; + +const invalid = { general, specific }; +>invalid : Symbol(invalid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 5)) +>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 17)) +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 18, 26)) + +const optional = 3; +>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 20, 5)) + +const veryInvalid = { +>veryInvalid : Symbol(veryInvalid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 22, 5)) + + general, +>general : Symbol(general, Decl(shorthandPropertyAssignmentsAsExpression.ts, 22, 21)) + + specific, +>specific : Symbol(specific, Decl(shorthandPropertyAssignmentsAsExpression.ts, 23, 9)) + + optional as number | undefined, +>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 24, 10)) + +}; + +interface ContainsValid { +>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2)) + + required: Valid; +>required : Symbol(ContainsValid.required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 28, 25)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + + optional?: Valid; +>optional : Symbol(ContainsValid.optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 29, 17)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) +} + +const fullContains: ContainsValid = { +>fullContains : Symbol(fullContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 33, 5)) +>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2)) + + required: {} as Valid, +>required : Symbol(required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 33, 37)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + + optional: {} as Valid, +>optional : Symbol(optional, Decl(shorthandPropertyAssignmentsAsExpression.ts, 34, 23)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + +}; + +const invalidContains: ContainsValid = { +>invalidContains : Symbol(invalidContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 38, 5)) +>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2)) + + required: 7 as Valid, +>required : Symbol(required, Decl(shorthandPropertyAssignmentsAsExpression.ts, 38, 40)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + +}; + +const extraContains: ContainsValid = { +>extraContains : Symbol(extraContains, Decl(shorthandPropertyAssignmentsAsExpression.ts, 42, 5)) +>ContainsValid : Symbol(ContainsValid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 26, 2)) + + extra: number as Valid, +>extra : Symbol(extra, Decl(shorthandPropertyAssignmentsAsExpression.ts, 42, 38)) +>Valid : Symbol(Valid, Decl(shorthandPropertyAssignmentsAsExpression.ts, 0, 0)) + +}; + diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.types b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.types new file mode 100644 index 0000000000000..0a83c7b464f40 --- /dev/null +++ b/tests/baselines/reference/shorthandPropertyAssignmentsAsExpression.types @@ -0,0 +1,127 @@ +=== tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts === +interface Valid { +>Valid : Valid + + general: number; +>general : number + + specific: 0; +>specific : 0 + + optional?: 1; +>optional : 1 +} + +const general = 2; +>general : 2 +>2 : 2 + +let specific = 0; +>specific : number +>0 : 0 + +const valid = { general, specific }; +>valid : { general: number; specific: number; } +>{ general, specific } : { general: number; specific: number; } +>general : number +>specific : number + +specific = 2; +>specific = 2 : 2 +>specific : number +>2 : 2 + +const expressionValid = { +>expressionValid : { general: number; specific: 0; } +>{ general, specific as 0,} : { general: number; specific: 0; } + + general, +>general : number + + specific as 0, +>specific : 0 + +}; + +const invalid = { general, specific }; +>invalid : { general: number; specific: number; } +>{ general, specific } : { general: number; specific: number; } +>general : number +>specific : number + +const optional = 3; +>optional : 3 +>3 : 3 + +const veryInvalid = { +>veryInvalid : { general: number; specific: number; optional: number; } +>{ general, specific, optional as number | undefined,} : { general: number; specific: number; optional: number; } + + general, +>general : number + + specific, +>specific : number + + optional as number | undefined, +>optional : number + +}; + +interface ContainsValid { +>ContainsValid : ContainsValid + + required: Valid; +>required : Valid +>Valid : Valid + + optional?: Valid; +>optional : Valid +>Valid : Valid +} + +const fullContains: ContainsValid = { +>fullContains : ContainsValid +>ContainsValid : ContainsValid +>{ required: {} as Valid, optional: {} as Valid,} : { required: Valid; optional: Valid; } + + required: {} as Valid, +>required : Valid +>{} as Valid : Valid +>{} : {} +>Valid : Valid + + optional: {} as Valid, +>optional : Valid +>{} as Valid : Valid +>{} : {} +>Valid : Valid + +}; + +const invalidContains: ContainsValid = { +>invalidContains : ContainsValid +>ContainsValid : ContainsValid +>{ required: 7 as Valid,} : { required: Valid; } + + required: 7 as Valid, +>required : Valid +>7 as Valid : Valid +>7 : 7 +>Valid : Valid + +}; + +const extraContains: ContainsValid = { +>extraContains : ContainsValid +>ContainsValid : ContainsValid +>{ extra: number as Valid,} : { extra: Valid; } + + extra: number as Valid, +>extra : Valid +>number as Valid : Valid +>number : any +>Valid : Valid + +}; + diff --git a/tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts b/tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts new file mode 100644 index 0000000000000..df72ba3cef1dc --- /dev/null +++ b/tests/cases/compiler/shorthandPropertyAssignmentsAsExpression.ts @@ -0,0 +1,47 @@ +// @target: ES5 + +interface Valid { + general: number; + specific: 0; + optional?: 1; +} + +const general = 2; +let specific = 0; + +const valid = { general, specific }; + +specific = 2; + +const expressionValid = { + general, + specific as 0, +}; + +const invalid = { general, specific }; + +const optional = 3; + +const veryInvalid = { + general, + specific, + optional as number | undefined, +}; + +interface ContainsValid { + required: Valid; + optional?: Valid; +} + +const fullContains: ContainsValid = { + required: {} as Valid, + optional: {} as Valid, +}; + +const invalidContains: ContainsValid = { + required: 7 as Valid, +}; + +const extraContains: ContainsValid = { + extra: number as Valid, +};