-
Notifications
You must be signed in to change notification settings - Fork 867
Preserve enum type in custom attribute argument of type obj (#995) #19975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 14 commits
013cb43
a832f00
7c298d0
fa33419
21727a7
a311000
a8fa4a0
3ee9599
b65b775
0ca1338
de66dd8
7c088ce
2e80ce0
0dcfa99
cbe0400
4c3317a
b015e46
dc2adca
7ad207a
b02c0c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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). | ||
|
|
@@ -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 | ||
|
|
||
| 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. | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
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 | ||
|
|
||
|
|
@@ -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 _ -> | ||
|
T-Gro marked this conversation as resolved.
Outdated
|
||
| // Assume an enumeration. Note: the underlying integer width is not present in the | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When is this code path exercised?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Added |
||
| // 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" | ||
|
|
@@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 -> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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 | ||
|
|
||
| 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\"" |
Uh oh!
There was an error while loading. Please reload this page.