-
-
Notifications
You must be signed in to change notification settings - Fork 669
feat: add type narrow to support better type check #2352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 34 commits
df38498
f55e01a
aab3bfd
5fd12b3
c623709
999ce39
9bfac7e
2ccd59b
3c7d0dd
bbc3fe1
86f7bce
4ea7f9e
58d53a4
e6b9291
a9d22ea
f344b10
53d5811
df62468
5cac092
67ce5bd
450d490
b0f3aef
3ae97bd
3277d93
c5d94fa
b1ea921
8979ac8
cecd1a8
2abc9a4
60547ae
81a63e3
6625289
5b244a3
403cae8
9115a5c
a01d883
4824b44
9b23ad2
4ea58aa
f0763d0
71f26aa
db96d70
2506962
e245743
5d401b8
dc2f28a
41d4e4b
265eff1
e2256c8
9af6064
ac12ee8
0a0135b
bfc9ed8
85387c8
fb117fa
3554be4
3edce9d
51de90e
c11571f
5ff9408
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,7 +87,8 @@ import { | |
PropertyPrototype, | ||
IndexSignature, | ||
File, | ||
mangleInternalName | ||
mangleInternalName, | ||
TypedElement | ||
} from "./program"; | ||
|
||
import { | ||
|
@@ -96,7 +97,8 @@ import { | |
LocalFlags, | ||
FieldFlags, | ||
ConditionKind, | ||
findUsedLocals | ||
findUsedLocals, | ||
invertedCondition | ||
} from "./flow"; | ||
|
||
import { | ||
|
@@ -2554,7 +2556,7 @@ export class Compiler extends DiagnosticEmitter { | |
|
||
// Compile the body assuming the condition turned out true | ||
var bodyFlow = flow.fork(); | ||
bodyFlow.inheritNonnullIfTrue(condExpr); | ||
bodyFlow.inheritNarrowedTypeIfTrue(condExpr); | ||
this.currentFlow = bodyFlow; | ||
var bodyStmts = new Array<ExpressionRef>(); | ||
var body = statement.statement; | ||
|
@@ -2699,7 +2701,8 @@ export class Compiler extends DiagnosticEmitter { | |
var thenStmts = new Array<ExpressionRef>(); | ||
var thenFlow = flow.fork(); | ||
this.currentFlow = thenFlow; | ||
thenFlow.inheritNonnullIfTrue(condExpr); | ||
thenFlow.inheritNarrowedTypeIfTrue(condExpr); | ||
|
||
if (ifTrue.kind == NodeKind.BLOCK) { | ||
this.compileStatements((<BlockStatement>ifTrue).statements, false, thenStmts); | ||
} else { | ||
|
@@ -2717,7 +2720,7 @@ export class Compiler extends DiagnosticEmitter { | |
let elseStmts = new Array<ExpressionRef>(); | ||
let elseFlow = flow.fork(); | ||
this.currentFlow = elseFlow; | ||
elseFlow.inheritNonnullIfFalse(condExpr); | ||
elseFlow.inheritNarrowedTypeIfFalse(condExpr); | ||
if (ifFalse.kind == NodeKind.BLOCK) { | ||
this.compileStatements((<BlockStatement>ifFalse).statements, false, elseStmts); | ||
} else { | ||
|
@@ -2735,12 +2738,11 @@ 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 | ||
); | ||
if (thenFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) { | ||
flow.inheritNarrowedTypeIfFalse(condExpr); | ||
} | ||
return module.if(condExpr, | ||
module.flatten(thenStmts) | ||
); | ||
|
@@ -2972,6 +2974,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; | ||
|
@@ -2994,6 +2997,8 @@ export class Compiler extends DiagnosticEmitter { | |
); | ||
pendingElements.delete(dummy); | ||
flow.freeScopedDummyLocal(name); | ||
|
||
initType = this.currentType; | ||
} | ||
|
||
// Otherwise infer type from initializer | ||
|
@@ -3013,6 +3018,7 @@ export class Compiler extends DiagnosticEmitter { | |
continue; | ||
} | ||
type = this.currentType; | ||
initType = this.currentType; | ||
|
||
// Error if there's neither a type nor an initializer | ||
} else { | ||
|
@@ -3135,7 +3141,7 @@ export class Compiler extends DiagnosticEmitter { | |
} | ||
if (initExpr) { | ||
initializers.push( | ||
this.makeLocalAssignment(local, initExpr, type, false) | ||
this.makeLocalAssignment(local, initExpr, initType ? initType : type, false) | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
} else { | ||
// no need to assign zero | ||
|
@@ -3235,7 +3241,7 @@ export class Compiler extends DiagnosticEmitter { | |
|
||
// Compile the body assuming the condition turned out true | ||
var bodyFlow = flow.fork(); | ||
bodyFlow.inheritNonnullIfTrue(condExpr); | ||
bodyFlow.inheritNarrowedTypeIfTrue(condExpr); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me that once narrowing is in place, that the nonnull machinery becomes obsolete. Hence wondering if it would be sufficient to upgrade the nonnull machinery to narrowing so there is only one concept in the end? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and it can resolve the nonull bug mentioned in the last of #2360. |
||
this.currentFlow = bodyFlow; | ||
var bodyStmts = new Array<ExpressionRef>(); | ||
var body = statement.statement; | ||
|
@@ -4543,7 +4549,7 @@ export class Compiler extends DiagnosticEmitter { | |
|
||
let rightFlow = flow.fork(); | ||
this.currentFlow = rightFlow; | ||
rightFlow.inheritNonnullIfTrue(leftExpr); | ||
rightFlow.inheritNarrowedTypeIfTrue(leftExpr); | ||
|
||
// simplify if only interested in true or false | ||
if (contextualType == Type.bool || contextualType == Type.void) { | ||
|
@@ -4566,13 +4572,15 @@ 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; | ||
rightFlow.freeScopedLocals(); | ||
flow.inheritBranch(rightFlow); | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.currentFlow = flow; | ||
|
||
// simplify if copying left is trivial | ||
|
@@ -4587,7 +4595,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, | ||
|
@@ -4607,7 +4615,7 @@ export class Compiler extends DiagnosticEmitter { | |
|
||
let rightFlow = flow.fork(); | ||
this.currentFlow = rightFlow; | ||
rightFlow.inheritNonnullIfFalse(leftExpr); | ||
rightFlow.inheritNarrowedTypeIfFalse(leftExpr); | ||
|
||
// simplify if only interested in true or false | ||
if (contextualType == Type.bool || contextualType == Type.void) { | ||
|
@@ -4630,13 +4638,15 @@ 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; | ||
rightFlow.freeScopedLocals(); | ||
flow.inheritBranch(rightFlow); | ||
this.currentFlow = flow; | ||
|
||
// simplify if copying left is trivial | ||
|
@@ -4651,7 +4661,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()), | ||
|
@@ -5938,7 +5948,8 @@ export class Compiler extends DiagnosticEmitter { | |
assert(targetType != Type.void); | ||
var valueExpr = this.compileExpression(valueExpression, targetType); | ||
var valueType = this.currentType; | ||
return this.makeAssignment( | ||
|
||
let assignmentExpression = this.makeAssignment( | ||
target, | ||
this.convertExpression(valueExpr, valueType, targetType, false, valueExpression), | ||
valueType, | ||
|
@@ -5947,6 +5958,7 @@ export class Compiler extends DiagnosticEmitter { | |
elementExpression, | ||
contextualType != Type.void | ||
); | ||
return assignmentExpression; | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** Makes an assignment expression or block, assigning a value to a target. */ | ||
|
@@ -6155,28 +6167,33 @@ 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); | ||
else flow.unsetLocalFlag(localIndex, LocalFlags.WRAPPED); | ||
} | ||
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. */ | ||
|
@@ -6190,24 +6207,33 @@ export class Compiler extends DiagnosticEmitter { | |
/** Whether to tee the value. */ | ||
tee: bool | ||
): ExpressionRef { | ||
let expressionRef: ExpressionRef; | ||
var module = this.module; | ||
var type = global.type; | ||
var flow = this.currentFlow; | ||
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; | ||
return module.block(null, [ | ||
expressionRef = module.block(null, [ | ||
module.global_set(global.internalName, valueExpr), | ||
module.global_get(global.internalName, typeRef) | ||
], typeRef); | ||
} else { // global = value | ||
this.currentType = Type.void; | ||
return module.global_set(global.internalName, | ||
expressionRef = module.global_set(global.internalName, | ||
valueExpr | ||
); | ||
} | ||
if (type.isReference) { | ||
let narrowedType = valueType; | ||
if (!valueType.isNullableReference || flow.isNonnull(valueExpr, type)) { | ||
narrowedType = valueType.nonNullableType; | ||
} | ||
flow.setAssignType(expressionRef, global, narrowedType); | ||
} | ||
return expressionRef; | ||
} | ||
|
||
/** Makes an assignment to a field. */ | ||
|
@@ -6768,7 +6794,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) | ||
); | ||
|
@@ -7761,7 +7787,8 @@ export class Compiler extends DiagnosticEmitter { | |
switch (target.kind) { | ||
case ElementKind.LOCAL: { | ||
let local = <Local>target; | ||
let localType = local.type; | ||
let narrowedType = flow.getNarrowedType(local); | ||
let localType = narrowedType ? narrowedType : local.type; | ||
assert(localType != Type.void); | ||
if (this.pendingElements.has(local)) { | ||
this.error( | ||
|
@@ -7777,9 +7804,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) { | ||
|
@@ -7798,7 +7822,8 @@ export class Compiler extends DiagnosticEmitter { | |
if (!this.compileGlobal(global)) { // reports; not yet compiled if a static field | ||
return module.unreachable(); | ||
} | ||
let globalType = global.type; | ||
let narrowedType = flow.getNarrowedType(global); | ||
let globalType = narrowedType ? narrowedType : global.type; | ||
if (this.pendingElements.has(global)) { | ||
this.error( | ||
DiagnosticCode.Variable_0_used_before_its_declaration, | ||
|
@@ -7908,7 +7933,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((<IdentifierExpression>expression.expression).text); | ||
if (element instanceof TypedElement) { | ||
flow.setConditionNarrowedType(instanceExpression, <TypedElement>element, expectedType); | ||
} | ||
} | ||
return instanceExpression; | ||
} | ||
|
||
private makeInstanceofType(expression: InstanceOfExpression, expectedType: Type): ExpressionRef { | ||
|
@@ -9264,13 +9296,13 @@ export class Compiler extends DiagnosticEmitter { | |
|
||
var outerFlow = this.currentFlow; | ||
var ifThenFlow = outerFlow.fork(); | ||
ifThenFlow.inheritNonnullIfTrue(condExpr); | ||
ifThenFlow.inheritNarrowedTypeIfTrue(condExpr); | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.currentFlow = ifThenFlow; | ||
var ifThenExpr = this.compileExpression(ifThen, ctxType); | ||
var ifThenType = this.currentType; | ||
|
||
var ifElseFlow = outerFlow.fork(); | ||
ifElseFlow.inheritNonnullIfFalse(condExpr); | ||
ifElseFlow.inheritNarrowedTypeIfFalse(condExpr); | ||
this.currentFlow = ifElseFlow; | ||
var ifElseExpr = this.compileExpression(ifElse, ctxType == Type.auto ? ifThenType : ctxType); | ||
var ifElseType = this.currentType; | ||
|
@@ -10587,7 +10619,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"), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to compiling with
Constraincts.CONV_IMPLICIT
, I thinkinitType
is always the same astype
here. Sure that the additionalinitType
variable is needed?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider this testcase. Actually
initType
andtype
are not always the same in nullable