diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 8d9d75e12a8..e89c537d37a 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -147,6 +147,7 @@ * Import: Don't walk non-F# assemblies when labelling trait constraint sources (PR [#20090](https://github.com/dotnet/fsharp/pull/20090)) * Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap (PR [#20088](https://github.com/dotnet/fsharp/pull/20088)) * IL: fix leaking binary view ([PR #20250](https://github.com/dotnet/fsharp/pull/20250)) +* A project reusing cached framework imports is checked with its own `--pathmap` instead of the map of the project that filled the cache, so the ranges of its in-memory reference data name its real files. ([Issue #20474](https://github.com/dotnet/fsharp/issues/20474), [PR #20476](https://github.com/dotnet/fsharp/pull/20476)) ### Added diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 4f02bda91e7..f93bb9e7dba 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -557,12 +557,13 @@ type FrameworkImportsCache(size) = let node = this.GetNode(tcConfig, frameworkDLLs, nonFrameworkResolutions) let! tcGlobals, frameworkTcImports = node.GetOrComputeValue() - // If the tcGlobals was loaded from a different project, langVersion and realsig may be different - // for each cached project. So here we create a new tcGlobals, with the existing framework values - // and updated realsig and langversion + // If the tcGlobals was loaded from a different project, langVersion, realsig and pathMap may be + // different for each cached project. So here we create a new tcGlobals, with the existing framework + // values and the updated realsig, langversion and pathMap let tcGlobals = if tcGlobals.langVersion <> tcConfig.langVersion - || tcGlobals.realsig <> tcConfig.realsig then + || tcGlobals.realsig <> tcConfig.realsig + || tcGlobals.pathMap <> tcConfig.pathMap then TcGlobals( tcGlobals.compilingFSharpCore, tcGlobals.ilg, @@ -574,7 +575,7 @@ type FrameworkImportsCache(size) = tcGlobals.tryFindSysTypeCcuHelper, tcGlobals.emitDebugInfoInQuotations, tcGlobals.noDebugAttributes, - tcGlobals.pathMap, + tcConfig.pathMap, tcConfig.langVersion, tcConfig.realsig, tcConfig.compilationMode diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 691dde3e802..e7e83676b70 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -964,13 +964,14 @@ type internal TransparentCompiler // Prepare the frameworkTcImportsCache let! tcGlobals, frameworkTcImports = ComputeFrameworkImports tcConfig frameworkDLLs nonFrameworkResolutions - // If the tcGlobals was loaded from a different project, langVersion and realsig may be different - // for each cached project. So here we create a new tcGlobals, with the existing framework values - // and updated realsig and langversion + // If the tcGlobals was loaded from a different project, langVersion, realsig and pathMap may be + // different for each cached project. So here we create a new tcGlobals, with the existing + // framework values and the updated realsig, langversion and pathMap let tcGlobals = if tcGlobals.langVersion <> tcConfig.langVersion || tcGlobals.realsig <> tcConfig.realsig + || tcGlobals.pathMap <> tcConfig.pathMap then TcGlobals( tcGlobals.compilingFSharpCore, @@ -983,7 +984,7 @@ type internal TransparentCompiler tcGlobals.tryFindSysTypeCcuHelper, tcGlobals.emitDebugInfoInQuotations, tcGlobals.noDebugAttributes, - tcGlobals.pathMap, + tcConfig.pathMap, tcConfig.langVersion, tcConfig.realsig, tcConfig.compilationMode diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index cc7e109373f..1d496b7b83b 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -1,4 +1,4 @@ - + @@ -537,6 +537,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/PathMap.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/PathMap.fs new file mode 100644 index 00000000000..18e99d829ce --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/PathMap.fs @@ -0,0 +1,57 @@ +module FSharpChecker.PathMap + +open System.IO +open System.Threading.Tasks +open Xunit +open FSharp.Test.ProjectGeneration +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Text + +let private checkWith (checker: FSharpChecker) (project: SyntheticProject) = + ProjectWorkflowBuilder(project, checker = checker).Yield() |> Async.Ignore + +/// The framework imports, and the TcGlobals with them, are cached per framework set; the path map of +/// the project that filled the cache must not reach the ranges a sibling exposes to its consumers. +[] +[] +[] +let ``a sibling's path map does not reach the ranges of a project without one`` (useTransparentCompiler: bool) : Task = + task { + let checker = FSharpChecker.Create(useTransparentCompiler = useTransparentCompiler) + let library = SyntheticProject.Create("Library", sourceFile "Library" []) + + let mapped = + { SyntheticProject.Create("Mapped", sourceFile "Mapped" []) with + OtherOptions = [ $"--pathmap:{Path.GetDirectoryName library.ProjectDir}=.\\" ] } + + let app = + { SyntheticProject.Create("App", sourceFile "App" [ "Library" ]) with + DependsOn = [ library ] } + + do! checkWith checker mapped + do! checkWith checker app + + let appFile = app.GetFilePath "App" + + let! _, answer = + checker.ParseAndCheckFileInProject( + appFile, + 0, + SourceText.ofString (File.ReadAllText appFile), + app.GetProjectOptions checker + ) + + let checkResults = + match answer with + | FSharpCheckFileAnswer.Succeeded checkResults -> checkResults + | FSharpCheckFileAnswer.Aborted -> failwith "the check was aborted" + + let libraryFunction = + checkResults.GetAllUsesOfAllSymbolsInFile() + |> Seq.tryFind (fun symbolUse -> symbolUse.Symbol.FullName = $"{library.Name}.ModuleLibrary.f") + |> Option.defaultWith (fun () -> + failwith + $"""ModuleLibrary.f not used; symbols: %A{checkResults.GetAllUsesOfAllSymbolsInFile() |> Seq.map _.Symbol.FullName |> Seq.distinct |> List.ofSeq}""") + + Assert.Equal(library.GetFilePath "Library", libraryFunction.Symbol.DeclarationLocation.Value.FileName) + }