Implement non-null assertions (#443)

This commit is contained in:
Daniel Wirtz
2019-01-30 09:56:13 +01:00
committed by GitHub
parent 2fe228ff00
commit d843772314
11 changed files with 413 additions and 32 deletions

View File

@ -116,10 +116,11 @@ export function nodeIsConstantValue(kind: NodeKind): bool {
export function nodeIsCallable(kind: NodeKind): bool {
switch (kind) {
case NodeKind.IDENTIFIER:
case NodeKind.ASSERTION: // if kind=NONNULL
case NodeKind.CALL:
case NodeKind.ELEMENTACCESS:
case NodeKind.PROPERTYACCESS:
case NodeKind.PARENTHESIZED: return true;
case NodeKind.PARENTHESIZED:
case NodeKind.PROPERTYACCESS: return true;
}
return false;
}
@ -286,14 +287,14 @@ export abstract class Node {
static createAssertionExpression(
assertionKind: AssertionKind,
expression: Expression,
toType: CommonTypeNode,
toType: CommonTypeNode | null,
range: Range
): AssertionExpression {
var expr = new AssertionExpression();
expr.range = range;
expr.assertionKind = assertionKind;
expr.expression = expression; expression.parent = expr;
expr.toType = toType; toType.parent = expr;
expr.toType = toType; if (toType) toType.parent = expr;
return expr;
}
@ -1282,7 +1283,8 @@ export class ArrayLiteralExpression extends LiteralExpression {
/** Indicates the kind of an assertion. */
export enum AssertionKind {
PREFIX,
AS
AS,
NONNULL
}
/** Represents an assertion expression. */
@ -1294,7 +1296,7 @@ export class AssertionExpression extends Expression {
/** Expression being asserted. */
expression: Expression;
/** Target type. */
toType: CommonTypeNode;
toType: CommonTypeNode | null;
}
/** Represents a binary expression. */

View File

@ -91,6 +91,7 @@ import {
Source,
Range,
DecoratorKind,
AssertionKind,
Statement,
BlockStatement,
@ -2704,12 +2705,25 @@ export class Compiler extends DiagnosticEmitter {
}
compileAssertionExpression(expression: AssertionExpression, contextualType: Type): ExpressionRef {
var toType = this.resolver.resolveType( // reports
expression.toType,
this.currentFunction.flow.contextualTypeArguments
);
if (!toType) return this.module.createUnreachable();
return this.compileExpression(expression.expression, toType, ConversionKind.EXPLICIT, WrapMode.NONE);
switch (expression.assertionKind) {
case AssertionKind.PREFIX:
case AssertionKind.AS: {
let toType = this.resolver.resolveType( // reports
assert(expression.toType),
this.currentFunction.flow.contextualTypeArguments
);
if (!toType) return this.module.createUnreachable();
return this.compileExpression(expression.expression, toType, ConversionKind.EXPLICIT, WrapMode.NONE);
}
case AssertionKind.NONNULL: {
assert(!expression.toType);
let expr = this.compileExpressionRetainType(expression.expression, contextualType, WrapMode.NONE);
this.currentType = this.currentType.nonNullableType;
return expr;
}
default: assert(false);
}
return this.module.createUnreachable();
}
private f32ModInstance: Function | null = null;

View File

@ -477,15 +477,26 @@ export class ASTBuilder {
visitAssertionExpression(node: AssertionExpression): void {
var sb = this.sb;
if (node.assertionKind == AssertionKind.PREFIX) {
sb.push("<");
this.visitTypeNode(node.toType);
sb.push(">");
this.visitNode(node.expression);
} else {
this.visitNode(node.expression);
sb.push(" as ");
this.visitTypeNode(node.toType);
switch (node.assertionKind) {
case AssertionKind.PREFIX: {
sb.push("<");
this.visitTypeNode(assert(node.toType));
sb.push(">");
this.visitNode(node.expression);
break;
}
case AssertionKind.AS: {
this.visitNode(node.expression);
sb.push(" as ");
this.visitTypeNode(assert(node.toType));
break;
}
case AssertionKind.NONNULL: {
this.visitNode(node.expression);
sb.push("!");
break;
}
default: assert(false);
}
}

View File

@ -3472,6 +3472,15 @@ export class Parser extends DiagnosticEmitter {
);
break;
}
case Token.EXCLAMATION: {
expr = Node.createAssertionExpression(
AssertionKind.NONNULL,
expr,
null,
tn.range(startPos, tn.pos)
);
break;
}
// InstanceOfExpression
case Token.INSTANCEOF: {
let isType = this.parseType(tn); // reports
@ -3835,7 +3844,8 @@ function determinePrecedence(kind: Token): Precedence {
case Token.MINUS_MINUS: return Precedence.UNARY_POSTFIX;
case Token.DOT:
case Token.NEW:
case Token.OPENBRACKET: return Precedence.MEMBERACCESS;
case Token.OPENBRACKET:
case Token.EXCLAMATION: return Precedence.MEMBERACCESS;
}
return Precedence.NONE;
}

View File

@ -48,7 +48,8 @@ import {
Expression,
IntegerLiteralExpression,
UnaryPrefixExpression,
UnaryPostfixExpression
UnaryPostfixExpression,
AssertionKind
} from "./ast";
import {
@ -685,17 +686,29 @@ export class Resolver extends DiagnosticEmitter {
}
switch (expression.kind) {
case NodeKind.ASSERTION: {
if ((<AssertionExpression>expression).assertionKind == AssertionKind.NONNULL) {
return this.resolveExpression(
(<AssertionExpression>expression).expression,
contextualFunction,
contextualType,
reportMode
);
}
let type = this.resolveType(
(<AssertionExpression>expression).toType,
assert((<AssertionExpression>expression).toType),
contextualFunction.flow.contextualTypeArguments,
reportMode
);
if (!type) return null;
let classType = type.classReference;
if (!classType) return null;
let element: Element | null = type.classReference;
if (!element) {
let signature = type.signatureReference;
if (!signature) return null;
element = signature.asFunctionTarget(this.program);
}
this.currentThisExpression = null;
this.currentElementExpression = null;
return classType;
return element;
}
case NodeKind.UNARYPREFIX: {
// TODO: overloads
@ -885,11 +898,7 @@ export class Resolver extends DiagnosticEmitter {
} else {
let signature = returnType.signatureReference;
if (signature) {
let functionTarget = signature.cachedFunctionTarget;
if (!functionTarget) {
functionTarget = new FunctionTarget(this.program, signature);
signature.cachedFunctionTarget = functionTarget;
}
let functionTarget = signature.asFunctionTarget(this.program);
// reuse resolvedThisExpression (might be property access)
// reuse resolvedElementExpression (might be element access)
return functionTarget;

View File

@ -526,6 +526,13 @@ export class Signature {
this.type = Type.u32.asFunction(this);
}
asFunctionTarget(program: Program): FunctionTarget {
var target = this.cachedFunctionTarget;
if (!target) this.cachedFunctionTarget = target = new FunctionTarget(program, this);
else assert(target.program == program);
return target;
}
/** Gets the known or, alternatively, generic parameter name at the specified index. */
getParameterName(index: i32): string {
var parameterNames = this.parameterNames;