[email protected], [email protected]
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
restrictV
to 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 keyx
with the valueVM1
, and the keyy
with the valueVM2
. - We use the notation
VI[x -> VM]
to denote the map which maps every key inVI
to its corresponding value inVI
exceptx
, which is mapped toVM
in 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 elementsa
andb
. - We use the notation
a::l
wherel
is a list to denote a list beginning witha
and followed by all of the elements ofl
.
- We use the notation
-
Stacks
- We use the notation
push(s, x)
to mean pushingx
onto the top of the stacks
. - We use the notation
pop(s)
to mean the stack that results from removing the top element ofs
. Ifs
is empty, the result is undefined. - We use the notation
top(s)
to mean the top element of the stacks
. Ifs
is empty, the result is undefined. - Informally, we also use
a::t
to describe a stacks
such thattop(s)
isa
andpop(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.
-
declaredType
is the type assigned to the variable at its declaration site (either explicitly or by type inference). -
promotedTypes
is an ordered set of types that the variable has been promoted to, with the final entry in the ordered set being the current promoted type of the variable. Note that each entry in the ordered set must be a subtype of all previous entries, and of the declared type. -
tested
is 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. -
assigned
is a boolean value indicating whether the variable has definitely been assigned at the given point in the source code. Whenassigned
is true, we say that the variable is definitely assigned at that point. -
unassigned
is a boolean value indicating whether the variable has definitely not been assigned at the given point in the source code. Whenunassigned
is 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). -
writeCaptured
is 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.
-
reachable
is a stack of boolean values modeling the reachability of the given point in the source code. Thei
th element of the stack (counting from the top of the stack) encodes whether thei-1
th enclosing control flow split is reachable from theith
enclosing control flow split (or wheni
is 0, whether the current program point is reachable from the enclosing control flow split). So if the bottom element ofreachable
isfalse
, 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 thereachable
stack, since each element of the stack models the reachability of one control flow split from its enclosing control flow split. -
variableInfo
is 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)
, whereN
is a statement or expression, represents the flow model just prior to execution ofN
. -
after(N)
, whereN
is a statement or expression, represents the flow model just after execution ofN
, assuming thatN
completes normally (i.e. does not throw an exception or perform a jump such asreturn
,break
, orcontinue
). -
true(E)
, whereE
is an expression, represents the flow model just after execution ofE
, assuming thatE
completes normally and evaluates totrue
. -
false(E)
, whereE
is an expression, represents the flow model just after execution ofE
, assuming thatE
completes normally and evaluates tofalse
. -
break(S)
, whereS
is ado
,for
,switch
, orwhile
statement, represents the join of the flow models reaching eachbreak
statement targettingS
. -
continue(S)
, whereS
is ado
,for
,switch
, orwhile
statement, represents the join of the flow models reaching eachcontinue
statement targettingS
. -
assignedIn(S)
, whereS
is ado
,for
,switch
, orwhile
statement, or afor
element 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 ofS
is defined as:- If
S
is ado
orwhile
statement, the entire statementS
. - If
S
is afor
statement or afor
element in a collection, whoseforLoopParts
take the form of a traditional for loop, all ofS
except theforInitializerStatement
. - If
S
is afor
statement or afor
element in a collection, whoseforLoopParts
take 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 ofx
at its declaration site), but a loop of the formfor (x in ...) ...
(wherex
is declared elsewhere in the function) is considered to assign tox
. - If
S
is aswitch
statement, all ofS
except the switchexpression
.
- If
-
capturedIn(S)
, whereS
is ado
,for
,switch
, orwhile
statement, represents the set of variables assigned to in a local function or function expression in the recurrent part ofS
, where the "recurrent" part ofs
is 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)
, whereVM1
andVM2
are 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
p3 = p1 ^ p2
p1
andp2
are totally ordered subsets of a global partial order. Their intersection is a subset of each, and as such is also totally ordered.
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)
wherer2
isr
withtrue
pushed as the top element of the stack. -
drop(M)
, whereM = FlowModel(r, VM)
is defined asFlowModel(r1, VM)
wherer
is of the formn0::r1
. 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)
wherer
is of the formn0::n1::s
andr1 = (n0&&n1)::s
. The modelM1
is a flow model which collapses the top two elements of the reachability model fromM
into a single boolean which conservatively summarizes the reachability information present inM
. -
merge(M1, M2)
, whereM1
andM2
are flow models is the inverse ofsplit
and 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) = r0
then:- if
top(r1)
is true andtop(r2)
is false, thenM3
isFlowModel(pop(r1), VI1)
. - if
top(r1)
is false andtop(r2)
is true, thenM3
isFlowModel(pop(r2), VI2)
. - otherwise
M3
isjoin(unsplit(M1), unsplit(M2))
- if
-
join(M1, M2)
, whereM1
andM2
are 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) = r0
for somer0
r3
ispush(r0, top(r1) || top(r2))
VI3
is the map which maps each variablev
in the domain ofVI1
andVI2
tojoinV(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
,join
andjoinV
combinators are commutative and associative by construction.For brevity, we will sometimes extend
join
andmerge
to 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)
, whereVMB
andVMF
are variable models andb
is 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
c3
is true thenp3 = []
. (Captured variables can never be promoted.) - Otherwise, if
b
is true thenp3 = p2
- Otherwise, if the last entry in
p1
is a subtype of the last entry ofp2
, thenp3 = p1
elsep3 = 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)
, whereMB
andMF
are flow models andN
is 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) = r0
for somer0
r3
ispush(r0, top(rb) && top(rf))
b
is true ifv
is inN
and otherwise falseVI3
is the map which maps each variablev
in the domain ofVIB
andVIF
torestrictV(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 joiningM
with a model in which variables inwritten
might have been written to and variables incaptured
might have been write-captured. It is defined asFlowModel(r, VI1)
whereM
isFlowModel(r, VI0)
andVI1
is the map such that:VI0
mapsv
toVM0 = VariableModel(d0, p0, s0, a0, u0, c0)
- If
captured
containsv
thenVI1
mapsv
toVariableModel(d0, [], s0, a0, false, true)
- Otherwise if
written
containsv
thenVI1
mapsv
toVariableModel(d0, [], s0, a0, false, c0)
- Otherwise
VI1
mapsv
toVM0
-
inheritTestedV(VM1, VM2)
, whereVM1
andVM2
are variable models, represents a modification ofVM1
to 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)
, whereM1
andM2
are flow models, represents a modification ofM1
to 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)
VI3
is the map which maps each variablev
in the domain of bothVI1
andVI2
toinheritTestedV(VI1(v), VI2(v))
, and maps each variable in the domain ofVI1
but notVI2
toVI1(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 = S::l
or (promoted = []
anddeclared = S
)
Policy:
-
We say that at type
T
is a type of interest for a variablex
in a set of tested typestested
iftested
contains a typeS
such thatT
isS
, orT
is NonNull(S
). -
We say that a variable
x
is promotable via type test with typeT
given variable modelVM
ifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)
- and
captured
is false - and
S
is the current type ofx
inVM
- and not
S <: T
- and
T <: S
or (S
isX extends R
andT <: R
) or (S
isX & R
andT <: R
)
-
We say that a variable
x
is promotable via assignment of an expression of typeT
given variable modelVM
ifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)
- and
captured
is false - and
S
is the current type ofx
inVM
- and
T <: S
and notS <: T
- and
T
is a type of interest forx
intested
-
We say that a variable
x
is demotable via assignment of an expression of typeT
given variable modelVM
ifVM = VariableModel(declared, promoted, tested, assigned, unassigned, captured)
- and
captured
is false - and declared::promoted contains a type
S
such thatT
isS
orT
is NonNull(S
).
Definitions:
-
assign(x, E, M)
wherex
is a local variable,E
is an expression of inferred typeT
, andM = FlowModel(r, VI)
is the flow model forE
is defined to beFlowModel(r, VI[x -> VM])
where:VI(x) = VariableModel(declared, promoted, tested, assigned, unassigned, captured)
- if
captured
is true then:VM = VariableModel(declared, promoted, tested, true, false, captured)
.
- otherwise if
x
is promotable via assignment ofE
givenVM
VM = VariableModel(declared, T::promoted, tested, true, false, captured)
.
- otherwise if
x
is demotable via assignment ofE
givenVM
VM = VariableModel(declared, demoted, tested, true, false, captured)
.- where
previous
is the suffix ofpromoted
starting with the first typeS
such thatT <: S
, and:- if
S
is nullable and ifT <: Q
whereQ
is NonNull(S
) thendemoted
isQ::previous
- otherwise
demoted
isprevious
- if
-
stripParens(E1)
, whereE1
is an expression, is the result of stripping outer parentheses from the expressionE1
. It is defined to be the expressionE3
, where:- If
E1
is a parenthesized expression of the form(E2)
, thenE3
=stripParens(E2)
. - Otherwise,
E3
=E1
.
- If
-
equivalentToNull(T)
, whereT
is a type, indicates whetherT
is equivalent to theNull
type. It is defined to be true ifT <: Null
andNull <: T
; otherwise false. -
promote(E, T, M)
whereE
is an expression,T
is 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 forx
inVI
- If
x
is not promotable via type test toT
givenVM
, thenM3
=M
- Else
- Let
S
be the current type ofx
inVM
- If
T <: S
then letT1
=T
- Else if
S
isX extends R
then letT1
=X & T
- Else If
S
isX & R
then letT1
=X & T
- Else
x
is not promotable (shouldn't happen since we checked above) - Let
VM2 = VariableModel(declared, T1::promoted, T::tested, assigned, unassigned, captured)
- Let
M2 = FlowModel(r, VI[x -> VM2])
- If
T1 <: Never
thenM3
=unreachable(M2)
, otherwiseM3
=M2
- Let
- Let
- If
-
promoteToNonNull(E, M)
whereE
is an expression andM
is a flow model is defined to bepromote(E, T, M)
whereT0
is the type ofE
, andT
is NonNull(T0
). -
factor(T, S)
whereT
andS
are types defines the "remainder" ofT
whenS
has been removed from consideration by an instance check. It is defined as follows:- If
T <: S
thenNever
- Else if
T
isR?
andNull <: S
thenfactor(R, S)
- Else if
T
isR?
thenfactor(R, S)?
- Else if
T
isR*
andNull <: S
thenfactor(R, S)
- Else if
T
isR*
thenfactor(R, S)*
- Else if
T
isFutureOr<R>
andFuture<R> <: S
thenfactor(R, S)
- Else if
T
isFutureOr<R>
andR <: S
thenfactor(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
E
is done as usual, usingS
as the downwards typing context. All reference tox
withinE
are treated as having typeS
as usual. - Let
T
be the resulting inferred type ofE
. - The assignment is treated as an assignment of an expression of type
T
to 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
N
is an expression of the formx
where the type ofx
isT
then:- If
T <: Never
then:- Let
after(N) = unreachable(before(N))
.
- Let
- Otherwise:
- Let
after(N) = before(N)
.
- Let
- If
-
True literal: If
N
is the literaltrue
, then:- Let
true(N) = before(N)
. - Let
false(N) = unreachable(before(N))
.
- Let
-
False literal: If
N
is 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
N
is some other literal than the above, then:- Let
after(N) = before(N)
.
- Let
-
throw: If
N
is a throw expression of the formthrow E1
, then:- Let
before(E1) = before(N)
. - Let
after(N) = unreachable(after(E1))
- Let
-
Local-variable assignment: If
N
is an expression of the formx = E1
wherex
is a local variable, then:- Let
before(E1) = before(N)
. - Let
after(N) = assign(x, E1, after(E1))
. - Let
true(N) = assign(x, E1, true(E1))
. - Let
false(N) = assign(x, E1, false(E1))
.
- Let
-
operator== If
N
is an expression of the formE1 == E2
, where the static type ofE1
isT1
and the static type ofE2
isT2
, 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)
andT2
is non-nullable, orequivalentToNull(T2)
andT1
is non-nullable, then:- Let
true(N) = unreachable(after(E2))
. - Let
false(N) = after(E2)
.
- Let
- Otherwise, if
stripParens(E1)
is anull
literal, then:- Let
true(N) = after(E2)
. - Let
false(N) = promoteToNonNull(E2, after(E2))
.
- Let
- Otherwise, if
stripParens(E2)
is anull
literal, 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
null
literal cases to apply to any expression whose type isNull
, but this would be unsound in cases whereE2
assigns tox
. (Consider, for example,(int? x) => x == (x = null) ? true : x.isEven
, which tries to callnull.isEven
in the event of a non-null input). - Let
-
operator!= If
N
is an expression of the formE1 != E2
, it is treated as equivalent to the expression!(E1 == E2)
. -
instance check If
N
is an expression of the formE1 is S
where the static type ofE1
isT
then:- Let
before(E1) = before(N)
- Let
true(N) = promote(E1, S, after(E1))
- Let
false(N) = promote(E1, factor(T, S), after(E1))
- Let
-
type cast If
N
is an expression of the formE1 as S
where the static type ofE1
isT
then:- Let
before(E1) = before(N)
- Let
after(N) = promote(E1, S, after(E1))
- Let
-
Local variable conditional assignment: If
N
is an expression of the formx ??= E1
wherex
is 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
N
is an expression of the formE1 ??= E2
whereE1
is not a local variable, and the type ofE1
isT1
, then:- Let
before(E1) = before(N)
. - If
T1
is 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
N
is 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
N
is an if-null expression of the formE1 ?? E2
, where the type ofE1
isT1
, then:- Let
before(E1) = before(N)
. - If
T1
is 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
N
is 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
N
is 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 appropriateoperator
method. -
Null check operator: If
N
is an expression of the formE!
, then:- Let
before(E) = before(N)
. - Let
after(E) = promoteToNonNull(E, after(E))
.
- Let
-
Method invocation: If
N
is an expression of the formE1.m1(E2)
, then:- Let
before(E1) = before(N)
- Let
before(E2) = after(E1)
- Let
T
be the static return type of the invocation - If
T <: Never
then:- 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
N
is an expression statement of the formE
then:- Let
before(E) = before(N)
. - Let
after(N) = after(E)
.
- Let
-
Break statement: If
N
is a statement of the formbreak [L];
, then:-
Let
S
be the statement targeted by thebreak
. IfL
is not present, this is the innermostdo
,for
,switch
, orwhile
statement. Otherwise it is thedo
,for
,switch
, orwhile
statement with a label matchingL
. -
Update
break(S) = join(break(S), before(N))
. -
Let
after(N) = unreachable(before(N))
.
-
-
Continue statement: If
N
is a statement of the formcontinue [L];
then:-
Let
S
be the statement targeted by thecontinue
. IfL
is not present, this is the innermostdo
,for
, orwhile
statement. Otherwise it is thedo
,for
, orwhile
statement with a label matchingL
, or theswitch
statement 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
N
is a statement of the formreturn E1;
then:- Let
before(E) = before(N)
. - Let
after(N) = unreachable(after(E))
;
- Let
-
Conditional statement: If
N
is a conditional statement of the formif (E) S1 else S2
then:- 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
N
is a while statement of the formwhile (E) S
then:- 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
N
is 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
N
is 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
N
is 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
N
is a switch statement of the formswitch (E) {alternatives}
then:- Let
before(E) = before(N)
. - For each
C
inalternatives
with statement bodyS
:- If
C
is 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
N
is a try/catch statement of the formtry B alternatives
then:- Let
before(B) = before(N)
- For each catch block
on Ti Si
inalternatives
:- 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
N
is a try/finally statement of the formtry B1 finally B2
then:- 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
}