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 7a8279aac29..b71362d0fec 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -25,7 +25,6 @@ * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) * Improve static compilation of state machines. ([PR #19297](https://github.com/dotnet/fsharp/pull/19297)) * Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions. [PR #19347](https://github.com/dotnet/fsharp/pull/19347) - ### Added * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300)) * Type checker: recover on argument/overload checking ([PR #19314](https://github.com/dotnet/fsharp/pull/19314)) 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 d5c2087765e..b996a1b137c 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -17,6 +17,7 @@ * Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403)) * 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 `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..3b40ab34aeb 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -2036,8 +2036,18 @@ 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.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 7da5a63eef2..8f0c36b72e1 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -3514,13 +3514,6 @@ 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) = - tcref |> Array.unfold (fun tcref -> - match tcref.TypeContents.tcaug_super with - | Some (TType_app(sup, _, _)) -> Some(sup, sup) - | _ -> None) - //---------------------------------------------------------------------------- // Detect attributes //---------------------------------------------------------------------------- @@ -4174,16 +4167,12 @@ 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 +/// 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.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 42c0d0b1be4..e0e522fae48 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -2503,6 +2503,9 @@ val TryFindTyconRefStringAttributeFast: /// Try to find a specific attribute on a type definition, where the attribute accepts a bool argument. val TryFindTyconRefBoolAttribute: TcGlobals -> range -> BuiltinAttribInfo -> TyconRef -> bool option +/// Try to find the AllowMultiple value of the AttributeUsage attribute on a type definition. +val TryFindAttributeUsageAttribute: TcGlobals -> range -> TyconRef -> bool option + /// Try to find a specific attribute on a type definition val TyconRefHasAttribute: TcGlobals -> range -> BuiltinAttribInfo -> TyconRef -> bool @@ -2515,9 +2518,6 @@ val TyconRefHasWellKnownAttribute: g: TcGlobals -> flag: WellKnownILAttributes - /// 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 -val TryFindAttributeUsageAttribute: TcGlobals -> range -> TyconRef -> bool option - #if !NO_TYPEPROVIDERS /// returns Some(assemblyName) for success val TryDecodeTypeProviderAssemblyAttr: ILAttribute -> (string | null) option 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