diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index fecefad1434..0b4c66ef35f 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -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(0x2000) <> enum 0 + member x.WithSpecialName = x.With(attributes = (x.Attributes ||| MethodAttributes.SpecialName)) @@ -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(0x2000)))) + [] member x.DebugText = x.ToString() diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index 5e02f4c0c1e..72e3d25790c 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -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 * @@ -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. diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 5c174ecf9de..ae6a7e616d5 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -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 @@ -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 @@ -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 = @@ -9474,6 +9478,7 @@ and GenMethodForBinding .WithSynchronized(hasSynchronizedImplFlag) .WithNoInlining(hasNoInliningFlag) .WithAggressiveInlining(hasAggressiveInliningImplFlag) + .WithAsync(hasAsyncImplFlag) .With(isEntryPoint = isExplicitEntryPoint, securityDecls = secDecls) let mdef = @@ -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 @@ -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 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/AsyncHelpers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/AsyncHelpers.fs new file mode 100644 index 00000000000..48191ce1160 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/AsyncHelpers.fs @@ -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) + [] + 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>, [] f: 'T -> Task<'U>) : Task<'U> = + AsyncHelpers.Await t + |> f + + member inline _.Bind(t: Task, [] f: unit -> Task<'U>) : Task<'U> = + AsyncHelpers.Await t + |> f + + member inline _.Delay([]f: unit -> Task<'T>) : Task<'T> = f() + + [] + member inline _.Run(f: Task<'T>) : Task<'T> = f + +let ce = CEBuilder() + +let test() = + ce { + do! Task.Delay 100 + return 42 + } + +[] +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 + + [] + let ``InlineIfLambda attribute should be recognized`` () = + let code = """ +module TestModule + +open System.Runtime.CompilerServices + +type Builder() = + member inline _.Bind(x: int, [] 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 diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.Async.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.Async.fs new file mode 100644 index 00000000000..7bfa0c4c3fc --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.Async.fs @@ -0,0 +1,6 @@ +// Test for MethodImplOptions.Async (0x2000) - Runtime-Async support (.NET 10+) +module M +open System.Threading.Tasks + +[] +let asyncMethod () : Task = Task.FromResult(42) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.AsyncWithTask.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.AsyncWithTask.fs new file mode 100644 index 00000000000..4b364414bef --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.AsyncWithTask.fs @@ -0,0 +1,12 @@ +// Test for MethodImplOptions.Async with Task return type +module AsyncTaskTest +open System.Threading.Tasks + +type TestClass() = + [] + member _.AsyncInstanceMethod() : Task = + Task.FromResult("hello") + + [] + static member AsyncStaticMethod() : Task = + Task.FromResult(100) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.fs index a13fee6baaf..f55ef16bb3c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/MethodImplAttribute/MethodImplAttribute.fs @@ -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 + [] + 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 + [] + let ``AsyncWithTask_fs`` compilation = + compilation + |> verifyCompilation diff --git a/tests/FSharp.Test.Utilities/TestFramework.fs b/tests/FSharp.Test.Utilities/TestFramework.fs index f7ec7f48001..d0de94ad896 100644 --- a/tests/FSharp.Test.Utilities/TestFramework.fs +++ b/tests/FSharp.Test.Utilities/TestFramework.fs @@ -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"