Skip to content
1 change: 0 additions & 1 deletion docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 12 additions & 2 deletions src/Compiler/Checking/PostInferenceChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 5 additions & 16 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//----------------------------------------------------------------------------
Expand Down Expand Up @@ -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.
///
Expand Down
6 changes: 3 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,114 @@ type C() =
|> withReferences [csharpBaseClass]
|> compile
|> shouldSucceed

[<FSharp.Test.FactForNETCOREAPP>]
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

[<Child; Child>]
type C() = class end
"""
|> withReferences [csharpLib]
|> compile
|> shouldSucceed

[<FSharp.Test.FactForNETCOREAPP>]
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

[<Child; Child>]
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.")

[<FSharp.Test.FactForNETCOREAPP>]
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

[<Leaf; Leaf>]
type C() = class end
"""
|> withReferences [csharpLib]
|> compile
|> shouldSucceed

[<FSharp.Test.FactForNETCOREAPP>]
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

[<Child; Child>]
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.")

[<FSharp.Test.FactForNETCOREAPP>]
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()

[<Child; Child>]
type C() = class end
"""
|> withReferences [csharpLib]
|> compile
|> shouldSucceed
Loading