Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions src/Compiler/Service/IncrementalBuild.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -574,7 +575,7 @@ type FrameworkImportsCache(size) =
tcGlobals.tryFindSysTypeCcuHelper,
tcGlobals.emitDebugInfoInQuotations,
tcGlobals.noDebugAttributes,
tcGlobals.pathMap,
tcConfig.pathMap,
tcConfig.langVersion,
tcConfig.realsig,
tcConfig.compilationMode
Expand Down
9 changes: 5 additions & 4 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -983,7 +984,7 @@ type internal TransparentCompiler
tcGlobals.tryFindSysTypeCcuHelper,
tcGlobals.emitDebugInfoInQuotations,
tcGlobals.noDebugAttributes,
tcGlobals.pathMap,
tcConfig.pathMap,
tcConfig.langVersion,
tcConfig.realsig,
tcConfig.compilationMode
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -->
<Project Sdk="Microsoft.NET.Sdk">

Expand Down Expand Up @@ -537,6 +537,7 @@
<Compile Include="Signatures\MemberTests.fs" />
<Compile Include="StaticLinking\StaticLinking.fs" />
<Compile Include="FSharpChecker\CommonWorkflows.fs" />
<Compile Include="FSharpChecker\PathMap.fs" />
<Compile Include="FSharpChecker\ProjectSnapshot.fs" />
<Compile Include="FSharpChecker\TransparentCompiler.fs" />
<Compile Include="FSharpChecker\SymbolUse.fs" />
Expand Down
57 changes: 57 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/FSharpChecker/PathMap.fs
Original file line number Diff line number Diff line change
@@ -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.
[<Theory>]
[<InlineData(false)>]
[<InlineData(true)>]
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)
}
Loading