Scope yourself into troubles with AL

What if two variables are called the same? Or how about a function and variable with the same name? In this video, I take a look at scoping in AL. Check it out:

https://youtu.be/lkq2IFHqhcM

In this video, Erik explores a subtle but important concept in AL development for Business Central: variable scoping and name resolution. What happens when you name a variable the same as a built-in function like CompanyName? The answer might surprise you — and it’s exactly the kind of mistake that can silently break your code.

The Starting Point: Built-in Functions Without Parentheses

AL has an interesting quirk inherited from its origins in C/AL: when a procedure has no parameters, you don’t need to include the parentheses when calling it. This means that CompanyName and CompanyName() are both valid ways to call the built-in function that returns the current company name.

This made sense historically — in old AL (C/AL), there wasn’t really a difference between a global constant and a function call. We all got used to writing things like Record.Insert without parentheses. But this flexibility opens the door to a scoping problem.

The Setup: Shadowing a Built-in Function

Erik starts with a simple page extension and demonstrates how things go wrong step by step. Here’s the final code that illustrates the scoping issue:

pageextension 50100 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage()
    begin
        Message('This is %1', CompanyName());
        Test2();
    end;

    procedure Test2()
    begin
        CompanyName := 'Global Var';
        Message('This is %1', CompanyName);
        Test();
    end;

    procedure Test()
    var
        CompanyName: Text;
        Today: Date;
    begin
        Today := 20230101D;
        CompanyName := 'Local Var';
        Message('Today'' date is %1', Today);
    end;

    var
        CompanyName: Text;
}

How Name Resolution Works in AL

When the AL compiler encounters a name like CompanyName, it resolves it by searching from the innermost scope outward:

  1. Local variables — variables declared in the current procedure’s var section
  2. Global variables — variables declared at the object level
  3. Built-in functions and symbols — system-provided functions like CompanyName()

This means that as soon as you declare a global variable called CompanyName, every unqualified reference to CompanyName in that object now refers to your variable — not the built-in function. The built-in function is effectively “shadowed.”

What Each Message Shows

When you run the code above, you see three messages:

  • “This is Cronus Canada Incorporated” — In OnOpenPage, CompanyName() with explicit parentheses still calls the built-in function, even though a global variable with the same name exists.
  • “This is Global Var” — In Test2, CompanyName without parentheses resolves to the global variable, which has been assigned the value ‘Global Var’.
  • “Today’s date is 01/01/2023” — In Test, the local variable Today shadows the built-in Today function, so it shows the hardcoded date rather than the current date.

The Parentheses Escape Hatch

One key takeaway: you can always reach the built-in function by explicitly using parentheses. Writing CompanyName() forces the compiler to treat it as a function call, bypassing any variable with the same name. But this is a workaround, not a best practice.

Can You Access the Shadowed Global Variable from a Local Scope?

Erik also experiments with whether you can reach a global variable that’s been shadowed by a local variable of the same name. In the case of a page extension, you can use the protected var modifier and reference it through CurrPage, but in general, once a local variable shadows a global one, the global is inaccessible within that procedure.

What About Other Built-in Functions?

Erik tests a couple of other scenarios:

  • Format — Creating a variable called Format won’t cause as much trouble because Format always requires parameters, so the compiler can distinguish between the variable and the function call.
  • Today — This one is dangerous, just like CompanyName. The built-in Today function takes no parameters, so a variable named Today will completely shadow it. In the demo, the local Today variable holds 20230101D instead of the actual current date.

The Lesson: Don’t Get Clever with Scoping

Erik is candid about how this video came about: he made this exact mistake himself. He created a variable called CompanyName somewhere in his code, and it caused subtle bugs. Everything appeared to work, but the built-in function was being silently replaced by the variable. He had to go back and add parentheses in several places to get things working correctly.

The takeaways are clear:

  • Don’t name variables after built-in functions. Even if AL allows it, it creates confusing, error-prone code.
  • Be aware of AL’s scope resolution order: local → global → built-in.
  • Use parentheses explicitly when calling parameterless functions if there’s any chance of ambiguity.
  • Pay attention to syntax highlighting in VS Code — Erik noticed the color change when the identifier switched from a function call to a variable reference, which is a helpful visual cue.

Scoping bugs are particularly insidious because the code compiles and runs without errors — it just doesn’t do what you expect. Learn from Erik’s mistake and keep your variable names distinct from built-in function names.