Hello,
I've done some digging into the benefits of having a signature file in your editor.
The FSharpChecker
has a setting FSharpChecker.Create(enablePartialTypeChecking = true)
.
Rider enables this via:
When you invoke checker.ParseAndCheckFileInProject
, the backgroundChecker will construct a BoundModel
inside an IncrementalBuilder
:
service.fs
:
bc.ParseAndCheckFileInProject
->
getOrCreateBuilder
->
createBuilderNode
->
CreateOneIncrementalBuilder
->
IncrementalBuilder.TryCreateIncrementalBuilderForProjectOptions
IncrementalBuilder.fs
:
TryCreateIncrementalBuilderForProjectOptions
(takes enablePartialTypeChecking:bool
) ->
IncrementalBuilderState.Create(initialState)
This IncrementalBuilderState.Create
will create a BoundModel
for each fsharp file.
https://github.com/dotnet/fsharp/blob/f4f0e7e5173f525b4c3ff6476d82f60c9f01fbf4/src/Compiler/Service/IncrementalBuild.fs#L1146-L1147
Using IncrementalBuilderStateHelpers.createBoundModelGraphNode
, which will construct a GraphNode
that calls IncrementalBuilderHelpers.TypeCheckTask
.
This TypeCheckTask
will call prevBoundModel.Next
for the current file the evalutation of the GraphNode
will call BoundModel.TypeCheck
.
In TypeCheckTask
, we also see prevBoundModel.Next
which goes to the next file.
BoundModel
calls private TypeCheck
in the constructor, we see our first glimps of enablePartialTypeChecking
(named partialCheck
).
let sigNameOpt =
if partialCheck then
this.BackingSignature
else
None
If you have a signature file: syntaxTree.Parse
will return:
let canSkip = sigNameOpt.IsSome && FSharpImplFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName)
let input =
if canSkip then
ParsedInput.ImplFile(
ParsedImplFileInput(
fileName,
false,
sigNameOpt.Value,
[],
[],
[],
isLastCompiland,
{ ConditionalDirectives = []; CodeComments = [] }
)
)
Notice the rather empty syntax tree.
In CheckOneInput
(CheckOneInputAux
actually),
// Check if we've got an interface for this fragment
let rootSigOpt = tcState.tcsRootSigs.TryFind qualNameOfFile
They will try and find the signature type infomation based on the qualNameOfFile
.
If that is found (and the setting is active), the TypeChecking will be skipped entirely:
match rootSigOpt with
| Some rootSig when skipImplIfSigExists ->
// Delay the typecheck the implementation file until the second phase of parallel processing.
// Adjust the TcState as if it has been checked, which makes the signature for the file available later
// in the compilation order.
let tcStateForImplFile = tcState
let qualNameOfFile = file.QualifiedName
let priorErrors = checkForErrors ()
let ccuSigForFile, tcState =
AddCheckResultsToTcState
(tcGlobals, amap, hadSig, prefixPathOpt, tcSink, tcState.tcsTcImplEnv, qualNameOfFile, rootSig)
tcState
let partialResult =
(amap, conditionalDefines, rootSig, priorErrors, file, tcStateForImplFile, ccuSigForFile)
return Choice2Of2 partialResult, tcState
CheckOneInputAux
is also being called from CheckMultipleInputsInParallel
in the parallal PR:
And that somewhat explains the benefit of signature files both in the FSharpChecker and during compilation.
Did you have enablePartialTypeChecking = true
in the benchmark test?