Skip to content

Commit 427ab3f

Browse files
committed
Support translating foo! expressions into type asserts.
1 parent ed55add commit 427ab3f

File tree

8 files changed

+51
-11
lines changed

8 files changed

+51
-11
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/decorator/decorator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ DecoratorTest.propDecorators = {
3131
'y': [{ type: annotationDecorator },],
3232
};
3333
__decorate([
34-
decorator,
34+
decorator,
3535
__metadata('design:type', Number)
3636
], DecoratorTest.prototype, "x", void 0);
3737
function DecoratorTest_tsickle_Closure_declarations() {
@@ -50,7 +50,7 @@ function DecoratorTest_tsickle_Closure_declarations() {
5050
let DecoratedClass = class DecoratedClass {
5151
};
5252
DecoratedClass = __decorate([
53-
classDecorator,
53+
classDecorator,
5454
__metadata('design:paramtypes', [])
5555
], DecoratedClass);
5656
function DecoratedClass_tsickle_Closure_declarations() {

test_files/jsx/jsx.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
goog.module('test_files.jsx.jsx'); exports = {}; var module = module || {id: 'test_files/jsx/jsx.js'};let /** @type {!JSX.Element} */ simple = React.createElement("div", null);
22
let /** @type {string} */ hello = 'hello';
3-
let /** @type {!JSX.Element} */ helloDiv = React.createElement("div", null,
4-
hello,
5-
"hello, world",
3+
let /** @type {!JSX.Element} */ helloDiv = React.createElement("div", null,
4+
hello,
5+
"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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
goog.module('test_files.nullable.nullable');var module = module || {id: 'test_files/nullable/nullable.js'};class Primitives {
1+
goog.module('test_files.nullable.nullable'); exports = {}; var module = module || {id: 'test_files/nullable/nullable.js'};class Primitives {
22
}
33
function Primitives_tsickle_Closure_declarations() {
44
/** @type {(null|string)} */
@@ -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)