From 7e20ed7979669253cc01c0a58a23db354484c470 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 13 Apr 2026 12:02:12 +0200 Subject: [PATCH 1/2] Fix internal error when using custom attribute with optional value type argument (#8353) GenAttribArg in IlxGen.fs handles Const.Zero (the default for [] params without []) for System.Object, System.String, and System.Type, but not for primitive value types. This causes an internal error (FS0073) when applying an attribute with an [] value type parameter and no default. Add Const.Zero -> default value mappings for all 12 primitive types valid in custom attributes: bool, sbyte, int16, int32, int64, byte, uint16, uint32, uint64, single, double, char. Fixes #8353 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/CodeGen/IlxGen.fs | 12 ++ .../CustomAttributes/Basic/Basic.fs | 8 ++ .../Basic/OptionalAttributeArgs.fs | 127 ++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs 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 351cd67adcc..c17e953d466 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -44,6 +44,7 @@ * Fix signature generation: `namespace global` header dropped from generated signature. ([Issue #19593](https://github.com/dotnet/fsharp/issues/19593), [PR #19609](https://github.com/dotnet/fsharp/pull/19609)) * Fix signature generation: SRTP constraints use postfix syntax that fails conformance, now uses explicit type param declarations. ([Issue #19594](https://github.com/dotnet/fsharp/issues/19594), [PR #19609](https://github.com/dotnet/fsharp/pull/19609)) * Fix signature generation: type params with special characters missing backtick escaping. ([Issue #19595](https://github.com/dotnet/fsharp/issues/19595), [PR #19609](https://github.com/dotnet/fsharp/pull/19609)) +* Fix internal error when using custom attribute with `[]` value type parameter and no `[]`. ([Issue #8353](https://github.com/dotnet/fsharp/issues/8353), [PR #19484](https://github.com/dotnet/fsharp/pull/19484)) ### Added diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 593b695ca89..93898caeed4 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -10334,6 +10334,18 @@ and GenAttribArg amap g eenv x (ilArgTy: ILType) = | Const.Zero when isobj -> ILAttribElem.Null | Const.Zero when tynm = "System.String" -> ILAttribElem.String None | Const.Zero when tynm = "System.Type" -> ILAttribElem.Type None + | Const.Zero when tynm = "System.Boolean" -> ILAttribElem.Bool false + | Const.Zero when tynm = "System.SByte" -> ILAttribElem.SByte 0y + | Const.Zero when tynm = "System.Int16" -> ILAttribElem.Int16 0s + | Const.Zero when tynm = "System.Int32" -> ILAttribElem.Int32 0 + | Const.Zero when tynm = "System.Int64" -> ILAttribElem.Int64 0L + | Const.Zero when tynm = "System.Byte" -> ILAttribElem.Byte 0uy + | Const.Zero when tynm = "System.UInt16" -> ILAttribElem.UInt16 0us + | Const.Zero when tynm = "System.UInt32" -> ILAttribElem.UInt32 0u + | Const.Zero when tynm = "System.UInt64" -> ILAttribElem.UInt64 0UL + | Const.Zero when tynm = "System.Single" -> ILAttribElem.Single 0.0f + | Const.Zero when tynm = "System.Double" -> ILAttribElem.Double 0.0 + | Const.Zero when tynm = "System.Char" -> ILAttribElem.Char '\000' | Const.String i when isobj || tynm = "System.String" -> ILAttribElem.String(Some i) | _ -> error (InternalError("The type '" + tynm + "' may not be used as a custom attribute value", m)) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs index 2b51fbf11f7..333191c9b4b 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs @@ -303,6 +303,14 @@ module CustomAttributes_Basic = |> verifyCompileAndRun |> shouldSucceed + // SOURCE=OptionalAttributeArgs.fs # OptionalAttributeArgs.fs + // Regression test for https://github.com/dotnet/fsharp/issues/8353 + [] + let ``OptionalAttributeArgs_fs`` compilation = + compilation + |> verifyCompileAndRun + |> shouldSucceed + // SOURCE=W_ReturnType03b.fs SCFLAGS="--test:ErrorRanges" # W_ReturnType03b.fs [] let ``W_ReturnType03b_fs`` compilation = diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs new file mode 100644 index 00000000000..81f9b11a59b --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs @@ -0,0 +1,127 @@ +// #Regression #Conformance #DeclarationElements #Attributes +// Regression test for https://github.com/dotnet/fsharp/issues/8353 +// Verify that custom attributes with [] parameters (no DefaultParameterValue) compile for all value types +// and that the emitted default values are correct at runtime. + +open System +open System.Runtime.InteropServices + +type BoolAttribute(name: string, flag: bool) = + inherit Attribute() + member _.Flag = flag + new([] flag: bool) = BoolAttribute("", flag) + +type IntAttribute(name: string, value: int) = + inherit Attribute() + member _.Value = value + new([] value: int) = IntAttribute("", value) + +type ByteAttribute(name: string, value: byte) = + inherit Attribute() + member _.Value = value + new([] value: byte) = ByteAttribute("", value) + +type SByteAttribute(name: string, value: sbyte) = + inherit Attribute() + member _.Value = value + new([] value: sbyte) = SByteAttribute("", value) + +type Int16Attribute(name: string, value: int16) = + inherit Attribute() + member _.Value = value + new([] value: int16) = Int16Attribute("", value) + +type Int64Attribute(name: string, value: int64) = + inherit Attribute() + member _.Value = value + new([] value: int64) = Int64Attribute("", value) + +type UInt16Attribute(name: string, value: uint16) = + inherit Attribute() + member _.Value = value + new([] value: uint16) = UInt16Attribute("", value) + +type UInt32Attribute(name: string, value: uint32) = + inherit Attribute() + member _.Value = value + new([] value: uint32) = UInt32Attribute("", value) + +type UInt64Attribute(name: string, value: uint64) = + inherit Attribute() + member _.Value = value + new([] value: uint64) = UInt64Attribute("", value) + +type FloatAttribute(name: string, value: float) = + inherit Attribute() + member _.Value = value + new([] value: float) = FloatAttribute("", value) + +type SingleAttribute(name: string, value: float32) = + inherit Attribute() + member _.Value = value + new([] value: float32) = SingleAttribute("", value) + +type CharAttribute(name: string, value: char) = + inherit Attribute() + member _.Value = value + new([] value: char) = CharAttribute("", value) + +[] +type T1() = class end + +[] +type T2() = class end + +[] +type T3() = class end + +[] +type T4() = class end + +[] +type T5() = class end + +[] +type T6() = class end + +[] +type T7() = class end + +[] +type T8() = class end + +[] +type T9() = class end + +[] +type T10() = class end + +[] +type T11() = class end + +[] +type T12() = class end + +// Verify default values at runtime via reflection +let inline getAttr<'a when 'a :> Attribute> (t: Type) = t.GetCustomAttributes(typeof<'a>, false).[0] :?> 'a + +let check (name: string) (actual: 'a) (expected: 'a) = + if actual <> expected then + failwithf "%s: expected %A but got %A" name expected actual + +[] +let main _ = + check "bool" (getAttr(typeof)).Flag false + check "int" (getAttr(typeof)).Value 0 + check "byte" (getAttr(typeof)).Value 0uy + check "float" (getAttr(typeof)).Value 0.0 + check "single" (getAttr(typeof)).Value 0.0f + check "char" (getAttr(typeof)).Value '\000' + check "sbyte" (getAttr(typeof)).Value 0y + check "int16" (getAttr(typeof)).Value 0s + check "int64" (getAttr(typeof)).Value 0L + check "uint16" (getAttr(typeof)).Value 0us + check "uint32" (getAttr(typeof)).Value 0u + check "uint64" (getAttr(typeof)).Value 0UL + 0 + From 39eeb14bbc5fe4efb59ad4a42137af7b6aa65843 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 28 Apr 2026 11:13:42 +0200 Subject: [PATCH 2/2] Handle Const.Zero for enum types in custom attribute codegen Address review feedback: when a custom attribute has an [] parameter of an enum type (without []), Const.Zero is now resolved by looking up the enum's underlying primitive type and recursing into GenAttribArg. Also adds test coverage for enum types (int-based and byte-based). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/CodeGen/IlxGen.fs | 6 ++++- .../Basic/OptionalAttributeArgs.fs | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 93898caeed4..ceea51c2815 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -10307,7 +10307,7 @@ and GenAttribArg amap g eenv x (ilArgTy: ILType) = | Expr.Const(Const.Zero, _, _), ILType.Array _ -> ILAttribElem.Null // Detect standard constants - | Expr.Const(c, m, _), _ -> + | Expr.Const(c, m, ty), _ -> let tynm = ilArgTy.TypeSpec.Name let isobj = (tynm = "System.Object") @@ -10346,6 +10346,10 @@ and GenAttribArg amap g eenv x (ilArgTy: ILType) = | Const.Zero when tynm = "System.Single" -> ILAttribElem.Single 0.0f | Const.Zero when tynm = "System.Double" -> ILAttribElem.Double 0.0 | Const.Zero when tynm = "System.Char" -> ILAttribElem.Char '\000' + | Const.Zero when isEnumTy g ty -> + let underlyingTy = underlyingTypeOfEnumTy g ty + let underlyingIlTy = GenType amap m eenv.tyenv underlyingTy + GenAttribArg amap g eenv (Expr.Const(Const.Zero, m, underlyingTy)) underlyingIlTy | Const.String i when isobj || tynm = "System.String" -> ILAttribElem.String(Some i) | _ -> error (InternalError("The type '" + tynm + "' may not be used as a custom attribute value", m)) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs index 81f9b11a59b..b7d9f5136bb 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/OptionalAttributeArgs.fs @@ -66,6 +66,21 @@ type CharAttribute(name: string, value: char) = member _.Value = value new([] value: char) = CharAttribute("", value) +// Enum types with different underlying types +type MyIntEnum = A = 0 | B = 1 + +type MyByteEnum = A = 0uy | B = 1uy + +type EnumIntAttribute(name: string, value: MyIntEnum) = + inherit Attribute() + member _.Value = value + new([] value: MyIntEnum) = EnumIntAttribute("", value) + +type EnumByteAttribute(name: string, value: MyByteEnum) = + inherit Attribute() + member _.Value = value + new([] value: MyByteEnum) = EnumByteAttribute("", value) + [] type T1() = class end @@ -102,6 +117,12 @@ type T11() = class end [] type T12() = class end +[] +type T13() = class end + +[] +type T14() = class end + // Verify default values at runtime via reflection let inline getAttr<'a when 'a :> Attribute> (t: Type) = t.GetCustomAttributes(typeof<'a>, false).[0] :?> 'a @@ -123,5 +144,7 @@ let main _ = check "uint16" (getAttr(typeof)).Value 0us check "uint32" (getAttr(typeof)).Value 0u check "uint64" (getAttr(typeof)).Value 0UL + check "enum_int" (getAttr(typeof)).Value MyIntEnum.A + check "enum_byte" (getAttr(typeof)).Value MyByteEnum.A 0