paulberry@google.com, leafp@google.com
This is roughly based on the proposal discussed in https://docs.google.com/document/d/11Xs0b4bzH6DwDlcJMUcbx4BpvEKGz8MVuJWEfo_mirE/edit.
Status: Draft.
2020.06.29
- Fix handling of variables that are write captured in loops, switch statements, and try-blocks (such variables should be conservatively assumed to be captured).
2020.06.02
- Specify the interaction between downwards inference and promotion/demotion.
2020.01.16
- Modify
restrictVto make a variable definitely assigned in a try block or a finally block definitely assigned after the block. - Clarify that initialization promotion does not apply to formal parameters.
This defines the local analysis (within function and method bodies) that underlies type promotion, definite assignment analysis, and reachability analysis.
The Dart language spec requires type promotion analysis to determine when a local variable is known to have a more specific type than its declared type. We believe additional enhancement is necessary to support NNBD (“non-nullability by default”) including but not limited to the Enhanced Type Promotion proposal (see tracking issue).
For example, we should be able to handle these situations:
int stringLength1(String? stringOrNull) {
return stringOrNull.length; // error stringOrNull may be null
}
int stringLength2(String? stringOrNull) {
if (stringOrNull != null) return stringOrNull.length; // ok
return 0;
}The language spec also requires a small amount of reachability analysis,
to ensure control flow does not reach the end of a switch case.
To support NNBD a more complete form of reachability analysis is necessary to detect
when control flow “falls through” to the end of a function body and an implicit return null;,
which would be an error in a function with a non-nullable return type.
This would include but not be limited to the existing
Define 'statically known to not complete normally'
proposal.
For example, we should correctly detect this error situation:
int stringLength3(String? stringOrNull) {
if (stringOrNull != null) return stringOrNull.length;
} // error implied null return value
int stringLength4(String? stringOrNull) {
if (stringOrNull != null) {
return stringOrNull.length;
} else {
return 0;
}
} // ok!Finally, to support NNBD, we believe definite assignment analysis should be added to the spec, so that a user is not required to initialize non-nullable local variables if it can be proven that they will be assigned a non-null value before being read.
We use the generic term node to denote an expression, statement, top level function, method, constructor, or field. We assume that all nodes are labelled uniquely in some unspecified fashion such that mappings can be maintained from nodes to the results of flow analysis.
The flow analysis assumes that the set of variables which are assigned in any
given node has been pre-computed, and can be queried by a predicate Assigned
such that for a node N and a variable v, Assigned(N, v) is true iff
N syntactically contains an assignment to v (regardless of reachability of
that assignment).
-
Maps
- We use the notation
{x: VM1, y: VM2}to denote a map associating the keyxwith the valueVM1, and the keyywith the valueVM2. - We use the notation
VI[x -> VM]to denote the map which maps every key inVIto its corresponding value inVIexceptx, which is mapped toVMin the new map (regardless of any value associated with it inVI).
- We use the notation
-
Lists
- We use the notation
[a, b]to denote a list containing elementsaandb. - We use the notation
[...l, a]wherelis a list to denote a list beginning with all the elements ofland followed bya.
- We use the notation
-
Stacks
- We use the notation
push(s, x)to mean pushingxonto the top of the stacks. - We use the notation
pop(s)to mean the stack that results from removing the top element ofs. Ifsis empty, the result is undefined. - We use the notation
top(s)to mean the top element of the stacks. Ifsis empty, the result is undefined. - Informally, we also use
[...t, a]to describe a stackssuch thattop(s)isaandpop(s)ist.
- We use the notation
A variable model, denoted VariableModel(declaredType, promotedTypes, tested, assigned, unassigned, writeCaptured), represents what is statically
known to the flow analysis about the state of a variable at a given point in the
source code.
-
declaredTypeis the type assigned to the variable at its declaration site (either explicitly or by type inference). -
promotedTypesis a list of types that the variable has been promoted to, with the final type in the list being the current promoted type of the variable. Note that each type in the list must be a subtype of all previous types, and of the declared type. -
testedis a set of types which are considered "of interest" for the purposes of promotion, generally because the variable in question has been tested against the type on some path in some way. -
assignedis a boolean value indicating whether the variable has definitely been assigned at the given point in the source code. Whenassignedis true, we say that the variable is definitely assigned at that point. -
unassignedis a boolean value indicating whether the variable has definitely not been assigned at the given point in the source code. Whenunassignedis true, we say that the variable is definitely unassigned at that point. (Note that a variable cannot be both definitely assigned and definitely unassigned at any location). -
writeCapturedis a boolean value indicating whether a closure or an unevaluated late variable initializer might exist at the given point in the source code, which could potentially write to the variable. Note that for purposes of write captures performed by a late variable initializer, we only consider variable writes performed within the initializer expression itself; a late variable initializer is not per se considered to write to the late variable itself.
A flow model, denoted FlowModel(reachable, variableInfo), represents what
is statically known to flow analysis about the state of the program at a given
point in the source code.
-
reachableis a stack of boolean values modeling the reachability of the given point in the source code. Theith element of the stack (counting from the top of the stack) encodes whether thei-1th enclosing control flow split is reachable from theithenclosing control flow split (or wheniis 0, whether the current program point is reachable from the enclosing control flow split). So if the bottom element ofreachableisfalse, then the current program point is definitively known by flow analysis to be unreachable from the first enclosing control flow split. If it istrue, then the analysis cannot eliminate the possibility that the given point may be reached by some path. Each other element of the stack models the same property, starting from some control flow split between the start of the program and the current node, and treating the entry to the program as the initial control flow split. The true reachability of the current program point then is the conjunction of the elements of thereachablestack, since each element of the stack models the reachability of one control flow split from its enclosing control flow split. -
variableInfois a mapping from variables in scope at the given point to their associated variable models.
The following functions associate flow models to nodes:
-
before(N), whereNis a statement or expression, represents the flow model just prior to execution ofN. -
after(N), whereNis a statement or expression, represents the flow model just after execution ofN, assuming thatNcompletes normally (i.e. does not throw an exception or perform a jump such asreturn,break, orcontinue). -
true(E), whereEis an expression, represents the flow model just after execution ofE, assuming thatEcompletes normally and evaluates totrue. -
false(E), whereEis an expression, represents the flow model just after execution ofE, assuming thatEcompletes normally and evaluates tofalse. -
break(S), whereSis ado,for,switch, orwhilestatement, represents the join of the flow models reaching eachbreakstatement targettingS. -
continue(S), whereSis ado,for,switch, orwhilestatement, represents the join of the flow models reaching eachcontinuestatement targettingS. -
assignedIn(S), whereSis ado,for,switch, orwhilestatement, or aforelement in a collection, represents the set of variables assigned to in the recurrent part ofS, not counting initializations of variables at their declaration sites. The "recurrent" part ofSis defined as:- If
Sis adoorwhilestatement, the entire statementS. - If
Sis aforstatement or aforelement in a collection, whoseforLoopPartstake the form of a traditional for loop, all ofSexcept theforInitializerStatement. - If
Sis aforstatement or aforelement in a collection, whoseforLoopPartstake the form of a for-in loop, the body ofS. A loop of the formfor (var x in ...) ...is not considered to assign tox(becausevar x in ...is considered an initialization ofxat its declaration site), but a loop of the formfor (x in ...) ...(wherexis declared elsewhere in the function) is considered to assign tox. - If
Sis aswitchstatement, all ofSexcept the switchexpression.
- If
-
capturedIn(S), whereSis ado,for,switch, orwhilestatement, represents the set of variables assigned to in a local function or function expression in the recurrent part ofS, where the "recurrent" part ofsis defined as inassignedIn, above.
Note that true and false are defined for all expressions regardless of their
static types.
We also make use of the following auxiliary functions:
-
joinV(VM1, VM2), whereVM1andVM2are variable models, represents the union of two variable models, defined as follows:- If
VM1 = VariableModel(d1, p1, s1, a1, u1, c1)and - If
VM2 = VariableModel(d2, p2, s2, a2, u2, c2)then VM3 = VariableModel(d3, p3, s3, a3, u3, c3)whered3 = d1 = d2- Note that all models must agree on the declared type of a variable
p3is a list formed by taking all the types that are in bothp1andp2, and ordering them such that each type in the list is a subtype of all previous types.- _Note: it is not specified how to order elements of this list that are mutual subtypes of each other. This will soon be addressed by changing the behavior of flow analysis so that each type in the list is a proper subtype of the previous. (See dart-lang#4368.)
s3 = s1 U s2- The set of test sites is the union of the test sites on either path
a3 = a1 && a2- A variable is definitely assigned in the join of two models iff it is definitely assigned in both.
u3 = u1 && u2- A variable is definitely unassigned in the join of two models iff it is definitely unassigned in both.
c3 = c1 || c2- A variable is captured in the join of two models iff it is captured in either.
- If
-
split(M), whereM = FlowModel(r, VM)is a flow model which models program nodes inside of a control flow split, and is defined asFlowModel(r2, VM)wherer2isrwithtruepushed as the top element of the stack. -
drop(M), whereM = FlowModel(r, VM)is defined asFlowModel(r1, VM)whereris of the form[...r1, n0]. This is the flow model which drops the reachability information encoded in the top entry in the stack. -
unsplit(M), whereM = FlowModel(r, VM)is defined asM1 = FlowModel(r1, VM)whereris of the form[...s, n1, n0]andr1 = [...s, n0&&n1]. The modelM1is a flow model which collapses the top two elements of the reachability model fromMinto a single boolean which conservatively summarizes the reachability information present inM. -
merge(M1, M2), whereM1andM2are flow models is the inverse ofsplitand represents the result of joining two flow models at the merge of two control flow paths. IfM1 = FlowModel(r1, VI1)andM2 = FlowModel(r2, VI2)wherepop(r1) = pop(r2) = r0then:- if
top(r1)is true andtop(r2)is false, thenM3isFlowModel(pop(r1), VI1). - if
top(r1)is false andtop(r2)is true, thenM3isFlowModel(pop(r2), VI2). - otherwise
M3isjoin(unsplit(M1), unsplit(M2))
- if
-
join(M1, M2), whereM1andM2are flow models, represents the union of two flow models and is defined as follows:- We define
join(M1, M2)to beM3 = FlowModel(r3, VI3)where:M1 = FlowModel(r1, VI1)M2 = FlowModel(r2, VI2))pop(r1) = pop(r2) = r0for somer0r3ispush(r0, top(r1) || top(r2))VI3is the map which maps each variablevin the domain ofVI1andVI2tojoinV(VI1(v), VI2(v)). Note that any variable which is in domain of only one of the two is dropped, since it is no longer in scope.
The
merge,joinandjoinVcombinators are commutative and associative by construction.For brevity, we will sometimes extend
joinandmergeto more than two arguments in the obvious way. For example,join(M1, M2, M3)representsjoin(join(M1, M2), M3), andjoin(S), where S is a set of models, denotes the result of folding all models in S together usingjoin. - We define
-
restrictV(VMB, VMF, b), whereVMBandVMFare variable models andbis a boolean indicating wether the variable is written in the finally block, represents the composition of two variable models through a try/finally and is defined as follows:- If
VMB = VariableModel(d1, p1, s1, a1, u1, c1)and - If
VMF = VariableModel(d2, p2, s2, a2, u2, c2)then VM3 = VariableModel(d3, p3, s3, a3, u3, c3)whered3 = d1 = d2- Note that all models must agree on the declared type of a variable
c3 = c2- A variable is captured if it is captured in the model of the finally block (note that the finally block is analyzed using the join of the model from before the try block and after the try block, and so any captures from the try block are already modelled here).
- if
c3is true thenp3 = []. (Captured variables can never be promoted.) - Otherwise, if
bis true thenp3 = p2 - Otherwise, if the last entry in
p1is a subtype of the last entry ofp2, thenp3 = p1elsep3 = p2. If the variable is not written to in the finally block, then it is valid to use any promotions from the try block in any subsequent code (since if any subsequent code is executed, the try block must have completed normally). We only choose to do so if the last entry is more precise. (TODO: is this the right thing to do here?). s3 = s2- The set of types of interest is the set of types of interest in the finally block.
a3 = a2 || a1- A variable is definitely assigned after the finally block if it is definitely assigned by the try block or by the finally block (note that code after the finally block will only be executed if the try block completes normally).
u3 = u2- A variable is definitely unassigned if it is definitely unassigned in the model of the finally block (note that the finally block is analyzed using the join of the model from before the try block and after the try block, and so the absence of any assignments that may have occurred in the try block is already modelled here).
- If
-
restrict(MB, MF, N), whereMBandMFare flow models andNis a set of variables assigned in the finally clause, models the flow of information through a try/finally statement, and is defined as follows:- We define
restrict(MB, MF, N)to beM3 = FlowModel(r3, VI3)where:MB = FlowModel(rb, VIB)MF = FlowModel(rf, VIF))pop(rb) = pop(rf) = r0for somer0r3ispush(r0, top(rb) && top(rf))bis true ifvis inNand otherwise falseVI3is the map which maps each variablevin the domain ofVIBandVIFtorestrictV(VIB(v), VIF(v), b).
- We define
-
unreachable(M)represents the model corresponding to a program location which is unreachable, but is otherwise modeled by flow modelM = FlowModel(r, VI), and is defined asFlowModel(push(pop(r), false), VI) -
conservativeJoin(M, written, captured)represents a conservative approximation of the flow model that could result from joiningMwith a model in which variables inwrittenmight have been written to and variables incapturedmight have been write-captured. It is defined asFlowModel(r, VI1)whereMisFlowModel(r, VI0)andVI1is the map such that:VI0mapsvtoVM0 = VariableModel(d0, p0, s0, a0, u0, c0)- If
capturedcontainsvthenVI1mapsvtoVariableModel(d0, [], s0, a0, false, true) - Otherwise if
writtencontainsvthenVI1mapsvtoVariableModel(d0, [], s0, a0, false, c0) - Otherwise
VI1mapsvtoVM0
-
inheritTestedV(VM1, VM2), whereVM1andVM2are variable models, represents a modification ofVM1to include any additional types of interest fromVM2. It is defined as follows:- We define
inheritTestedV(VM1, VM2)to beVM3 = VariableModel(d1, p1, s3, a1, u1, c1)where:VM1 = VariableModel(d1, p1, s1, a1, u1, c1)VM2 = VariableModel(d2, p2, s2, a2, u2, c2)s3 = s1 U s2- The set of test sites is the union of the test sites on either path
- We define
-
inheritTested(M1, M2), whereM1andM2are flow models, represents a modification ofM1to include any additional types of interest fromM2. It is defined as follows:- We define
inheritTested(M1, M2)to beM3 = FlowModel(r1, VI3)where:M1 = FlowModel(r1, VI1)M2 = FlowModel(r2, VI2)VI3is the map which maps each variablevin the domain of bothVI1andVI2toinheritTestedV(VI1(v), VI2(v)), and maps each variable in the domain ofVI1but notVI2toVI1(v).
- We define
Promotion policy is defined by the following operations on flow models.
We say that the current type of a variable x in variable model VM is S where:
VM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)promoted = [...l, S]or (promoted = []anddeclared = S)
Policy:
-
We say that at type
Tis a type of interest for a variablexin a set of tested typestestediftestedcontains a typeSsuch thatTisS, orTis NonNull(S). -
We say that a variable
xis promotable via type test with typeTgiven variable modelVMifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)- and
capturedis false - and
Sis the current type ofxinVM - and not
S <: T - and
T <: Sor (SisX extends RandT <: R) or (SisX & RandT <: R)
-
We say that a variable
xis promotable via assignment of an expression of typeTgiven variable modelVMifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)- and
capturedis false - and
Sis the current type ofxinVM - and
T <: Sand notS <: T - and
Tis a type of interest forxintested
-
We say that a variable
xis demotable via assignment of an expression of typeTgiven variable modelVMifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)- and
capturedis false - and [...promoted, declared] contains a type
Ssuch thatTisSorTis NonNull(S).
Definitions:
-
assign(x, E, M)wherexis a local variable,Eis an expression of inferred typeT, andM = FlowModel(r, VI)is the flow model forEis defined to beFlowModel(r, VI[x -> VM])where:VI(x) = VariableModel(declared, promoted, tested, assigned, unassigned, captured)- if
capturedis true then:VM = VariableModel(declared, promoted, tested, true, false, captured).
- otherwise if
xis promotable via assignment ofEgivenVMVM = VariableModel(declared, [...promoted, T], tested, true, false, captured).
- otherwise if
xis demotable via assignment ofEgivenVMVM = VariableModel(declared, demoted, tested, true, false, captured).- where
previousis the prefix ofpromotedending with the first typeSsuch thatT <: S, and:- if
Sis nullable and ifT <: QwhereQis NonNull(S) thendemotedis[...previous, Q] - otherwise
demotedisprevious
- if
-
stripParens(E1), whereE1is an expression, is the result of stripping outer parentheses from the expressionE1. It is defined to be the expressionE3, where:- If
E1is a parenthesized expression of the form(E2), thenE3=stripParens(E2). - Otherwise,
E3=E1.
- If
-
equivalentToNull(T), whereTis a type, indicates whetherTis equivalent to theNulltype. It is defined to be true ifT <: NullandNull <: T; otherwise false. -
promote(E, T, M)whereEis an expression,Tis a type which it may be promoted to, andM = FlowModel(r, VI)is the flow model in which to promote, is defined to beM3, where:- If
stripParens(E)is not a promotion target, thenM3=M - If
stripParens(E)is a promotion targetx, then- Let
VM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)be the variable model forxinVI - If
xis not promotable via type test toTgivenVM, thenM3=M - Else
- Let
Sbe the current type ofxinVM - If
T <: Sthen letT1=T - Else if
SisX extends Rthen letT1=X & T - Else If
SisX & Rthen letT1=X & T - Else
xis not promotable (shouldn't happen since we checked above) - Let
VM2 = VariableModel(declared, [...promoted, T1], [...tested, T], assigned, unassigned, captured) - Let
M2 = FlowModel(r, VI[x -> VM2]) - If
T1 <: NeverthenM3=unreachable(M2), otherwiseM3=M2
- Let
- Let
- If
-
promoteToNonNull(E, M)whereEis an expression andMis a flow model is defined to bepromote(E, T, M)whereT0is the type ofE, andTis NonNull(T0). -
factor(T, S)whereTandSare types defines the "remainder" ofTwhenShas been removed from consideration by an instance check. It is defined as follows:- If
T <: SthenNever - Else if
TisR?andNull <: Sthenfactor(R, S) - Else if
TisR?thenfactor(R, S)? - Else if
TisR*andNull <: Sthenfactor(R, S) - Else if
TisR*thenfactor(R, S)* - Else if
TisFutureOr<R>andFuture<R> <: Sthenfactor(R, S) - Else if
TisFutureOr<R>andR <: Sthenfactor(Future<R>, S) - Else
T
- If
Given an assignment (or composite assignment) x = E where x has current type
S (possibly the result of promotion), inference and promotion interact as
follows.
- Inference for
Eis done as usual, usingSas the downwards typing context. All reference toxwithinEare treated as having typeSas usual. - Let
Tbe the resulting inferred type ofE. - The assignment is treated as an assignment of an expression of type
Tto a variable of typeS, with the usual promotion, demotion and errors applied.
The flow model pass is initiated by visiting the top level function, method, constructor, or field declaration to be analyzed.
Analysis of an expression N assumes that before(N) has been computed, and
uses it to derive after(N), true(N), and false(N).
If N is an expression, and the following rules specify the values to be
assigned to true(N) and false(N), but do not specify the value for
after(N), then it is by default assigned as follows:
after(N) = join(true(N), false(N)).
If N is an expression, and the following rules specify the value to be
assigned to after(N), but do not specify values for true(N) and false(N),
then they are all assigned the same value as after(N).
-
Variable or getter: If
Nis an expression of the formxwhere the type ofxisTthen:- If
T <: Neverthen:- Let
after(N) = unreachable(before(N)).
- Let
- Otherwise:
- Let
after(N) = before(N).
- Let
- If
-
True literal: If
Nis the literaltrue, then:- Let
true(N) = before(N). - Let
false(N) = unreachable(before(N)).
- Let
-
False literal: If
Nis the literalfalse, then:- Let
true(N) = unreachable(before(N)). - Let
false(N) = before(N).
- Let
-
TODO(paulberry): list, map, and set literals.
-
other literal: If
Nis some other literal than the above, then:- Let
after(N) = before(N).
- Let
-
throw: If
Nis a throw expression of the formthrow E1, then:- Let
before(E1) = before(N). - Let
after(N) = unreachable(after(E1))
- Let
-
Local-variable assignment: If
Nis an expression of the formx = E1wherexis a local variable, then:- Let
before(E1) = before(N). - Let
after(N) = assign(x, E1, after(E1)).
- Let
-
operator== If
Nis an expression of the formE1 == E2, where the static type ofE1isT1and the static type ofE2isT2, then:- Let
before(E1) = before(N). - Let
before(E2) = after(E1). - If
equivalentToNull(T1)andequivalentToNull(T2), then:- Let
true(N) = after(E2). - Let
false(N) = unreachable(after(E2)).
- Let
- Otherwise, if
equivalentToNull(T1)andT2is non-nullable, orequivalentToNull(T2)andT1is non-nullable, then:- Let
after(N) = after(E2). - Note that now that Dart no longer supports unsound null safety mode, it
would be sound (and probably preferable) to let
true(N) = unreachable(after(E2))andfalse(N) = after(E2). This improvement is being contemplated as part of dart-lang#3100.
- Let
- Otherwise, if
stripParens(E1)is anullliteral, then:- Let
true(N) = after(E2). - Let
false(N) = promoteToNonNull(E2, after(E2)).
- Let
- Otherwise, if
stripParens(E2)is anullliteral, then:- Let
true(N) = after(E1). - Let
false(N) = promoteToNonNull(E1, after(E2)).
- Let
- Otherwise:
- Let
after(N) = after(E2).
- Let
Note that it is tempting to generalize the two
nullliteral cases to apply to any expression whose type isNull, but this would be unsound in cases whereE2assigns tox. (Consider, for example,(int? x) => x == (x = null) ? true : x.isEven, which tries to callnull.isEvenin the event of a non-null input). - Let
-
operator!= If
Nis an expression of the formE1 != E2, it is treated as equivalent to the expression!(E1 == E2). -
instance check If
Nis an expression of the formE1 is Swhere the static type ofE1isTthen:- Let
before(E1) = before(N) - If
Tis a bottom type, then:- Let
true(N) = unreachable(after(E1)). - Let
false(N) = after(E1).
- Let
- Otherwise:
- Let
true(N) = promote(E1, S, after(E1)) - Let
false(N) = promote(E1, factor(T, S), after(E1))
- Let
- Let
-
negated instance check If
Nis an expression of the formE1 is! Swhere the static type ofE1isTthen:- Let
before(E1) = before(N) - If
Tis a bottom type, then:- Let
true(N) = after(E1). - Let
false(N) = unreachable(after(E1)).
- Let
- Otherwise:
- Let
true(N) = promote(E1, factor(T, S), after(E1)) - Let
false(N) = promote(E1, S, after(E1))
- Let
Note that flow analysis treats
E1 is! Sthe same as the equivalent expression!(E1 is S). - Let
-
type cast If
Nis an expression of the formE1 as Swhere the static type ofE1isTthen:- Let
before(E1) = before(N) - Let
after(N) = promote(E1, S, after(E1))
- Let
-
Local variable conditional assignment: If
Nis an expression of the formx ??= E1wherexis a local variable, then:- Let
before(E1) = split(promote(x, Null, before(N))). - Let
M1 = assign(x, E1, after(E1)) - Let
M2 = split(promoteToNonNull(x, before(N))) - Let
after(N) = merge(M1, M2)
- Let
TODO: This isn't really right, E1 isn't really an expression here.
-
Conditional assignment to a non local-variable: If
Nis an expression of the formE1 ??= E2whereE1is not a local variable, and the type ofE1isT1, then:- Let
before(E1) = before(N). - If
T1is strictly non-nullable, then:- Let
before(E2) = unreachable(after(E1)). - Let
after(N) = after(E1).
- Let
- Otherwise:
- Let
before(E2) = split(after(E1)). - Let
after(N) = merge(after(E2), split(after(E1))).
- Let
TODO(paulberry): this doesn't seem to match what's currently implemented.
- Let
-
Conditional expression: If
Nis a conditional expression of the formE1 ? E2 : E3, then:- Let
before(E1) = before(N). - Let
before(E2) = split(true(E1)). - Let
before(E3) = split(false(E1)). - Let
after(N) = merge(after(E2), after(E3)). - Let
true(N) = merge(true(E2), true(E3)). - Let
false(N) = merge(false(E2), false(E3)).
- Let
-
If-null: If
Nis an if-null expression of the formE1 ?? E2, where the type ofE1isT1, then:- Let
before(E1) = before(N). - If
T1is strictly non-nullable, then:- Let
before(E2) = unreachable(after(E1)). - Let
after(N) = after(E1).
- Let
- Otherwise:
- Let
before(E2) = split(after(E1)). - Let
after(N) = merge(after(E2), split(after(E1))).
- Let
TODO(paulberry): this doesn't seem to match what's currently implemented.
- Let
-
Shortcut and: If
Nis a shortcut "and" expression of the formE1 && E2, then:- Let
before(E1) = before(N). - Let
before(E2) = split(true(E1)). - Let
true(N) = unsplit(true(E2)). - Let
false(N) = merge(split(false(E1)), false(E2)).
- Let
-
Shortcut or: If
Nis a shortcut "or" expression of the formE1 || E2, then:- Let
before(E1) = before(N). - Let
before(E2) = split(false(E1)). - Let
false(N) = unsplit(false(E2)). - Let
true(N) = merge(split(true(E1)), true(E2)).
- Let
-
Binary operator: All binary operators other than
==,&&,||, and??are handled as calls to the appropriateoperatormethod. -
Null check operator: If
Nis an expression of the formE!, then:- Let
before(E) = before(N). - Let
after(E) = promoteToNonNull(E, after(E)).
- Let
-
Method invocation: If
Nis an expression of the formE1.m1(E2), then:- Let
before(E1) = before(N) - Let
before(E2) = after(E1) - Let
Tbe the static return type of the invocation - If
T <: Neverthen:- Let
after(N) = unreachable(after(E2)).
- Let
- Otherwise:
- Let
after(N) = after(E2).
- Let
TODO(paulberry): handle
E1.m1(E2, E3, ...). - Let
TODO: Add missing expressions, handle cascades and left-hand sides accurately
-
Expression statement: If
Nis an expression statement of the formEthen:- Let
before(E) = before(N). - Let
after(N) = after(E).
- Let
-
Break statement: If
Nis a statement of the formbreak [L];, then:-
Let
Sbe the statement targeted by thebreak. IfLis not present, this is the innermostdo,for,switch, orwhilestatement. Otherwise it is thedo,for,switch, orwhilestatement with a label matchingL. -
Update
break(S) = join(break(S), before(N)). -
Let
after(N) = unreachable(before(N)).
-
-
Continue statement: If
Nis a statement of the formcontinue [L];then:-
Let
Sbe the statement targeted by thecontinue. IfLis not present, this is the innermostdo,for, orwhilestatement. Otherwise it is thedo,for, orwhilestatement with a label matchingL, or theswitchstatement containing a switch case with a label matchingL. -
Update
continue(S) = join(continue(S), before(N)). -
Let
after(N) = unreachable(before(N)).
-
-
Return statement: If
Nis a statement of the formreturn E1;then:- Let
before(E) = before(N). - Let
after(N) = unreachable(after(E));
- Let
-
Conditional statement: If
Nis a conditional statement of the formif (E) S1 else S2then:- Let
before(E) = before(N). - Let
before(S1) = split(true(E)). - Let
before(S2) = split(false(E)). - Let
after(N) = merge(after(S1), after(S2)).
- Let
-
while statement: If
Nis a while statement of the formwhile (E) Sthen:- Let
before(E) = conservativeJoin(before(N), assignedIn(N), capturedIn(N)). - Let
before(S) = split(true(E)). - Let
after(N) = inheritTested(join(false(E), unsplit(break(S))), after(S)).
- Let
-
for statement: If
Nis a for statement of the formfor (D; C; U) S, then:- Let
before(D) = before(N). - Let
before(C) = conservativeJoin(after(D), assignedIn(N), capturedIn(N)). - Let
before(S) = split(true(C)). - Let
before(U) = merge(after(S), continue(S)). - Let
after(N) = inheritTested(join(false(C), unsplit(break(S))), after(U)).
- Let
-
do while statement: If
Nis a do while statement of the formdo S while (E)then:- Let
before(S) = conservativeJoin(before(N), assignedIn(N), capturedIn(N)). - Let
before(E) = join(after(S), continue(N)) - Let
after(N) = join(false(E), break(S))
- Let
-
for each statement: If
Nis a for statement of the formfor (T X in E) S,for (var X in E) S, orfor (X in E) S, then:- Let
before(E) = before(N) - Let
before(S) = conservativeJoin(after(E), assignedIn(N), capturedIn(N)) - Let
after(N) = join(before(S), break(S))
TODO(paulberry): this glosses over how we handle the implicit assignment to X. See dart-lang/sdk#42653.
- Let
-
switch statement: If
Nis a switch statement of the formswitch (E) {alternatives}then:- Let
before(E) = before(N). - For each
Cinalternativeswith statement bodyS:- If
Cis labelled letbefore(S) = conservativeJoin(after(E), assignedIn(N), capturedIn(N))otherwise letbefore(S) = after(E).
- If
- If the cases are exhaustive, then let
after(N) = break(N)otherwise letafter(N) = join(after(E), break(N)).
- Let
-
try catch: If
Nis a try/catch statement of the formtry B alternativesthen:- Let
before(B) = before(N) - For each catch block
on Ti Siinalternatives:- Let
before(Si) = conservativeJoin(before(N), assignedIn(B), capturedIn(B))
- Let
- Let
after(N) = join(after(B), after(C0), ..., after(Ck))
- Let
-
try finally: If
Nis a try/finally statement of the formtry B1 finally B2then:- Let
before(B1) = split(before(N)) - Let
before(B2) = split(join(drop(after(B1)), conservativeJoin(before(N), assignedIn(B1), capturedIn(B1)))) - Let
after(N) = restrict(after(B1), after(B2), assignedIn(B2))
- Let
void test() {
int? a = null;
if (false && a != null) {
a.and(3);
}
}Object x;
if (false && x is int) {
x.isEven
}