Not all code paths return a value
Procedures with a return type must produce a value on every reachable path. If one branch falls through without returning a value, AL can end up returning an implicit default value (0, false, empty text), which hides logic bugs and makes behavior harder to reason about.
This rule flags declarations where not all reachable code paths return a value. A path ending with a built-in error call does not return to the procedure and therefore does not need a return value.
Example
The following procedure misses a return value when the condition is false:
procedure GetAmount(IncludeVat: Boolean): Decimal
begin
if IncludeVat then
exit(AmountInclVat);
end;Return a value on all paths:
procedure GetAmount(IncludeVat: Boolean): Decimal
begin
if IncludeVat then
exit(AmountInclVat)
else
exit(AmountExclVat);
end;When the diagnostic is reported
The rule is reported when all of the following are true:
- The declaration is a procedure with an explicit return type
- At least one reachable path can end without returning a value
- The declaration is not marked with
TryFunction
Triggers are intentionally excluded from this rule, even when they declare a return type.
For named return variables, an assignment must be guaranteed on every path that reaches the end of the procedure. Assignments made through a var parameter or by using the return variable as a call receiver count, because those calls can write the value. The rule also follows calls in conditions, case selectors, loop conditions, for bounds, and foreach collections when the expression must execute. It remains conservative for the right side of and and or, and for either branch of a conditional expression, because those expressions can be skipped.
A case statement without else is accepted when it handles every enum or option value visible to the current compilation. Values added by an extension available at compilation time are included in that check.
Exception
Paths that end with a built-in call that never returns are accepted: Error(...), Record.FieldError(...), and FieldRef.FieldError(...).
procedure GetAmount(IncludeVat: Boolean): Decimal
begin
if IncludeVat then
exit(AmountInclVat)
else
Error('Prices without VAT are not supported.');
end;
procedure GetUnitPrice(Item: Record Item): Decimal
begin
if Item."Unit Price" > 0 then
exit(Item."Unit Price")
else
Item.FieldError("Unit Price", 'must be positive.');
end;Only these built-in methods qualify. A user-defined procedure named Error or FieldError is a normal call and does not terminate a path.
Known limitation: Error(ErrorInfo) is currently treated as terminating even when ErrorInfo.Collectible is true and the call runs in an ErrorBehavior::Collect scope. In that situation AL can continue after the call, but determining it requires data-flow and enclosing-call analysis beyond this rule’s current invocation classification.
A named return variable passed to a var parameter, or used as the receiver of a call, counts as assigned:
procedure BuildGreeting() Greeting: Text
begin
GetGreeting(Greeting);
end;
local procedure GetGreeting(var GreetingText: Text)
begin
GreetingText := 'Hello';
end;
procedure FindCustomer(CustomerNo: Code[20]) Customer: Record Customer
begin
Customer.Get(CustomerNo);
end;Passing it by value (without var) does not count.
See also
- LC0097 - Avoid mixing exit() and named return variable assignments
- Working with AL methods - Return values on Microsoft Learn
- AL control statements - Exit statement on Microsoft Learn