Skip to content
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

typeof doesn't work with ref struct types. #10426

Open
teo-tsirpanis opened this issue Nov 11, 2020 · 9 comments
Open

typeof doesn't work with ref struct types. #10426

teo-tsirpanis opened this issue Nov 11, 2020 · 9 comments

Comments

@teo-tsirpanis
Copy link
Contributor

The title is quite descriptive. Try running typeof<System.ReadOnlySpan<int>>;; in FSI to reproduce.

It will fail with error FS0412: A type instantiation involves a byref type. This is not permitted by the rules of Common IL.

Tested with .NET 5's dotnet fsi.

@cartermp
Copy link
Contributor

This is by design, since this kind of operation requires boxing. You'll notice that if you try GetType() it will throw an exception at runtime (like in C#).

@teo-tsirpanis
Copy link
Contributor Author

No, I'm not talking about GetType, I know that it doesn't work with ref structs because of boxing.

I am talking about the typeof function which is compiled into an ldtoken IL instruction. No ref struct is getting boxed.

The compiler should special-case typeof/typedefof to allow ref structs to be passed as type arguments, just like we can write byref<Span<int>>. This special-casing could help with #5019.

@teo-tsirpanis
Copy link
Contributor Author

Moreover, in C#, calling GetType() on a ref struct fails on compile-time. Perhaps F# could do the same.

@teo-tsirpanis
Copy link
Contributor Author

@dsyme, @vzarytovskii should we reopen this?

@vzarytovskii
Copy link
Member

If C# allows it, I think it makes sense to allow it as well

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Oct 23, 2022

@vzarytovskii I agree. As mentioned, this is typeof, not GetType(). We should also look at typedefof. Can you click the ReOpen button? Status is still Closed….

@cartermp
Copy link
Contributor

Let me re-open.

@cartermp cartermp reopened this Oct 23, 2022
@vzarytovskii vzarytovskii moved this to Not Planned in F# Compiler and Tooling Nov 2, 2022
@vzarytovskii vzarytovskii added this to the Backlog milestone Nov 2, 2022
@patrickallensimpson
Copy link

patrickallensimpson commented Aug 19, 2023

This has bitten me when doing Expression trees. I need to be able to locate a op_Implicit that converts Span into ReadOnlySpan.

open System.Linq.Expressions
let convertSpanToReadOnlySpan (expr:Expression) =
    if expr.Type = typeof<System.ReadOnlySpan<byte>> then expr
    elif expr.Type = typeof<System.Span<byte>> then
        let mi = typeof<System.Span<byte>>.GetMethod("op_Implicit",[| typeof<System.Span<byte>> |])
        Expression.Call(mi,expr)
    else
        failwith "the expr is not Span<byte> or ReadOnlySpan<byte>"

Every one of the typeof operators is showing error FS0412: A type instantiation involves a byref type. This is not permitted by the rules of Common IL.

Has any work on this been done yet? This is almost a year ago now since re-opening.

It also causes an issue for something like this as well:

/// this is a really nice little function for find and creating the appropriate op_Implicit to convert expr into the 'Out type
let opImplicit<'Out> (expr:Expression) =
    let mi = expr.Type.GetMethod("op_Implicit",[|expr.Type|])
    if mi.ReturnType = typeof<'Out> then
        Expression.Call(mi,expr)
    else
        let mi = typeof<'Out>.GetMethod("op_Implicit",[|expr.Type|])
        if mi.ReturnType = typeof<'Out> then
            Expression.Call(mi,expr)
        else
            failwith $"no op_Implicit operator exists to convert {expr.Type} to {typeof<'Out>}"

//This works but its ridculous since I can't use the op_Implicit I defined above
Expression.Constant(Array.init 16 byte)
|> fun buff ->
    let mi =    
        typeof<System.MemoryExtensions>.GetMethods()//("AsSpan",1,[|System.Type.MakeGenericMethodParameter(0)|])
        |> Seq.filter (fun mi -> mi.Name = "AsSpan" && mi.IsGenericMethod && (let p = mi.GetParameters() in p.Length = 1 && (p[0]).ParameterType.IsArray))
        |> Seq.head
    let mi' = mi.MakeGenericMethod(typeof<byte>)
    Expression.Call(mi',buff)
|> fun asspan ->
    //opImplicit<System.ReadOnlySpan<byte>> asspan    /// I'd like to be able to do this but I can't
    asspan.Type.GetMethods()
    |> Seq.filter (fun mi -> mi.Name = "op_Implicit")
    |> Seq.filter (fun mi -> mi.ReturnType.Name = "ReadOnlySpan`1")
    |> Seq.head
    |> fun mi ->
        Expression.Call(mi,asspan)
|> fun readonlyspan ->
    let mi =
        typeof<System.BitConverter>.GetMethods()
        |> Seq.filter (fun mi -> mi.Name = "ToInt32") // I have to do this because typeof<System.ReadOnlySpan<byte>> doesn't work
        |> Seq.filter (fun mi -> let p = mi.GetParameters() in p.Length = 1 && p[0].ParameterType.Name = "ReadOnlySpan`1") // I have to do this because typeof<System.ReadOnlySpan<byte>> doesn't work
        |> Seq.head
    Expression.Call(mi,readonlyspan)
|> fun convertToDouble ->
    Expression.Lambda<System.Func<int>>(convertToDouble).Compile().Invoke()

I'd be willing to work on this, if someone could point me to the section of the compiler that this issue would be in.

Thanks,
-Patrick

@edgarfgp
Copy link
Contributor

@patrickallensimpson Looking at the error number and its usage. I think a good place to start will be here inside of CheckTypeAux

Happy to help you to get started if you are interested .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: New
Development

No branches or pull requests

6 participants