Deprecating explicit and implicit with statements

APPLIES TO: Business Central 2020 release wave 2 and later

Note

With Business Central 2022 release wave 2, the AL:Go! template for creating new AL projects in Visual Studio Code, now enables explicit with statements by default, by adding the NoImplicitWith option to the features property in the generated app.json file.

The extensibility model and the AL programming language are successors to the C/AL language. And the with statement has up until now been supported in AL. While using the with statement might make code harder to read, it can also prevent code in Business Central online from being upgraded without changes to the code or even worse - upgraded, but with changed behavior. We distinguish between two types of with statements; the explicit type of with using the keyword, and the implicit with which isn't expressed directly in code. The next sections describe the explicit and implicit types one by one.

The explicit with statement

In Business Central online your code is recompiled when the platform and application versions are upgraded. The recompilation ensures that it's working and the recompile regenerates the runtime artifacts to match the new platform. Breaking changes without due warning aren't allowed, but the use of the with statement makes it impossible, as Microsoft, to make even additive changes in a nonbreaking way. This problem isn't alone isolated to changes made by Microsoft; any additive change has the potential to break a with statement in consuming code.

The following example illustrates code written using the with statement; referred to in this context as the explicit with.

codeunit 50140 MyCodeunit
{
    procedure DoStuff()
    var
        Customer: Record Customer;
    begin
        with Customer do begin
            // Do some work on the Customer record.
            Name := 'Foo';

            if IsDirty() then 
                Modify();
        end;
    end; 

    local procedure IsDirty(): Boolean
    begin
        exit(false);
    end;
}

The DoStuff() procedure processes work on the Customer record and calls a local procedure IsDirty() to check whether to update the record or not. Looking at the code above it looks like it does nothing since IsDirty() returns false - assuming that the IsDirty() call (line 11) is in fact calling the local IsDirty() procedure.

Symbol lookup

If we take another look at the above code sample again, what would happen to that code if IsDirty was added to the base application between two releases? To understand that, we need to take a look at how compilers turn syntax into symbols. When the AL compiler meets the IsDirty call it must bind the syntax name to a procedure symbol.

When the AL compiler searches for the symbol IsDirty() in the sample above it searches in following order:

  1. Customer table
    • User-defined members on the Customer table and Customer table extensions
    • Platform-defined members, for example, FindFirst() or Modify()
  2. MyCodeunit codeunit
    • User-defined members, for example, IsDirty()
    • Platform-defined members
  3. Globally defined members

The first time the search for IsDirty finds the name IsDirty, it doesn't continue to the next top-level group. That means that if a procedure named IsDirty is introduced in the Customer table (platform or application) that procedure will be found instead of the procedure in MyCodeunit.

The solution for the explicit with is to stop using it. Using the with statement can make your code vulnerable to upstream changes. The example below illustrates how to rewrite the sample in The explicit with statement:

// Safe version
codeunit 50140 MyCodeunit
{
    procedure DoStuff()
    var
        Customer: Record Customer;
    begin
        // Do some work on the Customer record.
        Customer.Name := 'Foo';

        if IsDirty() then 
            Customer.Modify();
    end; 

    local procedure IsDirty(): Boolean
    begin
        exit(false);
    end;
} 

The implicit with statement

The implicit with is injected automatically by the compiler in certain situations. The next sections describe, how it works on codeunits and pages.

Codeunits

When a codeunit has the TableNo property set, there's an implicit with around the code inside the OnRun trigger. The implicit with is indicated with the comments in the code example below.

codeunit 50140 MyCodeunit
{
    TableNo = Customer;

    trigger OnRun()
    begin
        // with Rec do begin
        SetRange("No.", '10000', '20000');
        if Find() then
            repeat
            until Next() = 0;

        if IsDirty() then
            Error('Something is not clean');
        // end;
    end;

    local procedure IsDirty(): Boolean
    begin
        exit(false);
    end;
}

