Skip to content

Ship FSharp.Core with a net10.0 target framework - #20229

Open
T-Gro wants to merge 38 commits into
mainfrom
t-gro-fsharpcore-net10-tfm-plan
Open

Ship FSharp.Core with a net10.0 target framework#20229
T-Gro wants to merge 38 commits into
mainfrom
t-gro-fsharpcore-net10-tfm-plan

Conversation

@T-Gro

@T-Gro T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Member

Ships FSharp.Core with an additional net10.0 target framework alongside netstandard2.0 and netstandard2.1. External .NET consumers resolve the net10.0 asset through normal nearest-TFM resolution instead of falling back to netstandard2.1.

The net version is a pinned knob (FSharpCoreShippedNetTargetFramework, default net10.0) that deliberately lags the in-development product TFM: F# built on net11 still ships lib/net10.0, and the pin moves only as an explicit, reviewed step when the product advances. Proto stays a single netstandard2.0 bootstrap; the extra target is added to non-Proto builds only.

net10.0 is a package-only asset. In-repo .NET consumers stay pinned to the netstandard2.1 asset (a central Directory.Build.targets rule for direct references, plus one direct pin in FSharp.Compiler.Service.Tests whose reference is only transitive), so the in-box compiler, the tools, and their IL and surface-area baselines are unchanged by adding the target. Removing that pin makes the in-repo test harnesses compile against net10.0 FSharp.Core — whose split-BCL references don't resolve in those contexts — and churns every EmittedIL baseline, so the pin stays.

The point of a real net target is to let FSharp.Core grow net-specific API and behavior over time rather than stay pinned to the netstandard2.1 surface; today it is built from the same sources, and future net-only additions live behind the widened #if … || NET guards.

Not done here (cross-repo, must follow this PR): the shipped-in-SDK path flows dotnet/fsharp → dotnet/dotnet (VMR) → dotnet/sdk, and full source-build validation needs a real VMR leg (DotNetBuildFromVMR). Those cannot be exercised from this repo.

Draft: opening for CI signal and review of the pinned-TFM policy before finalizing.

T-Gro and others added 12 commits August 6, 2026 14:57
…b (net10.0) + lag guard

The shipped FSharp.Core net TFM is a pinned-stable knob that lags the in-dev
product TFM (FSharpNetCoreProductTargetFramework=net11.0) and tracks the latest
stable released .NET. A build/pack-time guard scoped to the FSharp.Core project
fails if the pin is not strictly less than the product TFM.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add $(FSharpCoreShippedNetTargetFramework) (net10.0) to the non-Proto TFMs and
make FSharp.Core actually compile against the net10 BCL:

- prim-types.fs: move the #endif of the '#if !NET5_0_OR_GREATER' block so the
  structural 'namespace Microsoft.FSharp.Core' + opens are unconditional (they
  were trapped inside the pre-.NET5 polyfill guard and vanished on any net TFM,
  causing ~100 'System type not defined' errors). ns2.x output is unchanged.
- prim-types.fsi: guard the System.Diagnostics.CodeAnalysis polyfill with the
  matching '#if !NET5_0_OR_GREATER' so the signature drops it on net (the impl
  already did), fixing the sig/impl mismatch (FS0193).
- prim-types.{fs,fsi}: floor the .NET8+ CollectionBuilder/ScopedRef polyfills
  with '#if !NET8_0_OR_GREATER' (net BCL provides them).
- Widen collection-expression / IAsyncDisposable guards to '|| NET'
  (set.{fs,fsi}, tasks.{fs,fsi}, prim-types.{fs,fsi} List builders).
- local.fs / Query.fs: disambiguate net10 BCL overloads
  (String.Format ReadOnlySpan<obj>, ElementAt/Take Index/Range) by pinning the
  original obj[]/int overloads. No public-surface change.

Builds clean on netstandard2.0, netstandard2.1 and net10.0 (no warnings).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- FSharp.Core.nuspec: add a net10.0 dependency group and lib/net10.0 file
  entries (dll, xml, satellite resources), all driven by a
  $FSharpCoreShippedNetTargetFramework$ token so the package layout tracks the
  single pinned knob rather than hardcoding the TFM in the manifest.
- FSharp.Core.fsproj: surface the knob to the nuspec via
  <NuspecProperty Include="FSharpCoreShippedNetTargetFramework=..."> (non-Proto).
- Microsoft.FSharp.Compiler.fsproj: include the pinned TFM in the shipping-path
  pack override so the shipped FSharp.Core is packed for net10.0 too, not just
  netstandard2.0/2.1.

Verified via Arcade pack: nupkg contains lib/{netstandard2.0,netstandard2.1,net10.0}
with dll+xml+satellites and a <group targetFramework="net10.0" /> dependency group.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Modern .NET hosts (net11+) now bind lib/net10.0, so the SurfaceArea test
must verify against a net baseline (references System.Runtime, not
netstandard). Reorder platform detection to '#if NET' first, add
FSharp.Core.SurfaceArea.net.{debug,release}.bsl, and document that the
netstandard2.1 baselines are intentionally retained but no longer
exercised (ns2.1 coverage moves to the ApiCompat identity gate).

The net member surface is byte-identical to ns2.1 (only assembly-
reference lines differ), confirming the net10.0 == ns2.1 invariant.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Add a dedicated FSharp.Core.ApiCompat project that runs the SDK's
Microsoft.DotNet.ApiCompat.Task.ValidateAssembliesTask (registered by
Sdk.targets, not Arcade, not package validation) comparing the freshly
built netstandard2.1 (contract) and net10.0 implementation assemblies in
strict/bidirectional mode with an empty suppression set. This enforces
the net10.0 == netstandard2.1 public-surface invariant.

Wire it as a required, blocking FSharpCore_ApiCompat job (no
continueOnError) in azure-pipelines-PR.yml.

Verified locally: passes clean on identically-built DLLs; strict mode
correctly FAILS on injected additions (ns2.0-vs-net10), non-strict
silently passes them (why strict is mandated).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Allow-list audit of every TFM-discriminating #if/#elif guard in
src/FSharp.Core/**/*.{fs,fsi}: each must be a reviewed <file>:<expr>
pair, so a bare '#if NETSTANDARD2_1' (excludes the net TFM) or bare
'#if !NET' (drops a BCL polyfill on every net TFM) or any un-reviewed
new TFM guard fails.

Implemented as a portable F# fsx (git ls-files + .NET regex, not
'git grep -P' which needs PCRE) and wired as a blocking step in the
required CheckCodeFormatting job, matching the repo's FSCompCheck.fsx /
TestSplit.fsx source-hygiene idiom.

Verified: self-test OK; passes on the shipped tree (10 allow-listed
guards); FAILS on an injected bare '#if NETSTANDARD2_1'.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…rifier

Portable fsx that discovers the shipped net TFM lib folder from the produced
FSharp.Core nupkg (never a hard-coded literal) and asserts lib/{netstandard2.0,
netstandard2.1,<pin>} DLL+XML present and non-degenerate, satellites present,
a <group targetFramework> dependency group in the nuspec, and a UNIFORM
AssemblyVersion across all three lib assemblies.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Isolated net-TFM consumer (targets $(FSharpCoreShippedNetTargetFramework)) that
source-maps FSharp.Core to the locally built package and STRUCTURALLY witnesses,
from obj/project.assets.json, that both compile and runtime bind to
lib/<pin>/FSharp.Core.dll and NOT to any netstandard asset. Driver pins the exact
built version, purges only the cached fsharp.core/<ver>, and runs a widened
IAsyncDisposable task member as the runtime smoke (roll-forward for the net run).

Verified locally: net10.0 -> lib/net10.0 (compile+runtime) + smoke green;
net8.0 control -> lib/netstandard2.1 (witness bites).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Multi-target the AheadOfTime Trimming (x3) and NativeAOT consumers on
net9.0;$(FSharpCoreShippedNetTargetFramework) so the shipped net-TFM lib is
exercised under PublishTrimmed/PublishAot. The gate is publish SUCCESS under
TreatWarningsAsErrors=true (IL2xxx/IL3050 => errors); trimmed sizes for the net
pin are report-only (-expected_len -1) since byte counts churn per preview. Both
check.ps1 scripts derive the pin from the knob (no literal net10.0 to drift).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…nto PR pipeline

- e2e-1 runs in EndToEndBuildTests right after the -pack step.
- e2e-2 runs in Build_And_Test_AOT_Windows after the AheadOfTime checks, with the
  consumer's project.assets.json published as always()/continueOnError failure
  evidence.
e2e-5 (trim/AOT net-TFM legs) needs no new wiring: the existing AheadOfTime
check.ps1 chain already runs the multi-targeted projects.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@github-actions

