-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
nullish coalescing commit #32883
nullish coalescing commit #32883
Conversation
b33eeac
to
2ef3e0f
Compare
Is this the real life? 😍 |
@typescript-bot test this. |
src/compiler/checker.ts
Outdated
@@ -23917,6 +23918,10 @@ namespace ts { | |||
return getTypeFacts(leftType) & TypeFacts.Falsy ? | |||
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : | |||
leftType; | |||
case SyntaxKind.QuestionQuestionToken: | |||
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? | |||
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to also return a union type for when strictNullChecks
is off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to say yes, every value could be null/undefined if strictNullChecks is off and we cannot detect that.
same as:
declare const a: string
let b = a || 1 // b is string || number
src/compiler/factory.ts
Outdated
@@ -3044,6 +3044,10 @@ namespace ts { | |||
return createBinary(left, SyntaxKind.BarBarToken, right); | |||
} | |||
|
|||
export function createNullishCoalescing(left: Expression, right: Expression) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: createNullishCoalesce
? I dunno, what do you think @rbuckton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Progress looks great!
|
||
//// [nullishCoalescingOperator1.js] | ||
"use strict"; | ||
var aa1 = typeof a1 !== "undefined" && a1 !== null ? a1 : 'whatever'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as a heads up, we might want to just go with a1 != null
, since document.all
is a weird anomaly of the language anyway that should ideally never be used in a ??
expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DanielRosenwasser
Sorry for the late comment after TypeScript 3.7 release.
But what about the shorter implementation of ??
operator that you have suggested?
This implementation with less code is better for all of us, I think.
declare let a: any, b: any, c: any;
let x1 = (a ?? b as any) || c;
let x2 = c || (a ?? b as any);
let x3 = ((a ?? b) as any) || c;
let x4 = c || ((a ?? b) as any);
let x5 = (a ?? b) as any || c;
let x6 = c || (a ?? b) as any;
let y1 = (a ?? b as any) && c;
let y2 = c && (a ?? b as any);
let y3 = ((a ?? b) as any) && c;
let y4 = c && ((a ?? b) as any);
let y5 = (a ?? b) as any && c;
let y6 = c && (a ?? b) as any; |
… grammar restrictions when assertions are used.
…red). Added tests to ensure calls and property accesses are only called once.
…wl/TypeScript into nullish-coalescing-operator
@DanielRosenwasser I've added the tests, emit appears to work as expected keeping the
Hope I didn't miss the point of the tests 😅 @rbuckton or @DanielRosenwasser But if //test2.ts
let x = { y: 1, x: 0 }
let v = 0;
Object.defineProperty(x, "x", {
get() {
return v++;
}
})
export = x;
//usage.ts
import { x, y } from './test2'
let xo = x ?? "NO"
console.log(xo);
let yo = y ?? "NO"
console.log(yo); This will output I would just use the approach babel uses, use a temporary variable always ex. Thoughts ? |
I think "use a temporary unless it's an identifier" is usually the approach we take. |
@DanielRosenwasser Mkay 😕, so ignore the corner case above ? Just to make sure I was clear in what I was trying to say |
Nullish coalescing operator
@dragomirtitian
|
@typescript-bot pack this. |
Hey @Kingwl, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running |
src/compiler/types.ts
Outdated
@@ -1502,6 +1503,7 @@ namespace ts { | |||
export type LogicalOperator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually kind of strange because it's not really a LogicalOperator. Should it be moved out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you be able to update the API baselines?
We'd really like to merge this in by tomorrow for the beta. There seem to be only minor issues (control flow analysis not being entirely correct), but those can be fixed by the RC.
default: | ||
return visitEachChild(node, visitor, context); | ||
} | ||
} | ||
|
||
function createNotNullCondition(node: Expression) { | ||
return createBinary( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since optional chaining uses strict equality, can you switch this to strict equality?
createBinary(
createBinary(
temp
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
createNull()
),
createToken(SyntaxKind.AmpersandAmpersandToken),
createBinary(
temp,
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
createVoidZero()
)
);
@@ -943,7 +943,8 @@ namespace ts { | |||
else { | |||
return node.kind === SyntaxKind.BinaryExpression && ( | |||
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || | |||
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken); | |||
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken || | |||
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??
isn't really a logical operator. This results in control-flow treating the branch as something "truthy" not something defined/undefined.
I'd like to see some tests for control flow.
I’ll update that today |
…cript into nullish-coalescing-operator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some trivial things.
src/compiler/binder.ts
Outdated
@@ -1435,6 +1435,14 @@ namespace ts { | |||
bindCondition(node.right, trueTarget, falseTarget); | |||
} | |||
|
|||
function bindNullishCollasingExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: bindNullishCollasingExpression
-> bindNullishCoalescingExpression
@@ -1461,12 +1469,15 @@ namespace ts { | |||
|
|||
function bindBinaryExpressionFlow(node: BinaryExpression) { | |||
const operator = node.operatorToken.kind; | |||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) { | |||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This if-chain would be easier to read as a switch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to keep this because of so many other code similar to that.
src/compiler/scanner.ts
Outdated
@@ -1828,6 +1831,9 @@ namespace ts { | |||
pos++; | |||
return token = SyntaxKind.GreaterThanToken; | |||
case CharacterCodes.question: | |||
if (text.charCodeAt(pos + 1) === CharacterCodes.question) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: If you re-order the pos++
before the if
, the code become marginally simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to be consistent with the existed code.
# Conflicts: # src/compiler/scanner.ts # src/compiler/transformers/esnext.ts # src/compiler/utilities.ts # tests/baselines/reference/api/tsserverlibrary.d.ts # tests/baselines/reference/api/typescript.d.ts
@Kingwl: Thanks for the contribution! My changes were mostly to merge the control-flow behavior of nullish-coalesce with the control-flow behavior of optional chaining and to resolve merge conflicts so that this could be merged with master for the 3.7 beta. Thanks again for all of the hard work! |
Thank you all for your hard work on this and congrats on the 3.7 release! I have a small question out of curiosity. For let x = (foo !== null && foo !== undefined) ? foo : bar;
// vs
let x = foo != null ? foo : bar; |
|
follow https://tc39.es/proposal-nullish-coalescing/
Fixes #26578
TODO: