microsoft / microsoft/AL

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

Open
#8,249 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug vscode-integration
Dominant language
PowerShell
Stars
881
Forks
285
Avg merge
3d 36m
Merged PRs (30d)
1

Description

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: https://github.com/SShadowS/al-lsp-for-agents/issues/17

Internal work item: AB#634055

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked docs/issue-17/README.md and the frame trace, then inspect the textDocument/didOpen path and the named SyntaxTree, MergedNamespaceDeclaration.MakeChildren, and SourceNamespaceSymbol.BuildObjectSymbolsFromDeclaration areas. Done means the casing-mismatched URI is treated as the same file as the on-disk path and no duplicate AL0197 or AL0264 diagnostics are produced.

Written by the indexing model from the issue text.

Assessment

Tech stack
vscode
Domain
compilers, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.