diff --git a/src/compiler.ts b/src/compiler.ts index f2eaac1bba..a79bfdf889 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -90,7 +90,7 @@ import { PropertyPrototype, IndexSignature, File, - mangleInternalName + mangleInternalName, } from "./program"; import { @@ -99,7 +99,8 @@ import { LocalFlags, FieldFlags, ConditionKind, - findUsedLocals + findUsedLocals, + invertedCondition } from "./flow"; import { @@ -213,6 +214,7 @@ import { import { ShadowStackPass } from "./passes/shadowstack"; +import { typeOr } from "./narrow"; import { liftRequiresExportRuntime, @@ -2524,8 +2526,7 @@ export class Compiler extends DiagnosticEmitter { this.currentFlow = flow; // Compile the body assuming the condition turned out true - var bodyFlow = flow.fork(); - bodyFlow.inheritNonnullIfTrue(condExpr); + var bodyFlow = flow.forkTrueBranch(condExpr); this.currentFlow = bodyFlow; var bodyStmts = new Array(); var body = statement.statement; @@ -2668,9 +2669,9 @@ export class Compiler extends DiagnosticEmitter { // Compile ifTrue assuming the condition turned out true var thenStmts = new Array(); - var thenFlow = flow.fork(); + var thenFlow = flow.forkTrueBranch(condExpr); this.currentFlow = thenFlow; - thenFlow.inheritNonnullIfTrue(condExpr); + if (ifTrue.kind == NodeKind.BLOCK) { this.compileStatements((ifTrue).statements, false, thenStmts); } else { @@ -2686,9 +2687,8 @@ export class Compiler extends DiagnosticEmitter { // Compile ifFalse assuming the condition turned out false, if present if (ifFalse) { let elseStmts = new Array(); - let elseFlow = flow.fork(); + let elseFlow = flow.forkFalseBranch(condExpr); this.currentFlow = elseFlow; - elseFlow.inheritNonnullIfFalse(condExpr); if (ifFalse.kind == NodeKind.BLOCK) { this.compileStatements((ifFalse).statements, false, elseStmts); } else { @@ -2706,12 +2706,8 @@ export class Compiler extends DiagnosticEmitter { module.flatten(elseStmts) ); } else { + flow.inheritNarrowedTypeIfFalse(condExpr); flow.inheritBranch(thenFlow); - flow.inheritNonnullIfFalse(condExpr, - thenFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS) - ? null // thenFlow terminates: just inherit - : thenFlow // must become nonnull in thenFlow otherwise - ); return module.if(condExpr, module.flatten(thenStmts) ); @@ -2943,6 +2939,7 @@ export class Compiler extends DiagnosticEmitter { let name = declaration.name.text; let type: Type | null = null; let initExpr: ExpressionRef = 0; + let initType: Type | null = null; // Resolve type if annotated let typeNode = declaration.type; @@ -2965,6 +2962,8 @@ export class Compiler extends DiagnosticEmitter { ); pendingElements.delete(dummy); flow.freeScopedDummyLocal(name); + + initType = this.currentType; } // Otherwise infer type from initializer @@ -2984,6 +2983,7 @@ export class Compiler extends DiagnosticEmitter { continue; } type = this.currentType; + initType = this.currentType; // Error if there's neither a type nor an initializer } else { @@ -3106,7 +3106,7 @@ export class Compiler extends DiagnosticEmitter { } if (initExpr) { initializers.push( - this.makeLocalAssignment(local, initExpr, type, false) + this.makeLocalAssignment(local, initExpr, initType || type, false) ); } else { // no need to assign zero @@ -3205,8 +3205,7 @@ export class Compiler extends DiagnosticEmitter { this.currentFlow = flow; // Compile the body assuming the condition turned out true - var bodyFlow = flow.fork(); - bodyFlow.inheritNonnullIfTrue(condExpr); + var bodyFlow = flow.forkTrueBranch(condExpr); this.currentFlow = bodyFlow; var bodyStmts = new Array(); var body = statement.statement; @@ -4570,9 +4569,8 @@ export class Compiler extends DiagnosticEmitter { leftExpr = this.compileExpression(left, contextualType.exceptVoid, inheritedConstraints); leftType = this.currentType; - let rightFlow = flow.fork(); + let rightFlow = flow.forkTrueBranch(leftExpr); this.currentFlow = rightFlow; - rightFlow.inheritNonnullIfTrue(leftExpr); // simplify if only interested in true or false if (contextualType == Type.bool || contextualType == Type.void) { @@ -4595,12 +4593,14 @@ export class Compiler extends DiagnosticEmitter { expr = module.if(leftExpr, rightExpr, module.i32(0)); } } + flow.inheritBranch(rightFlow, condKind); this.currentFlow = flow; this.currentType = Type.bool; } else { rightExpr = this.compileExpression(right, leftType, inheritedConstraints | Constraints.CONV_IMPLICIT); rightType = this.currentType; + flow.inheritBranch(rightFlow); rightFlow.freeScopedLocals(); this.currentFlow = flow; @@ -4616,7 +4616,7 @@ export class Compiler extends DiagnosticEmitter { } else { let tempLocal = flow.getTempLocal(leftType); if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.WRAPPED); - if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.NONNULL); + if (flow.isNonnull(leftExpr, leftType)) flow.setNarrowedType(tempLocal, leftType.nonNullableType); expr = module.if( this.makeIsTrueish(module.local_tee(tempLocal.index, leftExpr, leftType.isManaged), leftType, left), rightExpr, @@ -4634,9 +4634,8 @@ export class Compiler extends DiagnosticEmitter { leftExpr = this.compileExpression(left, contextualType.exceptVoid, inheritedConstraints); leftType = this.currentType; - let rightFlow = flow.fork(); + let rightFlow = flow.forkFalseBranch(leftExpr); this.currentFlow = rightFlow; - rightFlow.inheritNonnullIfFalse(leftExpr); // simplify if only interested in true or false if (contextualType == Type.bool || contextualType == Type.void) { @@ -4659,12 +4658,14 @@ export class Compiler extends DiagnosticEmitter { expr = module.if(leftExpr, module.i32(1), rightExpr); } } + flow.inheritBranch(rightFlow, invertedCondition(condKind)); this.currentFlow = flow; this.currentType = Type.bool; } else { rightExpr = this.compileExpression(right, leftType, inheritedConstraints | Constraints.CONV_IMPLICIT); rightType = this.currentType; + flow.inheritBranch(rightFlow); rightFlow.freeScopedLocals(); this.currentFlow = flow; @@ -4680,7 +4681,7 @@ export class Compiler extends DiagnosticEmitter { } else { let temp = flow.getTempLocal(leftType); if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); - if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.NONNULL); + if (flow.isNonnull(leftExpr, leftType)) flow.setNarrowedType(temp, leftType.nonNullableType); expr = module.if( this.makeIsTrueish(module.local_tee(temp.index, leftExpr, leftType.isManaged), leftType, left), module.local_get(temp.index, leftType.toRef()), @@ -4688,7 +4689,7 @@ export class Compiler extends DiagnosticEmitter { ); flow.freeTempLocal(temp); } - this.currentType = leftType; + this.currentType = typeOr(leftType.nonNullableType, rightType) || rightType; } break; } @@ -5657,6 +5658,7 @@ export class Compiler extends DiagnosticEmitter { assert(targetType != Type.void); var valueExpr = this.compileExpression(valueExpression, targetType); var valueType = this.currentType; + return this.makeAssignment( target, this.convertExpression(valueExpr, valueType, targetType, false, valueExpression), @@ -5886,16 +5888,13 @@ export class Compiler extends DiagnosticEmitter { /** Whether to tee the value. */ tee: bool ): ExpressionRef { + let expressionRef: ExpressionRef; var module = this.module; var flow = this.currentFlow; var type = local.type; assert(type != Type.void); var localIndex = local.index; - if (type.isNullableReference) { - if (!valueType.isNullableReference || flow.isNonnull(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.NONNULL); - else flow.unsetLocalFlag(localIndex, LocalFlags.NONNULL); - } flow.setLocalFlag(localIndex, LocalFlags.INITIALIZED); if (type.isShortIntegerValue) { if (!flow.canOverflow(valueExpr, type)) flow.setLocalFlag(localIndex, LocalFlags.WRAPPED); @@ -5903,11 +5902,19 @@ export class Compiler extends DiagnosticEmitter { } if (tee) { // local = value this.currentType = type; - return module.local_tee(localIndex, valueExpr, type.isManaged); + expressionRef = module.local_tee(localIndex, valueExpr, type.isManaged); } else { // void(local = value) this.currentType = Type.void; - return module.local_set(localIndex, valueExpr, type.isManaged); + expressionRef = module.local_set(localIndex, valueExpr, type.isManaged); } + if (type.isReference) { + let narrowedType = valueType; + if (!valueType.isNullableReference || flow.isNonnull(valueExpr, type)) { + narrowedType = valueType.nonNullableType; + } + flow.setAssignType(expressionRef, local, narrowedType); + } + return expressionRef; } /** Makes an assignment to a global. */ @@ -5925,7 +5932,6 @@ export class Compiler extends DiagnosticEmitter { var type = global.type; assert(type != Type.void); var typeRef = type.toRef(); - valueExpr = this.ensureSmallIntegerWrap(valueExpr, type); // globals must be wrapped if (tee) { // (global = value), global this.currentType = type; @@ -6499,7 +6505,7 @@ export class Compiler extends DiagnosticEmitter { findUsedLocals(paramExpr, usedLocals); // inlining is aware of wrap/nonnull states: if (!previousFlow.canOverflow(paramExpr, paramType)) flow.setLocalFlag(argumentLocal.index, LocalFlags.WRAPPED); - if (flow.isNonnull(paramExpr, paramType)) flow.setLocalFlag(argumentLocal.index, LocalFlags.NONNULL); + if (flow.isNonnull(paramExpr, paramType) && paramType.isNullableReference) flow.setNarrowedType(argumentLocal, paramType.nonNullableType); body.unshift( module.local_set(argumentLocal.index, paramExpr, paramType.isManaged) ); @@ -7492,7 +7498,7 @@ export class Compiler extends DiagnosticEmitter { switch (target.kind) { case ElementKind.LOCAL: { let local = target; - let localType = local.type; + let localType = flow.getVariantType(local); assert(localType != Type.void); if (this.pendingElements.has(local)) { this.error( @@ -7508,9 +7514,6 @@ export class Compiler extends DiagnosticEmitter { } let localIndex = local.index; assert(localIndex >= 0); - if (localType.isNullableReference && flow.isLocalFlag(localIndex, LocalFlags.NONNULL, false)) { - localType = localType.nonNullableType; - } this.currentType = localType; if (target.parent != flow.parentFunction) { @@ -7529,6 +7532,7 @@ export class Compiler extends DiagnosticEmitter { if (!this.compileGlobal(global)) { // reports; not yet compiled if a static field return module.unreachable(); } + // TODO: global type narrow let globalType = global.type; if (this.pendingElements.has(global)) { this.error( @@ -7639,7 +7643,14 @@ export class Compiler extends DiagnosticEmitter { this.currentType = Type.bool; return this.module.unreachable(); } - return this.makeInstanceofType(expression, expectedType); + let instanceExpression = this.makeInstanceofType(expression, expectedType); + if (expression.expression.kind == NodeKind.IDENTIFIER) { + let element = flow.lookup((expression.expression).text); + if (element instanceof Local) { + flow.setConditionNarrowedType(instanceExpression, element, expectedType); + } + } + return instanceExpression; } private makeInstanceofType(expression: InstanceOfExpression, expectedType: Type): ExpressionRef { @@ -8999,14 +9010,12 @@ export class Compiler extends DiagnosticEmitter { } var outerFlow = this.currentFlow; - var ifThenFlow = outerFlow.fork(); - ifThenFlow.inheritNonnullIfTrue(condExpr); + var ifThenFlow = outerFlow.forkTrueBranch(condExpr); this.currentFlow = ifThenFlow; var ifThenExpr = this.compileExpression(ifThen, ctxType); var ifThenType = this.currentType; - var ifElseFlow = outerFlow.fork(); - ifElseFlow.inheritNonnullIfFalse(condExpr); + var ifElseFlow = outerFlow.forkFalseBranch(condExpr); this.currentFlow = ifElseFlow; var ifElseExpr = this.compileExpression(ifElse, ctxType == Type.auto ? ifThenType : ctxType); var ifElseType = this.currentType; @@ -10345,7 +10354,7 @@ export class Compiler extends DiagnosticEmitter { var flow = this.currentFlow; var temp = flow.getTempLocal(type); if (!flow.canOverflow(expr, type)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); - flow.setLocalFlag(temp.index, LocalFlags.NONNULL); + flow.setNarrowedType(temp, type); var staticAbortCallExpr = this.makeStaticAbort( this.ensureStaticString("unexpected null"), diff --git a/src/flow.ts b/src/flow.ts index 12f8e0ea16..c88d361ac3 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -63,12 +63,6 @@ import { getSelectElse, getCallTarget, getLocalSetIndex, - getIfCondition, - getUnaryValue, - getCallOperandAt, - getCallOperandCount, - isConstZero, - isConstNonZero } from "./module"; import { @@ -89,8 +83,9 @@ import { } from "./util"; import { - BuiltinNames -} from "./builtins"; + NarrowedTypeMap, + TypeNarrowChecker +} from "./narrow"; /** Control flow flags indicating specific conditions. */ export const enum FlowFlags { @@ -171,7 +166,7 @@ export const enum LocalFlags { /** Local is properly wrapped. Relevant for small integers. */ WRAPPED = 1 << 1, /** Local is non-null. */ - NONNULL = 1 << 2, + // NONNULL = 1 << 2, /** Local is initialized. */ INITIALIZED = 1 << 3 } @@ -192,6 +187,12 @@ export const enum ConditionKind { FALSE } +export function invertedCondition(kind: ConditionKind): ConditionKind { + if (kind == ConditionKind.TRUE) return ConditionKind.FALSE; + if (kind == ConditionKind.FALSE) return ConditionKind.TRUE; + return kind; +} + /** A control flow evaluator. */ export class Flow { @@ -217,9 +218,10 @@ export class Flow { private constructor( /** Function this flow belongs to. */ - public parentFunction: Function + public parentFunction: Function, + typeNarrowChecker: TypeNarrowChecker = new TypeNarrowChecker() ) { - /* nop */ + this.typeNarrowChecker = typeNarrowChecker; } /** Parent flow. */ @@ -238,6 +240,11 @@ export class Flow { localFlags: LocalFlags[] = []; /** Field flags on `this`. Constructors only. */ thisFieldFlags: Map | null = null; + /** type narrow */ + narrowedTypes: NarrowedTypeMap | null = null; + /** handle conditional type narrow, eg if (condi) */ + typeNarrowChecker: TypeNarrowChecker; + /** Function being inlined, when inlining. */ inlineFunction: Function | null = null; /** The label we break to when encountering a return statement, when inlining. */ @@ -296,7 +303,7 @@ export class Flow { /** Forks this flow to a child flow. */ fork(resetBreakContext: bool = false): Flow { - var branch = new Flow(this.parentFunction); + var branch = new Flow(this.parentFunction, this.typeNarrowChecker); branch.parent = this; branch.outer = this.outer; if (resetBreakContext) { @@ -312,6 +319,8 @@ export class Flow { branch.breakLabel = this.breakLabel; } branch.localFlags = this.localFlags.slice(); + let narrowedTypes = this.narrowedTypes; + branch.narrowedTypes = narrowedTypes ? narrowedTypes.clone() : null; if (this.actualFunction.is(CommonFlags.CONSTRUCTOR)) { let thisFieldFlags = assert(this.thisFieldFlags); branch.thisFieldFlags = cloneMap(thisFieldFlags); @@ -323,6 +332,18 @@ export class Flow { return branch; } + /** Fork this flow to a child flow in branch */ + forkTrueBranch(condExpr: ExpressionRef, resetBreakContext: bool = false): Flow { + let branch = this.fork(resetBreakContext); + branch.inheritNarrowedTypeIfTrue(condExpr); + return branch; + } + forkFalseBranch(condExpr: ExpressionRef, resetBreakContext: bool = false): Flow { + let branch = this.fork(resetBreakContext); + branch.inheritNarrowedTypeIfFalse(condExpr); + return branch; + } + /** Gets a free temporary local of the specified type. */ getTempLocal(type: Type, except: BitSet | null = null): Local { var parentFunction = this.parentFunction; @@ -352,6 +373,7 @@ export class Flow { temps.length = k; local.type = type; local.flags = CommonFlags.NONE; + this.removeNarrowedType(local); this.unsetLocalFlag(local.index, ~0); return local; } @@ -367,6 +389,7 @@ export class Flow { local = parentFunction.addLocal(type); } } + this.removeNarrowedType(local); this.unsetLocalFlag(local.index, ~0); return local; } @@ -606,6 +629,56 @@ export class Flow { localFlags[index] = flags & ~flag; } + /** set type assign */ + setAssignType(expr: ExpressionRef, element: TypedElement, type: Type): void { + assert(element.kind == ElementKind.LOCAL, "type narrowing only support Local"); + if (type && !type.isReference) return; + this.setNarrowedType(element, type); + this.typeNarrowChecker.setAssignType(expr, element, type); + } + /** set type narrow */ + setNarrowedType(element: TypedElement, type: Type): void { + assert(element.kind == ElementKind.LOCAL, "type narrowing only support Local"); + if (!type.isReference) return; + let narrowedTypes = this.narrowedTypes || new NarrowedTypeMap(); + this.narrowedTypes = narrowedTypes; + narrowedTypes.set(element, type); + } + /** get type narrow, return null if not exist */ + getVariantType(element: TypedElement): Type { + if (element.kind != ElementKind.LOCAL) return element.type; + const narrowedTypes = this.narrowedTypes || new NarrowedTypeMap(); + this.narrowedTypes = narrowedTypes; + return narrowedTypes.get(element) || element.type; + } + /** do not trace `element` type narrow */ + removeNarrowedType(element: TypedElement): void { + assert(element.kind == ElementKind.LOCAL, "type narrowing only support Local"); + let thisNarrowedTypes = this.narrowedTypes; + if (thisNarrowedTypes) thisNarrowedTypes.delete(element); + this.typeNarrowChecker.removeElement(element); + } + + /** set conditional type narrow */ + setConditionNarrowedType(expr: ExpressionRef, element: TypedElement, type: Type): void { + assert(element.kind == ElementKind.LOCAL, "type narrowing only support Local"); + if (type && !type.isReference) return; + this.typeNarrowChecker.setConditionNarrowedType(expr, element, type); + } + + /** take effect conditional type narrow if condition is true */ + inheritNarrowedTypeIfTrue(condi: ExpressionRef): void { + let narrowedTypes = this.narrowedTypes || new NarrowedTypeMap(); + this.narrowedTypes = narrowedTypes; + this.typeNarrowChecker.collectNarrowedTypeIfTrue(condi, this, narrowedTypes); + } + /** take effect conditional type narrow if condition is false */ + inheritNarrowedTypeIfFalse(condi: ExpressionRef): void { + let narrowedTypes = this.narrowedTypes || new NarrowedTypeMap(); + this.narrowedTypes = narrowedTypes; + this.typeNarrowChecker.collectNarrowedTypeIfFalse(condi, this, narrowedTypes); + } + /** Initializes `this` field flags. */ initThisFieldFlags(): void { var actualFunction = this.actualFunction; @@ -707,6 +780,7 @@ export class Flow { this.flags = this.flags | otherFlags; // what happens before is still true this.localFlags = other.localFlags; + this.narrowedTypes = other.narrowedTypes; this.thisFieldFlags = other.thisFieldFlags; } @@ -805,11 +879,19 @@ export class Flow { thisLocalFlags[i] = thisFlags & otherFlags & ( LocalFlags.CONSTANT | LocalFlags.WRAPPED | - LocalFlags.NONNULL | LocalFlags.INITIALIZED ); } + // narrowed types + if (!other.isAny(FlowFlags.BREAKS | FlowFlags.TERMINATES)) { + let thisNarrowedTypes = this.narrowedTypes; + let otherNarrowedTypes = other.narrowedTypes; + if (thisNarrowedTypes && otherNarrowedTypes) { + thisNarrowedTypes.mergeAnd(otherNarrowedTypes); + } + } + // field flags do not matter here since there's only INITIALIZED, which can // only be set if it has been observed prior to entering the branch. } @@ -905,7 +987,7 @@ export class Flow { this.flags = newFlags | (this.flags & (FlowFlags.UNCHECKED_CONTEXT | FlowFlags.CTORPARAM_CONTEXT)); - // local flags + // local flags and type var thisLocalFlags = this.localFlags; if (leftFlags & FlowFlags.TERMINATES) { if (!(rightFlags & FlowFlags.TERMINATES)) { @@ -913,12 +995,16 @@ export class Flow { for (let i = 0, k = rightLocalFlags.length; i < k; ++i) { thisLocalFlags[i] = rightLocalFlags[i]; } + let rightNarrowedTypes = right.narrowedTypes; + this.narrowedTypes = rightNarrowedTypes ? rightNarrowedTypes.clone() : null; } } else if (rightFlags & FlowFlags.TERMINATES) { let leftLocalFlags = left.localFlags; for (let i = 0, k = leftLocalFlags.length; i < k; ++i) { thisLocalFlags[i] = leftLocalFlags[i]; } + let leftNarrowedTypes = left.narrowedTypes; + this.narrowedTypes = leftNarrowedTypes ? leftNarrowedTypes.clone() : null; } else { let leftLocalFlags = left.localFlags; let numLeftLocalFlags = leftLocalFlags.length; @@ -931,10 +1017,18 @@ export class Flow { thisLocalFlags[i] = leftFlags & rightFlags & ( LocalFlags.CONSTANT | LocalFlags.WRAPPED | - LocalFlags.NONNULL | LocalFlags.INITIALIZED ); } + + // narrow type + let leftNarrowedTypes = left.narrowedTypes; + let rightNarrowedTypes = right.narrowedTypes; + if (leftNarrowedTypes && rightNarrowedTypes) { + let narrowedTypes = rightNarrowedTypes.clone(); + narrowedTypes.mergeAnd(leftNarrowedTypes); + this.narrowedTypes = narrowedTypes; + } } // field flags (currently only INITIALIZED, so can simplify) @@ -974,8 +1068,10 @@ export class Flow { return true; } } + let beforeType = before.getVariantType(local); + let afterType = after.getVariantType(local); if (type.isNullableReference) { - if (before.isLocalFlag(i, LocalFlags.NONNULL) && !after.isLocalFlag(i, LocalFlags.NONNULL)) { + if (beforeType != afterType) { return true; } } @@ -991,9 +1087,11 @@ export class Flow { if (this.isLocalFlag(i, LocalFlags.WRAPPED) != other.isLocalFlag(i, LocalFlags.WRAPPED)) { this.unsetLocalFlag(i, LocalFlags.WRAPPED); // assume not wrapped } - if (this.isLocalFlag(i, LocalFlags.NONNULL) != other.isLocalFlag(i, LocalFlags.NONNULL)) { - this.unsetLocalFlag(i, LocalFlags.NONNULL); // assume possibly null - } + } + let thisNarrowedTypes = this.narrowedTypes; + let otherNarrowedTypes = other.narrowedTypes; + if (thisNarrowedTypes && otherNarrowedTypes) { + thisNarrowedTypes.mergeAnd(otherNarrowedTypes); } } @@ -1007,221 +1105,18 @@ export class Flow { case ExpressionId.LocalSet: { if (!isLocalTee(expr)) break; let local = this.parentFunction.localsByIndex[getLocalSetIndex(expr)]; - return !local.type.isNullableReference || this.isLocalFlag(local.index, LocalFlags.NONNULL, false); + let localType = this.getVariantType(local); + return !localType.isNullableReference; } case ExpressionId.LocalGet: { let local = this.parentFunction.localsByIndex[getLocalGetIndex(expr)]; - return !local.type.isNullableReference || this.isLocalFlag(local.index, LocalFlags.NONNULL, false); + let localType = this.getVariantType(local); + return !localType.isNullableReference; } } return false; } - /** Updates local states to reflect that this branch is only taken when `expr` is true-ish. */ - inheritNonnullIfTrue( - /** Expression being true. */ - expr: ExpressionRef, - /** If specified, only set the flag if also nonnull in this flow. */ - iff: Flow | null = null - ): void { - // A: `expr` is true-ish -> Q: how did that happen? - - // The iff argument is useful in situations like - // - // if (!ref) { - // ref = new Ref(); - // } - // // inheritNonnullIfFalse(`!ref`, thenFlow) -> ref != null - // - - switch (getExpressionId(expr)) { - case ExpressionId.LocalSet: { - if (!isLocalTee(expr)) break; - let local = this.parentFunction.localsByIndex[getLocalSetIndex(expr)]; - if (!iff || iff.isLocalFlag(local.index, LocalFlags.NONNULL)) { - this.setLocalFlag(local.index, LocalFlags.NONNULL); - } - this.inheritNonnullIfTrue(getLocalSetValue(expr), iff); // must have been true-ish as well - break; - } - case ExpressionId.LocalGet: { - let local = this.parentFunction.localsByIndex[getLocalGetIndex(expr)]; - if (!iff || iff.isLocalFlag(local.index, LocalFlags.NONNULL)) { - this.setLocalFlag(local.index, LocalFlags.NONNULL); - } - break; - } - case ExpressionId.If: { - let ifFalse = getIfFalse(expr); - if (ifFalse && isConstZero(ifFalse)) { - // Logical AND: (if (condition ifTrue 0)) - // the only way this had become true is if condition and ifTrue are true - this.inheritNonnullIfTrue(getIfCondition(expr), iff); - this.inheritNonnullIfTrue(getIfTrue(expr), iff); - } - break; - } - case ExpressionId.Unary: { - switch (getUnaryOp(expr)) { - case UnaryOp.EqzI32: - case UnaryOp.EqzI64: { - this.inheritNonnullIfFalse(getUnaryValue(expr), iff); // !value -> value must have been false - break; - } - } - break; - } - case ExpressionId.Binary: { - switch (getBinaryOp(expr)) { - case BinaryOp.EqI32: - case BinaryOp.EqI64: { - let left = getBinaryLeft(expr); - let right = getBinaryRight(expr); - if (isConstNonZero(left)) { - this.inheritNonnullIfTrue(right, iff); // TRUE == right -> right must have been true - } else if (isConstNonZero(right)) { - this.inheritNonnullIfTrue(left, iff); // left == TRUE -> left must have been true - } - break; - } - case BinaryOp.NeI32: - case BinaryOp.NeI64: { - let left = getBinaryLeft(expr); - let right = getBinaryRight(expr); - if (isConstZero(left)) { - this.inheritNonnullIfTrue(right, iff); // FALSE != right -> right must have been true - } else if (isConstZero(right)) { - this.inheritNonnullIfTrue(left, iff); // left != FALSE -> left must have been true - } - break; - } - } - break; - } - case ExpressionId.Call: { - // handle string eq/ne/not overloads - let name = getCallTarget(expr); - if (name == BuiltinNames.String_eq) { - assert(getCallOperandCount(expr) == 2); - let left = getCallOperandAt(expr, 0); - let right = getCallOperandAt(expr, 1); - if (isConstNonZero(left)) { - this.inheritNonnullIfTrue(right, iff); // TRUE == right -> right must have been true - } else if (isConstNonZero(right)) { - this.inheritNonnullIfTrue(left, iff); // left == TRUE -> left must have been true - } - } else if (name == BuiltinNames.String_ne) { - assert(getCallOperandCount(expr) == 2); - let left = getCallOperandAt(expr, 0); - let right = getCallOperandAt(expr, 1); - if (isConstZero(left)) { - this.inheritNonnullIfTrue(right, iff); // FALSE != right -> right must have been true - } else if (isConstZero(right)) { - this.inheritNonnullIfTrue(left, iff); // left != FALSE -> left must have been true - } - } else if (name == BuiltinNames.String_not) { - assert(getCallOperandCount(expr) == 1); - this.inheritNonnullIfFalse(getCallOperandAt(expr, 0), iff); // !value -> value must have been false - } else if (name == BuiltinNames.tostack) { - assert(getCallOperandCount(expr) == 1); - this.inheritNonnullIfTrue(getCallOperandAt(expr, 0), iff); - } - break; - } - } - } - - /** Updates local states to reflect that this branch is only taken when `expr` is false-ish. */ - inheritNonnullIfFalse( - /** Expression being false. */ - expr: ExpressionRef, - /** If specified, only set the flag if also nonnull in this flow. */ - iff: Flow | null = null - ): void { - // A: `expr` is false-ish -> Q: how did that happen? - switch (getExpressionId(expr)) { - case ExpressionId.Unary: { - switch (getUnaryOp(expr)) { - case UnaryOp.EqzI32: - case UnaryOp.EqzI64: { - this.inheritNonnullIfTrue(getUnaryValue(expr), iff); // !value -> value must have been true - break; - } - } - break; - } - case ExpressionId.If: { - let ifTrue = getIfTrue(expr); - let ifFalse = getIfFalse(expr); - if (ifFalse && isConstNonZero(ifTrue)) { - // Logical OR: (if (condition 1 ifFalse)) - // the only way this had become false is if condition and ifFalse are false - this.inheritNonnullIfFalse(getIfCondition(expr), iff); - this.inheritNonnullIfFalse(getIfFalse(expr), iff); - } - break; - } - case ExpressionId.Binary: { - switch (getBinaryOp(expr)) { - // remember: we want to know how the _entire_ expression became FALSE (!) - case BinaryOp.EqI32: - case BinaryOp.EqI64: { - let left = getBinaryLeft(expr); - let right = getBinaryRight(expr); - if (isConstZero(left)) { - this.inheritNonnullIfTrue(right, iff); // !(FALSE == right) -> right must have been true - } else if (isConstZero(right)) { - this.inheritNonnullIfTrue(left, iff); // !(left == FALSE) -> left must have been true - } - break; - } - case BinaryOp.NeI32: - case BinaryOp.NeI64: { - let left = getBinaryLeft(expr); - let right = getBinaryRight(expr); - if (isConstNonZero(left)) { - this.inheritNonnullIfTrue(right, iff); // !(TRUE != right) -> right must have been true - } else if (isConstNonZero(right)) { - this.inheritNonnullIfTrue(left, iff); // !(left != TRUE) -> left must have been true - } - break; - } - } - break; - } - case ExpressionId.Call: { - // handle string eq/ne/not overloads - let name = getCallTarget(expr); - if (name == BuiltinNames.String_eq) { - assert(getCallOperandCount(expr) == 2); - let left = getCallOperandAt(expr, 0); - let right = getCallOperandAt(expr, 1); - if (isConstZero(left)) { - this.inheritNonnullIfTrue(right, iff); // !(FALSE == right) -> right must have been true - } else if (isConstZero(right)) { - this.inheritNonnullIfTrue(left, iff); // !(left == FALSE) -> left must have been true - } - } else if (name == BuiltinNames.String_ne) { - assert(getCallOperandCount(expr) == 2); - let left = getCallOperandAt(expr, 0); - let right = getCallOperandAt(expr, 1); - if (isConstNonZero(left)) { - this.inheritNonnullIfTrue(right, iff); // !(TRUE != right) -> right must have been true - } else if (isConstNonZero(right)) { - this.inheritNonnullIfTrue(left, iff); // !(left != TRUE) -> left must have been true - } - } else if (name == BuiltinNames.String_not) { - assert(getCallOperandCount(expr) == 1); - this.inheritNonnullIfTrue(getCallOperandAt(expr, 0), iff); // !(!value) -> value must have been true - } else if (name == BuiltinNames.tostack) { - assert(getCallOperandCount(expr) == 1); - this.inheritNonnullIfFalse(getCallOperandAt(expr, 0), iff); - } - break; - } - } - } - /** * Tests if an expression can possibly overflow in the context of this flow. Assumes that the * expression might already have overflown and returns `false` only if the operation neglects diff --git a/src/narrow.ts b/src/narrow.ts new file mode 100644 index 0000000000..734a2d437e --- /dev/null +++ b/src/narrow.ts @@ -0,0 +1,435 @@ +import { BuiltinNames } from "./builtins"; +import { Flow } from "./flow"; +import { + BinaryOp, + ExpressionId, + ExpressionRef, + getBinaryLeft, + getBinaryOp, + getBinaryRight, + getCallOperandAt, + getCallOperandCount, + getCallTarget, + getExpressionId, + getIfCondition, + getIfFalse, + getIfTrue, + getLocalGetIndex, + getLocalSetIndex, + getLocalSetValue, + getUnaryOp, + getUnaryValue, + isConstNonZero, + isConstZero, + isLocalTee, + UnaryOp, +} from "./module"; +import { TypedElement } from "./program"; +import { Type, TypeFlags } from "./types"; + +/** both a and b can assign to return type */ +export function typeAnd(a: Type | null, b: Type | null): Type | null { + if (a == null || b == null) { + return null; + } else { + const nonnulla = a.nonNullableType; + const nonnullb = b.nonNullableType; + const nullable = a.is(TypeFlags.NULLABLE) || b.is(TypeFlags.NULLABLE); + if (nonnulla.isAssignableTo(nonnullb)) { + return nullable ? b.nullableType : nonnullb; + } else if (nonnullb.isAssignableTo(nonnulla)) { + return nullable ? a.nullableType : nonnulla; + } else { + return null; + } + } +} + +/** return type can assign a and b, aggressive type narrow */ +export function typeOr(a: Type | null, b: Type | null): Type | null { + if (a == null) { + return b; + } else if (b == null) { + return a; + } else { + const nonnulla = a.nonNullableType; + const nonnullb = b.nonNullableType; + const nullable = a.is(TypeFlags.NULLABLE) && b.is(TypeFlags.NULLABLE); + if (nonnulla.isAssignableTo(nonnullb)) { + // a extends b + return nullable ? a.nullableType : nonnulla; + } else if (nonnullb.isAssignableTo(nonnulla)) { + // b extends a + return nullable ? b.nullableType : nonnullb; + } else { + return null; + } + } +} + +export class NarrowedTypeMap { + private typeMap: Map = new Map(); + get size(): i32 { + return this.typeMap.size; + } + get(element: TypedElement): Type | null { + if (this.typeMap.has(element)) { + let type = assert(this.typeMap.get(element)); + if (type == Type.void) { + return null; + } + return type; + } else { + return null; + } + } + set(element: TypedElement, type: Type): void { + this.typeMap.set(element, type); + } + /** set TypedElement as non-nullable */ + setNonnull(typedElement: TypedElement): void { + let typeMap = this.typeMap; + if (typeMap.has(typedElement)) { + let type = assert(typeMap.get(typedElement)); + typeMap.set(typedElement, type.nonNullableType); + } else { + typeMap.set(typedElement, typedElement.type.nonNullableType); + } + } + delete(key: TypedElement): bool { + return this.typeMap.delete(key); + } + clone(): NarrowedTypeMap { + let map = this.typeMap; + let other = new NarrowedTypeMap(); + let _key = Map_keys(map); + for (let i = 0, k = _key.length; i < k; i++) { + let key = _key[i]; + let value = assert(map.get(key)); + other.typeMap.set(key, value); + } + return other; + } + mergeOr(other: NarrowedTypeMap): void { + let aMap = this.typeMap; + let bMap = other.typeMap; + let bKeys = Map_keys(bMap); + for (let i = 0, k = bKeys.length; i < k; i++) { + let key = bKeys[i]; + let aType = aMap.has(key) ? assert(aMap.get(key)) : null; + let bType = assert(bMap.get(key)); + let mergedType = typeOr(aType, bType); + if (mergedType) { + aMap.set(key, mergedType); + } else { + aMap.delete(key); + } + } + } + mergeAnd(other: NarrowedTypeMap): void { + let aMap = this.typeMap; + let bMap = other.typeMap; + let bKeys = Map_keys(bMap); + let aKeys = Map_keys(aMap); + for (let i = 0, k = aKeys.length; i < k; i++) { + let akey = aKeys[i]; + if (!bMap.has(akey)) { + aMap.delete(akey); + } + } + for (let i = 0, k = bKeys.length; i < k; i++) { + let key = bKeys[i]; + let aType = aMap.has(key) ? assert(aMap.get(key)) : null; + let bType = assert(bMap.get(key)); + let mergedType = typeAnd(aType, bType); + if (mergedType) { + aMap.set(key, mergedType); + } else { + aMap.delete(key); + } + } + } + + mergeElementOr(narrowedElement: NarrowedTypeElement): void { + let thisTypeMap = this.typeMap; + let element = narrowedElement.element; + let aType = thisTypeMap.has(element) ? assert(this.typeMap.get(element)) : null; + let bType = narrowedElement.type; + let mergedType = typeOr(aType, bType); + if (mergedType) { + thisTypeMap.set(element, mergedType); + } else { + thisTypeMap.delete(element); + } + } + + toString(): string { + let key = Map_keys(this.typeMap); + let value = Map_values(this.typeMap); + let str = new Array(); + for (let i = 0, k = key.length; i < k; i++) { + str.push(`${key[i].internalName}: ${value[i]}`); + } + return "narrowedTypes: " + str.join("; "); + } +} + +class NarrowedTypeElement { + constructor(public element: TypedElement, public type: Type) {} +} + +export class TypeNarrowChecker { + /** expression in condition, meeting this expr means type can be narrowed */ + condiMap: Map = new Map(); + /** expression in condition, meeting this expr means element assigned as a type */ + assignMap: Map = new Map(); + + /** set conditional narrowed type */ + setConditionNarrowedType(expr: ExpressionRef, element: TypedElement, type: Type): void { + let condiMap = this.condiMap; + if (expr <= 0) return; + assert(!condiMap.has(expr)); + condiMap.set(expr, new NarrowedTypeElement(element, type)); + } + /** set type of assigned element */ + setAssignType(expr: ExpressionRef, element: TypedElement, type: Type): void { + let assignMap = this.assignMap; + if (expr <= 0) return; + assert(!assignMap.has(expr)); + assignMap.set(expr, new NarrowedTypeElement(element, type)); + } + /** remove TypedElement from mapping, called when element is no longer used or re-used */ + removeElement(element: TypedElement): void { + let condiKeys = Map_keys(this.condiMap); + let condiValues = Map_values(this.condiMap); + for (let i = 0, k = condiValues.length; i < k; i++) { + if (condiValues[i].element == element) this.condiMap.delete(condiKeys[i]); + } + let assignKeys = Map_keys(this.assignMap); + let assignValues = Map_values(this.assignMap); + for (let i = 0, k = assignValues.length; i < k; i++) { + if (assignValues[i].element == element) this.condiMap.delete(assignKeys[i]); + } + } + + collectNarrowedTypeIfTrue(expr: ExpressionRef, flow: Flow, typeMap: NarrowedTypeMap | null = null): NarrowedTypeMap { + if (typeMap == null) typeMap = new NarrowedTypeMap(); + // visit children + switch (getExpressionId(expr)) { + case ExpressionId.LocalSet: { + if (!isLocalTee(expr)) break; + this.collectNarrowedTypeIfTrue(getLocalSetValue(expr), flow, typeMap); + break; + } + case ExpressionId.If: { + let condition = getIfCondition(expr); + let ifTrue = getIfTrue(expr); + let ifFalse = getIfFalse(expr); + if (ifFalse && isConstZero(ifFalse)) { + // Logical AND: (if (condition ifTrue 0)) + // the only way this had become true is if condition and ifTrue are true + this.collectNarrowedTypeIfTrue(condition, flow, typeMap); + this.collectNarrowedTypeIfTrue(ifTrue, flow, typeMap); + } + if (ifFalse && isConstNonZero(ifTrue)) { + // Logical OR: (if (condition 1 ifFalse)) + // the only way this had become false is if condition and ifFalse are false + let subMapFalse = typeMap.clone(); + this.collectNarrowedTypeIfTrue(condition, flow, typeMap); + this.collectNarrowedTypeIfTrue(ifFalse, flow, subMapFalse); + typeMap.mergeAnd(subMapFalse); + } + break; + } + case ExpressionId.Unary: { + switch (getUnaryOp(expr)) { + case UnaryOp.EqzI32: + case UnaryOp.EqzI64: { + this.collectNarrowedTypeIfFalse(getUnaryValue(expr), flow, typeMap); // !value -> value must have been false + + break; + } + } + break; + } + case ExpressionId.Binary: { + switch (getBinaryOp(expr)) { + case BinaryOp.EqI32: + case BinaryOp.EqI64: { + let left = getBinaryLeft(expr); + let right = getBinaryRight(expr); + if (isConstNonZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // TRUE == right -> right must have been true + } else if (isConstNonZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // left == TRUE -> left must have been true + } + break; + } + case BinaryOp.NeI32: + case BinaryOp.NeI64: { + let left = getBinaryLeft(expr); + let right = getBinaryRight(expr); + if (isConstZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // TRUE == right -> right must have been true + } else if (isConstZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // TRUE == right -> right must have been true + } + break; + } + } + break; + } + case ExpressionId.Call: { + // handle string eq/ne/not overloads + let name = getCallTarget(expr); + if (name == BuiltinNames.String_eq) { + assert(getCallOperandCount(expr) == 2); + let left = getCallOperandAt(expr, 0); + let right = getCallOperandAt(expr, 1); + if (isConstNonZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // TRUE == right -> right must have been true + } else if (isConstNonZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // left == TRUE -> left must have been true + } + } else if (name == BuiltinNames.String_ne) { + assert(getCallOperandCount(expr) == 2); + let left = getCallOperandAt(expr, 0); + let right = getCallOperandAt(expr, 1); + if (isConstZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // FALSE != right -> right must have been true + } else if (isConstZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // left != FALSE -> left must have been true + } + } else if (name == BuiltinNames.String_not) { + assert(getCallOperandCount(expr) == 1); + this.collectNarrowedTypeIfFalse(getCallOperandAt(expr, 0), flow, typeMap); // !value -> value must have been false + } else if (name == BuiltinNames.tostack) { + assert(getCallOperandCount(expr) == 1); + this.collectNarrowedTypeIfTrue(getCallOperandAt(expr, 0), flow, typeMap); + } + break; + } + } + + // update expr + const expressionMap = this.condiMap; + if (expressionMap.has(expr)) { + const narrowedTypeElement = assert(expressionMap.get(expr)); + typeMap.mergeElementOr(narrowedTypeElement); + } + const assignMap = this.assignMap; + if (assignMap.has(expr)) { + const assignTypeElement = assert(assignMap.get(expr)); + typeMap.set(assignTypeElement.element, assignTypeElement.type); + } + + // nullable check + switch (getExpressionId(expr)) { + case ExpressionId.LocalSet: { + if (isLocalTee(expr)) { + const local = flow.parentFunction.localsByIndex[getLocalSetIndex(expr)]; + typeMap.setNonnull(local); + } + break; + } + case ExpressionId.LocalGet: { + const local = flow.parentFunction.localsByIndex[getLocalGetIndex(expr)]; + typeMap.setNonnull(local); + } + } + + return typeMap; + } + + collectNarrowedTypeIfFalse(expr: ExpressionRef, flow: Flow, typeMap: NarrowedTypeMap | null = null): NarrowedTypeMap { + if (typeMap == null) typeMap = new NarrowedTypeMap(); + switch (getExpressionId(expr)) { + case ExpressionId.Unary: { + switch (getUnaryOp(expr)) { + case UnaryOp.EqzI32: + case UnaryOp.EqzI64: { + this.collectNarrowedTypeIfTrue(getUnaryValue(expr), flow, typeMap); // !value -> value must have been true + break; + } + } + break; + } + case ExpressionId.If: { + let ifTrue = getIfTrue(expr); + let ifFalse = getIfFalse(expr); + if (ifFalse && isConstNonZero(ifTrue)) { + // Logical OR: (if (condition 1 ifFalse)) + // the only way this had become false is if condition and ifFalse are false + this.collectNarrowedTypeIfFalse(getIfCondition(expr), flow, typeMap); + this.collectNarrowedTypeIfFalse(getIfFalse(expr), flow, typeMap); + } + break; + } + case ExpressionId.Binary: { + switch (getBinaryOp(expr)) { + // remember: we want to know how the _entire_ expression became FALSE (!) + case BinaryOp.EqI32: + case BinaryOp.EqI64: { + let left = getBinaryLeft(expr); + let right = getBinaryRight(expr); + if (isConstZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // !(FALSE == right) -> right must have been true + } else if (isConstZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // !(left == FALSE) -> left must have been true + } + break; + } + case BinaryOp.NeI32: + case BinaryOp.NeI64: { + let left = getBinaryLeft(expr); + let right = getBinaryRight(expr); + if (isConstNonZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // !(TRUE != right) -> right must have been true + } else if (isConstNonZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // !(left != TRUE) -> left must have been true + } + break; + } + } + break; + } + case ExpressionId.Call: { + // handle string eq/ne/not overloads + let name = getCallTarget(expr); + if (name == BuiltinNames.String_eq) { + assert(getCallOperandCount(expr) == 2); + let left = getCallOperandAt(expr, 0); + let right = getCallOperandAt(expr, 1); + if (isConstZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // !(FALSE == right) -> right must have been true + } else if (isConstZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // !(left == FALSE) -> left must have been true + } + } else if (name == BuiltinNames.String_ne) { + assert(getCallOperandCount(expr) == 2); + let left = getCallOperandAt(expr, 0); + let right = getCallOperandAt(expr, 1); + if (isConstNonZero(left)) { + this.collectNarrowedTypeIfTrue(right, flow, typeMap); // !(TRUE != right) -> right must have been true + } else if (isConstNonZero(right)) { + this.collectNarrowedTypeIfTrue(left, flow, typeMap); // !(left != TRUE) -> left must have been true + } + } else if (name == BuiltinNames.String_not) { + assert(getCallOperandCount(expr) == 1); + this.collectNarrowedTypeIfTrue(getCallOperandAt(expr, 0), flow, typeMap); // !(!value) -> value must have been true + } else if (name == BuiltinNames.tostack) { + assert(getCallOperandCount(expr) == 1); + this.collectNarrowedTypeIfFalse(getCallOperandAt(expr, 0), flow, typeMap); + } + break; + } + } + // update expr + const assignMap = this.assignMap; + if (assignMap.has(expr)) { + const assignTypeElement = assert(assignMap.get(expr)); + typeMap.set(assignTypeElement.element, assignTypeElement.type); + } + return typeMap; + } +} diff --git a/src/resolver.ts b/src/resolver.ts index 9297041b2a..2f090e8ab5 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1332,7 +1332,7 @@ export class Resolver extends DiagnosticEmitter { case ElementKind.LOCAL: case ElementKind.FIELD: { // someVar.prop let variableLikeElement = target; - let type = variableLikeElement.type; + const type = ctxFlow.getVariantType(variableLikeElement); assert(type != Type.void); let classReference = type.getClassOrWrapper(this.program); if (!classReference) { diff --git a/tests/compiler/for.debug.wat b/tests/compiler/for.debug.wat index ad2868a9be..8b92a70c7a 100644 --- a/tests/compiler/for.debug.wat +++ b/tests/compiler/for.debug.wat @@ -2544,6 +2544,7 @@ (local $i i32) (local $ref i32) (local $var$2 i32) + (local $var$3 i32) global.get $~lib/memory/__stack_pointer i32.const 4 i32.sub @@ -2559,10 +2560,10 @@ call $for/Ref#constructor local.tee $ref i32.store $0 - loop $for-loop|0 + loop $for-loop|1 local.get $ref - local.set $var$2 - local.get $var$2 + local.set $var$3 + local.get $var$3 if local.get $i i32.const 1 @@ -2580,7 +2581,7 @@ local.tee $ref i32.store $0 end - br $for-loop|0 + br $for-loop|1 end end local.get $i diff --git a/tests/compiler/for.release.wat b/tests/compiler/for.release.wat index 5948bac92f..1a3699d2ed 100644 --- a/tests/compiler/for.release.wat +++ b/tests/compiler/for.release.wat @@ -1285,7 +1285,7 @@ call $for/Ref#constructor local.tee $0 i32.store $0 - loop $for-loop|08 + loop $for-loop|17 local.get $0 if local.get $1 @@ -1303,7 +1303,7 @@ local.tee $0 i32.store $0 end - br $for-loop|08 + br $for-loop|17 end end local.get $1 @@ -1351,10 +1351,10 @@ call $for/Ref#constructor local.tee $0 i32.store $0 - loop $for-loop|012 + loop $for-loop|011 call $for/Ref#constructor if - block $for-break011 + block $for-break010 local.get $1 i32.const 1 i32.add @@ -1364,13 +1364,13 @@ if i32.const 0 local.set $0 - br $for-break011 + br $for-break010 end global.get $~lib/memory/__stack_pointer call $for/Ref#constructor local.tee $0 i32.store $0 - br $for-loop|012 + br $for-loop|011 end end end diff --git a/tests/compiler/nullable.json b/tests/compiler/nullable.json index fdef0a1a3b..d97e588a95 100644 --- a/tests/compiler/nullable.json +++ b/tests/compiler/nullable.json @@ -2,7 +2,15 @@ "asc_flags": [ ], "stderr": [ - "TS2322: Type 'nullable/Example | null' is not assignable to type 'nullable/Example'.", - "EOF" + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'." ] } diff --git a/tests/compiler/nullable.ts b/tests/compiler/nullable.ts index 3cffa68d67..75a4c90585 100644 --- a/tests/compiler/nullable.ts +++ b/tests/compiler/nullable.ts @@ -1,7 +1,57 @@ -class Example {} +class Ref {} -function notNullable(a: Example): void {} +declare function getBool(): bool; +function notNullable(a: Ref): void {} +// "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", notNullable(null); -ERROR("EOF"); +export function testAssign(v: Ref | null): void { + if (v != null) { + v = null; + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } +} +export function testAssignLogicAnd(v: Ref | null): void { + if (v != null) { + getBool() && (v = null); + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } +} +export function testAssignLogicOr(v: Ref | null): void { + if (v != null) { + getBool() || (v = null); + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } +} + +export function testAssignInCondiLogicAnd(v: Ref | null): void { + if (getBool() && !(v = null)) { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } else { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } + if (getBool() && (v = null)) { + } else { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } +} +export function testAssignInCondiLogicOr(v: Ref | null): void { + if (getBool() || (v = null)) { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } else { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } + if (getBool() || !(v = null)) { + // "TS2322: Type 'nullable/Ref | null' is not assignable to type 'nullable/Ref'.", + notNullable(v); + } +} diff --git a/tests/compiler/possibly-null.debug.wat b/tests/compiler/possibly-null.debug.wat index bee25aca6f..4739ae7e3c 100644 --- a/tests/compiler/possibly-null.debug.wat +++ b/tests/compiler/possibly-null.debug.wat @@ -29,7 +29,10 @@ (export "testLogicalAndMulti" (func $export:possibly-null/testLogicalAndMulti)) (export "testLogicalOrMulti" (func $export:possibly-null/testLogicalOrMulti)) (export "testAssign" (func $export:possibly-null/testAssign)) + (export "testAssignInCondi" (func $export:possibly-null/testAssignInCondi)) (export "testNeverNull" (func $export:possibly-null/testNeverNull)) + (export "testLogicalOrTypeInfer" (func $export:possibly-null/testLogicalOrTypeInfer)) + (export "testInit" (func $export:possibly-null/testInit)) (func $possibly-null/testTrue (param $a i32) local.get $a if @@ -251,6 +254,18 @@ i32.const 0 drop ) + (func $possibly-null/testAssignInCondi (param $a i32) + i32.const 0 + local.tee $a + if + i32.const 0 + drop + else + i32.const 1 + i32.eqz + drop + end + ) (func $possibly-null/testNeverNull (param $a i32) local.get $a if @@ -258,6 +273,59 @@ drop end ) + (func $possibly-null/testLogicalOrTypeInfer (param $a i32) (param $b i32) + (local $c i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store $0 + global.get $~lib/memory/__stack_pointer + local.get $a + if (result i32) + local.get $a + else + local.get $b + end + local.tee $c + i32.store $0 + i32.const 0 + drop + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $possibly-null/testInit (param $a i32) + (local $b i32) + (local $c i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store $0 + local.get $a + local.set $b + i32.const 0 + drop + global.get $~lib/memory/__stack_pointer + local.get $a + call $possibly-null/requireNonNull + local.tee $c + i32.store $0 + i32.const 0 + drop + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) (func $~stack_check global.get $~lib/memory/__stack_pointer global.get $~lib/memory/__data_end @@ -563,6 +631,22 @@ i32.add global.set $~lib/memory/__stack_pointer ) + (func $export:possibly-null/testAssignInCondi (param $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $possibly-null/testAssignInCondi + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) (func $export:possibly-null/testNeverNull (param $0 i32) global.get $~lib/memory/__stack_pointer i32.const 4 @@ -579,4 +663,40 @@ i32.add global.set $~lib/memory/__stack_pointer ) + (func $export:possibly-null/testLogicalOrTypeInfer (param $0 i32) (param $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store $0 offset=4 + local.get $0 + local.get $1 + call $possibly-null/testLogicalOrTypeInfer + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $export:possibly-null/testInit (param $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $possibly-null/testInit + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) ) diff --git a/tests/compiler/possibly-null.release.wat b/tests/compiler/possibly-null.release.wat index c31959d81a..830d2a52dc 100644 --- a/tests/compiler/possibly-null.release.wat +++ b/tests/compiler/possibly-null.release.wat @@ -23,7 +23,10 @@ (export "testLogicalAndMulti" (func $export:possibly-null/testLogicalAndMulti)) (export "testLogicalOrMulti" (func $export:possibly-null/testLogicalAndMulti)) (export "testAssign" (func $export:possibly-null/testLogicalAndMulti)) + (export "testAssignInCondi" (func $export:possibly-null/testTrue)) (export "testNeverNull" (func $export:possibly-null/testTrue)) + (export "testLogicalOrTypeInfer" (func $export:possibly-null/testLogicalOrTypeInfer)) + (export "testInit" (func $export:possibly-null/testInit)) (func $export:possibly-null/testTrue (param $0 i32) (local $1 i32) global.get $~lib/memory/__stack_pointer @@ -188,4 +191,104 @@ i32.add global.set $~lib/memory/__stack_pointer ) + (func $export:possibly-null/testLogicalOrTypeInfer (param $0 i32) (param $1 i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + block $folding-inner0 + global.get $~lib/memory/__stack_pointer + i32.const 1024 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $2 + local.get $0 + i32.store $0 + local.get $2 + local.get $1 + i32.store $0 offset=4 + local.get $2 + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1024 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $2 + i32.const 0 + i32.store $0 + local.get $2 + local.get $0 + local.get $1 + local.get $0 + select + i32.store $0 + local.get $2 + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + return + end + i32.const 17440 + i32.const 17488 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + ) + (func $export:possibly-null/testInit (param $0 i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + block $folding-inner0 + global.get $~lib/memory/__stack_pointer + i32.const 1024 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $1 + local.get $0 + i32.store $0 + local.get $1 + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1024 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $1 + i32.const 0 + i32.store $0 + local.get $1 + local.get $0 + i32.store $0 + local.get $1 + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + return + end + i32.const 17440 + i32.const 17488 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + ) ) diff --git a/tests/compiler/possibly-null.ts b/tests/compiler/possibly-null.ts index 4e1d79a989..760f73b8c9 100644 --- a/tests/compiler/possibly-null.ts +++ b/tests/compiler/possibly-null.ts @@ -119,9 +119,28 @@ export function testAssign(a: Ref | null, b: Ref): void { a = b; if (isNullable(a)) ERROR("should be non-nullable"); } +export function testAssignInCondi(a: Ref | null): void { + if ((a = null)) { + if (isNullable(a)) ERROR("should be non-nullable"); + } else { + if (!isNullable(a)) ERROR("should be non-nullable"); + } +} export function testNeverNull(a: Ref | null): void { if (a) { a!; // INFO AS225: Expression is never 'null'. } } + +export function testLogicalOrTypeInfer(a: Ref | null, b: Ref): void { + let c: Ref = a || b; + if (isNullable(c)) ERROR("should be non-nullable"); +} + +export function testInit(a: Ref): void { + let b: Ref | null = a; + if (isNullable(b)) ERROR("should be non-nullable"); + let c: Ref | null = requireNonNull(a); + if (isNullable(c)) ERROR("should be non-nullable"); +} diff --git a/tests/compiler/typenarrow-error.json b/tests/compiler/typenarrow-error.json new file mode 100644 index 0000000000..f7ab94db3a --- /dev/null +++ b/tests/compiler/typenarrow-error.json @@ -0,0 +1,41 @@ +{ + "asc_flags": [ + ], + "stderr": [ + "TS2339: Property 'b1' does not exist on type 'typenarrow-error/A", + + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b2' does not exist on type 'typenarrow-error/A", + + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b3' does not exist on type 'typenarrow-error/A", + + "TS2339: Property 'b4' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b4' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b4' does not exist on type 'typenarrow-error/A", + "TS2339: Property 'b4' does not exist on type 'typenarrow-error/A", + + "TS2339: Property 'c1' does not exist on type 'typenarrow-error/A" + + ] +} diff --git a/tests/compiler/typenarrow-error.ts b/tests/compiler/typenarrow-error.ts new file mode 100644 index 0000000000..ff9c331fb4 --- /dev/null +++ b/tests/compiler/typenarrow-error.ts @@ -0,0 +1,99 @@ +class A {} +class B extends A { + b1: i32; + b2: i32; + b3: i32; + b4: i32; + b5: i32; + b6: i32; +} +class C extends B { + c1: i32; +} + +export function testAssign(v0: A): void { + // TS2339: Property 'b1' does not exist on type 'typenarrow-error/A + if (v0 instanceof B) { + v0 = new A(); + v0.b1; + } +} + +export function testOr(v0: A, v1: A): void { + // TS2339: Property 'b2' does not exist on type 'typenarrow-error/A'. + if (v0 instanceof B || v1 instanceof B) { + v0.b2; + v1.b2; + } else { + v0.b2; + v1.b2; + } + if (!(v0 instanceof B) || !(v1 instanceof B)) { + v0.b2; + v1.b2; + } + if (v0 instanceof B || !(v1 instanceof B)) { + v0.b2; + v1.b2; + } else { + v0.b2; + } + if (!(v0 instanceof B) || v1 instanceof B) { + v0.b2; + v1.b2; + } else { + v1.b2; + } +} + +export function testAnd(v0: A, v1: A): void { + // TS2339: Property 'b3' does not exist on type 'typenarrow-error/A'. + if (v0 instanceof B && v1 instanceof B) { + } else { + v0.b3; + v1.b3; + } + if (!(v0 instanceof B) && !(v1 instanceof B)) { + v0.b3; + v1.b3; + } else { + v0.b3; + v1.b3; + } + if (v0 instanceof B && !(v1 instanceof B)) { + v1.b3; + } else { + v0.b3; + v1.b3; + } + if (!(v0 instanceof B) && v1 instanceof B) { + v0.b3; + } else { + v0.b3; + v1.b3; + } +} + +export function testAssignOr(v0: A, v1: A): void { + // TS2339: Property 'b4' does not exist on type 'typenarrow-error/A + if (v0 instanceof B || (v0 = v1)) { + v0.b4; + } else { + v0.b4; + } +} +export function testAssignAnd(v0: A, v1: A): void { + // TS2339: Property 'b4' does not exist on type 'typenarrow-error/A + if (v0 instanceof B && (v0 = v1)) { + v0.b4; + } else { + v0.b4; + } +} + +export function testLogicOr(v0: A): void { + // TS2339: Property 'c1' does not exist on type 'typenarrow-error/A + if (v0 instanceof B || v0 instanceof C) { + v0.c1; + } +} diff --git a/tests/compiler/typenarrow.debug.wat b/tests/compiler/typenarrow.debug.wat new file mode 100644 index 0000000000..7b93328093 --- /dev/null +++ b/tests/compiler/typenarrow.debug.wat @@ -0,0 +1,444 @@ +(module + (type $i32_i32_=>_none (func (param i32 i32))) + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (type $i32_=>_none (func (param i32))) + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $none_=>_none (func)) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/shared/runtime/Runtime.Stub i32 (i32.const 0)) + (global $~lib/shared/runtime/Runtime.Minimal i32 (i32.const 1)) + (global $~lib/shared/runtime/Runtime.Incremental i32 (i32.const 2)) + (global $~lib/rt/__rtti_base i32 (i32.const 48)) + (global $~lib/memory/__data_end i32 (i32.const 108)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 16492)) + (global $~lib/memory/__heap_base i32 (i32.const 16492)) + (memory $0 1) + (data (i32.const 12) "\1c\00\00\00\00\00\00\00\00\00\00\00\05\00\00\00\08\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00") + (data (i32.const 48) "\07\00\00\00 \00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00 \00\00\00\03\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\04\00\00\00") + (table $0 2 2 funcref) + (elem $0 (i32.const 1) $typenarrow/B#b1) + (export "memory" (memory $0)) + (export "condiNarrow" (func $export:typenarrow/condiNarrow)) + (func $~lib/rt/__instanceof (param $ptr i32) (param $classId i32) (result i32) + (local $id i32) + (local $rttiBase i32) + local.get $ptr + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.set $id + global.get $~lib/rt/__rtti_base + local.set $rttiBase + local.get $id + local.get $rttiBase + i32.load $0 + i32.le_u + if + loop $do-loop|0 + local.get $id + local.get $classId + i32.eq + if + i32.const 1 + return + end + local.get $rttiBase + i32.const 4 + i32.add + local.get $id + i32.const 8 + i32.mul + i32.add + i32.load $0 offset=4 + local.tee $id + br_if $do-loop|0 + end + end + i32.const 0 + ) + (func $typenarrow/B#b1 (param $this i32) + nop + ) + (func $typenarrow/B#check (param $this i32) (result i32) + i32.const 1 + ) + (func $typenarrow/condiNarrow (param $v0 i32) (param $v1 i32) + (local $var$2 i32) + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if + local.get $v0 + call $typenarrow/B#b1 + local.get $v0 + local.set $var$2 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + if + nop + else + local.get $v0 + call $typenarrow/B#b1 + local.get $v0 + local.set $var$2 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + else + i32.const 0 + end + if + local.get $v0 + call $typenarrow/B#b1 + local.get $v1 + call $typenarrow/B#b1 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + else + i32.const 0 + end + if + local.get $v0 + call $typenarrow/B#b1 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + if (result i32) + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + else + i32.const 0 + end + if + local.get $v1 + call $typenarrow/B#b1 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + if (result i32) + i32.const 1 + else + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + end + if + nop + else + i32.const 32 + drop + i32.const 32 + drop + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + i32.const 1 + else + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + end + if + nop + else + i32.const 32 + drop + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + if (result i32) + i32.const 1 + else + local.get $v1 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + end + if + nop + else + i32.const 32 + drop + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + local.get $v0 + call $typenarrow/B#check + else + i32.const 0 + end + if + nop + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + i32.eqz + if (result i32) + i32.const 1 + else + local.get $v0 + call $typenarrow/B#check + end + if + nop + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 6 + call $~lib/rt/__instanceof + end + if (result i32) + i32.const 1 + else + i32.const 0 + end + if + local.get $v0 + i32.load $0 + drop + local.get $v0 + call $typenarrow/B#b1 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + i32.const 1 + else + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 6 + call $~lib/rt/__instanceof + end + end + if + local.get $v0 + call $typenarrow/B#b1 + end + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 4 + call $~lib/rt/__instanceof + end + if (result i32) + local.get $v0 + local.tee $var$2 + i32.eqz + if (result i32) + i32.const 0 + else + local.get $var$2 + i32.const 6 + call $~lib/rt/__instanceof + end + else + i32.const 0 + end + i32.eqz + if + nop + else + local.get $v0 + call $typenarrow/B#b1 + local.get $v0 + i32.load $0 + drop + end + ) + (func $~stack_check + global.get $~lib/memory/__stack_pointer + global.get $~lib/memory/__data_end + i32.lt_s + if + i32.const 16512 + i32.const 16560 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + ) + (func $export:typenarrow/condiNarrow (param $0 i32) (param $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store $0 offset=4 + local.get $0 + local.get $1 + call $typenarrow/condiNarrow + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + ) +) diff --git a/tests/compiler/typenarrow.json b/tests/compiler/typenarrow.json new file mode 100644 index 0000000000..1bdd02b1be --- /dev/null +++ b/tests/compiler/typenarrow.json @@ -0,0 +1,4 @@ +{ + "asc_flags": [ + ] +} diff --git a/tests/compiler/typenarrow.release.wat b/tests/compiler/typenarrow.release.wat new file mode 100644 index 0000000000..c7fcbbc747 --- /dev/null +++ b/tests/compiler/typenarrow.release.wat @@ -0,0 +1,763 @@ +(module + (type $i32_i32_=>_none (func (param i32 i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 17516)) + (memory $0 1) + (data (i32.const 1036) "\1c") + (data (i32.const 1048) "\05\00\00\00\08\00\00\00\01") + (data (i32.const 1072) "\07\00\00\00 \00\00\00\00\00\00\00 ") + (data (i32.const 1100) " \00\00\00\00\00\00\00 \00\00\00\03") + (data (i32.const 1124) " \00\00\00\04") + (export "memory" (memory $0)) + (export "condiNarrow" (func $export:typenarrow/condiNarrow)) + (func $typenarrow/condiNarrow (param $0 i32) (param $1 i32) + (local $2 i32) + local.get $0 + if + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|0 + local.get $2 + i32.const 4 + i32.ne + if + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|0 + end + end + end + end + local.get $0 + if + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|02 + local.get $2 + i32.const 4 + i32.ne + if + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|02 + end + end + end + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof4 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|06 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof4 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|06 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 0 + local.get $1 + select + if + block $__inlined_func$~lib/rt/__instanceof7 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|09 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof7 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|09 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof12 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|014 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof12 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|014 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 0 + local.get $1 + select + if + block $__inlined_func$~lib/rt/__instanceof15 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|017 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof15 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|017 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof19 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|021 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof19 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|021 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 1 + local.get $1 + select + i32.eqz + if + block $__inlined_func$~lib/rt/__instanceof22 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|024 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof22 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|024 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof26 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|028 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof26 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|028 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 0 + local.get $1 + select + if + block $__inlined_func$~lib/rt/__instanceof29 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|031 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof29 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|031 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof32 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|034 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof32 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|034 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 1 + local.get $1 + select + i32.eqz + if + block $__inlined_func$~lib/rt/__instanceof35 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|037 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof35 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|037 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof38 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $2 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|040 + i32.const 1 + local.get $2 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof38 + drop + local.get $2 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $2 + br_if $do-loop|040 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 0 + local.get $1 + select + if + block $__inlined_func$~lib/rt/__instanceof41 (result i32) + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|043 + i32.const 1 + local.get $1 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof41 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|043 + end + end + i32.const 0 + end + drop + end + local.get $0 + if + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|046 + local.get $1 + i32.const 4 + i32.ne + if + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|046 + end + end + end + end + local.get $0 + if + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|049 + local.get $1 + i32.const 4 + i32.ne + if + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|049 + end + end + end + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof51 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|053 + i32.const 1 + local.get $1 + i32.const 6 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof51 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|053 + end + end + i32.const 0 + end + else + i32.const 0 + end + if + local.get $0 + i32.load $0 + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof55 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|057 + i32.const 1 + local.get $1 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof55 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|057 + end + end + i32.const 0 + end + else + i32.const 0 + end + i32.const 1 + local.get $0 + select + i32.eqz + if + block $__inlined_func$~lib/rt/__instanceof58 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|060 + i32.const 1 + local.get $1 + i32.const 6 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof58 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|060 + end + end + i32.const 0 + end + drop + end + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof62 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|064 + i32.const 1 + local.get $1 + i32.const 4 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof62 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|064 + end + end + i32.const 0 + end + else + i32.const 0 + end + if (result i32) + local.get $0 + if (result i32) + block $__inlined_func$~lib/rt/__instanceof65 (result i32) + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=12 + local.tee $1 + i32.const 1072 + i32.load $0 + i32.le_u + if + loop $do-loop|067 + i32.const 1 + local.get $1 + i32.const 6 + i32.eq + br_if $__inlined_func$~lib/rt/__instanceof65 + drop + local.get $1 + i32.const 3 + i32.shl + i32.const 1076 + i32.add + i32.load $0 offset=4 + local.tee $1 + br_if $do-loop|067 + end + end + i32.const 0 + end + else + i32.const 0 + end + else + i32.const 0 + end + if + local.get $0 + i32.load $0 + drop + end + ) + (func $export:typenarrow/condiNarrow (param $0 i32) (param $1 i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1132 + i32.lt_s + if + i32.const 17536 + i32.const 17584 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + local.tee $2 + local.get $0 + i32.store $0 + local.get $2 + local.get $1 + i32.store $0 offset=4 + local.get $0 + local.get $1 + call $typenarrow/condiNarrow + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + ) +) diff --git a/tests/compiler/typenarrow.ts b/tests/compiler/typenarrow.ts new file mode 100644 index 0000000000..145d0f356c --- /dev/null +++ b/tests/compiler/typenarrow.ts @@ -0,0 +1,76 @@ +class A {} + +class B extends A { + b1(): void {} + check(): bool { + return true; + } +} +class C extends B { + c1: i32; +} +class D extends A { + d1: i32; +} + +export function condiNarrow(v0: A, v1: A): void { + // noraml + if (v0 instanceof B) { + v0.b1(); + let t: B = v0; + } + + // not + if (!(v0 instanceof B)) { + } else { + v0.b1(); + let t: B = v0; + } + + // and + if (v0 instanceof B && v1 instanceof B) { + v0.b1(); + v1.b1(); + } + if (v0 instanceof B && !(v1 instanceof B)) { + v0.b1(); + } + if (!(v0 instanceof B) && v1 instanceof B) { + v1.b1(); + } + + // or + if (!(v0 instanceof B) || !(v1 instanceof B)) { + } else { + v0.b1; + v1.b1; + } + if (v0 instanceof B || !(v1 instanceof B)) { + } else { + v1.b1; + } + if (!(v0 instanceof B) || v1 instanceof B) { + } else { + v0.b1; + } + + // in condition check for logic operator + if (v0 instanceof B && v0.check()) { + } + if (!(v0 instanceof B) || v0.check()) { + } + + // compatibiltiy + if (v0 instanceof C && v0 instanceof B) { + v0.c1; + v0.b1(); + } + if (v0 instanceof B || v0 instanceof C) { + v0.b1(); + } + if (!(v0 instanceof B && v0 instanceof C)) { + } else { + v0.b1(); + v0.c1; + } +} diff --git a/tests/compiler/while.debug.wat b/tests/compiler/while.debug.wat index f0ad3d97f0..4ecdd386c1 100644 --- a/tests/compiler/while.debug.wat +++ b/tests/compiler/while.debug.wat @@ -2588,7 +2588,7 @@ call $while/Ref#constructor local.tee $ref i32.store $0 - loop $while-continue|0 + loop $while-continue|1 local.get $ref local.set $var$2 local.get $var$2 @@ -2609,7 +2609,7 @@ local.tee $ref i32.store $0 end - br $while-continue|0 + br $while-continue|1 end end local.get $i diff --git a/tests/compiler/while.release.wat b/tests/compiler/while.release.wat index e89c5fa407..4693e5688e 100644 --- a/tests/compiler/while.release.wat +++ b/tests/compiler/while.release.wat @@ -1319,7 +1319,7 @@ call $while/Ref#constructor local.tee $3 i32.store $0 - loop $while-continue|08 + loop $while-continue|17 local.get $3 if local.get $1 @@ -1337,7 +1337,7 @@ local.tee $3 i32.store $0 end - br $while-continue|08 + br $while-continue|17 end end local.get $1 @@ -1382,10 +1382,10 @@ call $while/Ref#constructor local.tee $1 i32.store $0 - loop $while-continue|012 + loop $while-continue|010 call $while/Ref#constructor if - block $while-break|011 + block $while-break|09 local.get $3 i32.const 1 i32.add @@ -1395,9 +1395,9 @@ if i32.const 0 local.set $1 - br $while-break|011 + br $while-break|09 end - br $while-continue|012 + br $while-continue|010 end end end