Skip to content

Commit 02a2d1b

Browse files
committed
Support translating foo! expressions into type asserts.
1 parent ea76d3d commit 02a2d1b

File tree

7 files changed

+45
-5
lines changed

7 files changed

+45
-5
lines changed

src/tsickle.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,12 @@ class ClosureRewriter extends Rewriter {
310310
}
311311

312312
/** Emits a type annotation in JSDoc, or {?} if the type is unavailable. */
313-
emitJSDocType(node: ts.Node, additionalDocTag?: string) {
313+
emitJSDocType(node: ts.Node, additionalDocTag?: string, type?: ts.Type) {
314314
this.emit(' /**');
315315
if (additionalDocTag) {
316316
this.emit(' ' + additionalDocTag);
317317
}
318-
this.emit(` @type {${this.typeToClosure(node)}} */`);
318+
this.emit(` @type {${this.typeToClosure(node, type)}} */`);
319319
}
320320

321321
/**
@@ -532,10 +532,30 @@ class Annotator extends ClosureRewriter {
532532
// When TypeScript emits JS, it removes one layer of "redundant"
533533
// parens, but we need them for the Closure type assertion. Work
534534
// around this by using two parens. See test_files/coerce.*.
535+
// TODO: the comment is currently dropped from pure assignments due to
536+
// https://github.com/Microsoft/TypeScript/issues/9873
535537
this.emit('((');
536538
this.writeNode(node);
537539
this.emit('))');
538540
return true;
541+
case ts.SyntaxKind.NonNullExpression:
542+
const nnexpr = node as ts.NonNullExpression;
543+
let type = this.program.getTypeChecker().getTypeAtLocation(nnexpr.expression);
544+
if (type.flags & ts.TypeFlags.Union) {
545+
const nonNullUnion =
546+
(type as ts.UnionType)
547+
.types.filter(
548+
t => (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0);
549+
const typeCopy = Object.assign({}, type as ts.UnionType);
550+
typeCopy.types = nonNullUnion;
551+
type = typeCopy;
552+
}
553+
this.emitJSDocType(nnexpr, undefined, type);
554+
// See comment above.
555+
this.emit('((');
556+
this.writeNode(nnexpr.expression);
557+
this.emit('))');
558+
return true;
539559
default:
540560
break;
541561
}

src/type-translator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class TypeTranslator {
272272
return '?';
273273
} else if (type.flags & ts.TypeFlags.Union) {
274274
let unionType = type as ts.UnionType;
275-
let parts = unionType.types.map(t => this.translate(t, notNull));
275+
let parts = unionType.types.map(t => this.translate(t, true));
276276
// In union types that include boolean literal and other literals can
277277
// end up repeating the same closure type. For example: true | boolean
278278
// will be translated to boolean | boolean. Remove duplicates to produce

test_files/jsx/jsx.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ let /** @type {!JSX.Element} */ helloDiv = React.createElement("div", null,
44
hello,
55
"hello, world",
66
React.createElement(Component, null));
7-
React.render(helloDiv, document.body);
7+
React.render(helloDiv, /** @type {!HTMLElement} */ ((document.body)));

test_files/jsx/jsx.tsickle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ let /** @type {!JSX.Element} */ helloDiv = <div>
2626
<Component/>
2727
</div>;
2828

29-
React.render(helloDiv, document.body!);
29+
React.render(helloDiv, /** @type {!HTMLElement} */(( document.body)));
3030

test_files/nullable/nullable.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@ function NonPrimitives_tsickle_Closure_declarations() {
2626
/** @type {(undefined|!NonPrimitive)} */
2727
NonPrimitives.prototype.optional;
2828
}
29+
/**
30+
* @param {(string|number)} val
31+
* @return {void}
32+
*/
33+
function takesNonNullable(val) { }
34+
let /** @type {{field: (null|string|number)}} */ x = { field: null };
35+
takesNonNullable(/** @type {(string|number)} */ ((x.field)));

test_files/nullable/nullable.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ class NonPrimitives {
1515
nullableUndefinable: NonPrimitive|null|undefined;
1616
optional?: NonPrimitive;
1717
}
18+
19+
function takesNonNullable(val: string|number) {}
20+
21+
let x: {field: string | null | number} = {field: null};
22+
takesNonNullable(x.field!);

test_files/nullable/nullable.tsickle.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,11 @@ NonPrimitives.prototype.nullableUndefinable;
4141
NonPrimitives.prototype.optional;
4242
}
4343

44+
/**
45+
* @param {(string|number)} val
46+
* @return {void}
47+
*/
48+
function takesNonNullable(val: string|number) {}
49+
50+
let /** @type {{field: (null|string|number)}} */ x: {field: string | null | number} = {field: null};
51+
takesNonNullable( /** @type {(string|number)} */((x.field)));

0 commit comments

Comments
 (0)