mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Safe navigation in destructuring
This commit is contained in:
+114
-8
@@ -2402,6 +2402,109 @@ namespace ts {
|
||||
return updated;
|
||||
}
|
||||
|
||||
export const enum CreateRefFlags {
|
||||
Readable = 1 << 0,
|
||||
Writable = 1 << 1,
|
||||
ReadWrite = Readable | Writable,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an expression that can be used to bind a reference to an identifier or property.
|
||||
*
|
||||
* For an identifier `x`, this creates an expression similar to the following:
|
||||
*
|
||||
* ({
|
||||
* get value() { return x; },
|
||||
* set value(_a) { x = _a; }
|
||||
* })
|
||||
*
|
||||
* For a property `x.y`, this creates an expression similar to the following:
|
||||
*
|
||||
* (_a = x, {
|
||||
* get value() { return _a.y; },
|
||||
* set value(_b) { _a.y = _b; }
|
||||
* })
|
||||
*
|
||||
* For an element access `x[y()]`, this creates an expression similar to the following:
|
||||
*
|
||||
* (_a = x, _b = y(), {
|
||||
* get value() { return _a[_b]; },
|
||||
* set value(_c) { _a[_b] = _c; }
|
||||
* })
|
||||
*/
|
||||
export function createRef(node: PropertyAccessExpression | ElementAccessExpression | Identifier, recordTempVariable: (node: Identifier) => void, flags?: CreateRefFlags) {
|
||||
if (!flags) flags = CreateRefFlags.ReadWrite;
|
||||
const paramName = flags & CreateRefFlags.Writable ? createTempVariable(/*recordTempVariable*/ undefined) : undefined;
|
||||
let expressions: Expression[];
|
||||
let getValue: Expression;
|
||||
let putValue: Expression;
|
||||
if (isPropertyAccessOrElementAccess(node)) {
|
||||
expressions = [];
|
||||
const { reference } = createPropertyReference(node, recordTempVariable, expressions, /*captureArgumentExpression*/ true);
|
||||
// Copy null propagation flags
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
reference.flags |= NodeFlags.PropagateNull;
|
||||
}
|
||||
if (flags & CreateRefFlags.Readable) {
|
||||
getValue = reference
|
||||
}
|
||||
if (flags & CreateRefFlags.Writable) {
|
||||
putValue = setSourceMapRange(createAssignment(reference, paramName), node);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (flags & CreateRefFlags.Readable) {
|
||||
getValue = node;
|
||||
}
|
||||
if (flags & CreateRefFlags.Writable) {
|
||||
putValue = setSourceMapRange(createAssignment(node, paramName), node);
|
||||
}
|
||||
}
|
||||
|
||||
const properties: ObjectLiteralElementLike[] = [];
|
||||
if (getValue) {
|
||||
properties.push(
|
||||
setSourceMapRange(
|
||||
createGetAccessor(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
"value",
|
||||
/*parameters*/ [],
|
||||
/*type*/ undefined,
|
||||
createBlock([setSourceMapRange(createReturn(getValue), node)])
|
||||
),
|
||||
node
|
||||
)
|
||||
);
|
||||
}
|
||||
if (putValue) {
|
||||
properties.push(
|
||||
setSourceMapRange(
|
||||
createSetAccessor(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
"value",
|
||||
[createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, paramName)],
|
||||
createBlock([setSourceMapRange(createStatement(putValue), node)])
|
||||
),
|
||||
node
|
||||
)
|
||||
);
|
||||
}
|
||||
const refExpression = setSourceMapRange(
|
||||
setCommentRange(
|
||||
createObjectLiteral(properties, /*multiLine*/ false),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
if (expressions) {
|
||||
expressions.push(refExpression);
|
||||
return inlineExpressions(expressions);
|
||||
}
|
||||
return refExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an expression should be captured in a temp variable as part of an
|
||||
* emit transformation where side-effects could be observed.
|
||||
@@ -2460,7 +2563,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-reference-specification-type
|
||||
export interface Reference {
|
||||
export interface PropertyReference {
|
||||
baseValue: Expression;
|
||||
reference: PropertyAccessExpression | ElementAccessExpression;
|
||||
}
|
||||
@@ -2470,26 +2573,29 @@ namespace ts {
|
||||
* @param lhsReference The property or element access expression from which to create the reference.
|
||||
* @param recordTempVariable A callback used to record new temp variables.
|
||||
* @param expressions The array of expressions into which capturing assignments should be added.
|
||||
* @param captureArgumentExpression A value indicating whether to capture the argumentExpression of an ElementAccessExpression.
|
||||
*/
|
||||
export function createReference(lhsReference: PropertyAccessExpression | ElementAccessExpression, recordTempVariable: (temp: Identifier) => void, expressions: Expression[]): Reference {
|
||||
const baseValue = captureExpressionIfNeeded(lhsReference.expression, recordTempVariable, expressions);
|
||||
export function createPropertyReference(referenceExpression: PropertyAccessExpression | ElementAccessExpression, recordTempVariable: (temp: Identifier) => void, expressions: Expression[], captureArgumentExpression?: boolean): PropertyReference {
|
||||
const baseValue = captureExpressionIfNeeded(referenceExpression.expression, recordTempVariable, expressions);
|
||||
let reference: PropertyAccessExpression | ElementAccessExpression;
|
||||
if (isPropertyAccessExpression(lhsReference)) {
|
||||
if (isPropertyAccessExpression(referenceExpression)) {
|
||||
reference = setTextRange(
|
||||
createPropertyAccess(
|
||||
baseValue,
|
||||
lhsReference.name
|
||||
referenceExpression.name
|
||||
),
|
||||
lhsReference
|
||||
referenceExpression
|
||||
);
|
||||
}
|
||||
else {
|
||||
reference = setTextRange(
|
||||
createElementAccess(
|
||||
baseValue,
|
||||
lhsReference.argumentExpression
|
||||
captureArgumentExpression
|
||||
? captureExpressionIfNeeded(referenceExpression.argumentExpression, recordTempVariable, expressions)
|
||||
: referenceExpression.argumentExpression
|
||||
),
|
||||
lhsReference
|
||||
referenceExpression
|
||||
);
|
||||
}
|
||||
return { baseValue, reference };
|
||||
|
||||
@@ -55,10 +55,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
let expressions: Expression[];
|
||||
const compilerOptions = context.getCompilerOptions();
|
||||
const flattenContext: FlattenContext = {
|
||||
context,
|
||||
level,
|
||||
downlevelIteration: context.getCompilerOptions().downlevelIteration,
|
||||
downlevelIteration: compilerOptions.downlevelIteration,
|
||||
hoistTempVariables: true,
|
||||
emitExpression,
|
||||
emitBindingOrAssignment,
|
||||
|
||||
@@ -224,25 +224,95 @@ namespace ts {
|
||||
return visitEachChild(node, noDestructuringValue ? visitorNoDestructuringValue : visitor, context);
|
||||
}
|
||||
|
||||
function containsSingleNullPropagatingTarget(element: BindingOrAssignmentElement): boolean {
|
||||
const target = getTargetOfBindingOrAssignmentElement(element);
|
||||
if (isBindingOrAssignmentPattern(target)) {
|
||||
const elements = getElementsOfBindingOrAssignmentPattern(target);
|
||||
return elements.length === 1 && containsSingleNullPropagatingTarget(elements[0]);
|
||||
}
|
||||
return isPropertyAccessOrElementAccess(target)
|
||||
&& (target.flags & NodeFlags.PropagateNull) !== 0;
|
||||
}
|
||||
|
||||
function convertNullPropagatingReferenceToRefInAssignmentPattern(node: Node): VisitResult<Node> {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
return visitEachChild(node, convertNullPropagatingReferenceToRefInAssignmentPattern, context);
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
return updatePropertyAssignment(
|
||||
<PropertyAssignment>node,
|
||||
(<PropertyAssignment>node).name,
|
||||
visitNode((<PropertyAssignment>node).initializer, convertNullPropagatingReferenceToRefInAssignmentPattern, isExpression));
|
||||
case SyntaxKind.SpreadAssignment:
|
||||
return updateSpreadAssignment(
|
||||
<SpreadAssignment>node,
|
||||
visitNode((<SpreadAssignment>node).expression, convertNullPropagatingReferenceToRefInAssignmentPattern, isExpression));
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
return visitEachChild(node, convertNullPropagatingReferenceToRefInAssignmentPattern, context);
|
||||
case SyntaxKind.SpreadElement:
|
||||
return updateSpread(
|
||||
<SpreadElement>node,
|
||||
visitNode((<SpreadElement>node).expression, convertNullPropagatingReferenceToRefInAssignmentPattern, isExpression));
|
||||
case SyntaxKind.BinaryExpression:
|
||||
return updateBinary(
|
||||
<BinaryExpression>node,
|
||||
visitNode((<BinaryExpression>node).left, convertNullPropagatingReferenceToRefInAssignmentPattern, isExpression),
|
||||
(<BinaryExpression>node).right);
|
||||
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
const ref = createRef(<PropertyAccessExpression | ElementAccessExpression>node, hoistVariableDeclaration, CreateRefFlags.Writable);
|
||||
return setTextRange(createPropertyAccess(ref, "value"), node);
|
||||
}
|
||||
|
||||
default:
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a BinaryExpression that contains a destructuring assignment.
|
||||
*
|
||||
* @param node A BinaryExpression node.
|
||||
*/
|
||||
function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression {
|
||||
// NOTE: null propagation is not currently handled in destructuring assignments:
|
||||
// [x?.y] = [1] -> [_a] = _b = [1], x == null ? _a : x.y = _a
|
||||
// (or)-> [{ set value(_) { x == null ? _ : x.y = _ }}.value] = [1]
|
||||
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRest) {
|
||||
return flattenDestructuringAssignment(
|
||||
node,
|
||||
visitor,
|
||||
context,
|
||||
FlattenLevel.ObjectRest,
|
||||
!noDestructuringValue
|
||||
);
|
||||
if (isDestructuringAssignment(node)) {
|
||||
if (languageVersion >= ScriptTarget.ES2015) {
|
||||
// convert null propagating targets to `ref` bindings
|
||||
// we don't need to do this for ES5 as the down-leveling handles the binding
|
||||
node = visitNode(node, convertNullPropagatingReferenceToRefInAssignmentPattern, isBinaryExpression);
|
||||
}
|
||||
else {
|
||||
// The ES5/3 removes the need for a `ref` binding, but we must ensure we
|
||||
// capture the RHS.
|
||||
if (containsSingleNullPropagatingTarget(node) && !isIdentifier(node.right)) {
|
||||
const rhs = createTempVariable(hoistVariableDeclaration);
|
||||
node = createComma(
|
||||
createAssignment(rhs, node.right),
|
||||
updateBinary(
|
||||
node,
|
||||
node.left,
|
||||
rhs
|
||||
)
|
||||
) as BinaryExpression;
|
||||
aggregateTransformFlags(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
|
||||
|
||||
if (isDestructuringAssignment(node)) {
|
||||
if (node.left.transformFlags & TransformFlags.ContainsObjectRest) {
|
||||
return flattenDestructuringAssignment(
|
||||
node,
|
||||
visitor,
|
||||
context,
|
||||
FlattenLevel.ObjectRest,
|
||||
!noDestructuringValue
|
||||
);
|
||||
}
|
||||
}
|
||||
if (node.operatorToken.kind === SyntaxKind.CommaToken) {
|
||||
return updateBinary(
|
||||
node,
|
||||
visitNode(node.left, visitorNoDestructuringValue, isExpression),
|
||||
@@ -258,8 +328,10 @@ namespace ts {
|
||||
// x.y?.z = 1 -> _a = x.y, _b = 1, _a == null ? _b : _a.z = _b
|
||||
// x.y?.[z()] = 1 -> _a = x.y, _b = z(), _c = 1, _a == null ? _c : _a[_b] = _c
|
||||
const expressions: Expression[] = [];
|
||||
const { baseValue, reference } = createReference(referenceExpression, hoistVariableDeclaration, expressions);
|
||||
const rhsValue = captureExpression(node.right, hoistVariableDeclaration, expressions);
|
||||
const { baseValue, reference } = createPropertyReference(referenceExpression, hoistVariableDeclaration, expressions);
|
||||
const rhsValue = isGeneratedIdentifier(node.right)
|
||||
? node.right
|
||||
: captureExpression(node.right, hoistVariableDeclaration, expressions);
|
||||
expressions.push(
|
||||
setTextRange(
|
||||
createConditional(
|
||||
@@ -752,25 +824,31 @@ namespace ts {
|
||||
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, referenceExpression: Expression, data?: U): Expression {
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
if (isIdentifier(referenceExpression)) {
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
createEquality(referenceExpression, createNull()),
|
||||
createVoidZero(),
|
||||
finishExpression(node, referenceExpression, data)
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createConditional(
|
||||
createEquality(referenceExpression, createNull()),
|
||||
createVoidZero(),
|
||||
finishExpression(node, referenceExpression, data)
|
||||
),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
else {
|
||||
const temp = createTempVariable(hoistVariableDeclaration);
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
createEquality(
|
||||
createAssignment(temp, referenceExpression),
|
||||
createNull()
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createConditional(
|
||||
createEquality(
|
||||
createAssignment(temp, referenceExpression),
|
||||
createNull()
|
||||
),
|
||||
createVoidZero(),
|
||||
finishExpression(node, temp, data)
|
||||
),
|
||||
createVoidZero(),
|
||||
finishExpression(node, temp, data)
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
@@ -899,7 +977,7 @@ namespace ts {
|
||||
function visitUnaryMutationExpression<T extends UnaryExpression>(node: T, referenceExpression: Expression, updateNode: (node: T, referenceExpression: Expression) => T): Expression {
|
||||
if (referenceExpression.flags & NodeFlags.PropagateNull && isPropertyAccessOrElementAccess(referenceExpression)) {
|
||||
const expressions: Expression[] = [];
|
||||
const { baseValue, reference } = createReference(referenceExpression, hoistVariableDeclaration, expressions);
|
||||
const { baseValue, reference } = createPropertyReference(referenceExpression, hoistVariableDeclaration, expressions);
|
||||
expressions.push(
|
||||
setTextRange(
|
||||
createConditional(
|
||||
|
||||
Reference in New Issue
Block a user