Emit and check for safe navigation

This commit is contained in:
Ron Buckton
2017-02-23 14:32:24 -08:00
parent d9fceab72d
commit 9c0046c057
7 changed files with 350 additions and 291 deletions
+91 -39
View File
@@ -8917,6 +8917,32 @@ namespace ts {
return strictNullChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
}
function getNullPropagatingType(type: Type) {
return strictNullChecks && (getTypeFacts(type) & TypeFacts.EQUndefinedOrNull) === 0
? neverType
: undefinedType;
}
function propagateNullType(type: Type, propagatingType: Type) {
return propagatingType === neverType ? type : getUnionType([type, propagatingType]);
}
function propagateNullReturnType(signature: Signature, propagatingType: Type) {
if (propagatingType === neverType) {
return signature;
}
const returnType = getReturnTypeOfSignature(signature);
const propagatingReturnType = propagateNullType(returnType, propagatingType);
if (returnType === propagatingReturnType) {
return signature;
}
signature = cloneSignature(signature);
signature.resolvedReturnType = propagatingReturnType;
return signature;
}
/**
* Return true if type was inferred from an object literal or written as an object type literal
* with no call or construct signatures.
@@ -13096,8 +13122,8 @@ namespace ts {
}
function checkNonNullType(type: Type, errorNode: Node): Type {
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
if (kind) {
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags);
if (kind & TypeFlags.Nullable) {
error(errorNode, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ?
Diagnostics.Object_is_possibly_null_or_undefined :
Diagnostics.Object_is_possibly_undefined :
@@ -13156,7 +13182,11 @@ namespace ts {
}
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
const type = checkNonNullExpression(left);
const propagateNull = node.flags & NodeFlags.PropagateNull;
const objectType = propagateNull ? checkExpression(left) : checkNonNullExpression(left);
const type = propagateNull ? getNonNullableType(objectType) : objectType;
const propagatingType = propagateNull ? getNullPropagatingType(objectType) : neverType;
if (isTypeAny(type) || type === silentNeverType) {
return type;
}
@@ -13205,10 +13235,10 @@ namespace ts {
if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite ||
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
return propagateNullType(propType, propagatingType);
}
const flowType = getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined);
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
return propagateNullType(assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType, propagatingType);
}
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {
@@ -13279,7 +13309,10 @@ namespace ts {
function checkIndexedAccess(node: ElementAccessExpression): Type {
checkGrammarNullPropagation(node);
const objectType = checkNonNullExpression(node.expression);
const propagateNull = node.flags & NodeFlags.PropagateNull;
const objectType = propagateNull ? checkExpression(node.expression) : checkNonNullExpression(node.expression);
const type = propagateNull ? getNonNullableType(objectType) : objectType;
const propagatingType = propagateNull ? getNullPropagatingType(objectType) : neverType;
const indexExpression = node.argumentExpression;
if (!indexExpression) {
@@ -13299,16 +13332,16 @@ namespace ts {
const indexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : checkExpression(indexExpression);
if (objectType === unknownType || objectType === silentNeverType) {
return objectType;
if (type === unknownType || type === silentNeverType) {
return type;
}
if (isConstEnumObjectType(objectType) && indexExpression.kind !== SyntaxKind.StringLiteral) {
if (isConstEnumObjectType(type) && indexExpression.kind !== SyntaxKind.StringLiteral) {
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
return unknownType;
}
return checkIndexedAccessIndexType(getIndexedAccessType(objectType, indexType, node), node);
return propagateNullType(checkIndexedAccessIndexType(getIndexedAccessType(type, indexType, node), node), propagatingType);
}
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
@@ -14048,7 +14081,7 @@ namespace ts {
}
}
function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], headMessage?: DiagnosticMessage): Signature {
function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], propagatingType?: Type, headMessage?: DiagnosticMessage): Signature {
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
const isDecorator = node.kind === SyntaxKind.Decorator;
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -14154,7 +14187,7 @@ namespace ts {
result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma);
}
if (result) {
return result;
return propagateNullReturnType(result, propagatingType || neverType);
}
// No signatures were applicable. Now report errors based on the last applicable signature with
@@ -14209,7 +14242,7 @@ namespace ts {
if (candidate.typeParameters && typeArguments) {
candidate = getSignatureInstantiation(candidate, map(typeArguments, getTypeFromTypeNode));
}
return candidate;
return propagateNullReturnType(candidate, propagatingType || neverType);
}
}
}
@@ -14309,12 +14342,15 @@ namespace ts {
return resolveUntypedCall(node);
}
const funcType = checkNonNullExpression(node.expression);
if (funcType === silentNeverType) {
const propagateNull = node.flags & NodeFlags.PropagateNull;
const funcType = propagateNull ? checkExpression(node.expression) : checkNonNullExpression(node.expression);
const type = propagateNull ? getNonNullableType(funcType) : funcType;
if (type === silentNeverType) {
return silentNeverSignature;
}
const apparentType = getApparentType(funcType);
const apparentType = getApparentType(type);
if (apparentType === unknownType) {
// Another error has already been reported
return resolveErrorCall(node);
@@ -14330,10 +14366,10 @@ namespace ts {
// TS 1.0 Spec: 4.12
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
// types are provided for the argument expressions, and the result is always of type Any.
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) {
if (isUntypedFunctionCall(type, apparentType, callSignatures.length, constructSignatures.length)) {
// The unknownType indicates that an error already occurred (and was reported). No
// need to report another error in this case.
if (funcType !== unknownType && node.typeArguments) {
if (type !== unknownType && node.typeArguments) {
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
}
return resolveUntypedCall(node);
@@ -14343,14 +14379,16 @@ namespace ts {
// with multiple call signatures.
if (!callSignatures.length) {
if (constructSignatures.length) {
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(type));
}
else {
error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType));
}
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray);
const propagatingType = propagateNull ? getNullPropagatingType(funcType) : neverType;
return resolveCall(node, callSignatures, candidatesOutArray, propagatingType);
}
/**
@@ -14384,8 +14422,10 @@ namespace ts {
}
}
let expressionType = checkNonNullExpression(node.expression);
if (expressionType === silentNeverType) {
const propagateNull = node.flags & NodeFlags.PropagateNull;
const funcType = propagateNull ? checkExpression(node.expression) : checkNonNullExpression(node.expression);
const type = propagateNull ? getNonNullableType(funcType) : funcType;
if (type === silentNeverType) {
return silentNeverSignature;
}
@@ -14394,8 +14434,8 @@ namespace ts {
// function call, but using the construct signatures as the initial set of candidate
// signatures for overload resolution. The result type of the function call becomes
// the result type of the operation.
expressionType = getApparentType(expressionType);
if (expressionType === unknownType) {
const apparentType = getApparentType(type);
if (apparentType === unknownType) {
// Another error has already been reported
return resolveErrorCall(node);
}
@@ -14404,7 +14444,7 @@ namespace ts {
// Note, only class declarations can be declared abstract.
// In the case of a merged class-module or class-interface declaration,
// only the class declaration node will have the Abstract flag set.
const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol);
const valueDecl = apparentType.symbol && getClassLikeDeclarationOfSymbol(apparentType.symbol);
if (valueDecl && getModifierFlags(valueDecl) & ModifierFlags.Abstract) {
error(node, Diagnostics.Cannot_create_an_instance_of_the_abstract_class_0, declarationNameToString(valueDecl.name));
return resolveErrorCall(node);
@@ -14413,7 +14453,7 @@ namespace ts {
// TS 1.0 spec: 4.11
// If expressionType is of type Any, Args can be any argument
// list and the result of the operation is of type Any.
if (isTypeAny(expressionType)) {
if (isTypeAny(apparentType)) {
if (node.typeArguments) {
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
}
@@ -14424,19 +14464,21 @@ namespace ts {
// but we are not including construct signatures that may have been added to the Object or
// Function interface, since they have none by default. This is a bit of a leap of faith
// that the user will not add any.
const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
if (constructSignatures.length) {
if (!isConstructorAccessible(node, constructSignatures[0])) {
return resolveErrorCall(node);
}
return resolveCall(node, constructSignatures, candidatesOutArray);
const propagatingType = propagateNull ? getNullPropagatingType(funcType) : neverType;
return resolveCall(node, constructSignatures, candidatesOutArray, propagatingType);
}
// If expressionType's apparent type is an object type with no construct signatures but
// one or more call signatures, the expression is processed as a function call. A compile-time
// error occurs if the result of the function call is not Void. The type of the result of the
// operation is Any. It is an error to have a Void this type.
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
if (callSignatures.length) {
const signature = resolveCall(node, callSignatures, candidatesOutArray);
if (getReturnTypeOfSignature(signature) !== voidType) {
@@ -14566,7 +14608,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray, headMessage);
return resolveCall(node, callSignatures, candidatesOutArray, /*propagatingType*/ undefined, headMessage);
}
/**
@@ -15405,13 +15447,15 @@ namespace ts {
booleanType;
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
const propagateNull = node.operand.flags & NodeFlags.PropagateNull;
const nonNullType = propagateNull ? getNonNullableType(operandType) : checkNonNullType(operandType, node.operand);
const propagatingType = propagateNull ? getNullPropagatingType(operandType) : neverType;
const ok = checkArithmeticOperandType(node.operand, nonNullType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
if (ok) {
// run check only if former checks succeeded to avoid reporting cascading errors
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
}
return numberType;
return propagateNullType(numberType, propagatingType);
}
return unknownType;
}
@@ -15421,13 +15465,15 @@ namespace ts {
if (operandType === silentNeverType) {
return silentNeverType;
}
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
const propagateNull = node.operand.flags & NodeFlags.PropagateNull;
const nonNullType = propagateNull ? getNonNullableType(operandType) : checkNonNullType(operandType, node.operand);
const propagatingType = propagateNull ? getNullPropagatingType(operandType) : neverType;
const ok = checkArithmeticOperandType(node.operand, nonNullType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
if (ok) {
// run check only if former checks succeeded to avoid reporting cascading errors
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
}
return numberType;
return propagateNullType(numberType, propagatingType);
}
// Return true if type might be of the given kind. A union or intersection type might be of a given
@@ -15772,6 +15818,12 @@ namespace ts {
}
let leftType = checkExpression(left, contextualMapper);
let rightType = checkExpression(right, contextualMapper);
let propagateNull = operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment && left.flags & NodeFlags.PropagateNull;
let propagatingType = propagateNull ? getNullPropagatingType(leftType) : neverType;
if (propagateNull) {
leftType = getNonNullableType(leftType);
}
switch (operator) {
case SyntaxKind.AsteriskToken:
case SyntaxKind.AsteriskAsteriskToken:
@@ -15819,7 +15871,7 @@ namespace ts {
}
}
return numberType;
return propagateNullType(numberType, propagatingType);
case SyntaxKind.PlusToken:
case SyntaxKind.PlusEqualsToken:
if (leftType === silentNeverType || rightType === silentNeverType) {
@@ -15850,7 +15902,7 @@ namespace ts {
// Symbols are not allowed at all in arithmetic expressions
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
return resultType;
return propagateNullType(resultType, propagatingType);
}
}
@@ -15862,7 +15914,7 @@ namespace ts {
if (operator === SyntaxKind.PlusEqualsToken) {
checkAssignmentOperator(resultType);
}
return resultType;
return propagateNullType(resultType, propagatingType);
case SyntaxKind.LessThanToken:
case SyntaxKind.GreaterThanToken:
case SyntaxKind.LessThanEqualsToken:
+153 -76
View File
@@ -1739,7 +1739,7 @@ namespace ts {
}
export function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment;
export function createAssignment(left: Expression, right: Expression): BinaryExpression;
export function createAssignment(left: Expression, right: Expression): AssignmentExpression<EqualsToken>;
export function createAssignment(left: Expression, right: Expression) {
return createBinary(left, SyntaxKind.EqualsToken, right);
}
@@ -1748,6 +1748,10 @@ namespace ts {
return createBinary(left, SyntaxKind.EqualsEqualsToken, right);
}
export function createInequality(left: Expression, right: Expression) {
return createBinary(left, SyntaxKind.ExclamationEqualsToken, right);
}
export function createStrictEquality(left: Expression, right: Expression) {
return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right);
}
@@ -2398,26 +2402,30 @@ namespace ts {
return updated;
}
export interface CallBinding {
target: LeftHandSideExpression;
thisArg: Expression;
}
function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean {
const target = skipParentheses(node);
/**
* Determines whether an expression should be captured in a temp variable as part of an
* emit transformation where side-effects could be observed.
* @param node The expression to be tested.
* @param captureIdentifiers A value that indicates whether identifiers should be captured
* as well.
*/
function shouldBeCapturedInTempVariable(node: Expression, captureIdentifiers?: boolean): boolean {
const target = skipOuterExpressions(node);
switch (target.kind) {
case SyntaxKind.Identifier:
return cacheIdentifiers;
return captureIdentifiers;
case SyntaxKind.ThisKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return false;
case SyntaxKind.VoidExpression:
return (<VoidExpression>node).expression.kind === SyntaxKind.NumericLiteral;
case SyntaxKind.ArrayLiteralExpression:
const elements = (<ArrayLiteralExpression>target).elements;
if (elements.length === 0) {
return false;
}
return true;
return (<ArrayLiteralExpression>target).elements.length > 0;
case SyntaxKind.ObjectLiteralExpression:
return (<ObjectLiteralExpression>target).properties.length > 0;
default:
@@ -2425,82 +2433,151 @@ namespace ts {
}
}
export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers?: boolean): CallBinding {
const callee = skipOuterExpressions(expression, OuterExpressionKinds.All);
/**
* Captures an expression in a temp variable.
* @param expression The expression to capture.
* @param recordTempVariable A callback used to record new temp variables.
* @param expressions The array of expressions into which capturing assignments should be added.
*/
export function captureExpression(expression: Expression, recordTempVariable: (temp: Identifier) => void, expressions: Expression[]): Expression {
const temp = setTextRange(createTempVariable(recordTempVariable), expression);
expressions.push(setTextRange(createAssignment(temp, expression), expression));
return temp;
}
/**
* Captures an expression in a temp variable if it possibly contains side-effects.
* @param expression The expression to capture.
* @param recordTempVariable A callback used to record new temp variables.
* @param expressions The array of expressions into which capturing assignments should be added.
* @param captureIdentifiers A value indicating whether identifiers should be captured as well.
*/
export function captureExpressionIfNeeded(expression: Expression, recordTempVariable: (temp: Identifier) => void, expressions: Expression[], captureIdentifiers?: boolean): Expression {
if (shouldBeCapturedInTempVariable(expression, captureIdentifiers)) {
return captureExpression(expression, recordTempVariable, expressions);
}
return expression;
}
// https://tc39.github.io/ecma262/#sec-reference-specification-type
export interface Reference {
baseValue: Expression;
reference: PropertyAccessExpression | ElementAccessExpression;
}
/**
* Creates an approximation of an ECMAScript `Reference` specification type.
* @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.
*/
export function createReference(lhsReference: PropertyAccessExpression | ElementAccessExpression, recordTempVariable: (temp: Identifier) => void, expressions: Expression[]): Reference {
const baseValue = captureExpressionIfNeeded(lhsReference.expression, recordTempVariable, expressions);
let reference: PropertyAccessExpression | ElementAccessExpression;
if (isPropertyAccessExpression(lhsReference)) {
reference = setTextRange(
createPropertyAccess(
baseValue,
lhsReference.name
),
lhsReference
);
}
else {
reference = setTextRange(
createElementAccess(
baseValue,
lhsReference.argumentExpression
),
lhsReference
);
}
return { baseValue, reference };
}
export interface CallBinding {
target: LeftHandSideExpression;
thisArg: Expression;
}
/**
* Returns a `CallBinding` for an expression that contains a reference to an expression to call
* and the `this` to be used for the call.
* @param expression The callee of a CallExpression.
* @param recordTempVariable A callback used to record new temp variables.
* @param languageVersion An optional `ScriptTarget` used to control the behavior of a `super` callee.
* @param captureIdentifiers A value indicating whether identifiers should be captured as well.
*/
export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, captureIdentifiers?: boolean): CallBinding {
const callee = skipOuterExpressions(expression);
let thisArg: Expression;
let target: LeftHandSideExpression;
if (isSuperProperty(callee)) {
thisArg = createThis();
target = callee;
}
else if (callee.kind === SyntaxKind.SuperKeyword) {
else if (isSuper(callee)) {
thisArg = createThis();
target = languageVersion < ScriptTarget.ES2015
? setTextRange(createIdentifier("_super"), callee)
: <PrimaryExpression>callee;
: callee;
}
else if (getEmitFlags(callee) & EmitFlags.HelperName) {
thisArg = createVoidZero();
target = parenthesizeForAccess(callee);
}
else {
switch (callee.kind) {
case SyntaxKind.PropertyAccessExpression: {
if (shouldBeCapturedInTempVariable((<PropertyAccessExpression>callee).expression, cacheIdentifiers)) {
// for `a.b()` target is `(_a = a).b` and thisArg is `_a`
thisArg = createTempVariable(recordTempVariable);
target = createPropertyAccess(
setTextRange(
createAssignment(
thisArg,
(<PropertyAccessExpression>callee).expression
),
(<PropertyAccessExpression>callee).expression
else if (isPropertyAccessExpression(callee)) {
if (shouldBeCapturedInTempVariable(callee.expression, captureIdentifiers)) {
// for `a.b()` target is `(_a = a).b` and thisArg is `_a`
thisArg = createTempVariable(recordTempVariable);
target = setTextRange(
createPropertyAccess(
setTextRange(
createAssignment(
thisArg,
callee.expression
),
(<PropertyAccessExpression>callee).name,
callee.flags & NodeFlags.PropagateNull
);
setTextRange(target, callee);
}
else {
thisArg = (<PropertyAccessExpression>callee).expression;
target = <PropertyAccessExpression>callee;
}
break;
}
case SyntaxKind.ElementAccessExpression: {
if (shouldBeCapturedInTempVariable((<ElementAccessExpression>callee).expression, cacheIdentifiers)) {
// for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a`
thisArg = createTempVariable(recordTempVariable);
target = createElementAccess(
setTextRange(
createAssignment(
thisArg,
(<ElementAccessExpression>callee).expression
),
(<ElementAccessExpression>callee).expression
),
(<ElementAccessExpression>callee).argumentExpression,
callee.flags & NodeFlags.PropagateNull
);
setTextRange(target, callee);
}
else {
thisArg = (<ElementAccessExpression>callee).expression;
target = <ElementAccessExpression>callee;
}
break;
}
default: {
// for `a()` target is `a` and thisArg is `void 0`
thisArg = createVoidZero();
target = parenthesizeForAccess(expression);
break;
}
callee.expression
),
callee.name,
callee.flags & NodeFlags.PropagateNull
),
callee
);
}
else {
thisArg = callee.expression;
target = callee;
}
}
else if (isElementAccessExpression(callee)) {
if (shouldBeCapturedInTempVariable(callee.expression, captureIdentifiers)) {
// for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a`
thisArg = createTempVariable(recordTempVariable);
target = setTextRange(
createElementAccess(
setTextRange(
createAssignment(
thisArg,
callee.expression
),
callee.expression
),
callee.argumentExpression,
callee.flags & NodeFlags.PropagateNull
),
callee
);
}
else {
thisArg = callee.expression;
target = callee;
}
}
else {
// for `a()` target is `a` and thisArg is `void 0`
thisArg = createVoidZero();
target = parenthesizeForAccess(expression);
}
return { target, thisArg };
@@ -3271,8 +3348,8 @@ namespace ts {
export function skipAssertions(node: Expression): Expression;
export function skipAssertions(node: Node): Node;
export function skipAssertions(node: Node): Node {
while (isAssertionExpression(node)) {
node = (<AssertionExpression>node).expression;
while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) {
node = (<AssertionExpression | NonNullExpression>node).expression;
}
return node;
+5 -5
View File
@@ -3509,7 +3509,7 @@ namespace ts {
* 5) --UnaryExpression[?Yield]
*/
if (isUpdateExpression()) {
const incrementExpression = parseIncrementExpression();
const incrementExpression = parseUpdateExpression();
return token() === SyntaxKind.AsteriskAsteriskToken ?
<BinaryExpression>parseBinaryExpressionRest(getBinaryOperatorPrecedence(), incrementExpression) :
incrementExpression;
@@ -3577,7 +3577,7 @@ namespace ts {
return parseAwaitExpression();
}
default:
return parseIncrementExpression();
return parseUpdateExpression();
}
}
@@ -3617,9 +3617,9 @@ namespace ts {
}
/**
* Parse ES7 IncrementExpression. IncrementExpression is used instead of ES6's PostFixExpression.
* Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression.
*
* ES7 IncrementExpression[yield]:
* ES7 UpdateExpression[yield]:
* 1) LeftHandSideExpression[?yield]
* 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++
* 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]--
@@ -3627,7 +3627,7 @@ namespace ts {
* 5) --LeftHandSideExpression[?yield]
* In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression
*/
function parseIncrementExpression(): IncrementExpression {
function parseUpdateExpression(): UpdateExpression {
if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) {
const node = <PrefixUnaryExpression>createNode(SyntaxKind.PrefixUnaryExpression);
node.operator = <PrefixUnaryOperator>token();
+66 -162
View File
@@ -230,6 +230,9 @@ namespace ts {
* @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,
@@ -247,19 +250,30 @@ namespace ts {
);
}
const left = visitNode(node.left, visitor, isExpression);
const right = visitNode(node.right, visitor, isExpression);
if (isAssignmentExpression(node)) {
const nilReference = getNilReference(left);
if (nilReference) {
return updateNilReference(
nilReference,
updateBinary(node, nilReference.whenFalse, right)
const referenceExpression = skipOuterExpressions(node.left);
if (referenceExpression.flags & NodeFlags.PropagateNull && isPropertyAccessOrElementAccess(referenceExpression)) {
// x?.y = 1 -> _a = 1, x == null ? _a : x.y = _a
// x?.[y()] = 1 -> _a = y(), _b = 1, x == null ? _b : x[_a] = _b
// 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);
expressions.push(
setTextRange(
createConditional(
createEquality(baseValue, createNull()),
rhsValue,
updateBinary(node, reference, rhsValue)
),
node
)
);
return visitNode(inlineExpressions(expressions), visitor, isExpression);
}
}
return updateBinary(node, left, right);
return visitEachChild(node, visitor, context);
}
/**
@@ -733,38 +747,16 @@ namespace ts {
&& node.expression.kind !== SyntaxKind.SuperKeyword;
}
function getNilReference(expression: Expression): ConditionalExpression {
expression = skipOuterExpressions(expression);
return isConditionalExpression(expression)
&& isBinaryExpression(expression.condition)
&& expression.condition.operatorToken.kind === SyntaxKind.EqualsEqualsToken
&& expression.condition.right.kind === SyntaxKind.NullKeyword
&& isVoidZero(expression.whenTrue)
? expression
: undefined;
}
function updateNilReference(expression: ConditionalExpression, whenNotNil: Expression) {
return setTextRange(
createConditional(
expression.condition,
expression.whenTrue,
whenNotNil
),
whenNotNil
);
}
function propagateNull<T extends Node>(finishExpression: (node: T, nullableExpression: Expression) => Expression, node: T, nullableExpression: Expression): Expression;
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, nullableExpression: Expression, data: U): Expression;
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, nullableExpression: Expression, data?: U): Expression {
function propagateNull<T extends Node>(finishExpression: (node: T, nullableExpression: Expression) => Expression, node: T, referenceExpression: Expression): Expression;
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, referenceExpression: Expression, data: U): Expression;
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(nullableExpression)) {
if (isIdentifier(referenceExpression)) {
return setTextRange(
createConditional(
createEquality(nullableExpression, createNull()),
createEquality(referenceExpression, createNull()),
createVoidZero(),
finishExpression(node, nullableExpression, data)
finishExpression(node, referenceExpression, data)
),
node
);
@@ -774,7 +766,7 @@ namespace ts {
return setTextRange(
createConditional(
createEquality(
createAssignment(temp, nullableExpression),
createAssignment(temp, referenceExpression),
createNull()
),
createVoidZero(),
@@ -784,7 +776,7 @@ namespace ts {
);
}
}
return finishExpression(node, nullableExpression, data);
return finishExpression(node, referenceExpression, data);
}
function visitCallExpression(node: CallExpression): Expression {
@@ -800,30 +792,7 @@ namespace ts {
return propagateNull(finishNullableCallExpression, node, visitNode(target, visitor, isExpression), visitNode(thisArg, visitor, isExpression));
}
const expression = visitNode(node.expression, visitor, isExpression);
const argumentsArray = visitNodes(node.arguments, visitor, isExpression);
const nilReference = getNilReference(expression);
if (nilReference) {
// NilReference shortcut in expression
// x?.y() -> x == null ? void 0 : x.y();
// x?.[y]() -> x == null ? void 0 : x[y]();
return updateNilReference(
nilReference,
updateCall(
node,
nilReference.whenFalse,
/*typeArguments*/ undefined,
argumentsArray
)
);
}
return updateCall(
node,
expression,
/*typeArguments*/ undefined,
argumentsArray
);
return visitEachChild(node, visitor, context);
}
function finishNullableCallExpression(node: CallExpression, target: Expression, thisArg: Expression) {
@@ -852,7 +821,6 @@ namespace ts {
}
function visitNewExpression(node: NewExpression): Expression {
const expression = visitNode(node.expression, visitor, isExpression);
if (isNullPropagatingExpression(node)) {
// null propagation in new:
// new x?.() -> x == null ? void 0 : new x();
@@ -861,31 +829,10 @@ namespace ts {
// new x[y]?.() -> (_a = x[y]) == null ? void 0 : new _a();
// new (x.y)?.() -> (_a = x.y) == null ? void 0 : new _a();
// new (x[y])?.() -> (_a = x[y]) == null ? void 0 : new _a();
return propagateNull(finishNullableNewExpression, node, expression);
return propagateNull(finishNullableNewExpression, node, visitNode(node.expression, visitor, isExpression));
}
const argumentsArray = visitNodes(node.arguments, visitor, isExpression);
const nilReference = getNilReference(expression);
if (nilReference) {
// NilReference shortcut in expression
// new x?.y() -> x == null ? void 0 : new x.y();
// new x?.[y]() -> x == null ? void 0 : new x[y]();
return updateNilReference(
nilReference,
updateNew(
node,
nilReference.whenTrue,
/*typeArguments*/ undefined,
argumentsArray
)
);
}
return updateNew(
node,
expression,
/*typeArguments*/ undefined,
argumentsArray);
return visitEachChild(node, visitor, context);
}
function finishNullableNewExpression(node: NewExpression, expression: Expression) {
@@ -903,35 +850,14 @@ namespace ts {
}
function visitPropertyAccess(node: PropertyAccessExpression): Expression {
const expression = visitNode(node.expression, visitor, isExpression);
if (isNullPropagatingExpression(node)) {
// null propagation in property access
// x?.y -> x == null ? void 0 : x.y;
// x.y?.z -> (_a = x.y) == null ? void 0 : _a.z;
return propagateNull(finishNullablePropertyAccess, node, expression);
return propagateNull(finishNullablePropertyAccess, node, visitNode(node.expression, visitor, isExpression));
}
const name = visitNode(node.name, visitor, isIdentifier);
const nilReference = getNilReference(expression);
if (nilReference) {
// NilReference shortcut in expression
// x?.y.z -> x == null ? void 0 : x.y.z;
// x.y?.z.a -> (_a = x.y) == null ? void 0 : _a.z.a;
return updateNilReference(
nilReference,
updatePropertyAccess(
node,
nilReference.whenFalse,
name
)
);
}
return updatePropertyAccess(
node,
expression,
name
);
return visitEachChild(node, visitor, context);
}
function finishNullablePropertyAccess(node: PropertyAccessExpression, expression: Expression) {
@@ -948,35 +874,13 @@ namespace ts {
}
function visitElementAccess(node: ElementAccessExpression): Expression {
const expression = visitNode(node.expression, visitor, isExpression);
if (isNullPropagatingExpression(node)) {
// null propagation in element access
// x?.[y] -> x == null ? void 0 : x.[y];
// x.y?.[z] -> (_a = x.y) == null ? void 0 : _a.[z];
return propagateNull(finishNullableElementAccess, node, expression);
return propagateNull(finishNullableElementAccess, node, visitNode(node.expression, visitor, isExpression));
}
const argumentExpression = visitNode(node.argumentExpression, visitor, isExpression);
const nilReference = getNilReference(expression);
if (nilReference) {
// NilReference shortcut in expression
// x?.y.[z] -> x == null ? void 0 : x.y.[z];
// x.y?.z.[a] -> (_a = x.y) == null ? void 0 : _a.z.[a];
return updateNilReference(
nilReference,
updateElementAccess(
node,
nilReference.whenFalse,
argumentExpression
)
);
}
return updateElementAccess(
node,
expression,
argumentExpression
);
return visitEachChild(node, visitor, context);
}
function finishNullableElementAccess(node: ElementAccessExpression, expression: Expression) {
@@ -992,44 +896,44 @@ namespace ts {
);
}
function visitDelete(node: DeleteExpression) {
const expression = visitNode(node.expression, visitor, isExpression);
const nilReference = getNilReference(expression);
if (nilReference) {
return setTextRange(
createConditional(
nilReference.condition,
createTrue(),
updateDelete(node, nilReference.whenFalse)
),
node
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);
expressions.push(
setTextRange(
createConditional(
createEquality(baseValue, createNull()),
createVoidZero(),
updateNode(
node,
reference
)
),
node
)
);
return visitNode(inlineExpressions(expressions), visitor, isExpression);
}
return updateDelete(node, expression);
return visitEachChild(node, visitor, context);
}
function visitDelete(node: DeleteExpression) {
return visitUnaryMutationExpression(node, node.expression, updateDelete);
}
function visitPrefix(node: PrefixUnaryExpression) {
const operand = visitNode(node.operand, visitor, isExpression);
const nilReference = getNilReference(operand);
if (nilReference) {
return updateNilReference(
nilReference,
updatePrefix(node, nilReference.whenFalse)
);
if (isPrefixOrPostfixUpdateExpression(node)) {
return visitUnaryMutationExpression(node, node.operand, updatePrefix);
}
return updatePrefix(node, operand);
return visitEachChild(node, visitor, context);
}
function visitPostfix(node: PostfixUnaryExpression) {
const operand = visitNode(node.operand, visitor, isExpression);
const nilReference = getNilReference(operand);
if (nilReference) {
return updateNilReference(
nilReference,
updatePostfix(node, nilReference.whenFalse)
);
if (isPrefixOrPostfixUpdateExpression(node)) {
return visitUnaryMutationExpression(node, node.operand, updatePostfix);
}
return updatePostfix(node, operand);
return visitEachChild(node, visitor, context);
}
function enableSubstitutionForAsyncMethodsWithSuper() {
+1 -1
View File
@@ -1120,7 +1120,7 @@ namespace ts {
// .mark resumeLabel
// _b.apply(_a, _c.concat([%sent%, 2]));
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*cacheIdentifiers*/ true);
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*captureIdentifiers*/ true);
return setOriginalNode(
createFunctionApply(
cacheExpression(visitNode(target, visitor, isLeftHandSideExpression)),
+9 -5
View File
@@ -980,8 +980,8 @@
_unaryExpressionBrand: any;
}
export interface IncrementExpression extends UnaryExpression {
_incrementExpressionBrand: any;
export interface UpdateExpression extends UnaryExpression {
_updateExpressionBrand: any;
}
// see: https://tc39.github.io/ecma262/#prod-UpdateExpression
@@ -995,25 +995,29 @@
| SyntaxKind.ExclamationToken
;
export interface PrefixUnaryExpression extends IncrementExpression {
export interface PrefixUnaryExpression extends UpdateExpression {
kind: SyntaxKind.PrefixUnaryExpression;
operator: PrefixUnaryOperator;
operand: UnaryExpression;
}
export interface PrefixUnaryUpdateExpression extends PrefixUnaryExpression {
operator: SyntaxKind.PlusPlusToken | SyntaxKind.MinusMinusToken;
}
// see: https://tc39.github.io/ecma262/#prod-UpdateExpression
export type PostfixUnaryOperator
= SyntaxKind.PlusPlusToken
| SyntaxKind.MinusMinusToken
;
export interface PostfixUnaryExpression extends IncrementExpression {
export interface PostfixUnaryExpression extends UpdateExpression {
kind: SyntaxKind.PostfixUnaryExpression;
operand: LeftHandSideExpression;
operator: PostfixUnaryOperator;
}
export interface LeftHandSideExpression extends IncrementExpression {
export interface LeftHandSideExpression extends UpdateExpression {
_leftHandSideExpressionBrand: any;
}
+25 -3
View File
@@ -1153,9 +1153,12 @@ namespace ts {
* Determines whether a node is a property or element access expression for super.
*/
export function isSuperProperty(node: Node): node is SuperProperty {
const kind = node.kind;
return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression)
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
return isPropertyAccessOrElementAccess(node)
&& isSuper(node.expression);
}
export function isSuper(node: Node): node is SuperExpression {
return node.kind === SyntaxKind.SuperKeyword;
}
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
@@ -3104,6 +3107,19 @@ namespace ts {
return false;
}
export function isPrefixOrPostfixUpdateExpression(node: Node): node is PrefixUnaryUpdateExpression | PostfixUnaryExpression {
switch (node.kind) {
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
switch ((<PrefixUnaryExpression | PostfixUnaryExpression>node).operator) {
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
return true;
}
}
return false;
}
// Returns false if this heritage clause element's expression contains something unsupported
// (i.e. not a name or dotted name).
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean {
@@ -3805,6 +3821,12 @@ namespace ts {
return node.kind === SyntaxKind.ElementAccessExpression;
}
export function isPropertyAccessOrElementAccess(node: Node): node is PropertyAccessExpression | ElementAccessExpression {
const kind = node.kind;
return kind === SyntaxKind.PropertyAccessExpression
|| kind === SyntaxKind.ElementAccessExpression;
}
export function isBinaryExpression(node: Node): node is BinaryExpression {
return node.kind === SyntaxKind.BinaryExpression;
}