Skip to content
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

Add CodeActions for Number Constants: Convert between bases, Add digit group separators #1167

Merged
merged 15 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fake-cli": {
"version": "5.23.0",
"commands": [
"fake"
]
},
"paket": {
"version": "7.2.1",
"commands": [
"paket"
]
},
"octonav": {
"version": "0.0.1",
"commands": [
"octonav"
]
},
"dotnet-reportgenerator-globaltool": {
"version": "5.0.2",
"commands": [
"reportgenerator"
]
},
"fantomas": {
"version": "6.1.0",
"commands": [
"fantomas"
]
}
}
}
{
"version": 1,
"isRoot": true,
"tools": {
"fake-cli": {
"version": "5.23.0",
"commands": [
"fake"
]
},
"paket": {
"version": "7.2.1",
"commands": [
"paket"
]
},
"octonav": {
"version": "0.0.1",
"commands": [
"octonav"
]
},
"dotnet-reportgenerator-globaltool": {
"version": "5.0.2",
"commands": [
"reportgenerator"
]
},
"fantomas": {
"version": "6.2.0",
"commands": [
"fantomas"
]
}
}
}
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false

[*.fs, *.fsx]
[*.{fs,fsx}]
indent_size = 2
fsharp_max_array_or_list_width=80
fsharp_max_dot_get_expression_width=80
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ on:
jobs:
build:
env:
TEST_TIMEOUT_MINUTES: 30
TEST_TIMEOUT_MINUTES: 40
FSAC_TEST_DEFAULT_TIMEOUT : 120000 #ms, individual test timeouts
timeout-minutes: 30 # we have a locking issue, so cap the runs at ~20m to account for varying build times, etc
timeout-minutes: 40 # we have a locking issue, so cap the runs at ~20m to account for varying build times, etc
Copy link
Contributor

Choose a reason for hiding this comment

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

it concerns me a tiny bit that we keep climbing on this :(

Copy link
Member

Choose a reason for hiding this comment

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

I basically doubled the test suite with NamedText/RoslynSourceText. Might be worth switching to RoslynSourceText by default then eventually removing NamedText

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, so this isn't because individual tests are hanging, it's because the suite overall is going long? that's more understandable. I'd agree with both of the things you mention.

I'm guessing there's some Expecto cancellation weirdness that's preventing us from having a truly per-test timeout?

Copy link
Member

Choose a reason for hiding this comment

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

I'm guessing there's some Expecto cancellation weirdness that's preventing us from having a truly per-test timeout?

I think this does work (I see some tests fail at 2 mins) but we were hitting the overall timeout we set on the CI. I know I had to make a lot of tests sequential to keep the test suite passing consistently and that's a big part of it being slow too. Probably need to revisit the setup and see why things tend to not be thread safe. Might simply be we're not making an LSP server per test or something else.

I do vaguely remember the "wait for parse/check notifications" is how a lot of tests know how to continue after requesting a typecheck which can fail and not provide the correct feedback or gets lost in the logging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm guessing there's some Expecto cancellation weirdness that's preventing us from having a truly per-test timeout?

Yes, individual timeouts do work. It's "just" the overall timeout we run into.

I basically doubled the test suite with NamedText/RoslynSourceText. Might be worth switching to RoslynSourceText by default then eventually removing NamedText

I think that's reasonable. They should act the same, so just using one should be enough (and then maybe some tests to ensure they really act the same). Currently we're practically doing all tests twice -- despite not actually testing the XXXText, but the server functionality.
Locally the first thing I do before running tests is always uncomment NamedText -- which basically cuts the test time in halve.
Other "multipliers" are already uncommented ([1] [2]).


Probably need to revisit the setup and see why things tend to not be thread safe.

I think it's just the initialization and destruction of LSP server that must be sequential. The tests themselves should be able to run in parallel -- but are currently in sequenced. (Though moving tests into a nested testList doesn't seem to run them in parallel/affect performance? -> linked code doesn't seem to be limiting factor :/ )

Though there might also some issues with getting the correct notifications when tests are run in parallel (I think I remember an issue with that -- though might also be something outdated. I think notifications are now filter by file. So notifications should be assigned to correct tests.)


Might simply be we're not making an LSP server per test or something else.

We're already sharing a server per "logical group" (for example: per CodeFix -- but not for each individual test inside each CodeFix).
Though pushing that further outward might be good for speed. I remember just reusing a document (docTestList) has some noticeable (but not huge) impact. And a single doc should be way more light than a full LSP server.

We could use just one LSP server for all tests. But we're creating a lot of documents -- which I think get all cached in server? Also notifications from LSP Server to test environment are all cached. So might not actually be faster, but more memory intensive -- and definitely more difficult to debug.
Also: some tests require extra settings -> must get their own LSP server.

I think keeping the existing "One LSP server per logical group" makes most sense: State limited to logical context. And LSP settings explicit for that context.
(Though some behind the scene server caching and clearing a server state instead of throwing away might be reasonable too?)


I do vaguely remember the "wait for parse/check notifications" is how a lot of tests know how to continue after requesting a typecheck which can fail and not provide the correct feedback or gets lost in the logging.

Yes. Inclusive a required short delay even when correct notification already arrived ... just to ensure it's really the latest notification. Over 100s and 1000s of tests this adds up...

I think getting rid of this notification stream would be the best for tests: Difficult to filter & debug, might be out of order, lots of waiting to ensure/hope correct messages, lots of caching of old messages.
Replacing this with direct calls and direct returns would be great -- but then also not the LSP way :/

Copy link
Member

Choose a reason for hiding this comment

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

I think it's just the initialization and destruction of LSP server that must be sequential. The tests themselves should be able to run in parallel -- but are currently in sequenced. (Though moving tests into a nested testList doesn't seem to run them in parallel/affect performance? -> linked code doesn't seem to be limiting factor :/ )

We're already sharing a server per "logical group" (for example: per CodeFix -- but not for each individual test inside each CodeFix).
Though pushing that further outward might be good for speed. I remember just reusing a document (docTestList) has some noticeable (but not huge) impact. And a single doc should be way more light than a full LSP server.

Yeah it would be something to test. If an LSP server per test is more stable, is it worth the extra 10% in time it takes to do the init/shutdown dance? Hard to say unless we try it out. Although I don't think it's our biggest source of slowness. I think sequenced combined with my findings below is the painful combination.

Yes. Inclusive a required short delay even when correct notification already arrived ... just to ensure it's really the latest notification. Over 100s and 1000s of tests this adds up...

I think getting rid of this notification stream would be the best for tests: Difficult to filter & debug, might be out of order, lots of waiting to ensure/hope correct messages, lots of caching of old messages.
Replacing this with direct calls and direct returns would be great -- but then also not the LSP way :/

Yeah the biggest slowness I think is the |> Observable.bufferSpan (timeout) as we have to wait for that full timeout. It would probably be better to have to pass in your desired criteria and only wait as long as we need. Something like Observable.choose ... |> Observable.timeout. This would probably give another big boost in speeding things up.

We could move to some pull based diagnostics and poll until we get what we need but I fear we might end up not "truly" testing how an LSP is used from a client.

Copy link
Member

Choose a reason for hiding this comment

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

Might be worth switching to RoslynSourceText by default then eventually removing NamedText

The rest of the discussion is too long, didn't read... but we should do that, and also switch to the Adaptive server, and then remove the old version.

strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
Expand Down
15 changes: 5 additions & 10 deletions src/FsAutoComplete.Core/AdaptiveExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ module AdaptiveExtensions =


module Utils =
let cheapEqual (a: 'T) (b: 'T) =
ShallowEqualityComparer<'T>.Instance.Equals(a, b)
let cheapEqual (a: 'T) (b: 'T) = ShallowEqualityComparer<'T>.Instance.Equals(a, b)

/// <summary>
/// Maps and calls dispose before mapping of new values. Useful for cleaning up callbacks like AddMarkingCallback for tracing purposes.
Expand Down Expand Up @@ -75,8 +74,7 @@ module AVal =
/// <summary>
/// Maps and calls dispose before mapping of new values. Useful for cleaning up callbacks like AddMarkingCallback for tracing purposes.
/// </summary>
let mapDisposableTuple mapper value =
MapDisposableTupleVal(mapper, value) :> aval<_>
let mapDisposableTuple mapper value = MapDisposableTupleVal(mapper, value) :> aval<_>

/// <summary>
/// Calls a mapping function which creates additional dependencies to be tracked.
Expand Down Expand Up @@ -124,14 +122,12 @@ module AVal =

/// <summary>Creates an observable on the aval that will be executed whenever the avals value changed.</summary>
/// <param name="aval">The aval to get out-of-date information from.</param>
let onValueChangedWeak (aval: #aval<_>) =
Observable.Create(fun (obs: IObserver<_>) -> aval.AddCallback(obs.OnNext))
let onValueChangedWeak (aval: #aval<_>) = Observable.Create(fun (obs: IObserver<_>) -> aval.AddCallback(obs.OnNext))

module ASet =
/// Creates an amap with the keys from the set and the values given by mapping and
/// adaptively applies the given mapping function to all elements and returns a new amap containing the results.
let mapAtoAMap mapper src =
src |> ASet.mapToAMap mapper |> AMap.mapA (fun _ v -> v)
let mapAtoAMap mapper src = src |> ASet.mapToAMap mapper |> AMap.mapA (fun _ v -> v)

module AMap =
open FSharp.Data.Traceable
Expand Down Expand Up @@ -476,8 +472,7 @@ module AsyncAVal =
/// <summary>
/// Creates a constant async adaptive value always holding the given value.
/// </summary>
let constant (value: 'a) =
ConstantVal(Task.FromResult value) :> asyncaval<_>
let constant (value: 'a) = ConstantVal(Task.FromResult value) :> asyncaval<_>

/// <summary>
/// Creates a constant async adaptive value always holding the task.
Expand Down
21 changes: 7 additions & 14 deletions src/FsAutoComplete.Core/CodeGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,9 @@ module CodeGenerationUtils =
for _ in 0 .. count - 1 do
x.WriteLine ""

member __.Indent i =
indentWriter.Indent <- indentWriter.Indent + i
member __.Indent i = indentWriter.Indent <- indentWriter.Indent + i

member __.Unindent i =
indentWriter.Indent <- max 0 (indentWriter.Indent - i)
member __.Unindent i = indentWriter.Indent <- max 0 (indentWriter.Indent - i)

member __.Dump() = indentWriter.InnerWriter.ToString()

Expand Down Expand Up @@ -210,8 +208,7 @@ module CodeGenerationUtils =
let revd = List.rev xs
Some(List.rev revd.Tail, revd.Head)

let bracket (str: string) =
if str.Contains(" ") then "(" + str + ")" else str
let bracket (str: string) = if str.Contains(" ") then "(" + str + ")" else str

let formatType ctx (typ: FSharpType) =
let genericDefinition =
Expand Down Expand Up @@ -364,8 +361,7 @@ module CodeGenerationUtils =
else
displayName

let isEventMember (m: FSharpMemberOrFunctionOrValue) =
m.IsEvent || hasAttribute<CLIEventAttribute> m.Attributes
let isEventMember (m: FSharpMemberOrFunctionOrValue) = m.IsEvent || hasAttribute<CLIEventAttribute> m.Attributes

/// Rename a given argument if the identifier has been used

Expand Down Expand Up @@ -446,8 +442,7 @@ module CodeGenerationUtils =

writer.Unindent ctx.Indentation

let memberPrefix (m: FSharpMemberOrFunctionOrValue) =
if m.IsDispatchSlot then "override " else "member "
let memberPrefix (m: FSharpMemberOrFunctionOrValue) = if m.IsDispatchSlot then "override " else "member "

match m with
| MemberInfo.PropertyGetSet(getter, setter) ->
Expand Down Expand Up @@ -588,8 +583,7 @@ module CodeGenerationUtils =
/// Use this hack when FCS doesn't return enough information on .NET properties and events.
/// we use this to filter out the 'meta' members in favor of providing the underlying members for template generation
/// eg: a property _also_ has the relevant get/set members, so we don't need them.
let isSyntheticMember (m: FSharpMemberOrFunctionOrValue) =
m.IsProperty || m.IsEventAddMethod || m.IsEventRemoveMethod
let isSyntheticMember (m: FSharpMemberOrFunctionOrValue) = m.IsProperty || m.IsEventAddMethod || m.IsEventRemoveMethod

let isAbstractNonVirtualMember (m: FSharpMemberOrFunctionOrValue) =
// is an abstract member
Expand Down Expand Up @@ -733,8 +727,7 @@ module CodeGenerationUtils =
| _ -> lastValidToken

/// The code below is responsible for handling the code generation and determining the insert position
let getLineIdent (lineStr: string) =
lineStr.Length - lineStr.TrimStart(' ').Length
let getLineIdent (lineStr: string) = lineStr.Length - lineStr.TrimStart(' ').Length

let formatMembersAt
startColumn
Expand Down
45 changes: 15 additions & 30 deletions src/FsAutoComplete.Core/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ module private Result =

module AsyncResult =

let inline mapErrorRes ar : Async<CoreResponse<'a>> =
AsyncResult.foldResult id CoreResponse.ErrorRes ar
let inline mapErrorRes ar : Async<CoreResponse<'a>> = AsyncResult.foldResult id CoreResponse.ErrorRes ar

let recoverCancellationGeneric (ar: Async<Result<'t, exn>>) recoverInternal =
AsyncResult.foldResult id recoverInternal ar
Expand Down Expand Up @@ -584,8 +583,7 @@ module Commands =
| false, None -> currentIndex, false, acc

// Signature looks like <T> is Async<unit>
let inline removeSignPrefix (s: String) =
s.Split(" is ") |> Array.tryLast |> Option.defaultValue ""
let inline removeSignPrefix (s: String) = s.Split(" is ") |> Array.tryLast |> Option.defaultValue ""

let hints =
Array.init ((contents: ISourceText).GetLineCount()) (fun line -> (contents: ISourceText).GetLineString line)
Expand Down Expand Up @@ -697,8 +695,7 @@ module Commands =
//TODO: unite with `CodeFix/ResolveNamespace`
//TODO: Handle Nearest AND TopLevel. Currently it's just Nearest (vs. ResolveNamespace -> TopLevel) (#789)

let detectIndentation (line: string) =
line |> Seq.takeWhile ((=) ' ') |> Seq.length
let detectIndentation (line: string) = line |> Seq.takeWhile ((=) ' ') |> Seq.length

// adjust line
let pos =
Expand Down Expand Up @@ -1683,8 +1680,7 @@ type Commands
member x.TryGetFileCheckerOptionsWithLinesAndLineStr(file: string<LocalPath>, pos) =
state.TryGetFileCheckerOptionsWithLinesAndLineStr(file, pos)

member x.TryGetFileCheckerOptionsWithLines(file: string<LocalPath>) =
state.TryGetFileCheckerOptionsWithLines file
member x.TryGetFileCheckerOptionsWithLines(file: string<LocalPath>) = state.TryGetFileCheckerOptionsWithLines file

member x.TryGetFileVersion = state.TryGetFileVersion

Expand Down Expand Up @@ -1967,8 +1963,7 @@ type Commands
includeExternal
=
async {
let getAllSymbols () =
if includeExternal then tyRes.GetAllEntities true else []
let getAllSymbols () = if includeExternal then tyRes.GetAllEntities true else []

let! res = tyRes.TryGetCompletions pos lineStr filter getAllSymbols

Expand Down Expand Up @@ -2180,11 +2175,9 @@ type Commands

let summarySection = "/// <summary></summary>"

let parameterSection (name, _type) =
$"/// <param name=\"%s{name}\"></param>"
let parameterSection (name, _type) = $"/// <param name=\"%s{name}\"></param>"

let genericArg name =
$"/// <typeparam name=\"'%s{name}\"></typeparam>"
let genericArg name = $"/// <typeparam name=\"'%s{name}\"></typeparam>"

let returnsSection = "/// <returns></returns>"

Expand Down Expand Up @@ -2261,15 +2254,13 @@ type Commands
return usages |> Seq.map (fun u -> u.Range)
}

let tryGetFileSource symbolFile =
state.TryGetFileSource symbolFile |> Async.singleton
let tryGetFileSource symbolFile = state.TryGetFileSource symbolFile |> Async.singleton

let tryGetProjectOptionsForFsproj (fsprojPath: string<LocalPath>) =
state.ProjectController.GetProjectOptionsForFsproj(UMX.untag fsprojPath)
|> Async.singleton

let getAllProjectOptions () =
state.ProjectController.ProjectOptions |> Seq.map snd |> Async.singleton
let getAllProjectOptions () = state.ProjectController.ProjectOptions |> Seq.map snd |> Async.singleton

return!
Commands.symbolUseWorkspace
Expand Down Expand Up @@ -2301,14 +2292,11 @@ type Commands
}

member x.SymbolImplementationProject (tyRes: ParseAndCheckResults) (pos: Position) lineStr =
let getProjectOptions filePath =
state.GetProjectOptions' filePath |> Async.singleton
let getProjectOptions filePath = state.GetProjectOptions' filePath |> Async.singleton

let getUsesOfSymbol (filePath, opts, sym: FSharpSymbol) =
checker.GetUsesOfSymbol(filePath, opts, sym)
let getUsesOfSymbol (filePath, opts, sym: FSharpSymbol) = checker.GetUsesOfSymbol(filePath, opts, sym)

let getAllProjects () =
state.FSharpProjectOptions |> Seq.toList |> Async.singleton
let getAllProjects () = state.FSharpProjectOptions |> Seq.toList |> Async.singleton

Commands.symbolImplementationProject getProjectOptions getUsesOfSymbol getAllProjects tyRes pos lineStr
|> x.AsCancellable tyRes.FileName
Expand Down Expand Up @@ -2469,8 +2457,7 @@ type Commands
match tyResOpt with
| None -> ()
| Some tyRes ->
let getSourceLine lineNo =
(source :> ISourceText).GetLineString(lineNo - 1)
let getSourceLine lineNo = (source :> ISourceText).GetLineString(lineNo - 1)

let! simplified = SimplifyNames.getSimplifiableNames (tyRes.GetCheckResults, getSourceLine)
let simplified = Array.ofSeq simplified
Expand Down Expand Up @@ -2513,8 +2500,7 @@ type Commands
let version = Version.info ()
version.GitSha

member __.Quit() =
async { return [ CoreResponse.InfoRes "quitting..." ] }
member __.Quit() = async { return [ CoreResponse.InfoRes "quitting..." ] }

member x.ScopesForFile(file: string<LocalPath>) =
let getParseResultsForFile file =
Expand Down Expand Up @@ -2567,8 +2553,7 @@ type Commands
member __.SetWorkspaceRoot(root: string option) = workspaceRoot <- root
// linterConfiguration <- Lint.loadConfiguration workspaceRoot linterConfigFileRelativePath

member __.SetLinterConfigRelativePath(relativePath: string option) =
linterConfigFileRelativePath <- relativePath
member __.SetLinterConfigRelativePath(relativePath: string option) = linterConfigFileRelativePath <- relativePath
// linterConfiguration <- Lint.loadConfiguration workspaceRoot linterConfigFileRelativePath

// member __.FSharpLiterate (file: string<LocalPath>) =
Expand Down
6 changes: 2 additions & 4 deletions src/FsAutoComplete.Core/CompilerServiceInterface.fs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ type FSharpCompilerServiceChecker(hasAnalyzers, typecheckCacheSize, parallelRefe
"mscorlib" ]
|> List.map (fun p -> p + ".dll")

let containsBadRef (s: string) =
badRefs |> List.exists (fun r -> s.EndsWith r)
let containsBadRef (s: string) = badRefs |> List.exists (fun r -> s.EndsWith r)

fun (projOptions: FSharpProjectOptions) ->
{ projOptions with
Expand All @@ -122,8 +121,7 @@ type FSharpCompilerServiceChecker(hasAnalyzers, typecheckCacheSize, parallelRefe
{ projectOptions with
SourceFiles = files }

let (|Reference|_|) (opt: string) =
if opt.StartsWith "-r:" then Some(opt.[3..]) else None
let (|Reference|_|) (opt: string) = if opt.StartsWith "-r:" then Some(opt.[3..]) else None

/// ensures that all file paths are absolute before being sent to the compiler, because compilation of scripts fails with relative paths
let resolveRelativeFilePaths (projectOptions: FSharpProjectOptions) =
Expand Down
9 changes: 3 additions & 6 deletions src/FsAutoComplete.Core/DocumentationFormatter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,9 @@ module DocumentationFormatter =
}
|> String.concat ""

let typeConstraint (tc: FSharpType) =
sprintf ":> %s" (tc |> format displayContext |> fst)
let typeConstraint (tc: FSharpType) = sprintf ":> %s" (tc |> format displayContext |> fst)

let enumConstraint (ec: FSharpType) =
sprintf "enum<%s>" (ec |> format displayContext |> fst)
let enumConstraint (ec: FSharpType) = sprintf "enum<%s>" (ec |> format displayContext |> fst)

let delegateConstraint (tc: FSharpGenericParameterDelegateConstraint) =
sprintf
Expand Down Expand Up @@ -485,8 +483,7 @@ module DocumentationFormatter =
with _ ->
"Unknown"

let formatName (parameter: FSharpParameter) =
parameter.Name |> Option.defaultValue parameter.DisplayName
let formatName (parameter: FSharpParameter) = parameter.Name |> Option.defaultValue parameter.DisplayName

let isDelegate =
match func.EnclosingEntitySafe with
Expand Down
3 changes: 1 addition & 2 deletions src/FsAutoComplete.Core/DotnetNewTemplate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ module DotnetNewTemplate =

] } ]

let isMatch (filterstr: string) (x: string) =
x.ToLower().Contains(filterstr.ToLower())
let isMatch (filterstr: string) (x: string) = x.ToLower().Contains(filterstr.ToLower())

let nameMatch (filterstr: string) (x: string) = x.ToLower() = filterstr.ToLower()

Expand Down
Loading