Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0cb12fd
Initial plan for issue
Copilot May 21, 2025
fe08ab0
Fix CLIEvent IsEvent property and XmlDocSig prefix
Copilot May 21, 2025
a7c9a7c
Revert changes to CLIEvent handling to fix test failures
Copilot May 22, 2025
df2821a
Fix CLIEvent IsEvent property and XmlDocSig prefix with test adjustments
Copilot May 22, 2025
ea75396
Merge branch 'main' into copilot/fix-10273
T-Gro May 22, 2025
c7bb6c8
Fix CLIEvent attributes handling in tests while preserving event sema…
Copilot May 23, 2025
416e2c9
Move CLIEvent XmlDocSig prefix handling to GetXmlDocSigOfProp
Copilot May 23, 2025
f431be3
Merge branch 'main' into copilot/fix-10273
T-Gro May 29, 2025
f67e7dd
Generate correct XmlDocSig prefix at source instead of patching
Copilot May 29, 2025
d383cc0
Merge branch 'main' into copilot/fix-10273
T-Gro Jul 22, 2025
8a2d2cb
Fix CLIEvent properties to be recognized as events in Symbol API
Copilot Jul 22, 2025
dd901c7
Merge branch 'main' into copilot/fix-10273
T-Gro Jul 30, 2025
07f10f2
Update test expectations for CLIEvent properties now being recognized…
Copilot Jul 30, 2025
58afe62
Fix test expectations for CLIEvent properties now being recognized as…
Copilot Aug 1, 2025
079024f
Address review feedback: consolidate FSProp pattern matching and move…
Copilot Aug 1, 2025
d8d703d
Fix test expectations for CLIEvent properties now being recognized as…
Copilot Aug 1, 2025
f9eb1e5
Merge branch 'main' into copilot/fix-10273
T-Gro Sep 8, 2025
825cc43
Merge branch 'main' into copilot/fix-10273
T-Gro Sep 18, 2025
1fbc0a8
Fix test expectations for CLIEvent properties recognized as events
Copilot Sep 19, 2025
ee42dcb
Merge branch 'main' into copilot/fix-10273
T-Gro Sep 19, 2025
7f9885d
Merge branch 'main' into copilot/fix-10273
T-Gro Jan 21, 2026
1f620a5
Use CompileAsEvent instead of direct HasFSharpAttribute in XmlDocSig
T-Gro Jan 21, 2026
ca9804a
Fix reviewer feedback: use {caret} marker pattern and remove redundan…
T-Gro Jan 21, 2026
839689d
Add release notes for CLIEvent IsEvent fix #10273
T-Gro Jan 21, 2026
06695ad
clean
T-Gro Jan 22, 2026
a648124
Merge branch 'main' into copilot/fix-10273
T-Gro Jan 22, 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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671))
* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048))
* Fix object expressions in struct types generating invalid IL with byref fields causing TypeLoadException at runtime. ([Issue #19068](https://github.com/dotnet/fsharp/issues/19068), [PR #19070](https://github.com/dotnet/fsharp/pull/19070))
* Fix CLIEvent properties are now correctly recognized as events: `IsEvent` returns `true` and `XmlDocSig` uses `E:` prefix instead of `P:`. ([Issue #10273](https://github.com/dotnet/fsharp/issues/10273), [PR #18584](https://github.com/dotnet/fsharp/pull/18584))

### Added

Expand Down
5 changes: 4 additions & 1 deletion src/Compiler/Checking/infos.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2047,7 +2047,10 @@ type PropInfo =
/// Indicates if this is an F# property compiled as a CLI event, e.g. a [<CLIEvent>] property.
member x.IsFSharpEventProperty =
match x with
| FSProp(g, _, Some vref, None) -> vref.IsFSharpEventProperty g
| FSProp(g, _, getterOpt, setterOpt) ->
match getterOpt, setterOpt with
| Some vref, _ | None, Some vref -> vref.IsFSharpEventProperty g
| None, None -> false
#if !NO_TYPEPROVIDERS
| ProvidedProp _ -> false
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Symbols/Symbols.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,8 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) =
member _.IsEvent =
match d with
| E _ -> true
| P p when p.IsFSharpEventProperty -> true
| V v when v.IsFSharpEventProperty cenv.g -> true
| _ -> false

member _.EventForFSharpProperty =
Expand Down
7 changes: 4 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3527,6 +3527,7 @@ let TryFindFSharpAttributeOpt g tref attrs = match tref with None -> None | Some

let HasFSharpAttributeOpt g trefOpt attrs = match trefOpt with Some tref -> List.exists (IsMatchingFSharpAttribute g tref) attrs | _ -> false
let IsMatchingFSharpAttributeOpt g attrOpt (Attrib(tcref2, _, _, _, _, _, _)) = match attrOpt with Some (AttribInfo(_, tcref)) -> tyconRefEq g tcref tcref2 | _ -> false
let CompileAsEvent g attrs = HasFSharpAttribute g g.attrib_CLIEventAttribute attrs

[<return: Struct>]
let (|ExtractAttribNamedArg|_|) nm args =
Expand Down Expand Up @@ -9074,7 +9075,9 @@ let XmlDocSigOfVal g full path (v: Val) =
| SynMemberKind.Member -> "M:", v.CompiledName g.CompilerGlobalState
| SynMemberKind.PropertyGetSet
| SynMemberKind.PropertySet
| SynMemberKind.PropertyGet -> "P:", v.PropertyName
| SynMemberKind.PropertyGet ->
let prefix = if CompileAsEvent g v.Attribs then "E:" else "P:"
prefix, v.PropertyName

let path = if v.HasDeclaringEntity then prependPath path v.DeclaringEntity.CompiledName else path

Expand Down Expand Up @@ -9506,8 +9509,6 @@ let ModuleNameIsMangled g attrs =
| Some flags -> ((flags &&& enum_CompilationRepresentationAttribute_ModuleSuffix) <> 0)
| _ -> false

let CompileAsEvent g attrs = HasFSharpAttribute g g.attrib_CLIEventAttribute attrs

let MemberIsCompiledAsInstance g parent isExtensionMember (membInfo: ValMemberInfo) attrs =
// All extension members are compiled as static members
if isExtensionMember then
Expand Down
4 changes: 2 additions & 2 deletions tests/FSharp.Compiler.Service.Tests/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,11 @@ let attribsOfSymbol (symbol: FSharpSymbol) =
if v.IsDispatchSlot then yield "slot"
if v.IsModuleValueOrMember && not v.IsMember then yield "val"
if v.IsMember then yield "member"
if v.IsProperty then yield "prop"
if v.IsProperty && not v.IsEvent then yield "prop"
if v.IsEvent then yield "event"
if v.IsExtensionMember then yield "extmem"
if v.IsPropertyGetterMethod then yield "getter"
if v.IsPropertySetterMethod then yield "setter"
if v.IsEvent then yield "event"
if v.EventForFSharpProperty.IsSome then yield "clievent"
if v.IsEventAddMethod then yield "add"
if v.IsEventRemoveMethod then yield "remove"
Expand Down
52 changes: 26 additions & 26 deletions tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ let ``Test project3 all symbols in signature`` () =
("member set_InterfacePropertySet", ["slot"; "member"; "setter"]);
("property InterfacePropertySet", ["slot"; "member"; "prop"]);
("property InterfaceProperty", ["slot"; "member"; "prop"]);
("property InterfaceEvent", ["slot"; "member"; "prop"; "clievent"]);
("event InterfaceEvent", ["slot"; "member"; "event"; "clievent"]);
("CFoo", ["class"]);
("member .ctor", ["member"; "ctor"]);
("member AbstractClassMethod", ["slot"; "member"]);
Expand All @@ -1002,7 +1002,7 @@ let ``Test project3 all symbols in signature`` () =
("member set_AbstractClassPropertySet", ["slot"; "member"; "setter"]);
("property AbstractClassPropertySet", ["slot"; "member"; "prop"]);
("property AbstractClassProperty", ["slot"; "member"; "prop"]);
("property AbstractClassEvent", ["slot"; "member"; "prop"; "clievent"]);
("event AbstractClassEvent", ["slot"; "member"; "event"; "clievent"]);
("CBaseFoo", ["class"]); ("member .ctor", ["member"; "ctor"]);
("member BaseClassMethod", ["slot"; "member"]);
("member BaseClassMethod", ["member"; "overridemem"]);
Expand All @@ -1020,8 +1020,8 @@ let ``Test project3 all symbols in signature`` () =
("property BaseClassPropertySet", ["slot"; "member"; "prop"]);
("property BaseClassProperty", ["member"; "prop"; "overridemem"]);
("property BaseClassProperty", ["slot"; "member"; "prop"]);
("property BaseClassEvent", ["member"; "prop"; "overridemem"]);
("property BaseClassEvent", ["slot"; "member"; "prop"]);
("event BaseClassEvent", ["member"; "event"; "overridemem"]);
("event BaseClassEvent", ["slot"; "member"; "event"]);
("IFooImpl", ["class"]); ("member .ctor", ["member"; "ctor"]);
("member InterfaceMethod", ["member"; "overridemem"; "intfmem"]);
("member add_InterfaceEvent", ["member"; "overridemem"; "intfmem"]);
Expand All @@ -1038,7 +1038,7 @@ let ``Test project3 all symbols in signature`` () =
("member set_AbstractClassPropertySet", ["member"; "setter"; "overridemem"]);
("property AbstractClassPropertySet", ["member"; "prop"; "overridemem"]);
("property AbstractClassProperty", ["member"; "prop"; "overridemem"]);
("property AbstractClassEvent", ["member"; "prop"; "clievent"; "overridemem"]);
("event AbstractClassEvent", ["member"; "event"; "clievent"; "overridemem"]);
("CBaseFooImpl", ["class"]); ("member .ctor", ["member"; "ctor"]);
("member BaseClassMethod", ["member"; "overridemem"]);
("member add_BaseClassEvent", ["member"; "add"; "overridemem"]);
Expand All @@ -1048,7 +1048,7 @@ let ``Test project3 all symbols in signature`` () =
("member set_BaseClassPropertySet", ["member"; "setter"; "overridemem"]);
("property BaseClassPropertySet", ["member"; "prop"; "overridemem"]);
("property BaseClassProperty", ["member"; "prop"; "overridemem"]);
("property BaseClassEvent", ["member"; "prop"; "clievent"; "overridemem"])]
("event BaseClassEvent", ["member"; "event"; "clievent"; "overridemem"])]
|> List.iter (fun x ->
if results |> List.exists (fun y -> x = y) |> not then
failwithf "%A does not exist in the collection." x
Expand Down Expand Up @@ -1122,10 +1122,10 @@ let ``Test project3 all uses of all signature symbols`` () =
("file1", ((61, 20), (61, 37)), ["override"], ["slot"; "member"; "prop"]);
("file1", ((76, 23), (76, 44)), [], ["slot"; "member"; "prop"]);
("file1", ((34, 20), (34, 37)), ["override"], ["slot"; "member"; "prop"])]);
("property InterfaceEvent",
[("file1", ((8, 13), (8, 27)), ["defn"], ["slot"; "member"; "prop"; "clievent"]);
("file1", ((65, 20), (65, 34)), ["override"], ["slot"; "member"; "prop"; "clievent"]);
("file1", ((38, 20), (38, 34)), ["override"], ["slot"; "member"; "prop"; "clievent"])]);
("event InterfaceEvent",
[("file1", ((8, 13), (8, 27)), ["defn"], ["slot"; "member"; "event"; "clievent"]);
("file1", ((65, 20), (65, 34)), ["override"], ["slot"; "member"; "event"; "clievent"]);
("file1", ((38, 20), (38, 34)), ["override"], ["slot"; "member"; "event"; "clievent"])]);
("CFoo",
[("file1", ((11, 5), (11, 9)), ["defn"], ["class"]);
("file1", ((41, 12), (41, 16)), ["type"], ["class"]);
Expand Down Expand Up @@ -1170,10 +1170,10 @@ let ``Test project3 all uses of all signature symbols`` () =
[("file1", ((12, 13), (12, 34)), ["defn"], ["slot"; "member"; "prop"]);
("file1", ((70, 22), (70, 43)), ["override"], ["slot"; "member"; "prop"]);
("file1", ((43, 18), (43, 39)), ["override"], ["slot"; "member"; "prop"])]);
("property AbstractClassEvent",
[("file1", ((16, 13), (16, 31)), ["defn"], ["slot"; "member"; "prop"; "clievent"]);
("file1", ((74, 22), (74, 40)), ["override"], ["slot"; "member"; "prop"; "clievent"]);
("file1", ((47, 18), (47, 36)), ["override"], ["slot"; "member"; "prop"; "clievent"])]);
("event AbstractClassEvent",
[("file1", ((16, 13), (16, 31)), ["defn"], ["slot"; "member"; "event"; "clievent"]);
("file1", ((74, 22), (74, 40)), ["override"], ["slot"; "member"; "event"; "clievent"]);
("file1", ((47, 18), (47, 36)), ["override"], ["slot"; "member"; "event"; "clievent"])]);
("CBaseFoo",
[("file1", ((18, 5), (18, 13)), ["defn"], ["class"]);
("file1", ((50, 12), (50, 20)), ["type"], ["class"]);
Expand Down Expand Up @@ -1230,12 +1230,12 @@ let ``Test project3 all uses of all signature symbols`` () =
[("file1", ((20, 13), (20, 30)), ["defn"], ["slot"; "member"; "prop"]);
("file1", ((25, 15), (25, 32)), ["override"], ["slot"; "member"; "prop"]);
("file1", ((52, 18), (52, 35)), ["override"], ["slot"; "member"; "prop"])]);
("property BaseClassEvent",
[("file1", ((29, 15), (29, 29)), ["defn"], ["member"; "prop"; "overridemem"])]);
("property BaseClassEvent",
[("file1", ((24, 13), (24, 27)), ["defn"], ["slot"; "member"; "prop"]);
("file1", ((29, 15), (29, 29)), ["override"], ["slot"; "member"; "prop"]);
("file1", ((56, 18), (56, 32)), ["override"], ["slot"; "member"; "prop"])]);
("event BaseClassEvent",
[("file1", ((29, 15), (29, 29)), ["defn"], ["member"; "event"; "overridemem"])]);
("event BaseClassEvent",
[("file1", ((24, 13), (24, 27)), ["defn"], ["slot"; "member"; "event"]);
("file1", ((29, 15), (29, 29)), ["override"], ["slot"; "member"; "event"]);
("file1", ((56, 18), (56, 32)), ["override"], ["slot"; "member"; "event"])]);
("IFooImpl", [("file1", ((31, 5), (31, 13)), ["defn"], ["class"])]);
("member .ctor", [("file1", ((31, 5), (31, 13)), ["defn"], ["member"; "ctor"])]);
("member InterfaceMethod",
Expand Down Expand Up @@ -1268,8 +1268,8 @@ let ``Test project3 all uses of all signature symbols`` () =
[("file1", ((44, 18), (44, 42)), ["defn"], ["member"; "prop"; "overridemem"])]);
("property AbstractClassProperty",
[("file1", ((43, 18), (43, 39)), ["defn"], ["member"; "prop"; "overridemem"])]);
("property AbstractClassEvent",
[("file1", ((47, 18), (47, 36)), ["defn"], ["member"; "prop"; "clievent"; "overridemem"])]);
("event AbstractClassEvent",
[("file1", ((47, 18), (47, 36)), ["defn"], ["member"; "event"; "clievent"; "overridemem"])]);
("CBaseFooImpl", [("file1", ((49, 5), (49, 17)), ["defn"], ["class"])]);
("member .ctor", [("file1", ((49, 5), (49, 17)), ["defn"], ["member"; "ctor"])]);
("member BaseClassMethod",
Expand All @@ -1288,8 +1288,8 @@ let ``Test project3 all uses of all signature symbols`` () =
[("file1", ((53, 18), (53, 38)), ["defn"], ["member"; "prop"; "overridemem"])]);
("property BaseClassProperty",
[("file1", ((52, 18), (52, 35)), ["defn"], ["member"; "prop"; "overridemem"])]);
("property BaseClassEvent",
[("file1", ((56, 18), (56, 32)), ["defn"], ["member"; "prop"; "clievent"; "overridemem"])])]
("event BaseClassEvent",
[("file1", ((56, 18), (56, 32)), ["defn"], ["member"; "event"; "clievent"; "overridemem"])])]
set allUsesOfAllSymbols - set expected |> shouldEqual Set.empty
set expected - set allUsesOfAllSymbols |> shouldEqual Set.empty
(set expected = set allUsesOfAllSymbols) |> shouldEqual true
Expand Down Expand Up @@ -4031,13 +4031,13 @@ let ``Test project28 all symbols in signature`` () =
("FSharpMemberOrFunctionOrValue", "TestEvent2", "M:M.XmlDocSigTest.TestEvent2(System.Object)");
("FSharpMemberOrFunctionOrValue", "add_AnEvent", "M:M.XmlDocSigTest.add_AnEvent(Microsoft.FSharp.Control.FSharpHandler{System.Tuple{M.XmlDocSigTest,System.Object}})");
("FSharpMemberOrFunctionOrValue", "AProperty", "P:M.XmlDocSigTest.AProperty");
("FSharpMemberOrFunctionOrValue", "AnEvent", "P:M.XmlDocSigTest.AnEvent");
("FSharpMemberOrFunctionOrValue", "AnEvent", "E:M.XmlDocSigTest.AnEvent");
("FSharpMemberOrFunctionOrValue", "AnotherEvent", "P:M.XmlDocSigTest.AnotherEvent");
("FSharpMemberOrFunctionOrValue", "AnotherProperty", "P:M.XmlDocSigTest.AnotherProperty");
("FSharpMemberOrFunctionOrValue", "remove_AnEvent", "M:M.XmlDocSigTest.remove_AnEvent(Microsoft.FSharp.Control.FSharpHandler{System.Tuple{M.XmlDocSigTest,System.Object}})");
("FSharpMemberOrFunctionOrValue", "AnotherProperty", "P:M.XmlDocSigTest.AnotherProperty");
("FSharpMemberOrFunctionOrValue", "AnotherEvent", "P:M.XmlDocSigTest.AnotherEvent");
("FSharpMemberOrFunctionOrValue", "AnEvent", "P:M.XmlDocSigTest.AnEvent");
("FSharpMemberOrFunctionOrValue", "AnEvent", "E:M.XmlDocSigTest.AnEvent");
("FSharpMemberOrFunctionOrValue", "AProperty", "P:M.XmlDocSigTest.AProperty");
("FSharpField", "event1", "P:M.XmlDocSigTest.event1");
("FSharpField", "event2", "P:M.XmlDocSigTest.event2");
Expand Down
13 changes: 13 additions & 0 deletions tests/FSharp.Compiler.Service.Tests/Symbols.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,19 @@ type T() =
)

Assert.False hasPropertySymbols

[<Fact>]
let ``CLIEvent is recognized as event`` () =
let symbolUse = Checker.getSymbolUse """
type T() =
[<CLIEvent>]
member this.Ev{caret}ent = Event<int>().Publish
"""
match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as mfv ->
Assert.True mfv.IsEvent
Assert.StartsWith("E:", mfv.XmlDocSig)
| _ -> failwith "Expected FSharpMemberOrFunctionOrValue"

[<Fact>]
let ``CLIEvent 01 - Synthetic range`` () =
Expand Down
Loading