From fcf45fa0be61bf481a7970f598d674b9eaaef99c Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 26 May 2026 12:48:03 +0200 Subject: [PATCH 1/3] Fix delegate Invoke classification in declaration (#16982) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Service/SemanticClassification.fs | 13 +++- .../SemanticClassificationRegressions.fs | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Service/SemanticClassification.fs b/src/Compiler/Service/SemanticClassification.fs index 8fe61e20ddb..066ec2fb41a 100644 --- a/src/Compiler/Service/SemanticClassification.fs +++ b/src/Compiler/Service/SemanticClassification.fs @@ -292,7 +292,18 @@ module TcResolutionsExtensions = match minfos with | [] -> add m SemanticClassificationType.Method | _ -> - if + let isSynthesizedDelegateMemberInDecl = + minfos + |> List.forall (fun minfo -> + let name = minfo.LogicalName + + (name = "Invoke" || name = "BeginInvoke" || name = "EndInvoke") + && minfo.ApparentEnclosingTyconRef.IsFSharpDelegateTycon + && rangeContainsRange minfo.ApparentEnclosingTyconRef.Range m) + + if isSynthesizedDelegateMemberInDecl then + () + elif minfos |> List.forall (fun minfo -> minfo.IsExtensionMember || minfo.IsCSharpStyleExtensionMember) then diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SemanticClassificationRegressions.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SemanticClassificationRegressions.fs index 8af90a38e5d..04093eea56a 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SemanticClassificationRegressions.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SemanticClassificationRegressions.fs @@ -16,6 +16,12 @@ let getClassifications (source: string) = let checkResults = getTypeCheckResult results checkResults.GetSemanticClassification(None, RelatedSymbolUseKind.All) +/// Extract the source substring covered by a classification item's range (single-line ranges). +let private substringOfRange (source: string) (r: Range) = + let lines = source.Replace("\r\n", "\n").Split('\n') + let line = lines[r.StartLine - 1] + line.Substring(r.StartColumn, r.EndColumn - r.StartColumn) + /// (#15290 regression) Copy-and-update record fields must not be classified as type names. /// Before the fix, Item.Types was registered with mWholeExpr and ItemOccurrence.Use, producing /// a wide type classification that overshadowed the correct RecordField classification. @@ -136,3 +142,61 @@ type Animal = (7, 2, 8) // s.IsCircle && s.IsSquare — two on same line (12, 1, 7) // t.IsIdent — RequireQualifiedAccess (17, 1, 5) ] // this.IsCat — self-referential member + +/// (#16982) Delegate `Invoke` synthesized in a delegate declaration must not be classified as Method. +[] +let ``Delegate Invoke in declaration not classified as method`` () = + let source = """ +type MyDelegate = delegate of int -> string +""" + let classifications = getClassifications source + let invokeMethods = + classifications + |> Array.filter (fun c -> + c.Type = SemanticClassificationType.Method + && substringOfRange source c.Range = "Invoke") + Assert.Empty(invokeMethods) + +/// (#16982) Negative: at a real call site, `Invoke` must still classify as Method. +[] +let ``Delegate Invoke at call site classified as method`` () = + let source = """ +type MyDelegate = delegate of int -> string +let d = MyDelegate(fun i -> string i) +let result = d.Invoke(42) +""" + let classifications = getClassifications source + let invokeCallSite = + classifications + |> Array.filter (fun c -> + c.Type = SemanticClassificationType.Method && c.Range.StartLine = 4) + Assert.NotEmpty(invokeCallSite) + +/// (#16982) Generic delegate variant. +[] +let ``Generic delegate Invoke not classified as method in decl`` () = + let source = """ +type MyGenDelegate<'T> = delegate of 'T -> 'T +""" + let classifications = getClassifications source + let invokeMethods = + classifications + |> Array.filter (fun c -> + c.Type = SemanticClassificationType.Method + && substringOfRange source c.Range = "Invoke") + Assert.Empty(invokeMethods) + +/// (#16982) The synthesized async-pattern members `BeginInvoke`/`EndInvoke` must also be suppressed in the declaration. +[] +let ``BeginInvoke EndInvoke also not classified in decl`` () = + let source = """ +type MyDelegate = delegate of int -> string +""" + let classifications = getClassifications source + let asyncInvokeMethods = + classifications + |> Array.filter (fun c -> + c.Type = SemanticClassificationType.Method + && (let text = substringOfRange source c.Range + text = "BeginInvoke" || text = "EndInvoke")) + Assert.Empty(asyncInvokeMethods) From eb0b699af91b598f2dd724aa1baeb8ffe95cff95 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 26 May 2026 14:01:59 +0200 Subject: [PATCH 2/3] Apply remaining changes --- .executor-pid | 1 + 1 file changed, 1 insertion(+) create mode 100644 .executor-pid diff --git a/.executor-pid b/.executor-pid new file mode 100644 index 00000000000..095c4f333bd --- /dev/null +++ b/.executor-pid @@ -0,0 +1 @@ +27708 \ No newline at end of file From 30640443947fd6fae145b1280107ffb2a6b1e5f2 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 26 May 2026 14:30:31 +0200 Subject: [PATCH 3/3] Remove executor artifact --- .executor-pid | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .executor-pid diff --git a/.executor-pid b/.executor-pid deleted file mode 100644 index 095c4f333bd..00000000000 --- a/.executor-pid +++ /dev/null @@ -1 +0,0 @@ -27708 \ No newline at end of file