feat(compiler): support a non-null postfix assert (#16672)

Template expressions can now use a post-fix `!` operator
that asserts the target of the operator is not null. This is
similar to the TypeScript non-null assert operator. Expressions
generated in factories will be generated with the non-null assert
operator.

Closes: #10855
This commit is contained in:
Chuck Jazdzewski
2017-05-11 10:15:54 -07:00
committed by Jason Aden
parent 2eca6e67e1
commit b9521b568f
16 changed files with 122 additions and 5 deletions

View File

@ -348,6 +348,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
return convertToStatementIfNeeded(mode, o.not(this._visit(ast.expression, _Mode.Expression)));
}
visitNonNullAssert(ast: cdAst.NonNullAssert, mode: _Mode): any {
return convertToStatementIfNeeded(
mode, o.assertNotNull(this._visit(ast.expression, _Mode.Expression)));
}
visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) {
@ -509,6 +514,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitMethodCall(ast: cdAst.MethodCall) { return visit(this, ast.receiver); },
visitPipe(ast: cdAst.BindingPipe) { return null; },
visitPrefixNot(ast: cdAst.PrefixNot) { return null; },
visitNonNullAssert(ast: cdAst.NonNullAssert) { return null; },
visitPropertyRead(ast: cdAst.PropertyRead) { return visit(this, ast.receiver); },
visitPropertyWrite(ast: cdAst.PropertyWrite) { return null; },
visitQuote(ast: cdAst.Quote) { return null; },
@ -547,6 +553,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitMethodCall(ast: cdAst.MethodCall) { return true; },
visitPipe(ast: cdAst.BindingPipe) { return true; },
visitPrefixNot(ast: cdAst.PrefixNot) { return visit(this, ast.expression); },
visitNonNullAssert(ast: cdAst.PrefixNot) { return visit(this, ast.expression); },
visitPropertyRead(ast: cdAst.PropertyRead) { return false; },
visitPropertyWrite(ast: cdAst.PropertyWrite) { return false; },
visitQuote(ast: cdAst.Quote) { return false; },

View File

@ -166,6 +166,13 @@ export class PrefixNot extends AST {
}
}
export class NonNullAssert extends AST {
constructor(span: ParseSpan, public expression: AST) { super(span); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitNonNullAssert(this, context);
}
}
export class MethodCall extends AST {
constructor(span: ParseSpan, public receiver: AST, public name: string, public args: any[]) {
super(span);
@ -222,6 +229,7 @@ export interface AstVisitor {
visitMethodCall(ast: MethodCall, context: any): any;
visitPipe(ast: BindingPipe, context: any): any;
visitPrefixNot(ast: PrefixNot, context: any): any;
visitNonNullAssert(ast: NonNullAssert, context: any): any;
visitPropertyRead(ast: PropertyRead, context: any): any;
visitPropertyWrite(ast: PropertyWrite, context: any): any;
visitQuote(ast: Quote, context: any): any;
@ -230,7 +238,7 @@ export interface AstVisitor {
visit?(ast: AST, context?: any): any;
}
export class NullAstVisitor {
export class NullAstVisitor implements AstVisitor {
visitBinary(ast: Binary, context: any): any {}
visitChain(ast: Chain, context: any): any {}
visitConditional(ast: Conditional, context: any): any {}
@ -245,6 +253,7 @@ export class NullAstVisitor {
visitMethodCall(ast: MethodCall, context: any): any {}
visitPipe(ast: BindingPipe, context: any): any {}
visitPrefixNot(ast: PrefixNot, context: any): any {}
visitNonNullAssert(ast: NonNullAssert, context: any): any {}
visitPropertyRead(ast: PropertyRead, context: any): any {}
visitPropertyWrite(ast: PropertyWrite, context: any): any {}
visitQuote(ast: Quote, context: any): any {}
@ -303,6 +312,10 @@ export class RecursiveAstVisitor implements AstVisitor {
ast.expression.visit(this);
return null;
}
visitNonNullAssert(ast: NonNullAssert, context: any): any {
ast.expression.visit(this);
return null;
}
visitPropertyRead(ast: PropertyRead, context: any): any {
ast.receiver.visit(this);
return null;
@ -379,6 +392,10 @@ export class AstTransformer implements AstVisitor {
return new PrefixNot(ast.span, ast.expression.visit(this));
}
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
return new NonNullAssert(ast.span, ast.expression.visit(this));
}
visitConditional(ast: Conditional, context: any): AST {
return new Conditional(
ast.span, ast.condition.visit(this), ast.trueExp.visit(this), ast.falseExp.visit(this));
@ -461,6 +478,7 @@ export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
visitAll(ast.args);
},
visitPrefixNot(ast) { visit(ast.expression); },
visitNonNullAssert(ast) { visit(ast.expression); },
visitPropertyRead(ast) { visit(ast.receiver); },
visitPropertyWrite(ast) {
visit(ast.receiver);

View File

@ -10,7 +10,8 @@ import * as chars from '../chars';
import {CompilerInjectable} from '../injectable';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
import {escapeRegExp} from '../util';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
import {EOF, Lexer, Token, TokenType, isIdentifier, isQuote} from './lexer';
@ -523,6 +524,9 @@ export class _ParseAST {
this.expectCharacter(chars.$RPAREN);
result = new FunctionCall(this.span(result.span.start), result, args);
} else if (this.optionalOperator('!')) {
result = new NonNullAssert(this.span(result.span.start), result);
} else {
return result;
}
@ -811,6 +815,8 @@ class SimpleExpressionChecker implements AstVisitor {
visitPrefixNot(ast: PrefixNot, context: any) {}
visitNonNullAssert(ast: NonNullAssert, context: any) {}
visitConditional(ast: Conditional, context: any) {}
visitPipe(ast: BindingPipe, context: any) { this.errors.push('pipes'); }

View File

@ -344,6 +344,10 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
ast.condition.visitExpression(this, ctx);
return null;
}
visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: EmitterVisitorContext): any {
ast.condition.visitExpression(this, ctx);
return null;
}
abstract visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any;
abstract visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, context: any): any;

View File

@ -377,6 +377,15 @@ export class NotExpr extends Expression {
}
}
export class AssertNotNull extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(condition.type, sourceSpan);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitAssertNotNullExpr(this, context);
}
}
export class CastExpr extends Expression {
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
@ -503,6 +512,7 @@ export interface ExpressionVisitor {
visitExternalExpr(ast: ExternalExpr, context: any): any;
visitConditionalExpr(ast: ConditionalExpr, context: any): any;
visitNotExpr(ast: NotExpr, context: any): any;
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any;
visitCastExpr(ast: CastExpr, context: any): any;
visitFunctionExpr(ast: FunctionExpr, context: any): any;
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any;
@ -768,6 +778,11 @@ export class AstTransformer implements StatementVisitor, ExpressionVisitor {
new NotExpr(ast.condition.visitExpression(this, context), ast.sourceSpan), context);
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any {
return this.transformExpr(
new AssertNotNull(ast.condition.visitExpression(this, context), ast.sourceSpan), context);
}
visitCastExpr(ast: CastExpr, context: any): any {
return this.transformExpr(
new CastExpr(ast.value.visitExpression(this, context), ast.type, ast.sourceSpan), context);
@ -948,6 +963,10 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
ast.condition.visitExpression(this, context);
return ast;
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any {
ast.condition.visitExpression(this, context);
return ast;
}
visitCastExpr(ast: CastExpr, context: any): any {
ast.value.visitExpression(this, context);
return ast;
@ -1139,6 +1158,11 @@ export function not(expr: Expression, sourceSpan?: ParseSourceSpan | null): NotE
return new NotExpr(expr, sourceSpan);
}
export function assertNotNull(
expr: Expression, sourceSpan?: ParseSourceSpan | null): AssertNotNull {
return new AssertNotNull(expr, sourceSpan);
}
export function fn(
params: FnParam[], body: Statement[], type?: Type | null,
sourceSpan?: ParseSourceSpan | null): FunctionExpr {

View File

@ -233,6 +233,9 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
visitNotExpr(ast: o.NotExpr, ctx: _ExecutionContext): any {
return !ast.condition.visitExpression(this, ctx);
}
visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: _ExecutionContext): any {
return ast.condition.visitExpression(this, ctx);
}
visitCastExpr(ast: o.CastExpr, ctx: _ExecutionContext): any {
return ast.value.visitExpression(this, ctx);
}

View File

@ -129,6 +129,12 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
return null;
}
visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: EmitterVisitorContext): any {
const result = super.visitAssertNotNullExpr(ast, ctx);
ctx.print(ast, '!');
return result;
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
if (ctx.isExportedVar(stmt.name) && stmt.value instanceof o.ExternalExpr && !stmt.type) {
// check for a reexport