Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c628564
Initial plan
Copilot Sep 17, 2025
e8d910f
Add compiler compatibility test suite with anonymous records
Copilot Sep 17, 2025
bbefae5
Complete compiler compatibility test suite implementation
Copilot Sep 17, 2025
13ca28d
Final implementation of compiler compatibility test suite
Copilot Sep 17, 2025
c5a2700
Address code review feedback: parametrized tests, global.json support…
Copilot Sep 17, 2025
360ae63
Address code review: fix LoadLocalFSharpBuild, simplify versions, rem…
Copilot Sep 17, 2025
9ea30da
Fix test failures: replace Unicode checkmarks with platform-agnostic …
Copilot Sep 18, 2025
86a9207
Add .NET 9 test cases and build verification info
Copilot Sep 19, 2025
2720a6f
Enhance build verification with detailed compiler tracking
Copilot Sep 19, 2025
91da72a
Fix CI failures by removing .NET 9 dependency
Copilot Sep 19, 2025
9846cef
Merge branch 'main' into copilot/fix-7efdf64f-592c-4ce7-9871-9e7dadc5…
T-Gro Sep 22, 2025
194b5fe
Implement proper build-time generated BuildInfo files with MSBuild ta…
Copilot Sep 22, 2025
8fc9d44
Fix CI compilation errors by converting arrays to lists for String.co…
Copilot Sep 22, 2025
f3cd733
Add comprehensive build verification assertions to prevent regressions
Copilot Sep 22, 2025
8294e80
Remove BuildInfo files from repository - they should be generated at …
Copilot Sep 22, 2025
c2a1464
Merge branch 'main' into copilot/fix-7efdf64f-592c-4ce7-9871-9e7dadc5…
T-Gro Oct 16, 2025
9f5a170
address build errors
T-Gro Oct 16, 2025
ee94052
Make it actually correct via human intervention
T-Gro Oct 17, 2025
4b7d566
Apply suggestion from @T-Gro
T-Gro Oct 17, 2025
c5a8fa3
Merge branch 'main' into copilot/fix-7efdf64f-592c-4ce7-9871-9e7dadc5…
T-Gro Oct 17, 2025
45c0567
Fix SDK version compatibility and rollForward policy
Copilot Oct 17, 2025
44e61f0
Fix all remaining SDK version references to 9.0.300
Copilot Oct 17, 2025
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/10.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Diagnostics: add extended data for 'No constructors' error ([PR #18863](https://github.com/dotnet/fsharp/pull/18863))
* FSharpType.Format: support top-level prefix generic types style. ([PR #18897](https://github.com/dotnet/fsharp/pull/18897))
* FCS: allow getting captured types ([PR $18878](https://github.com/dotnet/fsharp/pull/18878))
* Add compiler compatibility test suite to verify anonymous records work across different F# compiler versions. Test projects under tests/projects/CompilerCompat verify backward and forward compatibility scenarios.

### Fixed

Expand Down
113 changes: 113 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
module FSharp.Compiler.ComponentTests.CompilerCompatibilityTests

open System
open System.IO
open Xunit
open FSharp.Test
open FSharp.Test.Assert
open TestFramework

type CompilerCompatibilityTests() =

let projectsPath = Path.GetFullPath(Path.Combine(__SOURCE_DIRECTORY__, "../projects/CompilerCompat"))
let libProjectPath = Path.Combine(projectsPath, "CompilerCompatLib")
let appProjectPath = Path.Combine(projectsPath, "CompilerCompatApp")

let runDotnetBuild projectPath useLocalCompiler =
let args =
if useLocalCompiler then
"build -c Release -p:LoadLocalFSharpBuild=True"
else
"build -c Release"

let (exitCode, output, error) = Commands.executeProcess "dotnet" args projectPath

if exitCode <> 0 then
let outputStr = String.concat "\n" output
let errorStr = String.concat "\n" error
failwith $"Build failed with exit code {exitCode}. Output: {outputStr}. Error: {errorStr}"

String.concat "\n" output

let runApp appBinaryPath =
let (exitCode, output, error) = Commands.executeProcess "dotnet" appBinaryPath (Path.GetDirectoryName(appBinaryPath))
(exitCode, String.concat "\n" output, String.concat "\n" error)

let cleanBinObjDirectories projectPath =
let binPath = Path.Combine(projectPath, "bin")
let objPath = Path.Combine(projectPath, "obj")

if Directory.Exists(binPath) then
Directory.Delete(binPath, true)
if Directory.Exists(objPath) then
Directory.Delete(objPath, true)

let getAppDllPath () =
// The app is built to artifacts directory due to Directory.Build.props
Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "artifacts", "bin", "CompilerCompatApp", "Release", "net8.0", "CompilerCompatApp.dll")

[<Fact>]
member _.``Baseline scenario - Both library and app built with local compiler``() =
// Clean previous builds
cleanBinObjDirectories libProjectPath
cleanBinObjDirectories appProjectPath

// Build library with local compiler
let libOutput = runDotnetBuild libProjectPath true
Assert.Contains("CompilerCompatLib -> ", libOutput)

// Build app with local compiler
let appOutput = runDotnetBuild appProjectPath true
Assert.Contains("CompilerCompatApp -> ", appOutput)

// Run app and verify it works
let appDllPath = getAppDllPath()
Assert.True(File.Exists(appDllPath), $"App DLL not found at {appDllPath}")

let (exitCode, output, _error) = runApp appDllPath
Assert.Equal(0, exitCode)
Assert.Contains("✓ All compiler compatibility tests passed", output)

[<Fact>]
member _.``Forward compatibility - Library built with SDK compiler, app with local compiler``() =
// Clean previous builds
cleanBinObjDirectories libProjectPath
cleanBinObjDirectories appProjectPath

// Build library with SDK compiler
let libOutput = runDotnetBuild libProjectPath false
Assert.Contains("CompilerCompatLib -> ", libOutput)

// Build app with local compiler (should use the library built with SDK compiler)
let appOutput = runDotnetBuild appProjectPath true
Assert.Contains("CompilerCompatApp -> ", appOutput)

// Run app and verify it works
let appDllPath = getAppDllPath()
Assert.True(File.Exists(appDllPath), $"App DLL not found at {appDllPath}")

let (exitCode, output, _error) = runApp appDllPath
Assert.Equal(0, exitCode)
Assert.Contains("✓ All compiler compatibility tests passed", output)

[<Fact>]
member _.``Backward compatibility - Library built with local compiler, app with SDK compiler``() =
// Clean previous builds
cleanBinObjDirectories libProjectPath
cleanBinObjDirectories appProjectPath

// Build library with local compiler
let libOutput = runDotnetBuild libProjectPath true
Assert.Contains("CompilerCompatLib -> ", libOutput)

// Build app with SDK compiler (should use the library built with local compiler)
let appOutput = runDotnetBuild appProjectPath false
Assert.Contains("CompilerCompatApp -> ", appOutput)

// Run app and verify it works
let appDllPath = getAppDllPath()
Assert.True(File.Exists(appDllPath), $"App DLL not found at {appDllPath}")

let (exitCode, output, _error) = runApp appDllPath
Assert.Equal(0, exitCode)
Assert.Contains("✓ All compiler compatibility tests passed", output)
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@
<Compile Include="FSharpChecker\SymbolUse.fs" />
<Compile Include="FSharpChecker\FindReferences.fs" />
<Compile Include="Attributes\AttributeCtorSetPropAccess.fs" />
<Compile Include="CompilerCompatibilityTests.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../CompilerCompatLib/CompilerCompatLib.fsproj" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions tests/projects/CompilerCompat/CompilerCompatApp/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
open CompilerCompatLib
open System

[<EntryPoint>]
let main _argv =
try
// Test basic anonymous record functionality
let record = Library.getAnonymousRecord()
printfn "Basic record: X=%d, Y=%s" record.X record.Y

// Verify expected values
if record.X <> 42 || record.Y <> "hello" then
printfn "ERROR: Basic record values don't match expected"
1
else
printfn "✓ Basic record test passed"

// Test complex anonymous record functionality
let complex = Library.getComplexAnonymousRecord()
printfn "Complex record: Simple.A=%d, Simple.B=%s" complex.Simple.A complex.Simple.B
printfn "Complex record: List has %d items" complex.List.Length
printfn "Complex record: Tuple=(%d, Value=%f)" (fst complex.Tuple) (snd complex.Tuple).Value

// Test function that takes anonymous record
let processed = Library.processAnonymousRecord({| X = 123; Y = "test" |})
printfn "Processed result: %s" processed

if processed = "Processed: X=123, Y=test" then
printfn "✓ All compiler compatibility tests passed"
0
else
printfn "ERROR: Processed result doesn't match expected"
1

with ex ->
printfn "ERROR: Exception occurred: %s" ex.Message
1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cd /home/runner/work/fsharp/fsharp/tests/projects/CompilerCompat/CompilerCompatApp
dotnet build -c Release -p:LoadLocalFSharpBuild=True /bl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions tests/projects/CompilerCompat/CompilerCompatLib/Library.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace CompilerCompatLib

module Library =
/// Returns an anonymous record to test compiler compatibility
let getAnonymousRecord () = {| X = 42; Y = "hello" |}

/// Returns a more complex anonymous record with nested structure
let getComplexAnonymousRecord () =
{|
Simple = {| A = 1; B = "test" |};
List = [ {| Id = 1; Name = "first" |}; {| Id = 2; Name = "second" |} ];
Tuple = (42, {| Value = 3.14; Label = "pi" |})
|}

/// Function that takes an anonymous record as parameter
let processAnonymousRecord (record: {| X: int; Y: string |}) =
sprintf "Processed: X=%d, Y=%s" record.X record.Y
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cd /home/runner/work/fsharp/fsharp/tests/projects/CompilerCompat/CompilerCompatLib
dotnet build -c Release -p:LoadLocalFSharpBuild=True /bl
Loading