Skip to content

Name a declaration from a path-mapped assembly from its root once - #20518

Open
xperiandri wants to merge 6 commits into
dotnet:mainfrom
xperiandri:fix/pathmap-declaration-location
Open

Name a declaration from a path-mapped assembly from its root once#20518
xperiandri wants to merge 6 commits into
dotnet:mainfrom
xperiandri:fix/pathmap-declaration-location

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Go To Definition into a sibling project of a solution built with a path map — the
<PathMap>$(MSBuildThisFileDirectory)=.\</PathMap> plus DeterministicSourcePaths that many
Directory.Build.props set — opens generated metadata instead of the source. The declaration
GetDeclarationLocation returns names the compile-time directory twice:

.\src\Domain\.\src\Domain\Types.fs

Nothing matches a path like that, so the editor takes the symbol for an external one.

Why

A build with a path map maps two things into the assembly's F# metadata: the file names of its
ranges, and the directory it compiled in, pickled as compileTimeWorkingDir and read back as
ccu.SourceCodeDirectory. SymbolHelpers.fileNameOfItem then joins the two with Path.Combine,
on the assumption that the directory is absolute and the file name relative to it. Under a map
both are relative to the same root, which the assembly never records, so the file name already
reaches that root on its own and the join repeats the directory.

#20470 keeps the map out of the options the IDE builds, which covers references between projects
held in memory. It cannot cover this: a referenced assembly on disk was built by MSBuild with the map
and carries the mapped names permanently.

The change

Join only when the directory is rooted. When it is not, the file name is as complete as the metadata
can make it and is returned unchanged. A build without a map keeps an absolute directory and takes the
same path as before; SourceCodeDirectory = "", which shared and IL-only CCUs use, gave the file name
back before and still does.

The test puts the map on the referenced project only and checks that the declaration, resolved from
the map's root, is the real file. Without the change it fails with the directory named twice:

Expected: ···"s\\MappedLibrary_efac676e\\FileLibrary.fs"
Actual:   ···"s\\MappedLibrary_efac676e\\MappedLibrary_efac676e\\Fi"···

fileNameOfItem has one caller, GetDeclarationLocation. On the branch this was developed on, the
full FSharp.Compiler.Service.Tests run passes with it (3798, none failed).

The editor still has to resolve a relative name against the solution rather than the process's
current directory; that is #20519.

Base

Stacked on #20476, which adds FSharpChecker/PathMap.fs; the diff shrinks to its own commit once that
merges.

🤖 Generated with Claude Code

xperiandri and others added 4 commits September 7, 2026 14:01
…imports are cached

FrameworkImportsCache keys the framework imports, and the TcGlobals built
with them, by the framework set alone. A project reusing the entry got a
fresh TcGlobals only when langVersion or realsig differed, and even then took
pathMap from the cached instance. Since TypedTreePickle applies that map to
every range it writes, the in-memory reference data of each project carried
the --pathmap of whichever project filled the cache first, and a project
without a map handed its consumers file names nothing on disk matches.

pathMap now takes part in the decision like langVersion and realsig, and the
new TcGlobals takes it from the project's own TcConfig, in the incremental
builder and the transparent compiler alike.

Fixes dotnet#20474

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing project

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A build that maps its source paths - as DeterministicSourcePaths does - maps the
directory it compiled in along with the file names of its ranges, so the two
reach the same root and neither records what that root is. `fileNameOfItem`
joined them anyway, on the assumption that the directory is absolute and the
file name relative to it, and the declaration of a symbol imported from such an
assembly came back naming the directory twice. Nothing matches a path like that,
so Go To Definition reported the symbol as external and opened generated
metadata instead of its source.

Join only when the directory is rooted. When it is not, the file name already
reaches the root on its own and is as complete as the metadata can make it.

The IDE drops the path map from the options it builds, so this is reached
through an assembly on disk - which is where a path map is normally applied, and
which no change to those options can rewrite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Design-Time
Affects-Design-Time: Changes IDE declaration navigation for mapped assemblies.

Generated by PR Tooling Safety Check · gpt56 754.1K ·

xperiandri and others added 2 commits September 11, 2026 04:26
On Linux and macOS a backslash is part of a file name, so a map to .\ left the
declaration's name unresolvable against the root there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

1 participant