Similar to the The explicit with statement, the code looks like it calls the local IsDirty method, but depending on the Customer table, extensions to the Customer table, and built-in methods that might not be the case. If any of these implement an IsDirty method with an identical signature that returns true, then the example above will fail with an error. If an IsDirty method with a different signature is implemented, then this code won't compile and will fail to upgrade.

Pages

Pages on tables have the same type of implicit with, but around the entire object. Everywhere inside the page object the fields and methods from the source tables are directly available without any prefix.

page 50143 ImplicitWith
{
    SourceTable = Customer;

    layout
    {
        area(Content)
        {
            field("No."; "No.") { }
            field(Name; Name)
            {
                trigger OnValidate()
                begin
                    Name := 'test';
                end;
            }
        }
    }

    trigger OnInit()
    begin
        if IsDirty() then Insert()
    end;

    local procedure IsDirty(): Boolean
    begin
        exit(Name <> '');
    end;
}

On pages, it's not only the code in triggers and procedures that is spanned by the implicit with on the source Rec; also the source expressions for the fields are covered.

Warnings and using pragma

From Business Central 2020 release wave 2 we begin to warn about the use of explicit and implicit with for extensions that are targeting the cloud. There are two different warnings: AL0604 and AL0606.

Note

The warnings will become errors with a future release. We will at the earliest remove with statement support from the Business Central 2021 release wave 2.

AL0606 - use of explicit with

The warning has a Quick Fix code action that allows you to convert one or more statements inside the with statement to fully qualified statements, which means statements as shown in The explicit with statement.

AL0604 - use of implicit with

Just qualifying with Rec. won't solve this problem. The IsDirty() will still be vulnerable to upstream change. We want to remove the implicit with, but also offer an opt-in model to avoid forcing everyone to upgrade their code at once.

The solution for that is to introduce pragmas in AL. A pragma is an instruction to the compiler on how it should understand the code. The pragma instructs the compiler not to create an implicit with for the Rec variable.

The following shows the syntax for the implicit with pragma. The pragma must be used before the beginning of the codeunit or page. For more information, see Pragma Directive and Pragma ImplicitWith.

#pragma implicitwith disable|restore

The fixed example under Pages will then look like this:

#pragma implicitwith disable
page 50143 ImplicitWith
{
    SourceTable = Customer;

    layout
    {
        area(Content)
        {
            field("No."; Rec."No.") { }
            field(Name; Rec.Name)
            {
                trigger OnValidate()
                begin
                    Rec.Name := 'test';
                end;
            }
        }
    }

    trigger OnInit()
    begin
        if IsDirty() then Rec.Insert()
    end;

    local procedure IsDirty(): Boolean
    begin
        exit(Name <> '');
    end;
} 
#pragma implicitwith restore

The Quick Fix code actions will automatically insert the pragma before and after the fixed object.

Tip

Remember to Enable Code Actions in the settings for the AL Language extension. For more information, see Code Actions.

In the app.json file, you can set the NoImplicitWith flag to disable implicit with when you've rewritten all code. For more information, see JSON Files.

Suppressing warnings

There are two ways of suppressing warnings to unclutter warnings while working on other issues and then afterwards you can fix the warnings at your own pace.

suppressWarnings setting

Warnings can be suppressed globally in an extension by specifying the suppressWarnings in the app.json file. For more information, see AL Language Extension Configuration. The syntax is:

"suppressWarnings": [ "AL0606", "AL0604" ]

Pragmas

It's also possible to use #pragma to suppress individual warnings for one or more lines of code. For more information, see Pragma Warning.

#pragma warning disable AL0606
    // No AL0606 will be shown for code here.
    with Customer do begin
        Name := 'Foo';
        Insert();
    end;

#pragma warning restore AL0606
// Suppression of AL0606 is restored to global state.

See Also

AL Development Environment
Developing Extensions in AL
Directives in AL
Pragma Directive
Pragma ImplicitWith
Pragma Warning
Best Practices for Deprecation of Code in the Base App
Microsoft Timeline for Deprecating Code in Business Central
AL Simple Statements