-
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
Add support for Optional Chaining #33294
Conversation
I've been looking forward to Optional Chaining in TypeScript for a long time! Looking forward to seeing this PR land. :) I'm curious, what was the reason that you decided to emit var _a, _b, _c;
(_a = a) == null ? void 0 : _a.b; // optional property access 'a?.b'
(_b = a) == null ? void 0 : _b[x]; // optional element access 'a?.[x]'
(_c = a) == null ? void 0 : _c(); // optional call 'a?.()' |
This is super exciting 🎉 Any plans to surface a type error if the value is not either |
@SimenB surfacing an error there would only help perfectly typed projects avoid runtime slowdowns, but it would also impose a syntax tax on projects that have to deal with the real world — infinite node_modules that are only partially typed, or where the types aren’t accurate. I wouldn’t want to have to cast I’d support a flag defaulting to off, |
Not at this time, no. Such an error would only be valid under strict mode (since That kind of rule may be better implemented as a lint rule rather than a TypeScript-specific flag. |
Actually, a?.b // optional property access
a?.[x] // optional element access
a?.() // optional call should get transpiled to: var _a, _a2, _a3;
(_a = a) === null || _a === void 0 ? void 0 : _a.b; // optional property access
(_a2 = a) === null || _a2 === void 0 ? void 0 : _a2[x]; // optional element access
(_a3 = a) === null || _a3 === void 0 ? void 0 : _a3(); // optional call |
@ExE-Boss: How does your example differ from my example in the description? |
Yours uses: var _a, _b, _c; // Implies optional access from different variables `a`, `b` and `c`. Whereas mine uses var _a, _a2, _a3; // Implies distinct optional accesses from variable `a`. |
@typescript-bot pack this |
Hey @orta, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running |
tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts
Show resolved
Hide resolved
tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts
Show resolved
Hide resolved
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 see some control flow tests - I'm pretty sure the control flow graph for
const q = Math.random() > 0.5 ? undefined : { b: 12 };
let b: number;
const a = q?.[b = 12, "b"];
b.toFixed(); // should fail under `strict` as `b` is not definitely assigned
needs to be updated.
To use the new PR in playground feature 😊, it seems intelisense has no suggestions declare let x: string | undefined;
let l = x?.length // no intelisense for length |
src/compiler/checker.ts
Outdated
@@ -8779,6 +8788,9 @@ namespace ts { | |||
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : | |||
getReturnTypeFromAnnotation(signature.declaration!) || | |||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration)); | |||
if (signature.isOptionalCall) { |
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 think there's enough of these fields now that isOptionalCall
, hasLiteralTypes
, and hasRestParameter
should probably be combined into a Flags
field of some kind.
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.
Related: instantiateSignature
doesn't propagate this forward, so rather than a direct access, this needs to follow .target
's to check if the uninstantiated signature has a isOptionalCall
field, no?
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.
That is correct.
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.
Also tests with generic type parameters on the signature being chained on, eg
declare function absorb<T>(): T;
declare const a: { m?<T>(obj: {x: T}): T } | undefined;
const n1: number = a?.m?({x: 12)}); // should be an error (`undefined` is not assignable to `number`)
const n2: number = a?.m?({x: absorb()}); // likewise
const n3: number | undefined = a?.m?({x: 12)}); // should be ok
const n4: number | undefined = a?.m?({x: absorb()}); // likewise
// Also a test showing `!` vs `?` for good measure
let t1 = a?.m?({x: 12});
t1 = a!.m!({x: 12});
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.
Found a crash in completions. In createCompletionEntry
, when converting property access to element access, there’s a line that searches the property access for a DotToken
and continues under the assumption that it must exist.
TypeError: Cannot read property 'getStart' of undefined
at createCompletionEntry (tsserver.js:103011:67)
at getCompletionEntriesFromSymbols (tsserver.js:103078:29)
at completionInfoFromData (tsserver.js:102945:17)
at Object.getCompletionsAtPosition (tsserver.js:102900:28)
96a91c9
to
0828674
Compare
@typescript-bot perf test this |
Heya @rbuckton, I've started to run the perf test suite on this PR at 0828674. You can monitor the build here. It should now contribute to this PR's status checks. Update: The results are in! |
@rbuckton Here they are:Comparison Report - master..33294
System
Hosts
Scenarios
|
# Conflicts: # src/compiler/checker.ts # src/compiler/diagnosticMessages.json
❤️ |
Hello, I'm not sure were to leave it, but there are some syntax problems in target code: |
@ekfn the TS version there is from an earlier commit in this PR. Wait sorry, it looks like the playground doesn’t have the new nightly yet. Here’s the commit where it was fixed, at any rate: e073c05 And here’s a screenshot of it working in master: |
@andrewbranch now it works on playground too. |
For me, the playground does show a syntax error, but it’s because it starts to parse as a conditional expression: Subtle difference, but you can tell because the JavaScript emit in the right pane emits a conditional expression with empty true and false sides, and none of the |
Sorry, my comment was that nightly works. I understand that the last nightly have this PR and #32883 included (or maybe it loads the previous nightly). Something its wrong because it try to parse Edit: locally with 3.7.0-dev.20191001 I see the same errors as on playground. |
3.7.0-dev.20191001, shown here, definitely works, but for me loading Nightly on the playground is clearly loading an older build. I’m not sure how to tell for sure what build it is. /cc @orta? |
Tested again locally and it works both (this PR and #32883). Probably I was using another version. |
Such as
If you are using @babel/plugin-proposal-optional-chaining, you can get as |
Optional chaining has now reached state 4: https://twitter.com/drosenwasser/status/1202310742436761600. Note, the README hasn't been updated yet. |
根据提供的PR:(microsoft/TypeScript#33294) 正确拼写:Optional Chaining。 并将其翻译为「可选链」
根据提供的PR:(microsoft/TypeScript#33294) 正确拼写:Optional Chaining。 并将其翻译为「可选链」
This PR adds support for the ECMAScript Optional Chaining proposal which is now at stage 3.
Optional Expressions
An optional expression is an expression involving a property access, element access, or call, in which the expression is guarded against
null
andundefined
through the use of the?.
token:In these cases, when
a
is eithernull
orundefined
, the result of the expression becomesundefined
. When this happens, any side effects to the right of the?.
operator are not evaluated.When emit down-level, the above expressions are translated into the following JavaScript:
Optional Chains
An optional chain is an optional expression followed by one or more subsequent regular property access, element access, or call argument lists:
When
a
is eithernull
orundefined
above, the rest of the chain is also not evaluated. However, ifa
is any other value, the expression is evaluated as normal. In the above cases, ifa.b
were eithernull
orundefined
, the expression would still throw as the?.
token only guards the value ofa
.When emit down-level, the above expressions are translated into the following JavaScript:
Optional Chaining and Generics
In addition to supporting
?.(
for an optional call, we also support supplying generic type arguments to an optional call:Optional Chaining and the Type System
When running outside of
strictNullChecks
, optional chaining should have no observable effect on the type system, asnull
andundefined
in most cases are removed or widened toany
.When running inside of
strictNullChecks
, optional chaining introduces a special internaloptional
marker type. Theoptional
type is a special case of theundefined
type, and is treated asundefined
for most type checking operations. Whenever the type checker encounters an optional expression, thenull
andundefined
types are removed from the type of the expression and theoptional
type is added in its place. In this way, an optional chain can detect whether its preceding expression's type introduced anull
orundefined
in any step of the chain to the right of the optional expression.For example:
In the first case, the expression
a?.b?.c
is safe to evaluate as both thea
variable and theb
property are guarded. In the second case, we can determine that it is not safe to evaluate thec
property ona?.b
as theb
property is possiblyundefined
.Fixes #16