Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@

* Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398))
* Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
* field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
* `Async.RunImmediate` renamed and replaced with impl of `FSharp.Core`'s `Async.RunSynchronouslyImmediate`, wherein `Exception`s are unwrapped (i.e., no egregious `AggregateException` wrapping). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804))

### Breaking Changes
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
4 changes: 4 additions & 0 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))

### Added

* `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804))
4 changes: 2 additions & 2 deletions src/Compiler/Driver/fsc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ let main1
// Import basic assemblies
let tcGlobals, frameworkTcImports =
TcImports.BuildFrameworkTcImports(foundationalTcConfigP, sysRes, otherRes)
|> Async.RunImmediate
|> Async.RunSynchronouslyImmediate

let ilSourceDocs =
[
Expand Down Expand Up @@ -642,7 +642,7 @@ let main1

let tcImports =
TcImports.BuildNonFrameworkTcImports(tcConfigP, frameworkTcImports, otherRes, knownUnresolved, dependencyProvider)
|> Async.RunImmediate
|> Async.RunSynchronouslyImmediate

// register tcImports to be disposed in future
disposables.Register tcImports
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ type StackGuard(name: string) =
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depthWhenJump})"
return f ()
}
|> Async.RunImmediate
|> Async.RunSynchronouslyImmediate
finally
depth.Value <- depth.Value - 1

Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4761,7 +4761,7 @@ type FsiEvaluationSession
try
let tcConfig = tcConfigP.Get(ctokStartup)

checker.FrameworkImportsCache.Get tcConfig |> Async.RunImmediate
checker.FrameworkImportsCache.Get tcConfig |> Async.RunSynchronouslyImmediate
with e ->
stopProcessingRecovery e range0
failwithf "Error creating evaluation session: %A" e
Expand All @@ -4775,7 +4775,7 @@ type FsiEvaluationSession
unresolvedReferences,
fsiOptions.DependencyProvider
)
|> Async.RunImmediate
|> Async.RunSynchronouslyImmediate
with e ->
stopProcessingRecovery e range0
failwithf "Error creating evaluation session: %A" e
Expand Down
26 changes: 12 additions & 14 deletions src/Compiler/Utilities/illib.fs
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,18 @@ module internal PervasiveAutoOpens =
let notFound () = raise (KeyNotFoundException())

type Async with

static member RunImmediate(computation: Async<'T>, ?cancellationToken) =
let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken

let ts = TaskCompletionSource<'T>()

let task = ts.Task

Async.StartWithContinuations(computation, ts.SetResult, ts.SetException, (fun _ -> ts.SetCanceled()), cancellationToken)

try
task.Result
with :? AggregateException as ex when ex.InnerExceptions.Count = 1 ->
raise (ex.InnerExceptions[0])
static member RunSynchronouslyImmediate(computation: Async<'T>, ?cancellationToken) =
let tcs = TaskCompletionSource<'T>()

Async.StartWithContinuations(
computation,
tcs.SetResult,
tcs.SetException,
tcs.SetException,
?cancellationToken = cancellationToken
)
// Synchronously block waiting for the result (i.e. even if continuations run on another thread, caller thread will be blocked)
tcs.Task.GetAwaiter().GetResult() // GetResult() unpacks the AggregateException that .Result would present

[<AbstractClass>]
type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) =
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Utilities/illib.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ module internal PervasiveAutoOpens =
type Async with

/// Runs the computation synchronously, always starting on the current thread.
static member RunImmediate: computation: Async<'T> * ?cancellationToken: CancellationToken -> 'T
static member RunSynchronouslyImmediate: computation: Async<'T> * ?cancellationToken: CancellationToken -> 'T

val foldOn: p: ('a -> 'b) -> f: ('c -> 'b -> 'd) -> z: 'c -> x: 'a -> 'd

Expand Down
77 changes: 38 additions & 39 deletions src/FSharp.Core/async.fs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,22 @@ module AsyncPrimitives =
ccont = (fun cexn -> ctxt.PostWithTrampoline syncCtxt (fun () -> ctxt.ccont cexn))
)

[<DebuggerHidden>]
let StartWithContinuations cancellationToken (computation: Async<'T>) cont econt ccont =
Comment thread
bartelink marked this conversation as resolved.
let trampolineHolder = TrampolineHolder()

trampolineHolder.ExecuteWithTrampoline(fun () ->
let ctxt =
AsyncActivation.Create
cancellationToken
trampolineHolder
(cont >> fake)
(econt >> fake)
(ccont >> fake)

computation.Invoke ctxt)
|> unfake

[<Sealed>]
[<AutoSerializable(false)>]
type SuspendedAsync<'T>(ctxt: AsyncActivation<'T>) =
Expand Down Expand Up @@ -1096,7 +1112,7 @@ module AsyncPrimitives =

/// Run the asynchronous workflow and wait for its result.
[<DebuggerHidden>]
let QueueAsyncAndWaitForResultSynchronously (token: CancellationToken) computation timeout =
let QueueAsyncAndWaitForResultSynchronously computation (token: CancellationToken) timeout =

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

can revert this, but trying to get arg order consistent across most involved functions

let token, innerCTS =
// If timeout is provided, we govern the async by our own CTS, to cancel
// when execution times out. Otherwise, the user-supplied token governs the async.
Expand Down Expand Up @@ -1138,31 +1154,24 @@ module AsyncPrimitives =
res.Commit()

[<DebuggerHidden>]
let RunImmediate (cancellationToken: CancellationToken) computation =
use resultCell = new ResultCell<AsyncResult<_>>()
let trampolineHolder = TrampolineHolder()

trampolineHolder.ExecuteWithTrampoline(fun () ->
let ctxt =
AsyncActivation.Create
cancellationToken
trampolineHolder
(fun res -> resultCell.RegisterResult(AsyncResult.Ok res, reuseThread = true))
(fun edi -> resultCell.RegisterResult(AsyncResult.Error edi, reuseThread = true))
(fun exn -> resultCell.RegisterResult(AsyncResult.Canceled exn, reuseThread = true))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

old behavior resulting in OperationCanceledException

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does it change RunSynchronously implementation?

@bartelink bartelink May 27, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@majocha in the PR as it stands, the core impl of RunSynchronouslyImmediate is:
https://github.com/bartelink/fsharp/blob/013acc1fca33bdfcd6f978d531bc0c6e1e34c199/src/FSharp.Core/async.fs#L1157

  1. that new code is based on OP of Add Async.RunImmediate fsharp/fslang-suggestions#1042 (comment)
  2. RunSynchronously used to delegate to https://github.com/dotnet/fsharp/pull/19804/changes#diff-7045116fe4610c3375ffcdd7df2ee411e71879bdf25890c8d3cf047c1b5208e2L1141 where conditions allow (no thread hop required)
  3. RunSynchronously now delegates to 1

The key diff is that:

  • the old way with the ResultCell Commit would result in OperationCanceledException being observed by caller if cancellation happened during running
  • the new way is tcs.Task.GetAwaiter().GetResult(), which results in a TaskCanceledException (which, to the best of my knowledge does not provide a way to wire in the CT info)

The pros from my perspective are:

Not an easy call, which is why I'm looking for someone else to vouch for it!

let RunSynchronouslyImmediate<'T> computation (cancellationToken: CancellationToken) =
let tcs = TaskCompletionSource<'T>()

computation.Invoke ctxt)
|> unfake

let res = resultCell.TryWaitForResultSynchronously().Value
res.Commit()
StartWithContinuations
cancellationToken
computation
tcs.SetResult
(fun edi -> tcs.SetException edi.SourceException)
tcs.SetException
// Synchronously block waiting for the result (i.e. even if continuations run on another thread, caller thread will be blocked)
tcs.Task.GetAwaiter().GetResult() // GetResult() unpacks the AggregateException that .Result would present
Comment thread
bartelink marked this conversation as resolved.

[<DebuggerHidden>]
let RunSynchronously cancellationToken (computation: Async<'T>) timeout =
// Reuse the current ThreadPool thread if possible.
let RunSynchronouslyBackgroundThreadPool (computation: Async<'T>) cancellationToken timeout =
// Run inline only where it's guaranteed to be safe
match SynchronizationContext.Current, Thread.CurrentThread.IsThreadPoolThread, timeout with
| null, true, None -> RunImmediate cancellationToken computation
| _ -> QueueAsyncAndWaitForResultSynchronously cancellationToken computation timeout
| null, true, None -> RunSynchronouslyImmediate computation cancellationToken // best stacktrace in case of exception
Comment thread
bartelink marked this conversation as resolved.
| _ -> QueueAsyncAndWaitForResultSynchronously computation cancellationToken timeout // less useful stack traces

[<DebuggerHidden>]
let Start cancellationToken (computation: Async<unit>) =
Expand All @@ -1174,22 +1183,6 @@ module AsyncPrimitives =
computation
|> unfake

[<DebuggerHidden>]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

moved up as required by RunSynchronously

note the whole PR could be made much shorter by having the Async layer call Async.FromContinuations

However, as mentioned in other places, I believe its for the best that RunSynchronouslyImmediate and the immediate path of RunSychronously should have strong clear ties in the lower layer as this PR does

let StartWithContinuations cancellationToken (computation: Async<'T>) cont econt ccont =
let trampolineHolder = TrampolineHolder()

trampolineHolder.ExecuteWithTrampoline(fun () ->
let ctxt =
AsyncActivation.Create
cancellationToken
trampolineHolder
(cont >> fake)
(econt >> fake)
(ccont >> fake)

computation.Invoke ctxt)
|> unfake

[<DebuggerHidden>]
let StartAsTask cancellationToken (computation: Async<'T>) taskCreationOptions =
let taskCreationOptions = defaultArg taskCreationOptions TaskCreationOptions.None
Expand Down Expand Up @@ -1511,7 +1504,13 @@ type Async =
| Some token when not token.CanBeCanceled -> timeout, token
| Some token -> None, token

RunSynchronously cancellationToken computation timeout
RunSynchronouslyBackgroundThreadPool computation cancellationToken timeout

static member RunSynchronouslyImmediate(computation: Async<'T>, ?cancellationToken: CancellationToken) =
let cancellationToken =
defaultArg cancellationToken defaultCancellationTokenSource.Token

RunSynchronouslyImmediate computation cancellationToken

static member Start(computation, ?cancellationToken) =
let cancellationToken =
Expand Down
96 changes: 66 additions & 30 deletions src/FSharp.Core/async.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -47,50 +47,86 @@ namespace Microsoft.FSharp.Control
[<CompiledName("FSharpAsync")>]
type Async =

/// <summary>Runs the asynchronous computation and await its result.</summary>
///
/// <remarks>If an exception occurs in the asynchronous computation then an exception is re-raised by this
/// function.
///
/// If no cancellation token is provided then the default cancellation token is used.
///
/// The computation is started on the current thread if <see cref="P:System.Threading.SynchronizationContext.Current"/> is null,
/// <see cref="P:System.Threading.Thread.CurrentThread"/> has <see cref="P:System.Threading.Thread.IsThreadPoolThread"/>
/// of <c>true</c>, and no timeout is specified. Otherwise the computation is started by queueing a new work item in the thread pool,
/// and the current thread is blocked awaiting the completion of the computation.
///
/// The timeout parameter is given in milliseconds. A value of -1 is equivalent to
/// <see cref="F:System.Threading.Timeout.Infinite"/>.
/// <summary><p>Runs the asynchronous computation on a threadpool thread, honoring the ambient

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

SynchronizationContext.SetSynchronizationContext ctx
async { return 1 } |> Async.RunSynchronously   // ctx.Post called 0 times; runs inline
  • "on a threadpool thread" -- false (fast-path is inline)
  • "honoring the ambient SynchronizationContext" -- false (never posts)
- /// <summary><p>Runs the asynchronous computation on a threadpool thread, honoring the ambient
- /// <see cref="T:System.Threading.SynchronizationContext"/>.</p>
+ /// <summary><p>Runs the computation and blocks the caller until it completes. Runs inline on the
+ /// calling thread when it is a thread-pool thread with no ambient SynchronizationContext and no
+ /// timeout; otherwise runs on the thread pool.</p>

@bartelink bartelink Jul 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Your wording feels much better (I was aware that my wording was technically not 100% correct - my aim was to emphasize the core difference vs RSI: even if you are doing something tiny, you'll incur a thread hop more often than you might expect from the name).

Also wanted to be pedantic on some specifics:

on a threadpool thread" -- false (fast-path is inline)

but fast path requires you to already be on a thread pool thread. So RunSynchronously didnt need to do a hop, but the code is run on a thread pool thread?

"honoring the ambient SynchronizationContext" -- false (never posts)

I think this is due to special casing triggered by the reuseThread = true parameter to RegisterResult (async.fs: 1004)

But I may well have missed the point you're trying to make.

Happy to apply your summary assuming all the above tallies with your understanding/intent.

/// <see cref="T:System.Threading.SynchronizationContext"/>.</p>
/// <p>During processing, the calling thread blocks awaiting the outcome.</p>
/// </summary>
/// <remarks>
/// <p>Note For F# interactive, F# scripts, and unit tests consider using
/// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronouslyImmediate`1"/>, which
/// always starts on the calling thread and presents a simpler stack trace in exception cases and/or under a debugger.</p>
/// <p>Computation runs directly on the calling thread when
/// <see cref="P:System.Threading.SynchronizationContext.Current"/> is <c>null</c>,
/// <see cref="P:System.Threading.Thread.IsThreadPoolThread"/> is <c>true</c>, and no timeout is specified.</p>
/// </remarks>
///
/// <param name="computation">The computation to run.</param>
/// <param name="timeout">The amount of time in milliseconds to wait for the result of the
/// computation before raising a <see cref="T:System.TimeoutException"/>. If no value is provided
/// for timeout then a default of -1 is used to correspond to <see cref="F:System.Threading.Timeout.Infinite"/>.</param>
/// <param name="timeout">The number of milliseconds to wait for the result of the
/// computation before raising a <see cref="T:System.TimeoutException"/>. If no value or -1 is provided
/// the timeout will be <see cref="F:System.Threading.Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">The cancellation token to be associated with the computation.
/// If one is not supplied, the default cancellation token is used.</param>
///
/// <returns>The result of the computation.</returns>
///
/// If omitted, <c>Async.DefaultCancellationToken</c> is used.</param>
/// <returns>The result of the computation. Any exception raised by the computation is propagated to the caller.</returns>
/// <category index="0">Starting Async Computations</category>
///
/// <example id="run-synchronously-1">
/// <code lang="fsharp">
/// printfn "A"
/// printfn "A" // runs on caller thread
///
/// let result = async {
/// printfn "B"
/// printfn "B" // runs on a background/threadpool thread
/// do! Async.Sleep(1000)
/// printfn "C"
/// 17
/// printfn "C" // continuation runs on a background/threadpool thread
/// return 17
/// } |> Async.RunSynchronously
///
/// printfn "D"
/// printfn "D" // runs on caller thread
/// </code>
/// Prints "A", "B" immediately, then "C", "D" in 1 second. result is set to 17.
/// <p>Prints "A", "B" immediately, then "C", "D" after 1 second.</p>
/// <p>Yields <c>result = 17</c>.</p>
/// </example>
static member RunSynchronously : computation:Async<'T> * ?timeout : int * ?cancellationToken:CancellationToken-> 'T


/// <summary><p>Starts the asynchronous computation on the calling thread, disregarding the ambient
/// <see cref="T:System.Threading.SynchronizationContext"/>.</p>
/// <p>During any asynchronous continuations after the first suspension, the calling thread blocks awaiting the outcome.</p>
/// </summary>
/// <remarks>
/// <p>Warning: blocks the calling thread for the duration of the computation. Calling it
/// from a UI thread will make the UI unresponsive and risks deadlock if any continuation in the
/// computation needs to be dispatched back to that context.</p>
/// <p>Normally preferred to <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously`1"/> for
/// interactive use in F# scripts and F# interactive (FSI), and for unit tests as: <br/>
/// - a breakpoint will show a clearer call stack prior to the first suspension (as opposed to it waiting for an asynchronous completion notification from another thread<br/>
/// - the stack trace in the case of an exception will have two fewer frames.
/// </p>
/// <p>Does not support a timeout; see
/// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously`1"/> if one is desired.</p>
/// <p>Does not ensure execution takes place on a threadpool thread; see

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

async { return 1 } |> Async.RunSynchronously   // also runs inline -- not a threadpool guarantee
  • cross-ref to RunSynchronously here implies a threadpool guarantee it doesn't provide
- /// <p>Does not ensure execution takes place on a threadpool thread; see
- /// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously`1"/> or
- /// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.SwitchToThreadPool"/> if this is required.</p>
+ /// <p>Does not ensure execution takes place on a threadpool thread; see
+ /// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.SwitchToThreadPool"/> if this is required.</p>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The key driver for me referring to both was to try to explain the key behavioral difference of consequence between RS and RSI in terms of other concrete methods as a pedagogical mechanism.

AIUI (but I may very well be wrong) RunSynchronously does provide this guarantee, as which:

  1. stays on current thread until the first asynchronous suspension if all these things are true:
  • the current thread is a threadpool thread
  • and SynchronizationContext.Current is null
  • and we're not trying to apply a timeout
  1. in all other cases it posts the work into the thread pool and uses the calling thread to await

Note I renamed the internal impl to RunSynchronouslyBackgroundThreadPool to try to convey the above semantics in shorthand.

All of that said, clearly my wording didn't get the point across as intended.

Hence I feel it may make more sense to reword it and/or remove it entirely as the proposed change turns it into a non-sequitur as the sentence no longer enables people to triangulate/bridge the concepts as intended?

/// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.RunSynchronously`1"/> or
/// <see cref="M:Microsoft.FSharp.Control.FSharpAsync.SwitchToThreadPool"/> if this is required.</p>
/// </remarks>
/// <param name="computation">The computation to run.</param>
/// <param name="cancellationToken">The cancellation token to be associated with the computation.
/// If omitted, <c>Async.DefaultCancellationToken</c> is used.</param>
/// <returns>The result of the computation. Any exception raised by the computation is propagated to the caller.</returns>
/// <category index="0">Starting Async Computations</category>
/// <example id="run-synchronously-immediate-1">
/// <code lang="fsharp">
/// printfn "A" // runs on calling thread
///
/// let result = async {
/// printfn "B" // ALSO runs on calling thread (hence immediately)
/// do! Async.Sleep(1000)
/// printfn "C" // runs in continuation context (depends on SynchronizationContext etc)
/// return 17
/// } |> Async.RunSynchronouslyImmediate
///
/// printfn "D" // runs on calling thread
/// </code>
/// <p>Prints "A", "B" immediately, then "C", "D" after 1 second.</p>
/// <p>Yields <c>result = 17</c>.</p>
/// </example>
static member RunSynchronouslyImmediate : computation : Async<'T> * ?cancellationToken : CancellationToken -> 'T

/// <summary>Starts the asynchronous computation in the thread pool. Do not await its result.</summary>
///
/// <remarks>If no cancellation token is provided then the default cancellation token is used.</remarks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let private assertAreEqual (expected, actual) =
let private checkFile (source: string) =
let _, checkFileAnswer =
checker.ParseAndCheckFileInProject(filePath, 0, FSharp.Compiler.Text.SourceText.ofString source, projectOptions)
|> Async.RunImmediate
|> Async.RunSynchronouslyImmediate

match checkFileAnswer with
| FSharpCheckFileAnswer.Aborted -> failwithf "ParseAndCheckFileInProject aborted"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ let x = 123
"""

let fileName, options = mkTestFileAndOptions [| |]
checker.ParseAndCheckFileInProject(fileName, 0, SourceText.ofString source, options) |> Async.RunImmediate |> ignore
checker.ParseAndCheckFileInProject(fileName, 0, SourceText.ofString source, options) |> Async.RunSynchronouslyImmediate |> ignore
gotRequest |> Assert.True
Loading
Loading