Skip to content

AL0264/AL0197 false positive when didOpen URI casing differs from on-disk path (related to #6077) #8249

@SShadowS

Description

@SShadowS

Relationship to prior reports

This is the same symptom as #6077 (closed COMPLETED, March 2026), but a different trigger. #6077 covered duplicate diagnostics after a file is renamed to a different case, which was fixed in BC 22.0 via the DidChangeWatchedFiles handler. The closer of #6077 explicitly invited new issues for any residual duplicate-diagnostics problems.

This report is not a rename. The casing of the URI in textDocument/didOpen differs from the on-disk path from the start of the session, and the duplicate-declaration errors fire immediately. It still reproduces on AL 18.0.2190758.

Environment

  • AL extension: ms-dynamics-smb.al 18.0.2190758
  • VS Code: 1.103.0 (run via @vscode/test-electron in a deterministic harness; the same configuration repros 5 out of 5 runs)
  • OS: Windows 11

Summary

When VS Code sends textDocument/didOpen with a file URI whose path casing differs from the on-disk casing (for example Cloud/Al/Activation/AppExperience.Codeunit.al vs the actual on-disk Cloud/AL/Activation/AppExperience.Codeunit.al), the AL Language Server treats the editor URI and its own filesystem-walked URI as two distinct files. Both produce a SingleObjectDeclaration for the same object. MergedNamespaceDeclaration.MakeChildren merges them under one MergedObjectDeclaration with two SyntaxReferences. SourceNamespaceSymbol.BuildObjectSymbolsFromDeclaration then creates two SourceCodeunitTypeSymbol entries, and SourceModuleSymbol.CheckForDuplicateIds correctly flags the duplicate. The user sees:

AL0197  An application object of type 'Codeunit' with name '<Name>'
        is already declared by the extension '<own project name>'
AL0264  An application object of type 'Codeunit' with ID '<id>'
        is already declared by the extension '<own project name>'

The "already declared by extension X" name in the message is the open project itself. Closing and reopening the offending file with its true on-disk casing makes the errors disappear.

Steps to reproduce

  1. AL workspace on Windows containing at least one object whose declaring file lives in a directory like Cloud/AL/Activation/.
  2. Open the file via a URI that uses different casing for any path component, for example Cloud/Al/Activation/AppExperience.Codeunit.al (lowercase l) instead of Cloud/AL/Activation/... (uppercase L). Windows resolves the path case-insensitively, so the file opens normally.
  3. Wait for the AL Language Server to publish diagnostics.

This can be triggered organically by:

  • Multi-root workspaces where one root spells a child path with different casing than another.
  • Extensions that issue didOpen from cached or normalized paths (test runners, navigation aids, language server wrappers).
  • Quick Open / workspace symbol search that surfaces paths from indexes whose casing has been folded.

Expected

The AL Language Server treats the two paths as the same file, parses it once, and produces one SingleObjectDeclaration. No AL0197 / AL0264 should fire.

Actual

The LS keeps two SyntaxTree instances (one from the FS scan during al/setActiveWorkspace, one from textDocument/didOpen) and processes them as separate declarations of the same object, producing AL0197 + AL0264 against the project itself.

Frame-level evidence

I built a stdio proxy that sits between VS Code and Microsoft.Dynamics.Nav.EditorServices.Host.exe and logs every JSON-RPC frame. The captured trace shows:

22:39:22.448  in  al/setActiveWorkspace      (no file list in params)
22:39:22.481  in  textDocument/didOpen
              uri = file:///.../Cloud/Al/Activation/AppExperience.Codeunit.al
                                       ^^ lowercase, as supplied by editor
22:39:22.514  in  al/didChangeActiveDocument (same lowercase URI)
              ... (33 ms race window between the FS-scan trigger and the
                   didOpen for the same physical file) ...

In the eventual diagnostics, both casings appear:

  • 13 diagnostics published under Cloud/Al/Activation/AppExperience.Codeunit.al (the didOpen URI), including AL0264 + AL0197 at line 0.
  • 169 diagnostics published under Cloud/AL/Activation/... (the FS-scan URI), including the AL0185 errors expected for an unopened file in the same project.

al/setActiveWorkspace sends only the workspace folder path and AL settings, no file list. The duplicate-tree state therefore originates inside the LS itself when its workspace walk records files with on-disk casing while editor input arrives with caller casing.

Suspected root cause

SyntaxTree / SingleObjectDeclaration instances are keyed by the raw path string. On Windows the comparison should be case-insensitive (or paths should be canonicalised via GetFinalPathNameByHandle or equivalent) before the merge step in MergedNamespaceDeclaration.MakeChildren runs. Today they are not, so two strings that the OS treats as the same file become two distinct logical entities inside the compiler.

This may share underlying machinery with #6077 (the file-rename path that was fixed via DidChangeWatchedFiles). The fix from BC 22.0 normalises tracking after a rename event, but does not appear to canonicalise URIs at the point of textDocument/didOpen, leaving this static-casing-mismatch path open.

Suggested fix

On Windows, canonicalise file URIs to their on-disk casing (or compare them case-insensitively) before they reach the SyntaxTree cache and before declarations are grouped in MergedNamespaceDeclaration.MakeChildren. The most defensive option is to resolve every incoming textDocument/didOpen URI through GetFinalPathNameByHandle so the LS uses one canonical form everywhere.

Investigation notes and minimal reproducer

Curated summary, decompilation pointers, and the JSON-RPC frame trace are at:

https://github.com/SShadowS/al-lsp-for-agents/blob/main/docs/issue-17/README.md

Original report on my own tracker, with reproduction context from a Continia Core based BC project: SShadowS/al-lsp-for-agents#17

Internal work item: AB#634055

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions