Skip to content
Closed
8 changes: 8 additions & 0 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,10 @@ type ILMethodDef

member x.IsMustRun = x.ImplAttributes &&& MethodImplAttributes.NoOptimization <> enum 0

/// Check if method is marked with MethodImplOptions.Async (0x2000)
/// This indicates the method uses runtime-async support (.NET 10+)
member x.IsAsync = x.ImplAttributes &&& enum<MethodImplAttributes>(0x2000) <> enum 0

member x.WithSpecialName =
x.With(attributes = (x.Attributes ||| MethodAttributes.SpecialName))

Expand Down Expand Up @@ -2132,6 +2136,10 @@ type ILMethodDef
member x.WithRuntime(condition) =
x.With(implAttributes = (x.ImplAttributes |> conditionalAdd condition MethodImplAttributes.Runtime))

/// Set the Async flag (MethodImplOptions.Async = 0x2000) for runtime-async support (.NET 10+)
member x.WithAsync(condition) =
x.With(implAttributes = (x.ImplAttributes |> conditionalAdd condition (enum<MethodImplAttributes>(0x2000))))

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()

Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,9 @@ type ILMethodDef =
/// SafeHandle finalizer must be run.
member IsMustRun: bool

/// Indicates method uses runtime-async support (MethodImplOptions.Async = 0x2000, .NET 10+)
member IsAsync: bool

/// Functional update of the value
member internal With:
?name: string *
Expand Down Expand Up @@ -1179,6 +1182,9 @@ type ILMethodDef =

member internal WithRuntime: bool -> ILMethodDef

/// Set the Async flag (MethodImplOptions.Async = 0x2000) for runtime-async support (.NET 10+)
member internal WithAsync: bool -> ILMethodDef

/// Tables of methods. Logically equivalent to a list of methods but
/// the table is kept in a form optimized for looking up methods by
/// name and arity.
Expand Down
16 changes: 11 additions & 5 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9002,8 +9002,11 @@ and ComputeMethodImplAttribs cenv (_v: Val) attrs =

// strip the MethodImpl pseudo-custom attribute
// The following method implementation flags are used here
// 0x80 - hasPreserveSigImplFlag
// 0x20 - synchronize
// 0x0008 - no inlining
// 0x0020 - synchronize
// 0x0080 - preserve sig
// 0x0100 - aggressive inlining
// 0x2000 - async (runtime-async support, .NET 10+)
// (See ECMA 335, Partition II, section 23.1.11 - Flags for methods [MethodImplAttributes])
let attrs =
attrs
Expand All @@ -9014,7 +9017,8 @@ and ComputeMethodImplAttribs cenv (_v: Val) attrs =
let hasSynchronizedImplFlag = (implflags &&& 0x20) <> 0x0
let hasNoInliningImplFlag = (implflags &&& 0x08) <> 0x0
let hasAggressiveInliningImplFlag = (implflags &&& 0x0100) <> 0x0
hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningImplFlag, hasAggressiveInliningImplFlag, attrs
let hasAsyncImplFlag = (implflags &&& 0x2000) <> 0x0
hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningImplFlag, hasAggressiveInliningImplFlag, hasAsyncImplFlag, attrs

and GenMethodForBinding
cenv
Expand Down Expand Up @@ -9197,7 +9201,7 @@ and GenMethodForBinding
| _ -> [], None

// check if the hasPreserveSigNamedArg and hasSynchronizedImplFlag implementation flags have been specified
let hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningFlag, hasAggressiveInliningImplFlag, attrs =
let hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningFlag, hasAggressiveInliningImplFlag, hasAsyncImplFlag, attrs =
ComputeMethodImplAttribs cenv v attrs

let securityAttributes, attrs =
Expand Down Expand Up @@ -9474,6 +9478,7 @@ and GenMethodForBinding
.WithSynchronized(hasSynchronizedImplFlag)
.WithNoInlining(hasNoInliningFlag)
.WithAggressiveInlining(hasAggressiveInliningImplFlag)
.WithAsync(hasAsyncImplFlag)
.With(isEntryPoint = isExplicitEntryPoint, securityDecls = secDecls)

let mdef =
Expand Down Expand Up @@ -10546,7 +10551,7 @@ and GenAbstractBinding cenv eenv tref (vref: ValRef) =
let memberInfo = Option.get vref.MemberInfo
let attribs = vref.Attribs

let hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningFlag, hasAggressiveInliningImplFlag, attribs =
let hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningFlag, hasAggressiveInliningImplFlag, hasAsyncImplFlag, attribs =
ComputeMethodImplAttribs cenv vref.Deref attribs

if memberInfo.MemberFlags.IsDispatchSlot && not memberInfo.IsImplemented then
Expand Down Expand Up @@ -10602,6 +10607,7 @@ and GenAbstractBinding cenv eenv tref (vref: ValRef) =
.WithSynchronized(hasSynchronizedImplFlag)
.WithNoInlining(hasNoInliningFlag)
.WithAggressiveInlining(hasAggressiveInliningImplFlag)
.WithAsync(hasAsyncImplFlag)

match memberInfo.MemberFlags.MemberKind with
| SynMemberKind.ClassConstructor
Expand Down
87 changes: 87 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/Conformance/AsyncHelpers.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.ComponentTests.Conformance

open Xunit
open FSharp.Test.Compiler

module AsyncHelpersTests =

// This test verifies that F# can compile code that uses AsyncHelpers
// when targeting .NET 10+ (when AsyncHelpers becomes available)
[<Fact>]
let ``Code using AsyncHelpers should compile with appropriate warnings`` () =
let code = """
module TestModule

open System
open System.Runtime.CompilerServices
open System.Threading.Tasks

#nowarn "57"

type CEBuilder() =
member inline _.Return(x: 'T) : Task<'T> = Task.FromResult(x)

member inline _.Bind(t: Task<'T>, [<InlineIfLambda>] f: 'T -> Task<'U>) : Task<'U> =
AsyncHelpers.Await t
|> f

member inline _.Bind(t: Task, [<InlineIfLambda>] f: unit -> Task<'U>) : Task<'U> =
AsyncHelpers.Await t
|> f

member inline _.Delay([<InlineIfLambda>]f: unit -> Task<'T>) : Task<'T> = f()

[<System.Runtime.CompilerServices.MethodImplAttribute(0x2000s)>]
member inline _.Run(f: Task<'T>) : Task<'T> = f

let ce = CEBuilder()

let test() =
ce {
do! Task.Delay 100
return 42
}

[<EntryPoint>]
let main _ = 0
"""
// This test documents that the code structure is valid F# syntax
// The actual AsyncHelpers.Await calls will only work when targeting .NET 10+
// For now, we expect a compilation error about AsyncHelpers not being defined
FSharp
|> withLangVersionPreview
|> asExe
|> withCode code
|> typecheck
|> shouldFail
|> ignore

[<Fact>]
let ``InlineIfLambda attribute should be recognized`` () =
let code = """
module TestModule

open System.Runtime.CompilerServices

type Builder() =
member inline _.Bind(x: int, [<InlineIfLambda>] f: int -> int) : int =
f x

member inline _.Return(x: int) : int = x

let builder = Builder()

let test() =
builder {
let! x = 42
return x
}
"""
FSharp
|> withLangVersionPreview
|> asExe
|> withCode code
|> compile
|> shouldSucceed
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Test for MethodImplOptions.Async (0x2000) - Runtime-Async support (.NET 10+)
module M
open System.Threading.Tasks

[<System.Runtime.CompilerServices.MethodImplAttribute(0x2000s)>]
let asyncMethod () : Task<int> = Task.FromResult(42)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Test for MethodImplOptions.Async with Task return type
module AsyncTaskTest
open System.Threading.Tasks

type TestClass() =
[<System.Runtime.CompilerServices.MethodImplAttribute(0x2000s)>]
member _.AsyncInstanceMethod() : Task<string> =
Task.FromResult("hello")

[<System.Runtime.CompilerServices.MethodImplAttribute(0x2000s)>]
static member AsyncStaticMethod() : Task<int> =
Task.FromResult(100)
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,15 @@ module MethodImplAttribute =
let ``Unmanaged_fs`` compilation =
compilation
|> verifyCompilation

// SOURCE=MethodImplAttribute.Async.fs SCFLAGS="-a -g --optimize-" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd MethodImplAttribute.Async.dll" # MethodImplAttribute.Async.fs - .NET 10 Runtime-Async
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"MethodImplAttribute.Async.fs"|])>]
let ``Async_fs`` compilation =
compilation
|> verifyCompilation

// SOURCE=MethodImplAttribute.AsyncWithTask.fs SCFLAGS="-a -g --optimize-" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd MethodImplAttribute.AsyncWithTask.dll" # MethodImplAttribute.AsyncWithTask.fs - .NET 10 Runtime-Async with Task
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"MethodImplAttribute.AsyncWithTask.fs"|])>]
let ``AsyncWithTask_fs`` compilation =
compilation
|> verifyCompilation
2 changes: 1 addition & 1 deletion tests/FSharp.Test.Utilities/TestFramework.fs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ let config configurationName envVars =
let repoRoot = SCRIPT_ROOT ++ ".." ++ ".."
let artifactsPath = repoRoot ++ "artifacts"
let artifactsBinPath = artifactsPath ++ "bin"
let coreClrRuntimePackageVersion = "5.0.0-preview.7.20364.11"
let coreClrRuntimePackageVersion = "10.0.0"
let csc_flags = "/nologo"
let vbc_flags = "/nologo"
let fsc_flags = "-r:System.Core.dll --nowarn:20 --define:COMPILED --preferreduilang:en-US"
Expand Down