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 @@ -150,6 +150,7 @@

### Added

* FCS: add FSharpCheckFileResults.FileSignature
* Added a "most concrete" tiebreaker for overload resolution (`--langversion:preview`). ([RFC FS-1340](https://github.com/fsharp/fslang-design/pull/834), [PR #19277](https://github.com/dotnet/fsharp/pull/19277))
* Added support for `OverloadResolutionPriorityAttribute` in overload resolution (`--langversion:preview`). ([RFC FS-1338](https://github.com/fsharp/fslang-design/pull/828), [PR #19277](https://github.com/dotnet/fsharp/pull/19277))
* Added internal synthesized-name replay infrastructure for compiler-generated names, preserving normal compilation output while enabling future hot reload name stability work.
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6392,7 +6392,8 @@ let CheckOneImplFile

let implFile = CheckedImplFile (qualNameOfFile, implFileTy, implFileContents, hasExplicitEntryPoint, isScript, anonRecdTypes, namedDebugPointsForInlinedCode)

return (topAttrs, implFile, envAtEnd, cenv.createsGeneratedProvidedTypes)
// implFile.Signature is a fresh copy or the explicit signature; only the inferred type shares its entities with the symbol uses
return (topAttrs, implFile, envAtEnd, cenv.createsGeneratedProvidedTypes, implFileTypePriorToSig)
}


Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/CheckDeclarations.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ val CheckOneImplFile:
ModuleOrNamespaceType option *
ParsedImplFileInput *
FSharpDiagnosticOptions ->
Cancellable<TopAttribs * CheckedImplFile * TcEnv * bool>
Cancellable<TopAttribs * CheckedImplFile * TcEnv * bool * ModuleOrNamespaceType>

val CheckOneSigFile:
TcGlobals *
Expand Down
37 changes: 17 additions & 20 deletions src/Compiler/Driver/ParseAndCheckInputs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,7 +1166,10 @@ let AddCheckResultsToTcState

ccuSigForFile, tcState

type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType
type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType * ModuleOrNamespaceType

let private PartialResultOnError (tcState: TcState) : PartialResult =
tcState.TcEnvFromSignatures, EmptyTopAttrs, None, tcState.tcsCcuSig, Construct.NewEmptyModuleOrNamespaceType(Namespace true)

/// Returns partial type check result for skipped implementation files.
let SkippedImplFilePlaceholder (tcConfig: TcConfig, tcImports: TcImports, tcGlobals, tcState, input: ParsedInput) =
Expand Down Expand Up @@ -1205,7 +1208,7 @@ let SkippedImplFilePlaceholder (tcConfig: TcConfig, tcImports: TcImports, tcGlob
CheckedImplFile(qualNameOfFile, rootSigTy, ModuleOrNamespaceContents.TMDefs [], false, false, StampMap [], Map.empty)

let tcEnvAtEnd = tcStateForImplFile.TcEnvFromImpls
Some((tcEnvAtEnd, EmptyTopAttrs, Some emptyImplFile, ccuSigForFile), tcState)
Some((tcEnvAtEnd, EmptyTopAttrs, Some emptyImplFile, ccuSigForFile, rootSigTy), tcState)

| _ -> None
| _ -> None
Expand Down Expand Up @@ -1285,7 +1288,7 @@ let CheckOneInput
tcsCreatesGeneratedProvidedTypes = tcState.tcsCreatesGeneratedProvidedTypes || createsGeneratedProvidedTypes
}

return (tcEnv, EmptyTopAttrs, None, ccuSigForFile), tcState
return (tcEnv, EmptyTopAttrs, None, ccuSigForFile, sigFileType), tcState

| ParsedInput.ImplFile file ->
let qualNameOfFile = file.QualifiedName
Expand All @@ -1300,7 +1303,7 @@ let CheckOneInput
let hadSig = rootSigOpt.IsSome

// Typecheck the implementation file
let! topAttrs, implFile, tcEnvAtEnd, createsGeneratedProvidedTypes =
let! topAttrs, implFile, tcEnvAtEnd, createsGeneratedProvidedTypes, ownSigForFile =
CheckOneImplFile(
tcGlobals,
amap,
Expand All @@ -1326,12 +1329,12 @@ let CheckOneInput
(tcGlobals, amap, hadSig, prefixPathOpt, tcSink, tcState.tcsTcImplEnv, qualNameOfFile, implFile.Signature)
tcState

let result = (tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile)
let result = (tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile, ownSigForFile)
return result, tcState

with RecoverableException e ->
errorRecovery e range0
return (tcState.TcEnvFromSignatures, EmptyTopAttrs, None, tcState.tcsCcuSig), tcState
return PartialResultOnError tcState, tcState
}

// Within a file, equip loggers to locally filter w.r.t. scope pragmas in each input
Expand All @@ -1355,7 +1358,7 @@ let CheckOneInputEntry (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tcG

/// Finish checking multiple files (or one interactive entry into F# Interactive)
let CheckMultipleInputsFinish (results, tcState: TcState) =
let tcEnvsAtEndFile, topAttrs, implFiles, ccuSigsForFiles = List.unzip4 results
let tcEnvsAtEndFile, topAttrs, implFiles, ccuSigsForFiles, _ = List.unzip5 results
let topAttrs = List.foldBack CombineTopAttrs topAttrs EmptyTopAttrs
let implFiles = List.choose id implFiles
// This is the environment required by fsi.exe when incrementally adding definitions
Expand All @@ -1366,13 +1369,6 @@ let CheckMultipleInputsFinish (results, tcState: TcState) =

(tcEnvAtEndOfLastFile, topAttrs, implFiles, ccuSigsForFiles), tcState

let CheckOneInputAndFinish (checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input) =
cancellable {
let! result, tcState = CheckOneInput(checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input)
let finishedResult = CheckMultipleInputsFinish([ result ], tcState)
return finishedResult
}

let CheckClosedInputSetFinish (declaredImpls: CheckedImplFile list, tcState) =
// Latest contents to the CCU
let ccuContents =
Expand All @@ -1393,7 +1389,7 @@ let CheckMultipleInputsSequential (ctok, checkForErrors, tcConfig, tcImports, tc
open FSharp.Compiler.GraphChecking

type State = TcState * bool
type FinalFileResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType
type FinalFileResult = PartialResult

/// Auxiliary type for re-using signature information in TcEnvFromImpls.
///
Expand Down Expand Up @@ -1501,7 +1497,7 @@ let CheckOneInputWithCallback
// Add the signature to the signature env (unless it had an explicit signature)
let ccuSigForFile = CombineCcuContentFragments [ sigFileType; tcState.tcsCcuSig ]

let partialResult = tcEnv, EmptyTopAttrs, None, ccuSigForFile
let partialResult = tcEnv, EmptyTopAttrs, None, ccuSigForFile, sigFileType

let tcState =
{ tcState with
Expand All @@ -1521,7 +1517,7 @@ let CheckOneInputWithCallback
let rootSigOpt = tcState.tcsRootSigs.TryFind qualNameOfFile

// Typecheck the implementation file
let! topAttrs, implFile, tcEnvAtEnd, createsGeneratedProvidedTypes =
let! topAttrs, implFile, tcEnvAtEnd, createsGeneratedProvidedTypes, ownSigForFile =
CheckOneImplFile(
tcGlobals,
amap,
Expand Down Expand Up @@ -1557,7 +1553,8 @@ let CheckOneInputWithCallback
implFile.Signature)
tcState

let partialResult = tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile
let partialResult =
(tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile, ownSigForFile)

let tcState =
{ fsTcState with
Expand All @@ -1570,7 +1567,7 @@ let CheckOneInputWithCallback

with RecoverableException e ->
errorRecovery e range0
return Finisher(node, (fun tcState -> (tcState.TcEnvFromSignatures, EmptyTopAttrs, None, tcState.tcsCcuSig), tcState))
return Finisher(node, (fun tcState -> PartialResultOnError tcState, tcState))
}

let AddSignatureResultToTcImplEnv (tcImports: TcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input: ParsedInput) =
Expand All @@ -1591,7 +1588,7 @@ let AddSignatureResultToTcImplEnv (tcImports: TcImports, tcGlobals, prefixPathOp

// This partial result will be discarded in the end of the graph resolution.
let partialResult: PartialResult =
tcState.tcsTcSigEnv, EmptyTopAttrs, None, ccuSigForFile
tcState.tcsTcSigEnv, EmptyTopAttrs, None, ccuSigForFile, rootSig

partialResult, tcState

Expand Down
20 changes: 4 additions & 16 deletions src/Compiler/Driver/ParseAndCheckInputs.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ type TcState =

member CreatesGeneratedProvidedTypes: bool

type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType
type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType * ModuleOrNamespaceType

/// Get the initial type checking state for a set of inputs
val GetInitialTcState: range * string * TcConfig * TcGlobals * TcImports * TcEnv * OpenDeclaration list -> TcState

/// Returns partial type check result for skipped implementation files.
val SkippedImplFilePlaceholder:
tcConfig: TcConfig * tcImports: TcImports * tcGlobals: TcGlobals * tcState: TcState * input: ParsedInput ->
((TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState) option
(PartialResult * TcState) option

/// Check one input, returned as an Eventually computation
val CheckOneInput:
Expand All @@ -182,7 +182,7 @@ val CheckOneInput:
tcSink: TcResultsSink *
tcState: TcState *
input: ParsedInput ->
Cancellable<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
Cancellable<PartialResult * TcState>

val CheckOneInputWithCallback:
node: NodeToTypeCheck ->
Expand Down Expand Up @@ -222,7 +222,7 @@ val TransformDependencyGraph: graph: Graph<FileIndex> * filePairs: FilePairMap -

/// Finish the checking of multiple inputs
val CheckMultipleInputsFinish:
(TcEnv * TopAttribs * 'T option * 'U) list * TcState -> (TcEnv * TopAttribs * 'T list * 'U list) * TcState
(TcEnv * TopAttribs * 'T option * 'U * 'V) list * TcState -> (TcEnv * TopAttribs * 'T list * 'U list) * TcState

/// Finish the checking of a closed set of inputs
val CheckClosedInputSetFinish: CheckedImplFile list * TcState -> TcState * CheckedImplFile list * ModuleOrNamespace
Expand All @@ -239,15 +239,3 @@ val CheckClosedInputSet:
eagerFormat: (PhasedDiagnostic -> PhasedDiagnostic) *
inputs: ParsedInput list ->
TcState * TopAttribs * CheckedImplFile list * TcEnv

/// Check a single input and finish the checking
val CheckOneInputAndFinish:
checkForErrors: (unit -> bool) *
tcConfig: TcConfig *
tcImports: TcImports *
tcGlobals: TcGlobals *
prefixPathOpt: LongIdent option *
tcSink: TcResultsSink *
tcState: TcState *
input: ParsedInput ->
Cancellable<(TcEnv * TopAttribs * CheckedImplFile list * ModuleOrNamespaceType list) * TcState>
2 changes: 2 additions & 0 deletions src/Compiler/Service/BackgroundCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ type internal BackgroundCompiler
let tcSymbolUses = tcInfoExtras.tcSymbolUses
let tcOpenDeclarations = tcInfoExtras.tcOpenDeclarations
let latestCcuSigForFile = tcInfo.latestCcuSigForFile
let latestOwnSigForFile = tcInfo.latestOwnSigForFile
let tcState = tcInfo.tcState
let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
let latestImplementationFile = tcInfoExtras.latestImplFile
Expand Down Expand Up @@ -1035,6 +1036,7 @@ type internal BackgroundCompiler
tcDiagnostics,
keepAssemblyContents,
Option.get latestCcuSigForFile,
Option.get latestOwnSigForFile,

@T-Gro T-Gro Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ Background FileSignature drops declarations hidden by .fsi with the legacy checker, even though its symbol uses contain those definitions. This needs the fully checked implementation signature rather than the skipped-file placeholder.

// Test.fsi
module Test
val visible: int

// Test.fs
module Test
let hidden = 41
let visible = hidden + 1

// FCS client, with project options for these files:
let checker =
    FSharpChecker.Create(useTransparentCompiler = false,
                         keepAllBackgroundResolutions = true)
let _, result =
    checker.GetBackgroundCheckResultsForFileInProject(testFs, options)
    |> Async.RunSynchronously
let test = result.FileSignature.FindEntityByPath ["Test"] |> Option.get
test.MembersFunctionsAndValues |> Seq.map _.DisplayName |> Seq.toList
// ["visible"]; foreground and transparent checking return ["hidden"; "visible"].

tcState.Ccu,
tcProj.TcImports,
tcEnvAtEnd.AccessRights,
Expand Down
22 changes: 17 additions & 5 deletions src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ type internal TypeCheckInfo
_sTcConfig: TcConfig,
g: TcGlobals,
ccuSigForFile: ModuleOrNamespaceType,
ownSigForFile: ModuleOrNamespaceType,
thisCcu: CcuThunk,
tcImports: TcImports,
tcAccessRights: AccessorDomain,
Expand Down Expand Up @@ -2845,6 +2846,9 @@ type internal TypeCheckInfo
member _.PartialAssemblySignatureForFile =
FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ccuSigForFile)

member _.FileSignature =
FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ownSigForFile)

@T-Gro T-Gro Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ Hidden nested types lose their declaring module: DeclaringEntity returns None in both checker modes although the parent is present in FileSignature. The symbol-resolution context needs the inferred contents too; the exported CCU signature cannot resolve that parent.

// Test.fsi
module Test
val visible: int

// Test.fs
module Test
let visible = 1
module Hidden =
    type Secret<'T> = { Item: 'T }

// FCS client, after checking Test.fs:
let hidden =
    result.FileSignature.FindEntityByPath ["Test"; "Hidden"]
    |> Option.get
hidden.NestedEntities[0].DeclaringEntity
// None; without Test.fsi this is Some Test.Hidden.


member _.AccessRights = tcAccessRights

member _.ProjectOptions = projectOptions
Expand Down Expand Up @@ -3376,7 +3380,7 @@ module internal ParseAndCheckFile =
new CompilationGlobalsScope(errHandler.DiagnosticsLogger, BuildPhase.TypeCheck)

let! result =
CheckOneInputAndFinish(
CheckOneInput(
checkForErrors,
tcConfig,
tcImports,
Expand All @@ -3394,7 +3398,7 @@ module internal ParseAndCheckFile =
let mty =
Construct.NewEmptyModuleOrNamespaceType(ModuleOrNamespaceKind.Namespace true)

return ((tcState.TcEnvFromSignatures, EmptyTopAttrs, [], [ mty ]), tcState)
return ((tcState.TcEnvFromSignatures, EmptyTopAttrs, None, mty, mty), tcState)
}

// Play background errors and warnings for this file.
Expand All @@ -3404,7 +3408,7 @@ module internal ParseAndCheckFile =
| FSharpDiagnosticSeverity.Hidden -> ()
| s -> diagnosticSink { diagnostic with Severity = s }

let (tcEnvAtEnd, _, implFiles, ccuSigsForFiles), tcState = resOpt
let (tcEnvAtEnd, _, implFileOpt, ccuSigForFile, ownSigForFile), tcState = resOpt

let symbolEnv = SymbolEnv(tcGlobals, tcState.Ccu, Some tcState.CcuSig, tcImports)
let errors = errHandler.CollectedDiagnostics(Some symbolEnv)
Expand All @@ -3413,7 +3417,8 @@ module internal ParseAndCheckFile =
TypeCheckInfo(
tcConfig,
tcGlobals,
List.head ccuSigsForFiles,
ccuSigForFile,
ownSigForFile,
tcState.Ccu,
tcImports,
tcEnvAtEnd.AccessRights,
Expand All @@ -3424,7 +3429,7 @@ module internal ParseAndCheckFile =
sink.GetSymbolUses(),
tcEnvAtEnd.NameEnv,
loadClosure,
List.tryHead implFiles,
implFileOpt,
sink.GetOpenDeclarations()
)

Expand Down Expand Up @@ -3595,6 +3600,11 @@ type FSharpCheckFileResults
| None -> failwith "not available"
| Some(scope, _builderOpt) -> scope.PartialAssemblySignatureForFile

member _.FileSignature =
match details with
| None -> failwith "not available"
| Some(scope, _builderOpt) -> scope.FileSignature

member _.ProjectContext =
match details with
| None -> failwith "not available"
Expand Down Expand Up @@ -3762,6 +3772,7 @@ type FSharpCheckFileResults
tcErrors: FSharpDiagnostic[],
keepAssemblyContents,
ccuSigForFile,
ownSigForFile,
thisCcu,
tcImports,
tcAccessRights,
Expand All @@ -3778,6 +3789,7 @@ type FSharpCheckFileResults
tcConfig,
tcGlobals,
ccuSigForFile,
ownSigForFile,
thisCcu,
tcImports,
tcAccessRights,
Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/Service/FSharpCheckerResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ type public FSharpCheckFileResults =
/// Get a view of the contents of the assembly up to and including the file just checked
member PartialAssemblySignature: FSharpAssemblySignature

/// Get a view of the contents of the file just checked, inferred even when a signature file hides them
member FileSignature: FSharpAssemblySignature

/// Get the resolution of the ProjectOptions
member ProjectContext: FSharpProjectContext

Expand Down Expand Up @@ -480,6 +483,7 @@ type public FSharpCheckFileResults =
tcErrors: FSharpDiagnostic[] *
keepAssemblyContents: bool *
ccuSigForFile: ModuleOrNamespaceType *
ownSigForFile: ModuleOrNamespaceType *
thisCcu: CcuThunk *
tcImports: TcImports *
tcAccessRights: AccessorDomain *
Expand Down
11 changes: 8 additions & 3 deletions src/Compiler/Service/IncrementalBuild.fs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ type TcInfo =

latestCcuSigForFile: ModuleOrNamespaceType option

latestOwnSigForFile: ModuleOrNamespaceType option

/// Accumulated diagnostics, last file first
tcDiagnosticsRev:PhasedDiagnostic[] list

Expand Down Expand Up @@ -264,7 +266,7 @@ type BoundModel private (
let hadParseErrors = not (Array.isEmpty parseErrors)
let input, moduleNamesDict = DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input

let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState =
let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile, ownSigForFile), tcState =
CheckOneInput (
(fun () -> hadParseErrors || diagnosticsLogger.ErrorCount > 0),
tcConfig, tcImports,
Expand All @@ -285,6 +287,7 @@ type BoundModel private (
tcEnvAtEndOfFile = tcEnvAtEndOfFile
moduleNamesDict = moduleNamesDict
latestCcuSigForFile = Some ccuSigForFile
latestOwnSigForFile = Some ownSigForFile
tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev
topAttribs = Some topAttribs
tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles
Expand All @@ -303,12 +306,13 @@ type BoundModel private (
| Some syntaxTree, Some (_, qualifiedName) when syntaxTree.HasSignature ->
let input, _, fileName, _ = syntaxTree.Skip qualifiedName
SkippedImplFilePlaceholder(tcConfig, tcImports, tcGlobals, prevTcInfo.tcState, input)
|> Option.map (fun ((_, topAttribs, _, ccuSigForFile), tcState) ->
|> Option.map (fun ((_, topAttribs, _, ccuSigForFile, ownSigForFile), tcState) ->
{
tcState = tcState
tcEnvAtEndOfFile = tcState.TcEnvFromImpls
moduleNamesDict = prevTcInfo.moduleNamesDict
latestCcuSigForFile = Some ccuSigForFile
latestOwnSigForFile = Some ownSigForFile
tcDiagnosticsRev = prevTcInfo.tcDiagnosticsRev
topAttribs = Some topAttribs
tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles
Expand Down Expand Up @@ -755,6 +759,7 @@ module IncrementalBuilderHelpers =
tcEnvAtEndOfFile=tcInitial
topAttribs=None
latestCcuSigForFile=None
latestOwnSigForFile=None
tcDiagnosticsRev = [ initialErrors ]
moduleNamesDict = Map.empty
tcDependencyFiles = basicDependencies
Expand Down Expand Up @@ -803,7 +808,7 @@ module IncrementalBuilderHelpers =

let results = [
for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles ->
tcInfo.tcEnvAtEndOfFile, defaultArg tcInfo.topAttribs EmptyTopAttrs, latestImplFile, tcInfo.latestCcuSigForFile
tcInfo.tcEnvAtEndOfFile, defaultArg tcInfo.topAttribs EmptyTopAttrs, latestImplFile, tcInfo.latestCcuSigForFile, tcInfo.latestOwnSigForFile
]

// Get the state at the end of the type-checking of the last file
Expand Down
Loading
Loading