-
Notifications
You must be signed in to change notification settings - Fork 21
if statement
An if
statement is a conditional statement; a conditional statement allows some portion of code to be executed depending on the value of a particular condition. Specifically, an if
statement allows the conditional execution of a block of code, zero or one times, with an optional alternative block of code (or another if
statement) to execute if and only if the first block of code is not executed.
The execution of an if
statement begins with the evaluation of the ConditionList (one or more Condition), which yields a Boolean
value, and that value controls whether the body of the statement will be executed. For a single Condition:
- The Condition can be an expression. In this case, the expression must yield at least one value, and the first value must be of type
Boolean
, which will be used as the result of the Condition. Any additional values are silently discarded. - Alternatively, the Condition can use an OptionalDeclaration (or OptionalDeclarationList), which defines one or more assignments that result from an expression. The expression must yield at least two values, and the first value must be of type
Boolean
, which will be used as the result of the Condition. - In either case, if the Condition expression short-circuits, then
False
will be used as the result of the ConditionList.
For a ConditionList with more than one Condition, evaluation begins with the first Condition. If the Condition evaluates to False
or short-circuits, then the evaluation of the ConditionList completes at that point with the value False
. If the Condition evaluates to True
, then evaluation proceeds to the next Condition; if every Condition, in turn, evaluates to True
, then the ConditionList evaluates to True
.
This simple example illustrates several of the possibilities discussed above, and uses a simple Boolean
expression for each of Condition in the two if
statements:
if (hot)
{
return "This porridge is too hot!";
}
else if (cold)
{
return "This porridge is too cold!";
}
else
{
return "This porridge is just right.";
}
The OptionalDeclaration and OptionalDeclarationList are used when the expression yields one or more values that are necessary within the body of the if
statement, as in this example, where the Boolean
value yielded from an Iterator.next()
call is consumed by (becomes the result of) the Condition, and the String
value yielded from that same call is assigned to a new variable s
, which can then be used in the “then” body of the if
statement, but is not assigned in the else
body of the if
statement:
void printNext(Iterator<String> iter)
{
@Inject Console console;
if (String s := iter.next())
{
console.println($"next string={s}");
}
else
{
console.println("iterator is empty");
}
}
The OptionalDeclaration and OptionalDeclarationList constructs is quite flexible. Consider the following interface FooBar
, and two variables fb1
and fb2
of the FooBar
type, where fb2
is also Nullable
:
interface FooBar
{
(Boolean, String, Int, Int) foo();
conditional (String, Int, Int) bar();
}
FooBar fb1;
FooBar? fb2;
The method foo()
always returns four values, but the method bar()
may return four values or a single False
value, because it declares a conditional return (just like the Iterator
in the previous example). For both foo()
and bar()
, the first returned value is of type Boolean
, and so both foo()
and bar()
can be used in an if
statement:
if (fb1.foo())
{
// do something ...
}
if (fb1.bar())
{
// do something ...
}
Furthermore, since each declares a total of four return values, the OptionalDeclarationList allows up to three assignments from each:
if ((String s, Int x, Int y) := fb1.foo())
{
console.println($"foo()=True, s={s}, x={x}, y={y}");
}
else
{
console.println($"foo()=False, s={s}, x={x}, y={y}");
}
However, in the case of a conditional return from bar()
, the else
clause does not have access to any of the return values beyond the first Boolean
, because the additional three return values are conditional:
if ((String s, Int x, Int y) := fb1.bar())
{
console.println($"bar()=True, s={s}, x={x}, y={y}");
}
else
{
// unlike the foo() example above, variables s, x, and y
// are NOT definitely assigned here, because bar() has a
// conditional return
console.println("bar()=False");
}
A short-circuiting expression will yield the same control flow as a False
value for the Condition, so if the Condition can short-circuit, then the assignments implied by the MultipleOptionalDeclaration cannot be assumed to have occurred before the else
clause:
// the postfix "?" operator can short-circuit
if ((String s, Int x, Int y) := fb2?.foo())
{
console.println($"foo()=True, s={s}, x={x}, y={y}");
}
else
{
// unlike the foo() example above, variables s, x, and y
// are NOT definitely assigned here, because the condition
// may have short-circuited at the '?' operator
console.println("foo()=False");
}
The Condition and ConditionList constructions are also used in the while
statement and the do
statement.
The ConditionList is reachable if the if
statement is reachable; the ConditionList completes if it is reachable and either completes or short-circuits. The “then” statement block is reachable if the ConditionList completes and the value of the ConditionList is not the constant False
. The else
clause is reachable if the ConditionList completes, and the value of the ConditionList is not the constant True
; in the case that the else
clause is not present, then the else
clause is assumed to be reachable and assumed to complete if an else
clause with an empty statement block would be reachable and would complete. The if
statement completes if the “then” statement block completes or if the else
clause completes.
If either the “then” statement block or the else
clause (if present) is unreachable because the ConditionList is the constant value False
or True
, that unreachability is not considered to be an error. For example:
if (False)
{
// this code is unreachable, which would normally be a
// compile-time error, but in order to preserve historical
// anachronisms, this unreachable code is permitted
foo();
}
Definite assignment rules:
- The VAS before the ConditionList is the VAS before the
if
statement. - The VAS before the “then” statement block is the VAST after the ConditionList.
- If the ConditionList can short-circuit, then the VAS at each possible point of short-circuiting is joined with the VASF after the ConditionList before it is provided to the
else
clause. - The VAS before the
else
clause is the VASF after the ConditionList. - In the case that the
else
clause is not present, then the VAS after theelse
clause is the VAS before theelse
clause. - The VAS after the
if
statement is the VAS after the “then” statement block joined with the VAS after theelse
clause.
The if
statement provides a local variable scope for any declarations in the ConditionList; that scope exists to the end of the if
statement, which is to say that both the “then” statement block and the else
clause are nested within that scope. The “then” statement block and the optional else
statement block each naturally provide a local variable scope, because they are statement blocks.
IfStatement: if ( ConditionList ) StatementBlock ElseStatementopt ElseStatement: else IfStatement else StatementBlock ConditionList: Condition ConditionList , Condition Condition: Expression OptionalDeclaration ConditionalAssignmentOp Expression ( OptionalDeclarationList , OptionalDeclaration ) ConditionalAssignmentOp Expression ConditionalAssignmentOp: := ?=
And from VariableStatement:
OptionalDeclarationList: OptionalDeclaration OptionalDeclarationList , OptionalDeclaration OptionalDeclaration: Assignable VariableTypeExpression Name VariableTypeExpression: val var TypeExpression Assignable: Name TernaryExpression . Name TernaryExpression ArrayIndexes