Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional chaining stage1 #32599

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2674,11 +2674,24 @@ namespace ts {
case SyntaxKind.PropertyAccessExpression:
return computePropertyAccess(<PropertyAccessExpression>node, subtreeFlags);

case SyntaxKind.CallChain:
return computeCallChain(<CallChain>node, subtreeFlags);

default:
return computeOther(node, kind, subtreeFlags);
}
}

function computeCallChain(node: CallChain, subtreeFlags: TransformFlags) {
let transformFlags = subtreeFlags;
if (node.typeArguments) {
transformFlags |= TransformFlags.AssertTypeScript;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes;
}

function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) {
let transformFlags = subtreeFlags;
const expression = node.expression;
Expand Down Expand Up @@ -3288,6 +3301,12 @@ namespace ts {
transformFlags |= TransformFlags.AssertJsx;
break;

case SyntaxKind.OptionalExpression:
case SyntaxKind.PropertyAccessChain:
case SyntaxKind.ElementAccessChain:
transformFlags |= TransformFlags.AssertESNext;
break;

case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
Expand Down Expand Up @@ -3449,7 +3468,6 @@ namespace ts {
break;

case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.NewExpression:
excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes;
if (subtreeFlags & TransformFlags.ContainsSpread) {
// If the this node contains a SpreadExpression, then it is an ES6
Expand Down
100 changes: 69 additions & 31 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ namespace ts {
category: Diagnostics.Experimental_Options,
description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators
},
{
name: "experimentalOptionalChaining",
type: "boolean",
category: Diagnostics.Experimental_Options,
description: Diagnostics.Enables_experimental_support_for_optional_chaining
},

// Advanced
{
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,11 @@
"category": "Error",
"code": 1254
},
"Experimental support for optional chaining is a feature that is subject to change in a future release. Set the 'experimentalOptionalChaining' option to remove this warning.": {
"category": "Error",
"code": 1255
},

"'with' statements are not allowed in an async function block.": {
"category": "Error",
"code": 1300
Expand Down Expand Up @@ -3314,6 +3319,10 @@
"category": "Message",
"code": 6185
},
"Enables experimental support for optional chaining.": {
"category": "Message",
"code": 6186
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
52 changes: 52 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,14 @@ namespace ts {
case SyntaxKind.BindingElement:
return emitBindingElement(<BindingElement>node);

// Optional chains
case SyntaxKind.PropertyAccessChain:
return emitPropertyAccessChain(<PropertyAccessChain>node);
case SyntaxKind.ElementAccessChain:
return emitElementAccessChain(<ElementAccessChain>node);
case SyntaxKind.CallChain:
return emitCallChain(<CallChain>node);

// Misc
case SyntaxKind.TemplateSpan:
return emitTemplateSpan(<TemplateSpan>node);
Expand Down Expand Up @@ -768,6 +776,8 @@ namespace ts {
return emitPropertyAccessExpression(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return emitElementAccessExpression(<ElementAccessExpression>node);
case SyntaxKind.OptionalExpression:
return emitOptionalExpression(<OptionalExpression>node);
case SyntaxKind.CallExpression:
return emitCallExpression(<CallExpression>node);
case SyntaxKind.NewExpression:
Expand Down Expand Up @@ -1245,6 +1255,48 @@ namespace ts {
write("]");
}

function emitOptionalExpression(node: OptionalExpression) {
emitExpression(node.expression);
emit(node.chain);
}

function emitPropertyAccessChain(node: PropertyAccessChain) {
if (node.chain) {
emit(node.chain);
write(".");
}
else {
write("?.");
}

emit(node.name);
}

function emitElementAccessChain(node: ElementAccessChain) {
if (node.chain) {
emit(node.chain);
}
else {
write("?.");
}

write("[");
emitExpression(node.argumentExpression);
write("]");
}

function emitCallChain(node: CallChain) {
if (node.chain) {
emit(node.chain);
}
else {
write("?.");
}

emitTypeArguments(node, node.typeArguments);
emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments);
}

function emitCallExpression(node: CallExpression) {
emitExpression(node.expression);
emitTypeArguments(node, node.typeArguments);
Expand Down
58 changes: 58 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,64 @@ namespace ts {
: node;
}

export function createOptionalExpression(expression: Expression, chain: OptionalChain) {
const node = <OptionalExpression>createSynthesizedNode(SyntaxKind.ElementAccessExpression);
node.expression = parenthesizeForAccess(expression);
node.chain = chain;
return node;
}

export function updateOptionalExpression(node: OptionalExpression, expression: Expression, chain: OptionalChain) {
return node.expression !== expression
|| node.chain !== chain
? updateNode(createOptionalExpression(expression, chain), node)
: node;
}

export function createPropertyAccessChain(chain: OptionalChain | undefined, name: Identifier) {
const node = <PropertyAccessChain>createSynthesizedNode(SyntaxKind.PropertyAccessChain);
node.chain = chain;
node.name = name;
return node;
}

export function updatePropertyAccessChain(node: PropertyAccessChain, chain: OptionalChain | undefined, name: Identifier) {
return node.chain !== chain
|| node.name !== name
? updateNode(createPropertyAccessChain(chain, name), node)
: node;
}

export function createElementAccessChain(chain: OptionalChain | undefined, argumentExpression: Expression) {
const node = <ElementAccessChain>createSynthesizedNode(SyntaxKind.ElementAccessChain);
node.chain = chain;
node.argumentExpression = argumentExpression;
return node;
}

export function updateElementAccessChain(node: ElementAccessChain, chain: OptionalChain | undefined, argumentExpression: Expression) {
return node.chain !== chain
|| node.argumentExpression !== argumentExpression
? updateNode(createElementAccessChain(chain, argumentExpression), node)
: node;
}

export function createCallChain(chain: OptionalChain | undefined, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentList: ReadonlyArray<Expression>) {
const node = <CallChain>createSynthesizedNode(SyntaxKind.CallChain);
node.chain = chain;
node.typeArguments = asNodeArray(typeArguments);
node.arguments = createNodeArray(argumentList);
return node;
}

export function updateCallChain(node: CallChain, chain: OptionalChain | undefined, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentList: ReadonlyArray<Expression>) {
return node.chain !== chain
|| node.typeArguments !== typeArguments
|| node.arguments !== argumentList
? updateNode(createCallChain(chain, typeArguments, argumentList), node)
: node;
}

export function createCall(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression>) {
const node = <CallExpression>createSynthesizedNode(SyntaxKind.CallExpression);
node.expression = parenthesizeForAccess(expression);
Expand Down
57 changes: 56 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ namespace ts {
case SyntaxKind.ElementAccessExpression:
return visitNode(cbNode, (<ElementAccessExpression>node).expression) ||
visitNode(cbNode, (<ElementAccessExpression>node).argumentExpression);
case SyntaxKind.OptionalExpression:
return visitNode(cbNode, (<OptionalExpression>node).expression) ||
visitNode(cbNode, (<OptionalExpression>node).chain);
case SyntaxKind.PropertyAccessChain:
return visitNode(cbNode, (<PropertyAccessChain>node).chain) ||
visitNode(cbNode, (<PropertyAccessChain>node).name);
case SyntaxKind.ElementAccessChain:
return visitNode(cbNode, (<ElementAccessChain>node).chain) ||
visitNode(cbNode, (<ElementAccessChain>node).argumentExpression);
case SyntaxKind.CallChain:
return visitNode(cbNode, (<CallChain>node).chain) ||
visitNodes(cbNode, cbNodes, (<CallChain>node).typeArguments) ||
visitNodes(cbNode, cbNodes, (<CallChain>node).arguments);
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return visitNode(cbNode, (<CallExpression>node).expression) ||
Expand Down Expand Up @@ -3864,7 +3877,7 @@ namespace ts {

// Now, we *may* be complete. However, we might have consumed the start of a
// CallExpression. As such, we need to consume the rest of it here to be complete.
return parseCallExpressionRest(expression);
return parseOptionalExpressionRest(parseCallExpressionRest(expression));
}

function parseMemberExpressionOrHigher(): MemberExpression {
Expand Down Expand Up @@ -4269,6 +4282,48 @@ namespace ts {
}
}

function parseOptionalExpressionRest(expression: LeftHandSideExpression) {
while (token() === SyntaxKind.QuestionDotToken) {
const fullStart = getNodePos();
nextToken();

let chain: OptionalChain;
while (true) {
if (parseOptional(SyntaxKind.OpenBracketToken)) {
const elementAccessChain = createNode(SyntaxKind.ElementAccessChain, fullStart) as ElementAccessChain;
elementAccessChain.chain = chain;
elementAccessChain.argumentExpression = parseExpression();
parseExpected(SyntaxKind.CloseBracketToken);
chain = finishNode(elementAccessChain);
continue;
}
else if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.OpenParenToken) {
const callChain = createNode(SyntaxKind.CallChain, fullStart) as CallChain;
callChain.chain = chain;
callChain.typeArguments = parseTypeArgumentsInExpression();
callChain.arguments = parseArgumentList();
chain = finishNode(callChain);
continue;
}
else if (!chain || parseOptional(SyntaxKind.DotToken)) {
const propertyAccessChain = createNode(SyntaxKind.PropertyAccessChain, fullStart) as PropertyAccessChain;
propertyAccessChain.chain = chain;
propertyAccessChain.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
chain = finishNode(propertyAccessChain);
continue;
}

const node = createNode(SyntaxKind.OptionalExpression, expression.pos) as OptionalExpression;
node.expression = expression;
node.chain = chain;
expression = finishNode(node);
break;
}
}

return expression;
}

function parseArgumentList() {
parseExpected(SyntaxKind.OpenParenToken);
const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ namespace ts {
"&&": SyntaxKind.AmpersandAmpersandToken,
"||": SyntaxKind.BarBarToken,
"?": SyntaxKind.QuestionToken,
"?.": SyntaxKind.QuestionDotToken,
":": SyntaxKind.ColonToken,
"=": SyntaxKind.EqualsToken,
"+=": SyntaxKind.PlusEqualsToken,
Expand Down Expand Up @@ -1553,6 +1554,9 @@ namespace ts {
pos++;
return token = SyntaxKind.GreaterThanToken;
case CharacterCodes.question:
if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) {
return pos += 2, token = SyntaxKind.QuestionDotToken;
}
pos++;
return token = SyntaxKind.QuestionToken;
case CharacterCodes.openBracket:
Expand Down
41 changes: 41 additions & 0 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace ts {
const previousOnSubstituteNode = context.onSubstituteNode;
context.onSubstituteNode = onSubstituteNode;

let optionalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression | undefined;
let enabledSubstitutions: ESNextSubstitutionFlags;
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
Expand All @@ -39,6 +40,7 @@ namespace ts {

const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
optionalExpression = undefined;
return visited;
}

Expand Down Expand Up @@ -103,6 +105,8 @@ namespace ts {
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.OptionalExpression:
return visitOptionalExpression(node as OptionalExpression);
default:
return visitEachChild(node, visitor, context);
}
Expand Down Expand Up @@ -718,6 +722,43 @@ namespace ts {
return statements;
}

function visitOptionalChain(node: OptionalChain | undefined, expression: Expression) {
if (!node) return expression;
switch (node.kind) {
case SyntaxKind.PropertyAccessChain:
const propertyAccessExpression = createPropertyAccess(
visitOptionalChain(node.chain, expression),
visitNode(node.name, visitor, isIdentifier));
return propertyAccessExpression;
case SyntaxKind.ElementAccessChain:
const elementAccessExpression = createElementAccess(
visitOptionalChain(node.chain, expression),
visitNode(node.argumentExpression, visitor, isExpression));
return elementAccessExpression;
case SyntaxKind.CallChain:
const callExpression = createCall(
visitOptionalChain(node.chain, expression),
/*typeArguments*/ undefined,
visitNodes(node.arguments, visitor, isExpression));
return callExpression;
}
}

function visitOptionalExpression(node: OptionalExpression) {
const root = visitNode(node.expression, visitor, isExpression);
const temp = createTempVariable(hoistVariableDeclaration);
// setSourceMapRange(temp, root);
const condition = createLogicalOr(
createStrictEquality(createAssignment(temp, root), createNull()),
createStrictEquality(temp, createVoidZero()));
// setSourceMapRange(condition, root);
const chain = visitOptionalChain(node.chain, temp);
const conditional = createConditional(condition, createVoidZero(), chain);
// setSourceMapRange(conditional, node);
// setCommentRange(conditional, node);
return conditional;
}

function enableSubstitutionForAsyncMethodsWithSuper() {
if ((enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper) === 0) {
enabledSubstitutions |= ESNextSubstitutionFlags.AsyncMethodsWithSuper;
Expand Down
Loading