From 3d971085ecea27a2eb89057b1f004b91f15036b2 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Mon, 7 Sep 2026 15:16:12 +0200 Subject: [PATCH 1/3] FCS: add FSharpCheckFileResults.FileSignature --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/Checking/CheckDeclarations.fs | 3 +- src/Compiler/Checking/CheckDeclarations.fsi | 2 +- src/Compiler/Driver/ParseAndCheckInputs.fs | 37 +++---- src/Compiler/Driver/ParseAndCheckInputs.fsi | 20 +--- src/Compiler/Service/BackgroundCompiler.fs | 2 + src/Compiler/Service/FSharpCheckerResults.fs | 22 +++- src/Compiler/Service/FSharpCheckerResults.fsi | 4 + src/Compiler/Service/IncrementalBuild.fs | 11 +- src/Compiler/Service/IncrementalBuild.fsi | 2 + src/Compiler/Service/TransparentCompiler.fs | 12 ++- src/Compiler/Service/TransparentCompiler.fsi | 2 + src/Compiler/Utilities/illib.fs | 5 + src/Compiler/Utilities/illib.fsi | 2 + ...iler.Service.SurfaceArea.netstandard20.bsl | 2 + .../FSharp.Compiler.Service.Tests/Symbols.fs | 100 ++++++++++++++++++ 16 files changed, 178 insertions(+), 49 deletions(-) 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..4e05d13624c 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -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. diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index c663b819996..cbe42a11d22 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -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) } diff --git a/src/Compiler/Checking/CheckDeclarations.fsi b/src/Compiler/Checking/CheckDeclarations.fsi index 25a2af850b9..c08e3c3f359 100644 --- a/src/Compiler/Checking/CheckDeclarations.fsi +++ b/src/Compiler/Checking/CheckDeclarations.fsi @@ -59,7 +59,7 @@ val CheckOneImplFile: ModuleOrNamespaceType option * ParsedImplFileInput * FSharpDiagnosticOptions -> - Cancellable + Cancellable val CheckOneSigFile: TcGlobals * diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 954809f9488..ece11e235e9 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -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) = @@ -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 @@ -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 @@ -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, @@ -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 @@ -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 @@ -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 = @@ -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. /// @@ -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 @@ -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, @@ -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 @@ -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) = @@ -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 diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi index 3e47c3c17c6..841c6e756bd 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fsi +++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi @@ -162,7 +162,7 @@ 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 @@ -170,7 +170,7 @@ val GetInitialTcState: range * string * TcConfig * TcGlobals * TcImports * TcEnv /// 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: @@ -182,7 +182,7 @@ val CheckOneInput: tcSink: TcResultsSink * tcState: TcState * input: ParsedInput -> - Cancellable<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState> + Cancellable val CheckOneInputWithCallback: node: NodeToTypeCheck -> @@ -222,7 +222,7 @@ val TransformDependencyGraph: graph: Graph * 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 @@ -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> diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 50f9881260f..af8f62714f3 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -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 @@ -1035,6 +1036,7 @@ type internal BackgroundCompiler tcDiagnostics, keepAssemblyContents, Option.get latestCcuSigForFile, + Option.get latestOwnSigForFile, tcState.Ccu, tcProj.TcImports, tcEnvAtEnd.AccessRights, diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 155d1b5f702..fea8666ba3a 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -357,6 +357,7 @@ type internal TypeCheckInfo _sTcConfig: TcConfig, g: TcGlobals, ccuSigForFile: ModuleOrNamespaceType, + ownSigForFile: ModuleOrNamespaceType, thisCcu: CcuThunk, tcImports: TcImports, tcAccessRights: AccessorDomain, @@ -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) + member _.AccessRights = tcAccessRights member _.ProjectOptions = projectOptions @@ -3376,7 +3380,7 @@ module internal ParseAndCheckFile = new CompilationGlobalsScope(errHandler.DiagnosticsLogger, BuildPhase.TypeCheck) let! result = - CheckOneInputAndFinish( + CheckOneInput( checkForErrors, tcConfig, tcImports, @@ -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. @@ -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) @@ -3413,7 +3417,8 @@ module internal ParseAndCheckFile = TypeCheckInfo( tcConfig, tcGlobals, - List.head ccuSigsForFiles, + ccuSigForFile, + ownSigForFile, tcState.Ccu, tcImports, tcEnvAtEnd.AccessRights, @@ -3424,7 +3429,7 @@ module internal ParseAndCheckFile = sink.GetSymbolUses(), tcEnvAtEnd.NameEnv, loadClosure, - List.tryHead implFiles, + implFileOpt, sink.GetOpenDeclarations() ) @@ -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" @@ -3762,6 +3772,7 @@ type FSharpCheckFileResults tcErrors: FSharpDiagnostic[], keepAssemblyContents, ccuSigForFile, + ownSigForFile, thisCcu, tcImports, tcAccessRights, @@ -3778,6 +3789,7 @@ type FSharpCheckFileResults tcConfig, tcGlobals, ccuSigForFile, + ownSigForFile, thisCcu, tcImports, tcAccessRights, diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi index b1b5f78f675..3b787a9e878 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fsi +++ b/src/Compiler/Service/FSharpCheckerResults.fsi @@ -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 @@ -480,6 +483,7 @@ type public FSharpCheckFileResults = tcErrors: FSharpDiagnostic[] * keepAssemblyContents: bool * ccuSigForFile: ModuleOrNamespaceType * + ownSigForFile: ModuleOrNamespaceType * thisCcu: CcuThunk * tcImports: TcImports * tcAccessRights: AccessorDomain * diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 4f02bda91e7..b6b87750233 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -197,6 +197,8 @@ type TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option + latestOwnSigForFile: ModuleOrNamespaceType option + /// Accumulated diagnostics, last file first tcDiagnosticsRev:PhasedDiagnostic[] list @@ -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, @@ -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 @@ -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 @@ -755,6 +759,7 @@ module IncrementalBuilderHelpers = tcEnvAtEndOfFile=tcInitial topAttribs=None latestCcuSigForFile=None + latestOwnSigForFile=None tcDiagnosticsRev = [ initialErrors ] moduleNamesDict = Map.empty tcDependencyFiles = basicDependencies @@ -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 diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi index 03c37da8216..d7d059d5cd9 100644 --- a/src/Compiler/Service/IncrementalBuild.fsi +++ b/src/Compiler/Service/IncrementalBuild.fsi @@ -68,6 +68,8 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option + latestOwnSigForFile: ModuleOrNamespaceType option + /// Accumulated errors, last file first tcDiagnosticsRev: PhasedDiagnostic[] list diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 691dde3e802..f58e6de9141 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -59,6 +59,8 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option + latestOwnSigForFile: ModuleOrNamespaceType option + /// Accumulated diagnostics, last file first tcDiagnosticsRev: PhasedDiagnostic[] list @@ -723,6 +725,7 @@ type internal TransparentCompiler tcEnvAtEndOfFile = tcInitial topAttribs = None latestCcuSigForFile = None + latestOwnSigForFile = None tcDiagnosticsRev = [ initialErrors ] moduleNamesDict = Map.empty tcDependencyFiles = basicDependencies @@ -1483,7 +1486,7 @@ type internal TransparentCompiler let partialResult, tcState = finisher tcInfo.tcState - let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, ownSigForFile = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1500,6 +1503,7 @@ type internal TransparentCompiler tcDiagnosticsRev = tcIntermediate.tcDiagnosticsRev @ tcInfo.tcDiagnosticsRev tcDependencyFiles = tcIntermediate.tcDependencyFiles @ tcInfo.tcDependencyFiles latestCcuSigForFile = Some ccuSigForFile + latestOwnSigForFile = Some ownSigForFile graphNode = Some node stateContainsNodes = tcInfo.stateContainsNodes |> Set.add node sink = @@ -1529,7 +1533,7 @@ type internal TransparentCompiler parsedInput) tcInfo.tcState - let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, ownSigForFile = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1543,6 +1547,7 @@ type internal TransparentCompiler tcEnvAtEndOfFile = tcEnvAtEndOfFile topAttribs = Some topAttribs latestCcuSigForFile = Some ccuSigForFile + latestOwnSigForFile = Some ownSigForFile graphNode = Some fileNode stateContainsNodes = tcInfo.stateContainsNodes |> Set.add fileNode }) @@ -1641,7 +1646,7 @@ type internal TransparentCompiler let! result, tcInfo = ComputeTcLastFile bootstrapInfo snapshotWithSources - let tcEnv, _topAttribs, checkedImplFileOpt, ccuSigForFile = result + let tcEnv, _topAttribs, checkedImplFileOpt, ccuSigForFile, ownSigForFile = result let tcState = tcInfo.tcState @@ -1716,6 +1721,7 @@ type internal TransparentCompiler tcDiagnostics, keepAssemblyContents, ccuSigForFile, + ownSigForFile, tcState.Ccu, bootstrapInfo.TcImports, tcEnv.AccessRights, diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 2d1c3506f0b..a7e4613a623 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -36,6 +36,8 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option + latestOwnSigForFile: ModuleOrNamespaceType option + /// Accumulated diagnostics, last file first tcDiagnosticsRev: PhasedDiagnostic[] list diff --git a/src/Compiler/Utilities/illib.fs b/src/Compiler/Utilities/illib.fs index 5842492b31d..15ab7ec47d5 100644 --- a/src/Compiler/Utilities/illib.fs +++ b/src/Compiler/Utilities/illib.fs @@ -571,6 +571,11 @@ module List = let c, d = List.unzip cd a, b, c, d + let unzip5 l = + let a, b, c, de = unzip4 (List.map (fun (x, y, z, w, v) -> (x, y, z, (w, v))) l) + let d, e = List.unzip de + a, b, c, d, e + let rec iter3 f l1 l2 l3 = match l1, l2, l3 with | h1 :: t1, h2 :: t2, h3 :: t3 -> diff --git a/src/Compiler/Utilities/illib.fsi b/src/Compiler/Utilities/illib.fsi index 7c598ffa7a2..08858a3045a 100644 --- a/src/Compiler/Utilities/illib.fsi +++ b/src/Compiler/Utilities/illib.fsi @@ -187,6 +187,8 @@ module internal List = val unzip4: l: ('a * 'b * 'c * 'd) list -> 'a list * 'b list * 'c list * 'd list + val unzip5: l: ('a * 'b * 'c * 'd * 'e) list -> 'a list * 'b list * 'c list * 'd list * 'e list + val iter3: f: ('a -> 'b -> 'c -> unit) -> l1: 'a list -> l2: 'b list -> l3: 'c list -> unit val takeUntil: p: ('a -> bool) -> l: 'a list -> 'a list * 'a list diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl index 5c9c346b613..77e549545b9 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl @@ -2121,7 +2121,9 @@ FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.EditorServi FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.EditorServices.ToolTipText GetDescription(FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`2[FSharp.Compiler.Symbols.FSharpGenericParameter,FSharp.Compiler.Symbols.FSharpType]], Boolean, FSharp.Compiler.Text.Range) FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.EditorServices.ToolTipText GetKeywordTooltip(Microsoft.FSharp.Collections.FSharpList`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.EditorServices.ToolTipText GetToolTip(Int32, Int32, System.String, Microsoft.FSharp.Collections.FSharpList`1[System.String], Int32, Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) +FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpAssemblySignature FileSignature FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpAssemblySignature PartialAssemblySignature +FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpAssemblySignature get_FileSignature() FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpAssemblySignature get_PartialAssemblySignature() FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpOpenDeclaration[] OpenDeclarations FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults: FSharp.Compiler.Symbols.FSharpOpenDeclaration[] get_OpenDeclarations() diff --git a/tests/FSharp.Compiler.Service.Tests/Symbols.fs b/tests/FSharp.Compiler.Service.Tests/Symbols.fs index dd6c68ddc20..6e517eb4db0 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -1875,3 +1875,103 @@ let r2 = {| ...r1; C = 3 |} |> Array.find (fun u -> not u.IsFromDefinition) if getRangeCoords su.Range <> getRangeCoords spreadUse.Range then failwith $"GetSymbolUseAtLocation range %A{getRangeCoords su.Range} should match GetUsesOfSymbolInFile range %A{getRangeCoords spreadUse.Range} (no leading '...')." + +module FileSignature = + open FSharp.Compiler.NameResolution + + // Copies of definitions keep the name and range, only the stamp tells them apart + let private stampOf (symbol: FSharpSymbol) = + match symbol.Item with + | Item.Value vref -> vref.Stamp + | Item.UnqualifiedType [ tcref ] + | Item.ModuleOrNamespaces [ tcref ] -> tcref.Stamp + | item -> failwith $"Unexpected item %A{item}" + + let private check (fileName: string) files = + let options = createProjectOptionsFromNamedSources files [] + let filePath = options.SourceFiles |> Array.find (fun path -> path.EndsWith fileName) + let _, checkResults = parseAndCheckFile filePath (System.IO.File.ReadAllText filePath) options + checkResults + + let private names (symbols: seq<#FSharpSymbol>) = + symbols |> Seq.map (fun symbol -> symbol.DisplayName) |> List.ofSeq |> List.sort + + let private find name (symbols: seq<#FSharpSymbol>) = + symbols |> Seq.find (fun symbol -> symbol.DisplayName = name) + + let private members (entity: FSharpEntity) = + Seq.append (Seq.cast entity.NestedEntities) (Seq.cast entity.MembersFunctionsAndValues) + + let private shouldMatchDefinitions (checkResults: FSharpCheckFileResults) (entity: FSharpEntity) = + for symbol in members entity do + let definition = + checkResults |> findSymbolUse (fun u -> u.IsFromDefinition && u.Symbol.DisplayName = symbol.DisplayName) + + stampOf symbol |> shouldEqual (stampOf definition.Symbol) + + let private fsi = """ +module Test + +type Visible = class end + +val f: int -> int +""" + + let private fs = """ +module Test + +type Visible = class end + +type Hidden = class end + +let g (x: int) = x + 1 + +let f x = g x +""" + + [] + let ``FileSignature contains the declarations of the checked file only`` () = + let firstSource = """ +module First + +let x = 1 +""" + let secondSource = """ +module Second + +type U = class end + +let y = First.x +""" + let checkResults = check "Second.fs" [ "First.fs", firstSource; "Second.fs", secondSource ] + + names checkResults.PartialAssemblySignature.Entities |> shouldEqual [ "First"; "Second" ] + names checkResults.FileSignature.Entities |> shouldEqual [ "Second" ] + + let second = checkResults.FileSignature.FindEntityByPath [ "Second" ] |> Option.get + names (members second) |> shouldEqual [ "U"; "y" ] + shouldMatchDefinitions checkResults second + + // The partial assembly signature is built from a copy + let secondCopy = checkResults.PartialAssemblySignature.FindEntityByPath [ "Second" ] |> Option.get + Assert.NotEqual(stampOf (find "y" (members secondCopy)), stampOf (find "y" (members second))) + + [] + let ``FileSignature of an implementation file hidden by a signature file`` () = + let checkResults = check "Test.fs" [ "Test.fsi", fsi; "Test.fs", fs ] + + let visible = checkResults.PartialAssemblySignature.FindEntityByPath [ "Test" ] |> Option.get + names (members visible) |> shouldEqual [ "Visible"; "f" ] + + let test = checkResults.FileSignature.FindEntityByPath [ "Test" ] |> Option.get + names (members test) |> shouldEqual [ "Hidden"; "Visible"; "f"; "g" ] + shouldMatchDefinitions checkResults test + Assert.NotEqual(stampOf (find "f" (members visible)), stampOf (find "f" (members test))) + + [] + let ``FileSignature of a signature file`` () = + let checkResults = check "Test.fsi" [ "Test.fsi", fsi; "Test.fs", fs ] + + let test = checkResults.FileSignature.FindEntityByPath [ "Test" ] |> Option.get + names (members test) |> shouldEqual [ "Visible"; "f" ] + shouldMatchDefinitions checkResults test From d6303c7d429c6441c794b7364f7be50776e452ab Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Thu, 10 Sep 2026 13:11:12 +0200 Subject: [PATCH 2/3] Review fixes --- src/Compiler/Driver/ParseAndCheckInputs.fs | 4 +- src/Compiler/Service/BackgroundCompiler.fs | 2 +- src/Compiler/Service/FSharpCheckerResults.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 24 +++++------ src/Compiler/Service/IncrementalBuild.fsi | 5 ++- src/Compiler/Service/TransparentCompiler.fs | 9 +---- src/Compiler/Service/TransparentCompiler.fsi | 2 - src/Compiler/Utilities/illib.fs | 5 --- src/Compiler/Utilities/illib.fsi | 2 - .../FSharp.Compiler.Service.Tests/Symbols.fs | 40 ++++++++++++++++++- 10 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index ece11e235e9..0569244e6f1 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -1358,7 +1358,9 @@ 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.unzip5 results + let tcEnvsAtEndFile, topAttrs, implFiles, ccuSigsForFiles = + results |> List.map (fun (a, b, c, d, _) -> a, b, c, d) |> List.unzip4 + 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 diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index af8f62714f3..a21235d8e34 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -973,7 +973,7 @@ type internal BackgroundCompiler let tcSymbolUses = tcInfoExtras.tcSymbolUses let tcOpenDeclarations = tcInfoExtras.tcOpenDeclarations let latestCcuSigForFile = tcInfo.latestCcuSigForFile - let latestOwnSigForFile = tcInfo.latestOwnSigForFile + let latestOwnSigForFile = tcInfoExtras.latestOwnSigForFile let tcState = tcInfo.tcState let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile let latestImplementationFile = tcInfoExtras.latestImplFile diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index fea8666ba3a..ad1941dc7d6 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2847,7 +2847,7 @@ type internal TypeCheckInfo FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ccuSigForFile) member _.FileSignature = - FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ownSigForFile) + FSharpAssemblySignature(g, thisCcu, ownSigForFile, tcImports, None, ownSigForFile) member _.AccessRights = tcAccessRights diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index b6b87750233..cbe4d9a289b 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -197,8 +197,6 @@ type TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option - latestOwnSigForFile: ModuleOrNamespaceType option - /// Accumulated diagnostics, last file first tcDiagnosticsRev:PhasedDiagnostic[] list @@ -221,6 +219,8 @@ type TcInfoExtras = /// Result of checking most recent file, if any latestImplFile: CheckedImplFile option + latestOwnSigForFile: ModuleOrNamespaceType option + /// If enabled, stores a linear list of ranges and strings that identify an Item(symbol) in a file. Used for background find all references. itemKeyStore: ItemKeyStore option @@ -232,7 +232,7 @@ type TcInfoExtras = x.tcSymbolUses type private SingleFileDiagnostics = PhasedDiagnostic array -type private TypeCheck = TcInfo * TcResultsSinkImpl * CheckedImplFile option * string * SingleFileDiagnostics +type private TypeCheck = TcInfo * TcResultsSinkImpl * CheckedImplFile option * ModuleOrNamespaceType option * string * SingleFileDiagnostics /// Bound model of an underlying syntax and typed tree. type BoundModel private ( @@ -287,7 +287,6 @@ type BoundModel private ( tcEnvAtEndOfFile = tcEnvAtEndOfFile moduleNamesDict = moduleNamesDict latestCcuSigForFile = Some ccuSigForFile - latestOwnSigForFile = Some ownSigForFile tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev topAttribs = Some topAttribs tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles @@ -298,7 +297,7 @@ type BoundModel private ( | _ -> None } - return tcInfo, sink, implFile, fileName, newErrors + return tcInfo, sink, implFile, Some ownSigForFile, fileName, newErrors } let skippedImplementationTypeCheck = @@ -306,13 +305,12 @@ 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, ownSigForFile), tcState) -> + |> Option.map (fun ((_, topAttribs, _, ccuSigForFile, _), 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 @@ -322,13 +320,13 @@ type BoundModel private ( let getTcInfo (typeCheck: GraphNode) = async { - let! tcInfo , _, _, _, _ = typeCheck.GetOrComputeValue() + let! tcInfo , _, _, _, _, _ = typeCheck.GetOrComputeValue() return tcInfo } |> GraphNode let getTcInfoExtras (typeCheck: GraphNode) = async { - let! _ , sink, implFile, fileName, _ = typeCheck.GetOrComputeValue() + let! _ , sink, implFile, ownSigForFile, fileName, _ = typeCheck.GetOrComputeValue() // Build symbol keys let itemKeyStore, semanticClassification = if enableBackgroundItemKeyStoreAndSemanticClassification then @@ -363,6 +361,7 @@ type BoundModel private ( { // Only keep the typed interface files when doing a "full" build for fsc.exe, otherwise just throw them away latestImplFile = if keepAssemblyContents then implFile else None + latestOwnSigForFile = ownSigForFile tcResolutions = (if keepAllBackgroundResolutions then sink.GetResolutions() else TcResolutions.Empty) tcSymbolUses = (if keepAllBackgroundSymbolUses then sink.GetSymbolUses() else TcSymbolUses.Empty) tcOpenDeclarations = sink.GetOpenDeclarations() @@ -371,12 +370,12 @@ type BoundModel private ( } } |> GraphNode - let defaultTypeCheck = async { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, "default typecheck - no syntaxTree", [||] } + let defaultTypeCheck = async { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, None, "default typecheck - no syntaxTree", [||] } let typeCheckNode = syntaxTreeOpt |> Option.map getTypeCheck |> Option.defaultValue defaultTypeCheck |> GraphNode let tcInfoExtras = getTcInfoExtras typeCheckNode let diagnostics = async { - let! _, _, _, _, diags = typeCheckNode.GetOrComputeValue() + let! _, _, _, _, _, diags = typeCheckNode.GetOrComputeValue() return diags } |> GraphNode @@ -759,7 +758,6 @@ module IncrementalBuilderHelpers = tcEnvAtEndOfFile=tcInitial topAttribs=None latestCcuSigForFile=None - latestOwnSigForFile=None tcDiagnosticsRev = [ initialErrors ] moduleNamesDict = Map.empty tcDependencyFiles = basicDependencies @@ -808,7 +806,7 @@ module IncrementalBuilderHelpers = let results = [ for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles -> - tcInfo.tcEnvAtEndOfFile, defaultArg tcInfo.topAttribs EmptyTopAttrs, latestImplFile, tcInfo.latestCcuSigForFile, tcInfo.latestOwnSigForFile + tcInfo.tcEnvAtEndOfFile, defaultArg tcInfo.topAttribs EmptyTopAttrs, latestImplFile, tcInfo.latestCcuSigForFile, () ] // Get the state at the end of the type-checking of the last file diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi index d7d059d5cd9..d10cea41a1e 100644 --- a/src/Compiler/Service/IncrementalBuild.fsi +++ b/src/Compiler/Service/IncrementalBuild.fsi @@ -68,8 +68,6 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option - latestOwnSigForFile: ModuleOrNamespaceType option - /// Accumulated errors, last file first tcDiagnosticsRev: PhasedDiagnostic[] list @@ -92,6 +90,9 @@ type internal TcInfoExtras = /// Result of checking most recent file, if any latestImplFile: CheckedImplFile option + /// Inferred signature of the most recent file, before any signature file is applied + latestOwnSigForFile: ModuleOrNamespaceType option + /// If enabled, stores a linear list of ranges and strings that identify an Item(symbol) in a file. Used for background find all references. itemKeyStore: ItemKeyStore option diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index f58e6de9141..eef56443bf6 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -59,8 +59,6 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option - latestOwnSigForFile: ModuleOrNamespaceType option - /// Accumulated diagnostics, last file first tcDiagnosticsRev: PhasedDiagnostic[] list @@ -725,7 +723,6 @@ type internal TransparentCompiler tcEnvAtEndOfFile = tcInitial topAttribs = None latestCcuSigForFile = None - latestOwnSigForFile = None tcDiagnosticsRev = [ initialErrors ] moduleNamesDict = Map.empty tcDependencyFiles = basicDependencies @@ -1486,7 +1483,7 @@ type internal TransparentCompiler let partialResult, tcState = finisher tcInfo.tcState - let tcEnv, topAttribs, _, ccuSigForFile, ownSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, _ = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1503,7 +1500,6 @@ type internal TransparentCompiler tcDiagnosticsRev = tcIntermediate.tcDiagnosticsRev @ tcInfo.tcDiagnosticsRev tcDependencyFiles = tcIntermediate.tcDependencyFiles @ tcInfo.tcDependencyFiles latestCcuSigForFile = Some ccuSigForFile - latestOwnSigForFile = Some ownSigForFile graphNode = Some node stateContainsNodes = tcInfo.stateContainsNodes |> Set.add node sink = @@ -1533,7 +1529,7 @@ type internal TransparentCompiler parsedInput) tcInfo.tcState - let tcEnv, topAttribs, _, ccuSigForFile, ownSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, _ = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1547,7 +1543,6 @@ type internal TransparentCompiler tcEnvAtEndOfFile = tcEnvAtEndOfFile topAttribs = Some topAttribs latestCcuSigForFile = Some ccuSigForFile - latestOwnSigForFile = Some ownSigForFile graphNode = Some fileNode stateContainsNodes = tcInfo.stateContainsNodes |> Set.add fileNode }) diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index a7e4613a623..2d1c3506f0b 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -36,8 +36,6 @@ type internal TcInfo = latestCcuSigForFile: ModuleOrNamespaceType option - latestOwnSigForFile: ModuleOrNamespaceType option - /// Accumulated diagnostics, last file first tcDiagnosticsRev: PhasedDiagnostic[] list diff --git a/src/Compiler/Utilities/illib.fs b/src/Compiler/Utilities/illib.fs index 15ab7ec47d5..5842492b31d 100644 --- a/src/Compiler/Utilities/illib.fs +++ b/src/Compiler/Utilities/illib.fs @@ -571,11 +571,6 @@ module List = let c, d = List.unzip cd a, b, c, d - let unzip5 l = - let a, b, c, de = unzip4 (List.map (fun (x, y, z, w, v) -> (x, y, z, (w, v))) l) - let d, e = List.unzip de - a, b, c, d, e - let rec iter3 f l1 l2 l3 = match l1, l2, l3 with | h1 :: t1, h2 :: t2, h3 :: t3 -> diff --git a/src/Compiler/Utilities/illib.fsi b/src/Compiler/Utilities/illib.fsi index 08858a3045a..7c598ffa7a2 100644 --- a/src/Compiler/Utilities/illib.fsi +++ b/src/Compiler/Utilities/illib.fsi @@ -187,8 +187,6 @@ module internal List = val unzip4: l: ('a * 'b * 'c * 'd) list -> 'a list * 'b list * 'c list * 'd list - val unzip5: l: ('a * 'b * 'c * 'd * 'e) list -> 'a list * 'b list * 'c list * 'd list * 'e list - val iter3: f: ('a -> 'b -> 'c -> unit) -> l1: 'a list -> l2: 'b list -> l3: 'c list -> unit val takeUntil: p: ('a -> bool) -> l: 'a list -> 'a list * 'a list diff --git a/tests/FSharp.Compiler.Service.Tests/Symbols.fs b/tests/FSharp.Compiler.Service.Tests/Symbols.fs index 6e517eb4db0..11c7a091226 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -1887,9 +1887,12 @@ module FileSignature = | Item.ModuleOrNamespaces [ tcref ] -> tcref.Stamp | item -> failwith $"Unexpected item %A{item}" - let private check (fileName: string) files = + let private projectFile (fileName: string) files = let options = createProjectOptionsFromNamedSources files [] - let filePath = options.SourceFiles |> Array.find (fun path -> path.EndsWith fileName) + options, options.SourceFiles |> Array.find (fun path -> path.EndsWith fileName) + + let private check fileName files = + let options, filePath = projectFile fileName files let _, checkResults = parseAndCheckFile filePath (System.IO.File.ReadAllText filePath) options checkResults @@ -1975,3 +1978,36 @@ let y = First.x let test = checkResults.FileSignature.FindEntityByPath [ "Test" ] |> Option.get names (members test) |> shouldEqual [ "Visible"; "f" ] shouldMatchDefinitions checkResults test + + [] + let ``FileSignature of a background check with the incremental builder`` () = + let checker = FSharpChecker.Create(useTransparentCompiler = false) + let options, filePath = projectFile "Test.fs" [ "Test.fsi", fsi; "Test.fs", fs ] + let _, checkResults = checker.GetBackgroundCheckResultsForFileInProject(filePath, options) |> Async.RunSynchronouslyImmediate + + let test = checkResults.FileSignature.FindEntityByPath [ "Test" ] |> Option.get + names (members test) |> shouldEqual [ "Hidden"; "Visible"; "f"; "g" ] + shouldMatchDefinitions checkResults test + + [] + let ``Entities in FileSignature are declared in its entities`` () = + let fsi = """ +module Test + +val visible: int +""" + let fs = """ +module Test + +let visible = 1 + +module Hidden = + type Secret = class end +""" + for files in [ [ "Test.fs", fs ]; [ "Test.fsi", fsi; "Test.fs", fs ] ] do + let checkResults = check "Test.fs" files + let test = checkResults.FileSignature.FindEntityByPath [ "Test" ] |> Option.get + let hidden = test.NestedEntities |> Seq.exactlyOne + let secret = hidden.NestedEntities |> Seq.exactlyOne + stampOf (Option.get hidden.DeclaringEntity) |> shouldEqual (stampOf test) + stampOf (Option.get secret.DeclaringEntity) |> shouldEqual (stampOf hidden) From 42d873c407e4730e43f6b8bf61ea1a02c44a2293 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Thu, 10 Sep 2026 20:12:25 +0200 Subject: [PATCH 3/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4e05d13624c..e69525ab175 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -150,7 +150,7 @@ ### Added -* FCS: add FSharpCheckFileResults.FileSignature +* FCS: add FSharpCheckFileResults.FileSignature ([PR #20478](https://github.com/dotnet/fsharp/pull/20478)) * 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.