Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
013cb43
Add PR link to release note
edgarfgp Jun 21, 2026
a832f00
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 21, 2026
7c298d0
Remove unrelated whitespace changes
edgarfgp Jun 21, 2026
fa33419
Fix formatting and add quotation regression test for enum preservation
edgarfgp Jun 21, 2026
21727a7
Add cross-language test: C#-defined enum in an F# attribute arg of ty…
edgarfgp Jun 21, 2026
a311000
Update FCS surface area baseline for ILAttribElem.Enum
edgarfgp Jun 21, 2026
a8fa4a0
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 22, 2026
3ee9599
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 23, 2026
b65b775
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 23, 2026
0ca1338
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 23, 2026
de66dd8
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 24, 2026
7c088ce
Only wrap genuine enums (0x55 tag) as ILAttribElem.Enum on decode
edgarfgp Jun 26, 2026
2e80ce0
Fix formatting in decodeILAttribData enum-wrap branch
edgarfgp Jun 26, 2026
0dcfa99
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jun 26, 2026
cbe0400
Address PR review: use g.ilg.typ_Object check and dedup decode comments
edgarfgp Jun 30, 2026
4c3317a
Keep enum-decode int32 note in il.fsi only; leave pre-existing fallba…
edgarfgp Jun 30, 2026
b015e46
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jul 1, 2026
dc2adca
Revert object-type check to name-based (fixes cross-assembly regression)
edgarfgp Jul 3, 2026
7ad207a
Merge branch 'main' into fix-995-enum-attr-obj
edgarfgp Jul 4, 2026
b02c0c5
Add int64 enum value exceeding Int32.MaxValue to enum-as-obj test
edgarfgp Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -21,6 +21,7 @@
* Extend the `=` adjacent to an interpolated string fix to the verbatim (`=$@"…"`, `=@$"…"`) and extended multi-dollar (`=$$"""…"""`) interpolated-string forms. ([Issue #16696](https://github.com/dotnet/fsharp/issues/16696), [PR #19984](https://github.com/dotnet/fsharp/pull/19984))
* Preserve type abbreviations (`string`, user-defined aliases) in the refined type of bindings introduced after a `| null` pattern in a `match` expression. ([Issue #19646](https://github.com/dotnet/fsharp/issues/19646), [PR #19745](https://github.com/dotnet/fsharp/pull/19745))
* Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714))
* Fix enum values losing their type when used in a custom attribute argument of type `obj` (they were stored as the underlying integer instead of the enum). ([Issue #995](https://github.com/dotnet/fsharp/issues/995), [PR #19975](https://github.com/dotnet/fsharp/pull/19975))
* Fix false-positive nullness warning (FS3261) when pattern matching narrows nullness inside seq/list/array comprehensions. ([Issue #19644](https://github.com/dotnet/fsharp/issues/19644), [PR #19743](https://github.com/dotnet/fsharp/pull/19743))
* Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710))
* Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511))
Expand Down
66 changes: 41 additions & 25 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,7 @@ type ILAttribElem =
| Type of ILType option
| TypeRef of ILTypeRef option
| Array of ILType * ILAttribElem list
| Enum of enumType: ILType * value: ILAttribElem

type ILAttributeNamedArg = string * ILType * bool * ILAttribElem

Expand Down Expand Up @@ -4897,6 +4898,8 @@ let rec encodeCustomAttrElemTypeForObject x =
| ILAttribElem.Single _ -> [| et_R4 |]
| ILAttribElem.Double _ -> [| et_R8 |]
| ILAttribElem.Array(elemTy, _) -> [| yield et_SZARRAY; yield! encodeCustomAttrElemType elemTy |]
// An enum boxed in 'object' is encoded as 0x55 followed by the enum type's qualified name.
| ILAttribElem.Enum(enumTy, _) -> encodeCustomAttrElemType enumTy

let parseILVersion (vstr: string) =
// matches "v1.2.3.4" or "1.2.3.4". Note, if numbers are missing, returns -1 (not 0).
Expand Down Expand Up @@ -4994,6 +4997,25 @@ let rec decodeCustomAttrElemType bytes sigptr x =
mkILArr1DTy elemTy, sigptr
| x when x = 0x50uy -> PrimaryAssemblyILGlobals.typ_Type, sigptr
| x when x = 0x51uy -> PrimaryAssemblyILGlobals.typ_Object, sigptr // SERIALIZATION_TYPE_TAGGED_OBJECT (ECMA-335 II.23.3)
| x when x = 0x55uy ->
// SERIALIZATION_TYPE_ENUM (ECMA-335 II.23.3): the enum type's qualified name follows.
// Occurs e.g. when an enum is boxed into an 'object'-typed argument.
let qualifiedName, sigptr = sigptr_get_serstring bytes sigptr

let unqualifiedName, rest =
let pieces = qualifiedName.Split ','

if pieces.Length > 1 then
pieces[0], Some(String.concat "," pieces[1..])
else
pieces[0], None
Comment thread
edgarfgp marked this conversation as resolved.

let scoref =
match rest with
| Some aname -> ILScopeRef.Assembly(ILAssemblyRef.FromAssemblyName(AssemblyName aname))
| None -> PrimaryAssemblyILGlobals.primaryAssemblyScopeRef

ILType.Value(mkILNonGenericTySpec (mkILTyRef (scoref, unqualifiedName))), sigptr
| _ -> failwithf "decodeCustomAttrElemType ilg: unrecognized custom element type: %A" x

/// Given a custom attribute element, encode it to a binary representation according to the rules in Ecma 335 Partition II.
Expand Down Expand Up @@ -5024,6 +5046,8 @@ let rec encodeCustomAttrPrimValue c =
for elem in elems do
yield! encodeCustomAttrPrimValue elem
|]
// The enum type is captured separately (in the type tag); the value is the underlying integer.
| ILAttribElem.Enum(_, value) -> encodeCustomAttrPrimValue value

and encodeCustomAttrValue ty c =
match ty, c with
Expand Down Expand Up @@ -5370,7 +5394,15 @@ let decodeILAttribData (ca: ILAttribute) =
ILAttribElem.Null, sigptr
else
let ty, sigptr = decodeCustomAttrElemType bytes sigptr et
parseVal ty sigptr
let v, sigptr = parseVal ty sigptr
// Only a genuine enum (the 0x55 tag) is wrapped as ILAttribElem.Enum, so it
Comment thread
edgarfgp marked this conversation as resolved.
Outdated
// re-encodes with its 0x55 enum tag (e.g. during static linking). Boxed primitives
// (et_I4, et_BOOLEAN, ...) also decode to an ILType.Value here but must be left as
// their primitive element. See https://github.com/dotnet/fsharp/issues/995.
if et = 0x55uy then
ILAttribElem.Enum(ty, v), sigptr
else
v, sigptr
| ILType.Array(shape, elemTy) when shape = ILArrayShape.SingleDimensional ->
let n, sigptr = sigptr_get_i32 bytes sigptr

Expand All @@ -5386,7 +5418,11 @@ let decodeILAttribData (ca: ILAttribute) =

let elems, sigptr = parseElems [] n sigptr
ILAttribElem.Array(elemTy, elems), sigptr
| ILType.Value _ -> (* assume it is an enumeration *)
| ILType.Value _ ->
Comment thread
T-Gro marked this conversation as resolved.
Outdated
// Assume an enumeration. Note: the underlying integer width is not present in the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this code path exercised?
Pls demonstrate with a test cases that uses long-backed enum and also has value(s) which would not fit an int32.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added LongEnum.Big = 5_000_000_000L in EnumValueAsObjectArg01.fs. It's past Int32.MaxValue, so if the decode truncated to int32 the value wouldn't be preserved and the test's Convert.ToInt64(...) = 5_000_000_000L assertion would fail.

// blob, so this reads it as int32. Enums with a non-int32 underlying type (byte,
// int16, int64, ...) are therefore not read correctly here; resolving that would
// require materializing the enum type, which this blob parser does not do.
let n, sigptr = sigptr_get_i32 bytes sigptr
ILAttribElem.Int32 n, sigptr
| _ -> failwith "decodeILAttribData: attribute data involves an enum or System.Type value"
Expand All @@ -5409,29 +5445,9 @@ let decodeILAttribData (ca: ILAttribute) =
let isPropByte, sigptr = sigptr_get_u8 bytes sigptr
let isProp = (int isPropByte = 0x54)
let et, sigptr = sigptr_get_u8 bytes sigptr
// We have a named value
let ty, sigptr =
if ( (* 0x50 = (int et) || *) 0x55 = (int et)) then
let qualified_tname, sigptr = sigptr_get_serstring bytes sigptr

let unqualified_tname, rest =
let pieces = qualified_tname.Split ','

if pieces.Length > 1 then
pieces[0], Some(String.concat "," pieces[1..])
else
pieces[0], None

let scoref =
match rest with
| Some aname -> ILScopeRef.Assembly(ILAssemblyRef.FromAssemblyName(AssemblyName aname))
| None -> PrimaryAssemblyILGlobals.primaryAssemblyScopeRef

let tref = mkILTyRef (scoref, unqualified_tname)
let tspec = mkILNonGenericTySpec tref
ILType.Value tspec, sigptr
else
decodeCustomAttrElemType bytes sigptr et
// We have a named value. The type tag (including the 0x55 enum form) is decoded by
// decodeCustomAttrElemType.
let ty, sigptr = decodeCustomAttrElemType bytes sigptr et

let nm, sigptr = sigptr_get_serstring bytes sigptr
let v, sigptr = parseVal ty sigptr
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,11 @@ type ILAttribElem =
| Type of ILType option
| TypeRef of ILTypeRef option
| Array of ILType * ILAttribElem list
/// Represents an enum value together with its enum type. Used when an enum is stored in a
/// custom-attribute argument of type 'object', so the enum type is preserved in the encoded
/// blob (ECMA-335 II.23.3) instead of being collapsed to its underlying integer. The second
/// element is the underlying integer value (e.g. ILAttribElem.Int32).
| Enum of enumType: ILType * value: ILAttribElem

/// Named args: values and flags indicating if they are fields or properties.
type ILAttributeNamedArg = string * ILType * bool * ILAttribElem
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/ilmorph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ let rec celem_ty2ty f celem =
| ILAttribElem.Type(Some ty) -> ILAttribElem.Type(Some(f ty))
| ILAttribElem.TypeRef(Some tref) -> ILAttribElem.TypeRef(Some (f (mkILBoxedType (mkILNonGenericTySpec tref))).TypeRef)
| ILAttribElem.Array(elemTy, elems) -> ILAttribElem.Array(f elemTy, List.map (celem_ty2ty f) elems)
| ILAttribElem.Enum(enumTy, value) -> ILAttribElem.Enum(f enumTy, celem_ty2ty f value)
| _ -> celem

let cnamedarg_ty2ty f ((nm, ty, isProp, elem): ILAttributeNamedArg) = (nm, f ty, isProp, celem_ty2ty f elem)
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Checking/AttributeChecking.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ let rec private evalILAttribElem elem =
| ILAttribElem.Double x -> box x
| ILAttribElem.Null -> null
| ILAttribElem.Array (_, a) -> box [| for i in a -> evalILAttribElem i |]
// An enum value: evaluate to its underlying integer value (the enum type itself is not materialized here).
| ILAttribElem.Enum (_, value) -> evalILAttribElem value
// TODO: typeof<..> in attribute values
| ILAttribElem.Type (Some _t) -> fail()
| ILAttribElem.Type None -> null
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Checking/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ module PrintTypes =
| ILAttribElem.TypeRef (Some ty) ->
LeftL.keywordTypedefof ^^ SepL.leftAngle ^^ PrintIL.layoutILTypeRef denv ty ^^ RightL.rightAngle
| ILAttribElem.TypeRef None -> emptyL
| ILAttribElem.Enum (_, value) -> layoutILAttribElement denv value

and layoutILAttrib denv (ty, args) =
let argsL = bracketL (sepListL RightL.comma (List.map (layoutILAttribElement denv) args))
Expand Down
15 changes: 15 additions & 0 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10407,6 +10407,21 @@ and GenAttribArg amap (g: TcGlobals) eenv x (ilArgTy: ILType) =
// Detect 'null' used for an array argument
| Expr.Const(Const.Zero, _, _), ILType.Array _ -> ILAttribElem.Null

// An enum value stored into an 'object'-typed argument must keep its enum type in the
// custom-attribute blob (ECMA-335 II.23.3), otherwise it round-trips as the underlying
// integer (e.g. 'Prop = MyEnum.B' surfaces as boxed int32). See
// https://github.com/dotnet/fsharp/issues/995. The enum type is carried alongside the
// underlying integer value, which is computed by recursing with the underlying IL type.
| Expr.Const(c, m, ty), _ when ilArgTy.TypeSpec.Name = "System.Object" && isEnumTy g ty ->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a function for checking ilArgTy being object instead of a vanilla name check?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ILType equality includes assembly scope, so ilArgTy = g.ilg.typ_Object is false for an object from a referenced assembly (e.g. NUnit [<TestCase(...)>]) and broke the Nu build. Kept the scope-agnostic name check.

let enumIlTy = GenType amap m eenv.tyenv ty
let underlyingTy = underlyingTypeOfEnumTy g ty
let underlyingIlTy = GenType amap m eenv.tyenv underlyingTy

let underlyingElem =
GenAttribArg amap g eenv (Expr.Const(c, m, underlyingTy)) underlyingIlTy

ILAttribElem.Enum(enumIlTy, underlyingElem)

// Detect standard constants
| Expr.Const(c, m, ty), _ ->
let tynm = ilArgTy.TypeSpec.Name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,48 @@ module CustomAttributes_Basic =
|> verifyCompileAndRun
|> shouldSucceed

// Regression for https://github.com/dotnet/fsharp/issues/995
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"EnumValueAsObjectArg01.fs"|])>]
let ``EnumValueAsObjectArg01_fs`` compilation =
compilation
|> verifyCompileAndRun
|> shouldSucceed

