Skip to content

Commit 9eb3406

Browse files
T-GroCopilot
andcommitted
Fix empty array encoding for all unencodable types and add struct tests
- Broaden isEncodableElemType check to also reject non-enum struct types, not just boxed reference types. Previously empty struct arrays compiled but produced invalid custom attribute metadata (encoded as enum type), causing CustomAttributeFormatException at runtime. - Add positive test for empty struct array in attribute (compile + ILVerify + run). - Add negative test for non-empty struct array in attribute (expect error 267). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d2be16e commit 9eb3406

2 files changed

Lines changed: 75 additions & 9 deletions

File tree

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10335,19 +10335,32 @@ and GenAttribArg amap (g: TcGlobals) eenv x (ilArgTy: ILType) =
1033510335
| Expr.Op(TOp.Array, [ elemTy ], args, m), _ ->
1033610336
let ilElemTy = GenType amap m eenv.tyenv elemTy
1033710337

10338-
// Check if element type can be encoded in custom attribute metadata (ECMA 335).
10338+
// Check if element type can be encoded in custom attribute metadata (ECMA-335 II.23.3).
1033910339
// Valid element types: primitives, enums, string, System.Type, System.Object.
10340-
let isUnencodableBoxedElemType =
10340+
let isEncodableElemType =
1034110341
match ilElemTy with
10342-
| ILType.Boxed tspec when
10343-
tspec.Name <> "System.String"
10344-
&& tspec.Name <> "System.Object"
10345-
&& tspec.Name <> "System.Type"
10346-
->
10347-
true
10342+
| ILType.Value tspec ->
10343+
match tspec.Name with
10344+
| "System.SByte"
10345+
| "System.Byte"
10346+
| "System.Int16"
10347+
| "System.UInt16"
10348+
| "System.Int32"
10349+
| "System.UInt32"
10350+
| "System.Int64"
10351+
| "System.UInt64"
10352+
| "System.Double"
10353+
| "System.Single"
10354+
| "System.Char"
10355+
| "System.Boolean" -> true
10356+
| _ -> isEnumTy g elemTy
10357+
| ILType.Boxed tspec ->
10358+
tspec.Name = "System.String"
10359+
|| tspec.Name = "System.Object"
10360+
|| tspec.Name = "System.Type"
1034810361
| _ -> false
1034910362

10350-
if isUnencodableBoxedElemType then
10363+
if not isEncodableElemType then
1035110364
if args.IsEmpty then
1035210365
// Empty arrays: substitute System.Object as element type since no elements need encoding.
1035310366
ILAttribElem.Array(g.ilg.typ_Object, [])

tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,56 @@ type B() = class end
343343
|> shouldFail
344344
|> withErrorCode 3885
345345
|> withDiagnosticMessageMatches "not a valid custom attribute argument type"
346+
347+
// https://github.com/dotnet/fsharp/issues/12796
348+
[<Fact>]
349+
let ``Issue 12796 - Non-empty array of struct type in attribute gives proper error`` () =
350+
FSharp
351+
"""
352+
module TestModule
353+
354+
open System.ComponentModel
355+
356+
[<Struct>]
357+
type MyStruct = { X: int }
358+
359+
[<DefaultValue([| { X = 1 } |] : MyStruct[])>]
360+
type B() = class end
361+
"""
362+
|> compile
363+
|> shouldFail
364+
|> withErrorCode 267
365+
366+
// https://github.com/dotnet/fsharp/issues/12796
367+
[<Fact>]
368+
let ``Issue 12796 - Empty array of struct type in attribute compiles, verifies IL, and runs`` () =
369+
FSharp
370+
"""
371+
module TestModule
372+
373+
open System
374+
open System.ComponentModel
375+
open System.Reflection
376+
377+
[<Struct>]
378+
type MyStruct = { X: int; Y: int }
379+
380+
[<DefaultValue([||] : MyStruct[])>]
381+
type T() = class end
382+
383+
[<EntryPoint>]
384+
let main _ =
385+
let attr = typeof<T>.GetCustomAttribute<DefaultValueAttribute>()
386+
let arr = attr.Value :?> obj[]
387+
if arr.Length <> 0 then failwith "Expected empty array"
388+
printfn "struct: len=%d" arr.Length
389+
0
390+
"""
391+
|> asExe
392+
|> compile
393+
|> shouldSucceed
394+
|> verifyPEFileWithSystemDlls
395+
|> shouldSucceed
396+
|> run
397+
|> shouldSucceed
398+
|> withStdOutContains "struct: len=0"

0 commit comments

Comments
 (0)