Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
baf99e8
Add language feature and baselines tests
kerams Jun 23, 2026
87cff9a
Implementation
kerams Jun 24, 2026
6682524
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 24, 2026
894f6f0
Release notes, custom delegate tests
kerams Jun 24, 2026
67b1ca7
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 25, 2026
4270507
verifyPEFileWithSystemDlls, remove realsig keying, RFC <-> baseline t…
kerams Jun 25, 2026
2d60fe4
Remove duplicate test cases
kerams Jun 25, 2026
dc32fcd
Refactor
kerams Jun 26, 2026
8017ff0
Build fix
kerams Jun 26, 2026
cd6e3c6
Build fix
kerams Jun 26, 2026
adc9b61
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 29, 2026
be7517d
Unit arg
kerams Jun 29, 2026
80a8668
Extension methods
kerams Jun 29, 2026
f2f8999
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 29, 2026
9ddc0f1
Value types
kerams Jun 29, 2026
d8f11c6
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 29, 2026
1f6ea38
Reformat
kerams Jun 29, 2026
fa4c43b
ASP.NET smoke test
kerams Jun 29, 2026
e96523d
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 30, 2026
8127868
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 30, 2026
49ba6f0
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jun 30, 2026
05a3970
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jul 2, 2026
0e5d1ce
Fix inline race
kerams Jul 2, 2026
0410172
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jul 2, 2026
f485b25
Post merge fixes
kerams Jul 2, 2026
2b9d108
Comments
kerams Jul 3, 2026
69a1c61
Merge branch 'main' of https://github.com/dotnet/fsharp into del
kerams Jul 3, 2026
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
362 changes: 278 additions & 84 deletions src/Compiler/CodeGen/IlxGen.fs
Comment thread
kerams marked this conversation as resolved.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1822,3 +1822,4 @@ featurePreprocessorElif,"#elif preprocessor directive"
3889,tastNamespaceAndTypeWithSameNameInAssembly,"The namespace '%s' clashes with the type '%s'."
featureExceptionFieldSerializationSupport,"emit GetObjectData and field-restoring deserialization constructor for exception types"
featureErrorOnMissingSignatureAttribute,"error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi"
featureDirectDelegateConstruction,"construct delegates that point directly at the target method, avoiding an intermediate closure"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
| DirectDelegateConstruction

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -263,6 +264,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution
LanguageFeature.ImplicitDIMCoverage, languageVersion110
LanguageFeature.ErrorOnMissingSignatureAttribute, previewVersion // Opt-in: turn FS3888 from warning into error
LanguageFeature.DirectDelegateConstruction, previewVersion
]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -459,6 +461,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
| LanguageFeature.ExceptionFieldSerializationSupport -> FSComp.SR.featureExceptionFieldSerializationSupport ()
| LanguageFeature.ErrorOnMissingSignatureAttribute -> FSComp.SR.featureErrorOnMissingSignatureAttribute ()
| LanguageFeature.DirectDelegateConstruction -> FSComp.SR.featureDirectDelegateConstruction ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
| DirectDelegateConstruction

/// LanguageVersion management
type LanguageVersion =
Expand Down
65 changes: 65 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypeProviders
#endif

/// The target of a direct-delegate forwarding call recognized by (|DirectDelegateForwardingCall|_|).
[<RequireQualifiedAccess>]
type internal DirectDelegateForwardingTarget =
/// A known F# value: a module-level function or a member. Carries the value reference, its use flags
/// and the call's type arguments; the consumer resolves storage to a method spec.
| FSharpVal of vref: ValRef * valUseFlags: ValUseFlag * tyargs: TypeInst
/// A direct IL method call (e.g. a BCL method), compiled as TOp.ILCall. Carries everything needed to
/// build the method spec directly: virtual/struct/ctor flags, use flags, the IL method reference and
/// the enclosing-type and method type instantiations.
| ILMethod of
isVirtual: bool *
isStruct: bool *
isCtor: bool *
valUseFlags: ValUseFlag *
ilMethRef: ILMethodRef *
enclTypeInst: TypeInst *
methInst: TypeInst

[<AutoOpen>]
module internal AddressOps =

Expand Down Expand Up @@ -1815,6 +1833,53 @@ module internal ExprTransforms =
)
| _ -> ValueNone

/// Recognizes the body of a delegate's Invoke method when it is a saturated, transparent forwarding
/// call `target leading... p0 p1 ...` to a known method, whose trailing arguments are exactly the
/// delegate's Invoke parameters in order. This is the shape that lets a delegate point directly at
/// `target` instead of an intermediate closure. Any leading arguments (e.g. an instance method's
/// receiver) are returned to the consumer, which decides whether a direct delegate can actually be
/// emitted (matching IL signature, receiver shape, no witnesses, etc.).
[<return: Struct>]
let (|DirectDelegateForwardingCall|_|) g (invokeParams: Val list) expr =
// The trailing arguments of the call must be exactly the delegate's Invoke parameters, forwarded
// verbatim and in order. The remaining leading arguments (e.g. an instance receiver) are returned
// for the consumer to handle.
let matchForwarding (args: Expr list) =
let numLeading = args.Length - invokeParams.Length

if numLeading >= 0 then
let leadingArgs, forwardedArgs = List.splitAt numLeading args

if
List.forall2
(fun (a: Expr) (tv: Val) ->
match stripDebugPoints a with
| Expr.Val(avref, _, _) -> valRefEq g avref (mkLocalValRef tv)
| _ -> false)
forwardedArgs
invokeParams
then
ValueSome leadingArgs
else
ValueNone
else
ValueNone

match stripDebugPoints expr with
| Expr.App(Expr.Val(vref, valUseFlags, _), _, tyargs, args, _) ->
match matchForwarding args with
| ValueSome leadingArgs -> ValueSome(DirectDelegateForwardingTarget.FSharpVal(vref, valUseFlags, tyargs), leadingArgs)
| ValueNone -> ValueNone
| Expr.Op(TOp.ILCall(isVirtual, _, isStruct, isCtor, valUseFlag, _, _, ilMethRef, enclTypeInst, methInst, _), _, args, _) ->
match matchForwarding args with
| ValueSome leadingArgs ->
ValueSome(
DirectDelegateForwardingTarget.ILMethod(isVirtual, isStruct, isCtor, valUseFlag, ilMethRef, enclTypeInst, methInst),
leadingArgs
)
| ValueNone -> ValueNone
| _ -> ValueNone

[<return: Struct>]
let (|DelegateInvokeExpr|_|) g expr =
match expr with
Expand Down
26 changes: 26 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.Syntax

/// The target of a direct-delegate forwarding call recognized by (|DirectDelegateForwardingCall|_|).
[<RequireQualifiedAccess>]
type internal DirectDelegateForwardingTarget =
/// A known F# value: a module-level function or a member. Carries the value reference, its use flags
/// and the call's type arguments; the consumer resolves storage to a method spec.
| FSharpVal of vref: ValRef * valUseFlags: ValUseFlag * tyargs: TypeInst
/// A direct IL method call (e.g. a BCL method), compiled as TOp.ILCall. Carries everything needed to
/// build the method spec directly: virtual/struct/ctor flags, use flags, the IL method reference and
/// the enclosing-type and method type instantiations.
| ILMethod of
isVirtual: bool *
isStruct: bool *
isCtor: bool *
valUseFlags: ValUseFlag *
ilMethRef: ILMethodRef *
enclTypeInst: TypeInst *
methInst: TypeInst

[<AutoOpen>]
module internal AddressOps =

Expand Down Expand Up @@ -544,6 +562,14 @@ module internal ExprTransforms =
[<return: Struct>]
val (|NewDelegateExpr|_|): TcGlobals -> Expr -> (Unique * Val list * Expr * range * (Expr -> Expr)) voption

/// Recognizes the body of a delegate's Invoke method when it is a saturated, transparent forwarding
/// call to a known method (an F# value or a direct IL call) whose trailing arguments are exactly the
/// delegate's Invoke parameters in order. Returns the resolved target and any leading (non-forwarded)
/// argument expressions, e.g. an instance method's receiver.
[<return: Struct>]
val (|DirectDelegateForwardingCall|_|):
TcGlobals -> Val list -> Expr -> (DirectDelegateForwardingTarget * Expr list) voption

[<return: Struct>]
val (|DelegateInvokeExpr|_|): TcGlobals -> Expr -> (Expr * TType * TypeInst * Expr * Expr * range) voption

Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module DelegateExtensionMethod

open System
open System.Runtime.CompilerServices

type Holder() =
class
end

[<Extension>]
type HolderExtensions =
[<Extension; NoCompilerInlining>]
static member Combine (h: Holder, x: int, y: int) : int = x + y

// An extension member compiles to a static method whose first parameter is the receiver, so using it as a
// delegate target binds that receiver as a leading argument - a partial application, which has no closed
// direct-delegate form. A closure must remain regardless of langversion.
let extensionEta (h: Holder) = Func<int, int, int>(fun a b -> h.Combine(a, b))
Loading
Loading