// Cross-language: the same scenario as EnumValueAsObjectArg01.fs, but with the enum and the
// attribute defined in C#. See https://github.com/dotnet/fsharp/issues/995.
[<Fact>]
let ``Enum defined in C# used in an F# attribute arg of type obj keeps its type`` () =
let csLib =
CSharp """
namespace CSharpLib
{
public enum MyEnum { A = 1, B = 2 }

[System.AttributeUsage(System.AttributeTargets.All)]
public class MyAttribute : System.Attribute
{
public object Prop { get; set; }
}
}
"""
|> withName "CSharpLib"

FSharp """
module Test
open System
open CSharpLib

[<My(Prop = MyEnum.B)>]
type MyClass = class end

let prop = (typeof<MyClass>.GetCustomAttributes(false)[0] :?> MyAttribute).Prop
if prop.GetType() <> typeof<MyEnum> then failwith "enum type was lost"
if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" then failwith "expected \"B\""
"""
|> withReferences [csLib]
|> compileExeAndRun
|> shouldSucceed

// SOURCE=E_AttributeApplication01.fs # E_AttributeApplication01.fs
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"E_AttributeApplication01.fs"|])>]
let ``E_AttributeApplication01_fs`` compilation =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// #Conformance #DeclarationElements #Attributes
// Regression test for https://github.com/dotnet/fsharp/issues/995
// An enum assigned to an attribute argument of type 'obj' must keep its enum type in the
// emitted metadata, instead of being stored as the underlying int32.

open System

type MyAttribute() =
inherit Attribute()
let mutable prop : obj = null
member _.Prop
with get () : obj = prop
and set (value: obj) = prop <- value

type MyEnum =
| A = 1
| B = 2

// An enum with a non-int32 underlying type, to exercise the encoded value width.
type LongEnum =
| P = 1L
| Q = 2L

[<My(Prop = MyEnum.B)>]
type MyClass = class end

[<My(Prop = LongEnum.Q)>]
type MyClassLong = class end

let propOf<'T> () = (typeof<'T>.GetCustomAttributes(false)[0] :?> MyAttribute).Prop

let intProp = propOf<MyClass> ()
if intProp.GetType() <> typeof<MyEnum> then failwith "MyEnum type was lost"
if Convert.ToString(intProp, Globalization.CultureInfo.InvariantCulture) <> "B" then failwith "expected \"B\""

let longProp = propOf<MyClassLong> ()
if longProp.GetType() <> typeof<LongEnum> then failwith "LongEnum type was lost"
if Convert.ToString(longProp, Globalization.CultureInfo.InvariantCulture) <> "Q" then failwith "expected \"Q\""
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem+Char: Char Item
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Char: Char get_Item()
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Double: Double Item
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Double: Double get_Item()
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Enum: ILAttribElem get_value()
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Enum: ILAttribElem value
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Enum: ILType enumType
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Enum: ILType get_enumType()
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int16: Int16 Item
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int16: Int16 get_Item()
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int32: Int32 Item
Expand All @@ -161,6 +165,7 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Bool
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Byte
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Char
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Double
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Enum
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Int16
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Int32
FSharp.Compiler.AbstractIL.IL+ILAttribElem+Tags: Int32 Int64
Expand Down Expand Up @@ -192,6 +197,7 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsBool
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsByte
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsChar
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsDouble
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsEnum
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsInt16
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsInt32
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean IsInt64
Expand All @@ -209,6 +215,7 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsBool()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsByte()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsChar()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsDouble()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsEnum()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsInt16()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsInt32()
FSharp.Compiler.AbstractIL.IL+ILAttribElem: Boolean get_IsInt64()
Expand All @@ -226,6 +233,7 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttr
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Byte
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Char
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Double
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Enum
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int16
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int32
FSharp.Compiler.AbstractIL.IL+ILAttribElem: FSharp.Compiler.AbstractIL.IL+ILAttribElem+Int64
Expand All @@ -243,6 +251,7 @@ FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewBool(Boolean)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewByte(Byte)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewChar(Char)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewDouble(Double)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewEnum(ILType, ILAttribElem)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewInt16(Int16)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewInt32(Int32)
FSharp.Compiler.AbstractIL.IL+ILAttribElem: ILAttribElem NewInt64(Int64)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type E = Microsoft.FSharp.Quotations.Expr;;
type StaticIndexedPropertyTest() =
static member IdxProp with get (n : int) = n + 1

type QuotationEnum =
| A = 1
| B = 2

module Check =
let argumentException f =
let mutable ex = false
Expand Down Expand Up @@ -103,6 +107,16 @@ type FSharpQuotationsTests() =
| NewTuple [ Value(:? int as i, _) ; Value(:? string as s, _) ] when i = 1 && s = "" -> ()
| _ -> Assert.Fail()

[<Fact>]
member x.``Quotation of an enum value preserves the enum type`` () =
// Related to https://github.com/dotnet/fsharp/issues/995: an enum literal is quoted as a
// Value node carrying the enum type, not the bare underlying integer.
match <@ QuotationEnum.B @> with
| Value(v, t) ->
Assert.Equal(typeof<QuotationEnum>, t)
Assert.Equal(box QuotationEnum.B, v)
| _ -> Assert.Fail()

[<Fact>]
member x.``NewTuple literal should not be recognized by NewStructTuple active pattern`` () =
match <@ (1, "") @> with
Expand Down
Loading