diff --git a/content/docs/analyzers/PlatformCop/PC0038.md b/content/docs/analyzers/PlatformCop/PC0038.md index 43c1e29..21d086b 100644 --- a/content/docs/analyzers/PlatformCop/PC0038.md +++ b/content/docs/analyzers/PlatformCop/PC0038.md @@ -99,8 +99,40 @@ end; Passing it by value (without `var`) does not count. +For a named `Record` return, assigning a field or property, or setting filters and keys, also counts as assigned. The named form avoids the extra record assignment that `exit(LocalRecord)` performs ([AL complex types](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-al-complextypes)); that assignment also drops filters, the current key and other view state ([The complexity of complex return types](https://waldo.be/2021/07/06/the-complexity-of-complex-return-types/)). + +{{< highlight al "hl_lines=3 4 9" >}} +procedure NewCustomer(CustomerNo: Code[20]) Customer: Record Customer +begin + Customer."No." := CustomerNo; + Customer.Name := 'New customer'; +end; + +procedure OpenSalesLines(DocumentNo: Code[20]) SalesLine: Record "Sales Line" +begin + SalesLine.SetRange("Document No.", DocumentNo); +end; +{{< /highlight >}} + +`Customer.Init()` and `Clear(Customer)` count as well, even as the only statement. Both apply the table's `InitValue` defaults, which an untouched record variable already holds. They differ only when something was set before: `Init()` keeps the primary key, filters and the current key, `Clear()` resets them. + +#### What a returned record carries to the caller + +A returned record behaves like a value. Filters and the current key set on a named return survive only while the caller uses the result directly in an expression. Every record assignment, whether `exit(LocalRecord)` inside the procedure or `SalesLine := OpenSalesLines(No)` in the caller, copies the field values only: + +| Caller code | Filters and key | +|---|---| +| `OpenSalesLines(No).GetFilters()` with a named return | kept | +| `SalesLine := OpenSalesLines(No);` | lost | +| any use when the procedure ends with `exit(LocalRecord)` | lost | + +`SalesLine.CopyFilters(OpenSalesLines(No))` and `SalesLine.Copy(OpenSalesLines(No))` do not compile, because both methods take a `var` record argument. The same assignment rule applies to a `temporary` record return: the caller's variable receives the current row, not the rows inserted into the temporary table. + +To hand filters or the contents of a temporary table to a caller, pass the record as a `var` parameter. For filters alone, returning `GetView()` as text and applying it with `SetView()` also carries the key and sort order. + ### See also - [LC0097](../../lintercop/lc0097/) - Avoid mixing exit() and named return variable assignments - [Working with AL methods - Return values](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-al-methods#declaring-methods) on Microsoft Learn - [AL control statements - Exit statement](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-al-control-statements#exit-statement) on Microsoft Learn +- [AL complex types](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-al-complextypes) on Microsoft Learn