diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index d2d59c319a1..d73f72f85f4 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -19,6 +19,7 @@ * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408))) * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440)) * Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506)) +* 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 diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 09e95d5bbf1..7924394c743 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -2036,8 +2036,19 @@ and CheckAttribs cenv env (attribs: Attribs) = |> Seq.filter (fun (_, count) -> count > 1) |> Seq.map fst |> Seq.toList - // Filter for allowMultiple = false - |> List.filter (fun (tcref, _, m) -> TryFindAttributeUsageAttribute cenv.g m tcref <> Some true) + // Filter for allowMultiple = false, walking the inheritance chain to find AttributeUsage + |> List.filter (fun (tcref, _, m) -> + let rec allowsMultiple (tcref: TyconRef) = + match TryFindAttributeUsageAttribute cenv.g m tcref with + | Some res -> res + | None -> + generalizedTyconRef cenv.g tcref + |> GetSuperTypeOfType cenv.g cenv.amap m + |> Option.bind (tryTcrefOfAppTy cenv.g >> ValueOption.toOption) + |> Option.map allowsMultiple + |> Option.defaultValue false + + not (allowsMultiple tcref)) if cenv.reportErrors then for tcref, _, m in duplicates do diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs index 488cc8e2521..c4bc3ae750c 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs @@ -812,30 +812,28 @@ module internal AttributeHelpers = | [ 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 + /// Try to find the AllowMultiple value of the AttributeUsage attribute on a type definition. let TryFindAttributeUsageAttribute g m tcref = - [| yield tcref; yield! supersOfTyconRef 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)) - (fun (Attrib(_, _, _, named, _, _, _)) -> - named - |> List.tryPick (function - | AttribNamedArg("AllowMultiple", _, _, AttribBoolArg res) -> Some res - | _ -> None)) - (fun (_, named) -> - named - |> List.tryPick (function - | "AllowMultiple", Some(:? bool as res: obj) -> Some res - | _ -> None))) + TryBindTyconRefAttribute + g + m + g.attrib_AttributeUsageAttribute + tcref + (fun (_, named) -> + named + |> List.tryPick (function + | "AllowMultiple", _, _, ILAttribElem.Bool res -> Some res + | _ -> None)) + (fun (Attrib(_, _, _, named, _, _, _)) -> + named + |> List.tryPick (function + | AttribNamedArg("AllowMultiple", _, _, AttribBoolArg res) -> Some res + | _ -> None)) + (fun (_, named) -> + named + |> List.tryPick (function + | "AllowMultiple", Some(:? bool as res: obj) -> Some res + | _ -> None)) /// Try to find a specific attribute on a type definition, where the attribute accepts a string argument. /// diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi index 76096c3d65e..8fc3910354a 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi @@ -194,7 +194,7 @@ module internal AttributeHelpers = /// Check if a TyconRef has AllowNullLiteralAttribute, returning Some true/Some false/None. val TyconRefAllowsNull: g: TcGlobals -> tcref: TyconRef -> bool option - /// Try to find the AttributeUsage attribute, looking for the value of the AllowMultiple named parameter + /// Try to find the AllowMultiple value of the AttributeUsage attribute on a type definition. val TryFindAttributeUsageAttribute: TcGlobals -> range -> TyconRef -> bool option val (|AttribBitwiseOrExpr|_|): TcGlobals -> Expr -> (Expr * Expr) voption diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs index 2c84aeb7b76..92649150c9d 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs @@ -1542,11 +1542,3 @@ module internal MemberRepresentation = match tycon.TypeContents.tcaug_super with | 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) = - tcref - |> Array.unfold (fun tcref -> - match tcref.TypeContents.tcaug_super with - | Some(TType_app(sup, _, _)) -> Some(sup, sup) - | _ -> None) diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi index e7e37aef6e3..1787715a6e3 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi @@ -357,9 +357,6 @@ module internal MemberRepresentation = val superOfTycon: TcGlobals -> Tycon -> TType - /// walk a TyconRef's inheritance tree, yielding any parent types as an array - val supersOfTyconRef: TyconRef -> TyconRef array - val GetTraitConstraintInfosOfTypars: TcGlobals -> Typars -> TraitConstraintInfo list val GetTraitWitnessInfosOfTypars: TcGlobals -> numParentTypars: int -> typars: Typars -> TraitWitnessInfos 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