github-actions Bot commented Aug 7, 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/FSharp.Core` docs/release-notes/.FSharp.Core/11.0.100.md
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@T-Gro

T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@T-Gro

T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@T-Gro

T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

…chronouslyImmediate

The merge from main introduced the new public member
Async.RunSynchronouslyImmediate. The dedicated net SurfaceArea baselines
(created before that merge) did not include it; regenerate both the debug
and release net baselines. The netstandard2.1 baseline already carried the
member via the merge, and the ApiCompat net10-vs-netstandard2.1 identity
gate still passes, so the surfaces remain identical.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@T-Gro

T-Gro commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI added 4 commits August 7, 2026 11:14
…ding (NoBloat)

Remove step-by-step and design-justification narration from the new e2e
scripts, the ApiCompat project, the #ifdef audit, and the SurfaceArea
platform-selection comment; keep only the non-obvious 'why' (the
ApiCompat-vs-package-validation trap, the macOS git-grep portability note,
the net<->baseline mapping invariant). No functional change; all gates
still pass locally.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Drop the fsproj header that duplicated the driver script's intent and a
redundant inline comment in the runtime smoke. The remaining comments are
non-obvious MSBuild 'why' (CPM/RollForward/implicit-ref opt-outs). No
functional change; e2e-2 still passes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Removes an agent-plan breadcrumb (files/plan.md 4e) from the ApiCompat
proj header comment and a (Milestone B) phase tag from a
VerifyNetResolution.fsx error string, per NoBloat. No behavior change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…1 baseline

- VerifyFSharpCorePackage.fsx: checkAsset helper collapses the duplicated
  dll/xml exists+size-floor assertions.
- VerifyNetResolution.fsx: close a fail-open in the structural witness
  (empty dllPaths without _._ now fails).
- AuditFSharpCoreTfmGuards.fsx: self-test now drives the real tfmGuardsIn
  extractor (was bypassing the #if/#elif line parser) + #elif coverage.
- SurfaceArea.fs: drop the unreachable #elif NETCOREAPP netstandard21 branch
  (a modern host defines NET); delete the now-orphaned netstandard21 baselines
  (ns2.1 surface is covered by the FSharp.Core.ApiCompat gate).
- Program.fs / ApiCompat proj / PR yaml: comment accuracy + trims.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Copilot AI added 2 commits August 7, 2026 15:32
…inistic restore

Pointing NuGet at the packages root (artifacts/packages/Release) resolved the
FSharp.Core nupkg locally (depth-1 Release/Shipping) but failed on CI with
NU1101 -- NuGet's folder source does not reliably recurse into the depth-2
Dependency/Shipping sub-lane where the package lands on CI.

Stage the freshly built nupkg into a clean flat feed (obj/localfeed, gitignored)
and point the consumer NuGet.Config at it. A depth-0 flat folder is resolved
deterministically on every OS/CI. The negative control is preserved:
packageSourceMapping keeps FSharp.Core local-only, and a missing build output
still fails closed (exit 2 before restore).

Verified: local run -> restore OK, compile+runtime both bind
lib/net10.0/FSharp.Core.dll, widened IAsyncDisposable member executes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…p.Core on .NET Core

The new net10.0 FSharp.Core target made this project — the only in-repo net
consumer without a direct FSharp.Core ProjectReference — resolve the net10.0
asset by nearest-TFM at deploy time, because the central net10.0->netstandard2.1
pin in Directory.Build.targets only rewrites direct references. net10.0
FSharp.Core carries a different set of assembly references than netstandard2.1,
which shifts FCS symbol enumeration on Windows and deterministically failed the
two CSharpProjectAnalysis tests on the compressed-metadata Windows coreclr legs.

Add an explicit direct FSharp.Core reference pinned to netstandard2.1, scoped to
the .NET Core product TFM inner build only. The project also multi-targets net472
on Windows, where netstandard2.1 FSharp.Core is unusable (it references
IAsyncDisposable, absent on net472); that inner build must keep resolving
netstandard2.0 transitively as before. Verified: net11.0 deploys a byte-identical
netstandard2.1 FSharp.Core and CSharpProjectAnalysis passes 6/6; the net472 inner
build has no direct FSharp.Core reference and resolves netstandard2.0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@T-Gro
T-Gro force-pushed the t-gro-fsharpcore-net10-tfm-plan branch from d110067 to efa90d5 Compare August 10, 2026 09:30
Copilot AI added 6 commits August 10, 2026 14:15
- Merge the two e2e drivers into one VerifyNetResolution.fsx (structural nupkg
  gate + consumer asset-resolution witness + runtime smoke); delete the duplicate
  VerifyFSharpCorePackage.fsx. Validated locally: pack + run passes, and a
  net-lib-removed nupkg fails the structural gate for the right reason.
- Delete the standalone AuditFSharpCoreTfmGuards.fsx + its CI step; #ifdef guard
  correctness is already enforced structurally (the net build must compile and the
  e2e runtime smoke executes a || NET-gated member).
- Trim narration comments on PR-added scaffolding (NetTfmResolution consumer,
  ApiCompat proj, the pin-leg comments in Trimming/NativeAOT).

Keeps ApiCompat (surface-parity gate) and the pre-existing NativeAOT/Trimming
projects (PR only extends them with the net pin leg).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
The expert review found the new FSharpCoreShippedNetTargetFramework pin (default
net10.0) had no source-build handling, unlike FSharpNetCoreProductTargetFramework
which Directory.Build.props already collapses to $(NetCurrent) under
DotNetBuildSourceOnly. A strict offline VMR build could try to build the lagging
net10.0 leg without its targeting pack.

Mirror the existing convention: collapse the pin to $(NetCurrent) in the same
source-build PropertyGroup (its ref pack is always present; nuspec token then
resolves to a built TFM), and skip the strict-less validation under source-build
(pin==product==$(NetCurrent) there, and the lagging invariant is a nuget.org
shipping concern only).

Verified locally: normal build unchanged (pin net10.0, validation active);
source-build collapses the pin in lockstep with the product TFM.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Round-2 expert review noted the new net10.0 FSharp.Core package leg was not
ILVerified (the harness only checked netstandard2.0 + netstandard2.1). Add the
shipped-net pin to the FSharp.Core matrix; the harness already maps any netN.0
to the generic 'netcoreapp' baseline, so no per-bump churn.

Verified locally with dotnet-ilverify against the net runtime refs: the net leg
verifies clean in both Debug and Release (0 [IL] errors), like its netstandard
siblings, so both baselines are empty.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
imba-review SHOULD-FIX: the resolution witness only exercised a consumer whose
TFM equalled the pin (net10.0), so lib/net10.0 bound by trivial exact match -
the scenario the net leg actually exists for (a newer product-TFM consumer
preferring lib/net10.0 over lib/netstandard2.1) was never tested. Multi-target
the consumer on both the pin and the in-dev product TFM; the witness already
iterates every target, so both legs are checked. Tighten the assertion to the
exact pinned lib/<pin> folder so the fallback leg provably lands there.

Verified locally against the built nupkg: net11.0 consumer binds FSharp.Core to
lib/net10.0 for compile+runtime (not netstandard2.1), as does the net10.0 leg.

Also (imba-review NITs): the NativeAOT/Trimming check.ps1 now evaluate the pin
via 'msbuild --getProperty' like ilverify.ps1 (honors overrides, format-proof)
instead of raw-text scraping; and the runtime smoke is labelled best-effort
(it is non-fatal on CI too) and passes -f for the now multi-TFM consumer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
The NetTfmResolution e2e was 280 LOC - 56% of the whole PR - and its consumer
project (fsproj + Program.fs + NuGet.Config + Directory.Build.*) plus the
restore/assets-parsing half existed only to assert NuGet's nearest-TFM resolver
picks lib/net10.0. That is framework behaviour we don't own; a real net consumer
of FSharp.Core already exists in the Trimming/NativeAOT net-pin legs.

Keep the one assertion nothing else covers - that packaging actually ships a
non-degenerate lib/<pin> (dll+xml), a satellite, and a matching nuspec group -
as a ~45-line standalone fsi check (VerifyNetPackage.fsx). Drop the consumer
project and the CI artifact-publish step.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Cross-model compaction rounds 2-3 (Opus/GPT/Gemini). Cut restated
narration in eng/TargetFrameworks.props, Directory.Build.{props,targets},
the ApiCompat gate and the AOT/Trimming fsprojs, and made
VerifyNetPackage.fsx fail-fast via a shared checkAsset helper. No gate,
guard, nuspec token or validation Error touched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@vzarytovskii

Copy link
Copy Markdown
Member

I am curious, if it ships in SDK, how much size does it add?

Source-build builds FSharp.Core for $(NetCurrent) (net11), which tripped
FS0072 at Query.fs LeftOuterJoin: net11 adds Enumerable overloads that
leave the GroupJoin result-selector's group untyped before DefaultIfEmpty.
Annotate it (seq<_>); no-op on the shipping netstandard/net10 legs.

Also drop the derived-major-version + strict-less-than validation target
in eng/TargetFrameworks.props: the shipped-net pin is now just a plain
'net10.0' property, bumped by hand.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@T-Gro

T-Gro commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

@vzarytovskii :
Yet to be measured and then assessed with SDK owners - I will report here and check with @baronfel .
If it is a big deal - a potential exchange might be dropping ns2.1 (keeping ns2.0 and net10) once net8 and net9 go out of support.

(if someone needs ns.1 and target <10 tfm, options to use older SDK or existing FSharp.Core by version pinning still work.
Desktop framework unaffected - ns2.0 would remain)

UPDATE: (@vzarytovskii )

Its 1.33MB in terms of .nupkg increase.
The other Fsharp.Core in the SDK is the one brought by the compiler itself, and that one is not tfm-multiplied. No increase there.

@baronfel

Copy link
Copy Markdown
Member

That seems entirely reasonable - we cut the size of the SDK by a good 60+ MB in 11 so there's some wiggle room here.

Copilot AI added 2 commits August 11, 2026 14:21
net10.0 is meant to deviate from netstandard2.1 (expose more APIs over time),
not maintain parity, and the in-box compiler and in-repo tests should bind the
new net10.0 FSharp.Core like any nearest-TFM consumer. So remove the defensive
scaffolding that pinned everything back to netstandard2.1:

- Remove the central net10->ns2.1 ProjectReference pin (Directory.Build.targets)
  and the FSharp.Compiler.Service.Tests transitive pin.
- Delete the assembly-level ApiCompat parity gate and its CI step.
- Delete the VerifyNetPackage e2e project and the dedicated net legs added to
  the AOT/Trimming/ILVerify rails; the existing suites now exercise net10.
- Trim the verbose explanatory comments in the props/nuspec/fsproj knob.

Refresh the FSharp.Core SurfaceArea baselines: with the pin gone the NETCOREAPP
UnitTests run binds net10.0, whose public type/member surface is identical to
netstandard2.1 -- only the referenced BCL assemblies differ (split framework
assemblies vs the netstandard facade).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
The previous commit removed the pin so in-repo consumers would bind the new
net10.0 FSharp.Core directly. CI showed that pin is load-bearing, not optional
scaffolding: with it gone the in-repo test harnesses compile against a net10.0
FSharp.Core whose split-BCL references don't resolve in those contexts (FS0193
"'System.IO' from 'System.Runtime' did not contain 'Stream'", FSharp.Core types
reported "not defined"), and every EmittedIL/Conformance IL baseline churns to
the split-BCL assembly scopes -- ~100 compile errors and ~688 test failures.

Restore the minimal mechanism only (no ApiCompat gate, no extra CI/test legs,
terse comments): the central Directory.Build.targets rule that rewrites direct
FSharp.Core ProjectReferences to netstandard2.1 for the .NET Core inner build,
and the FSharp.Compiler.Service.Tests direct pin (its FSharp.Core reference is
transitive, so the central rule doesn't reach it). net10.0 still builds and
packs into the FSharp.Core NuGet package; external .NET consumers resolve it by
nearest-TFM. In-repo builds stay on netstandard2.1, so no baseline churn.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
@T-Gro
T-Gro marked this pull request as ready for review August 12, 2026 17:43
@T-Gro
T-Gro requested a review from a team as a code owner August 12, 2026 17:43
@T-Gro
T-Gro requested a review from vzarytovskii August 12, 2026 17:43
@T-Gro T-Gro assigned abonie and unassigned abonie Aug 12, 2026
@T-Gro
T-Gro requested review from abonie and baronfel August 12, 2026 17:44
@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Aug 12, 2026
@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label Aug 12, 2026
T-Gro added 3 commits August 13, 2026 17:49
Route closure generic arguments through guarded type generation to prevent FS0073, and cover the shipped net10 FSharp.Core API, package assets, and IL verification.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f25b087-cec7-4d86-a31a-f1f6f00ccf1a
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f25b087-cec7-4d86-a31a-f1f6f00ccf1a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants