From a676432ac2c19006314daa71d27cda4f8c70a551 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Tue, 17 Feb 2026 10:23:34 -0500 Subject: [PATCH 1/3] Fix AttributeUsage.AllowMultiple not inherited for C#-defined attributes (#17107) The F# compiler was not walking the inheritance chain for IL-imported (C#) attribute types when checking AllowMultiple. The supersOfTyconRef function only used tcaug_super, which is not populated for IL types. This fix parameterizes TryFindAttributeUsageAttribute with a getSuper resolver function. The caller in PostInferenceChecks now passes a resolver using GetSuperTypeOfType, which correctly handles both F# and IL-imported types via ILTypeDef.Extends. --- src/Compiler/Checking/PostInferenceChecks.fs | 11 +- src/Compiler/TypedTree/TypedTreeOps.fs | 17 +-- src/Compiler/TypedTree/TypedTreeOps.fsi | 5 +- .../Language/AttributeCheckingTests.fs | 111 ++++++++++++++++++ 4 files changed, 133 insertions(+), 11 deletions(-) diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 16b29b71ab6..e4f868e3960 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -2037,7 +2037,16 @@ and CheckAttribs cenv env (attribs: Attribs) = |> Seq.map fst |> Seq.toList // Filter for allowMultiple = false - |> List.filter (fun (tcref, _, m) -> TryFindAttributeUsageAttribute cenv.g m tcref <> Some true) + |> List.filter (fun (tcref, _, m) -> + let getSuper (tcref: TyconRef) = + let ty = generalizedTyconRef cenv.g tcref + match GetSuperTypeOfType cenv.g cenv.amap m ty with + | Some superTy -> + match tryTcrefOfAppTy cenv.g superTy with + | ValueSome sup -> Some sup + | ValueNone -> None + | None -> None + TryFindAttributeUsageAttribute cenv.g m getSuper tcref <> Some true) if cenv.reportErrors then for tcref, _, m in duplicates do diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 89985c45ce5..0362dcec357 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -3489,12 +3489,12 @@ let superOfTycon (g: TcGlobals) (tycon: Tycon) = | None -> g.obj_ty_noNulls | Some ty -> ty -/// walk a TyconRef's inheritance tree, yielding any parent types as an array -let supersOfTyconRef (tcref: TyconRef) = +/// Walk a TyconRef's inheritance tree using the provided super-type resolver, yielding parent types as an array. +let supersOfTyconRefWith (getSuper: TyconRef -> TyconRef option) (tcref: TyconRef) = tcref |> Array.unfold (fun tcref -> - match tcref.TypeContents.tcaug_super with - | Some (TType_app(sup, _, _)) -> Some(sup, sup) - | _ -> None) + match getSuper tcref with + | Some sup -> Some(sup, sup) + | None -> None) //---------------------------------------------------------------------------- // Detect attributes @@ -3629,10 +3629,11 @@ let TryFindTyconRefBoolAttribute g m attribSpec tcref = | [ Some (:? bool as v : obj) ], _ -> Some v | _ -> None) -/// Try to find the resolved attributeusage for an type by walking its inheritance tree and picking the correct attribute usage value -let TryFindAttributeUsageAttribute g m tcref = +/// Try to find the resolved AttributeUsage for a type by walking its inheritance tree and picking the correct attribute usage value. +/// The getSuper function is used to resolve the super-type of each type in the chain, allowing correct handling of both F# and IL-imported types. +let TryFindAttributeUsageAttribute g m (getSuper: TyconRef -> TyconRef option) tcref = [| yield tcref - yield! supersOfTyconRef tcref |] + yield! supersOfTyconRefWith getSuper tcref |] |> Array.tryPick (fun tcref -> TryBindTyconRefAttribute g m g.attrib_AttributeUsageAttribute tcref (fun (_, named) -> named |> List.tryPick (function "AllowMultiple", _, _, ILAttribElem.Bool res -> Some res | _ -> None)) diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index e5bff312024..589dcb5d0e1 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -2400,8 +2400,9 @@ val TyconRefHasAttribute: TcGlobals -> range -> BuiltinAttribInfo -> TyconRef -> /// Try to find an attribute with a specific full name on a type definition val TyconRefHasAttributeByName: range -> string -> TyconRef -> bool -/// Try to find the AttributeUsage attribute, looking for the value of the AllowMultiple named parameter -val TryFindAttributeUsageAttribute: TcGlobals -> range -> TyconRef -> bool option +/// Try to find the AttributeUsage attribute, looking for the value of the AllowMultiple named parameter. +/// The getSuper function is used to walk the inheritance chain for both F# and IL-imported types. +val TryFindAttributeUsageAttribute: TcGlobals -> range -> (TyconRef -> TyconRef option) -> TyconRef -> bool option #if !NO_TYPEPROVIDERS /// returns Some(assemblyName) for success diff --git a/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs index d91997b68f0..898706bca5c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs @@ -77,3 +77,114 @@ type C() = |> withReferences [csharpBaseClass] |> compile |> shouldSucceed + + [] + let ``C# attribute subclass inherits AllowMultiple true from base`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed + + [] + let ``C# attribute subclass inherits AllowMultiple false from base`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public class BaseAttribute : Attribute { } + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 429, Line 4, Col 10, Line 4, Col 15, "The attribute type 'ChildAttribute' has 'AllowMultiple=false'. Multiple instances of this attribute cannot be attached to a single language element.") + + [] + let ``C# attribute multi-level inheritance inherits AllowMultiple true`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + public class MiddleAttribute : BaseAttribute { } + public class LeafAttribute : MiddleAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed + + [] + let ``C# attribute subclass with own AttributeUsage overrides base AllowMultiple`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 429, Line 4, Col 10, Line 4, Col 15, "The attribute type 'ChildAttribute' has 'AllowMultiple=false'. Multiple instances of this attribute cannot be attached to a single language element.") + + [] + let ``F# attribute subclass of C# base inherits AllowMultiple true`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +type ChildAttribute() = inherit BaseAttribute() + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed From 64a68997603f0d1a6029279106edcb32201ec1db Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Tue, 17 Feb 2026 11:28:23 -0500 Subject: [PATCH 2/3] Add release note for #17107 AllowMultiple inheritance fix --- docs/release-notes/.FSharp.Compiler.Service/10.0.300.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index 6a30bf55364..f19723387e1 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -14,6 +14,7 @@ * Fix FS0229 B-stream misalignment when reading metadata from assemblies compiled with LangVersion < 9.0, introduced by [#17706](https://github.com/dotnet/fsharp/pull/17706). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * Fix FS3356 false positive for instance extension members with same name on different types, introduced by [#18821](https://github.com/dotnet/fsharp/pull/18821). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) +* Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107)) ### Added * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300)) From a4bcb2365fd5989c84da608d467f48df1de2089a Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Tue, 17 Feb 2026 11:28:23 -0500 Subject: [PATCH 3/3] Add release note for #17107 AllowMultiple inheritance fix # Conflicts: # docs/release-notes/.FSharp.Compiler.Service/10.0.300.md --- docs/release-notes/.FSharp.Compiler.Service/10.0.300.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index f19723387e1..86ac43d9c73 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -14,7 +14,7 @@ * Fix FS0229 B-stream misalignment when reading metadata from assemblies compiled with LangVersion < 9.0, introduced by [#17706](https://github.com/dotnet/fsharp/pull/17706). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * Fix FS3356 false positive for instance extension members with same name on different types, introduced by [#18821](https://github.com/dotnet/fsharp/pull/18821). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) -* Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107)) +* Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107), [PR #19315](https://github.com/dotnet/fsharp/pull/19315)) ### Added * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300))