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..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,6 +150,7 @@ ### Added +* 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. 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..0569244e6f1 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,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.unzip4 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 @@ -1366,13 +1371,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 +1391,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 +1499,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 +1519,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 +1555,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 +1569,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 +1590,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..a21235d8e34 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 = tcInfoExtras.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..ad1941dc7d6 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, ownSigForFile, 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..cbe4d9a289b 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -219,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 @@ -230,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 ( @@ -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, @@ -295,7 +297,7 @@ type BoundModel private ( | _ -> None } - return tcInfo, sink, implFile, fileName, newErrors + return tcInfo, sink, implFile, Some ownSigForFile, fileName, newErrors } let skippedImplementationTypeCheck = @@ -303,7 +305,7 @@ 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, _), tcState) -> { tcState = tcState tcEnvAtEndOfFile = tcState.TcEnvFromImpls @@ -318,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 @@ -359,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() @@ -367,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 @@ -803,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.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 03c37da8216..d10cea41a1e 100644 --- a/src/Compiler/Service/IncrementalBuild.fsi +++ b/src/Compiler/Service/IncrementalBuild.fsi @@ -90,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 691dde3e802..eef56443bf6 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -1483,7 +1483,7 @@ type internal TransparentCompiler let partialResult, tcState = finisher tcInfo.tcState - let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, _ = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1529,7 +1529,7 @@ type internal TransparentCompiler parsedInput) tcInfo.tcState - let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult + let tcEnv, topAttribs, _, ccuSigForFile, _ = partialResult let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then @@ -1641,7 +1641,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 +1716,7 @@ type internal TransparentCompiler tcDiagnostics, keepAssemblyContents, ccuSigForFile, + ownSigForFile, tcState.Ccu, bootstrapInfo.TcImports, tcEnv.AccessRights, 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..11c7a091226 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -1875,3 +1875,139 @@ 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 projectFile (fileName: string) files = + let options = createProjectOptionsFromNamedSources files [] + 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 + + 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 + + [] + 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)