-
Notifications
You must be signed in to change notification settings - Fork 802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Tooling Optimization] Skip background type-checking on implementation files if signature files exist #10199
Conversation
This would be absolutely brilliant to get merged in - we make heavy use of signature files in our libraries and would love to see how this improves our IDE experience. |
@saul, here is a VSIX you can try out: VisualFSharpFull.zip The state of the current change does what it is intended to do; it should give you an indication of how well it improves your experience. Some tooling will not work, such as find-all refs, but I think go-to-def should still be functional. |
@jonsequitur @cartermp I can say that this is done now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overall approach seems reasonable. Just a quick check - do the following work without issue?
- Find Refs
- Rename
- Autocompletion
- Lightbulb suggesting names after a misspelling
- Add
open
code fix to something that was defined in another file or project - etc.
If there's high confidence in this kind of stuff then I think this is good. I'm eager to get it into dogfood builds to evaluate it across a larger codebase.
src/fsharp/service/service.fs
Outdated
@@ -687,75 +709,90 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | |||
/// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API) | |||
member __.GetBackgroundCheckResultsForFileInProject(filename, options, userOpName) = | |||
reactor.EnqueueAndAwaitOpAsync(userOpName, "GetBackgroundCheckResultsForFileInProject", filename, fun ctok -> | |||
cancellable { | |||
cancellable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this tabbing over now makes the inner scope the same, which looks worse :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I see why.
@@ -1022,16 +1023,91 @@ module IncrementalBuilderEventTesting = | |||
|
|||
module Tc = FSharp.Compiler.TypeChecker | |||
|
|||
[<AutoOpen>] | |||
module IncrementalBuildSyntaxTree = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is internal, but I think there needs to be some comments explaining why we need our own abstraction over a syntax tree for the incremental builder. If I were to need to change something in here, I'd have nothing to look at for context on why this exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need some kind of object type that wraps the information to lazily parse the source text to get a ParsedInput
(which is the root of the syntax tree). The name SyntaxTree
feels correct here.
|
||
/// Accumulated results of type checking. | ||
[<Sealed>] | ||
type SyntaxTree (tcConfig: TcConfig, fileParsed: Event<string>, lexResourceManager, sourceRange: range, filename: string, isLastCompiland) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also be private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. We need access to SyntaxTree
when the enclosing module is auto-opened.
I actually do not want the IncrementalBuildSyntaxTree
module; instead we should just define SyntaxTree
without putting in an auto-opened module. The reason why we have this module is because SyntaxTree
is already a module in the compiler, so I can't define the type with that name unless I put it in another module, and then open that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the change, and it looks good. I have some style questions and naming nonsense. But other than that it looks great.
let! topAttrs, implFile, _implFileHiddenType, tcEnvAtEnd, createsGeneratedProvidedTypes = | ||
TypeCheckOneImplFile (tcGlobals, tcState.tcsNiceNameGen, amap, tcState.tcsCcu, checkForErrors, conditionalDefines, tcSink, tcConfig.internalTestSpanStackReferring) tcImplEnv rootSigOpt file | ||
let typeCheckOne = | ||
if skipImplIfSigExists && hadSig then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, is hadsig the right name? It seems as if it should be hasSig.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hadSig
refers to if the impl file "had" a sig before we type-checked. hasSig
might mean if the impl has any kind of signature, whether it be generated after a type-check or from a '.fsi' file.
Honestly, hasSig
might also be correct depending on how you look at it.
The most accurate name would be hasSigFromSigFile
.
@@ -1022,16 +1023,91 @@ module IncrementalBuilderEventTesting = | |||
|
|||
module Tc = FSharp.Compiler.TypeChecker | |||
|
|||
[<AutoOpen>] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AutoOpen really? seems a bit broad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't say it's broad; there is only one item in the module, which is SyntaxTree
.
return state.Partial | ||
} | ||
|
||
member this.TcInfoFull = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not clear to me what this name means? the value gets an unnamed tuple of TcInfo, TcInfoOptional.
perhaps the name should be this.TcInfoAndTcInfoOptional ... which is yuk!!! but at least it tells us what we are getting.
oh I don't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, this name is not great, but I'm struggling to figure out what I should call it.
Basically, whenever I try to access this property, it's going to try to type-check the file to get the full and rich information that would be needed for tooling. TcInfoAndTcInfoOptional
is accurate and might be better though.
src/fsharp/service/service.fs
Outdated
let parseResults = FSharpParseFileResults(creationErrors, None, true, [| |]) | ||
let typedResults = FSharpCheckFileResults.MakeEmpty(filename, creationErrors, reactorOps, keepAssemblyContents) | ||
return (parseResults, typedResults) | ||
| Some builder -> | ||
let! (parseTreeOpt, _, _, untypedErrors) = builder.GetParseResultsForFile (ctok, filename) | ||
let! tcProj = builder.GetCheckResultsAfterFileInProject (ctok, filename) | ||
|
||
let tcInfo, tcInfoOptional = tcProj.TcInfoFull ctok |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you determine whether to space separate functions or have them right next to each other? I struggle with this too. I wondered why line 742, 733 and 735 are empty lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, the call TcInfoFull
is really important to see clearly as it can do a lot of computation depending on if enablePartialTypeChecking
in FSharpChecker
is set to true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 733/735 - the spaces should be removed.
let! (tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt) = builder.GetCheckResultsAndImplementationsForProject(ctok) | ||
let errorOptions = tcProj.TcConfig.errorSeverityOptions | ||
let fileName = TcGlobals.DummyFileNameForRangesWithoutASpecificLocation | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
818 and 820 are empty, the remainder not, I wondered why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same reasoning I mentioned above.
@cartermp All tooling should still work as before. There is a performance difference with find-all refs as we now have to re-type-check files that only have partial data(or no symbols recorded). When this optimization is enabled, background type-checking will not record any symbols on a file, making it partial. We do this to avoid extra work when it isn't needed. |
…n files if signature files exist (dotnet#10199) * Initial work to enable FSI optimizations in tooling * Remove extra type-check function * Not working but initial work to lazily eval * some work * simplify * simplify again * Using Async * work * work * builds * almost there * Minor change * minor refactor * working, but not quite optimized * Fix dependency files * Refactor types * Added 'enableLazyTypeChecking'. * Enable lazy type checking in VS * Added documentation * Added SyntaxTreeAccumulator * prevent parsing impl files if possible * Trying to fix tests * Refactor * Throw if enablePartialChecking and keepAssemblyContents are both enabled * minor comment update * minor refactor * Trying to pass tests * Only getting assembly data * Remove unnecessary change * Added some documentation * Minor comment update * Renamed quickCheck to skipImplIfSigExists * Minor update * Feedback
…n files if signature files exist (dotnet#10199) * Initial work to enable FSI optimizations in tooling * Remove extra type-check function * Not working but initial work to lazily eval * some work * simplify * simplify again * Using Async * work * work * builds * almost there * Minor change * minor refactor * working, but not quite optimized * Fix dependency files * Refactor types * Added 'enableLazyTypeChecking'. * Enable lazy type checking in VS * Added documentation * Added SyntaxTreeAccumulator * prevent parsing impl files if possible * Trying to fix tests * Refactor * Throw if enablePartialChecking and keepAssemblyContents are both enabled * minor comment update * minor refactor * Trying to pass tests * Only getting assembly data * Remove unnecessary change * Added some documentation * Minor comment update * Renamed quickCheck to skipImplIfSigExists * Minor update * Feedback
…n files if signature files exist (dotnet#10199) * Initial work to enable FSI optimizations in tooling * Remove extra type-check function * Not working but initial work to lazily eval * some work * simplify * simplify again * Using Async * work * work * builds * almost there * Minor change * minor refactor * working, but not quite optimized * Fix dependency files * Refactor types * Added 'enableLazyTypeChecking'. * Enable lazy type checking in VS * Added documentation * Added SyntaxTreeAccumulator * prevent parsing impl files if possible * Trying to fix tests * Refactor * Throw if enablePartialChecking and keepAssemblyContents are both enabled * minor comment update * minor refactor * Trying to pass tests * Only getting assembly data * Remove unnecessary change * Added some documentation * Minor comment update * Renamed quickCheck to skipImplIfSigExists * Minor update * Feedback
Well over a year ago, I experimented with an optimization trick that sped up tooling significantly. Here, I'm trying to see if it's worth actually getting it in.
When editing then saving files, we always have to re-type-check everything - sometimes entire project(s) even on a single file change. This means we are constantly building a lot of large structures and throwing them away in a short amount of time. This takes a lot of CPU time and memory.
The optimization basically stops the background type-checking of implementation files if a backing signature(.fsi) file exists. This should be safe to do as the signature file will always be the public-facing API over the implementation file; therefore, the implementation file doesn't matter in most cases. You will still have to type-check an implementation file if you open it, or perform a find-all references.
The optimization only affects projects who use signature files. But for FSharp.Compiler.Private, it gives a very significant speed up in order to get colorization, tool-tips, etc.
The following are some results from starting up VisualFSharp.sln:
service.fs
FSharpProjectOptionsManager.fs
This is very significant. At the very least, it would boost tooling performance when working in the compiler.
Remaining and currently known tasks:
Prevent parse/type-check of other files when modifying implementation files with backing signature files; allows modifying implementation files without ever having to analyze any other file or project.Skip parse/type-check of other files when modifying implementation files with backing signature files #10210 Will make a separate PR for that change, it's the inverse of this optimization.Implementation details:
FSharpChecker
,enablePartialTypeChecking
- this will enable the optimization described above.