fsprojects / FSharp.Management

The FSharp.Management project contains various type providers for the management of the machine.
http://fsprojects.github.io/FSharp.Management/
Other
91 stars 32 forks source link

Type coersion exception when calling Get-ExecutionPolicy #84

Open cagyirey opened 7 years ago

cagyirey commented 7 years ago

Description

The type inference engine gives the wrong result type when trying to list ExecutionPolicy objects.

Repro steps

  1. Create a new folder and initialize paket with the following paket.dependencies
source https://nuget.org/api/v2
nuget FSharp.Management
nuget System.Management.Automation
  1. Run this script from the root directory. Call getExecutionPolicy() or getLocalExecutionPolicy() to observe the results.

Expected behavior

The type inferer should not try to cast from a PSObject directly to List<ExecutionPolicy>. Perhaps it's the BaseObject that contains the execution policy list?

Actual behavior

An exception with the message Object of type 'System.Management.Automation.PSObject' cannot be converted to type 'Microsoft.FSharp.Collections.FSharpList1[Microsoft.PowerShell.ExecutionPolicy]' is thrown.

Related information

Repro

#I @".\packages\FSharp.Management\lib\net45"
#I @".\ackages\System.Management.Automation\lib\net40"

#r "FSharp.Management"
#r "FSharp.Management.PowerShell"
#r "System.Management.Automation"

open System
open System.Management.Automation

open Microsoft.PowerShell

open FSharp.Management

type PowerShell = PowerShellProvider<"Microsoft.PowerShell">

let private runCmdlet (cmd: _ -> PsCmdletResult<_, List<ErrorRecord>>) =
        let res = cmd ()
        match res with
        | Success psObj -> psObj
        | Failure err ->
            let ex =  (Seq.head err).Exception
            printfn "Error running cmdlet: %s" ex.Message; raise ex

let private unboxCmdlet (result: PsCmdletResult<_, List<ErrorRecord>>) =
    match result with
    | Success psObj -> psObj
    | Failure err -> raise (Seq.head err).Exception

// System.ArgumentException: Object of type 'System.Management.Automation.PSObject' cannot be converted to type 'Microsoft.FSharp.Collections.FSharpList`1[Microsoft.PowerShell.ExecutionPolicy]'.
let getExecutionPolicy () =
    PowerShell.``Get-ExecutionPolicy``(list=true)

// val it : PsCmdletResult<List<ExecutionPolicy>,List<ErrorRecord>> = Success [RemoteSigned {value__ = 1;}]
let getLocalExecutionPolicy () =
    PowerShell.``Get-ExecutionPolicy``(ExecutionPolicyScope.LocalMachine)
sergey-tihon commented 7 years ago

@cagyirey Thank you for the bug report.

Do you want to try to fix it?

cagyirey commented 7 years ago

Hi @sergey-tihon

I'll certainly take a shot at it. I've gotten similar errors with a few other cmdlets, and I don't have a good solution for debugging the type provider yet. Trying to examine local variables (i.e. the state of the type inferrer when the provider loads) in the Visual Studio debugger gives me an Internal F# Compiler Error. I might have to write a small tool for dumping cmdlet signatures unless you know anything about what I'm doing wrong in the debugger.

cagyirey commented 7 years ago

I've managed to learn a little more about the bug. line 134 in HostedRuntime.Execute is being triggered because - and this doesn't show up in the Get-ExecutionPolicy signature - listing the policies returns a collection of PSCustomObjects consisting of a MachineScope and the ExecutionPolicy. Line 134 here makes a PSObject out of that collection, but the type inferrer gets a List<ExecutionPolicy> here on line 33 for the other overload (the only one that shows up in the PSCommandSignature list) to Get-ExecutionPolicy. Line 138 ultimately ends up trying to construct a PSCmdletResult<List<ExecutionPolicy>, _> out of a boxed Collection<PSObject> because of the wrong ResultType.

cagyirey commented 7 years ago

Apparently PowerShell populates CommandInfo.OutputType from an attribute that goes on the cmdlet type. So there's no actual guarantee the output type will actually match the real output. It doesn't seem like there's a way to guess when it'll happen, otherwise a simple fix would be to infer PSObject for those cmdlets and let the user unbox it.

sergey-tihon commented 7 years ago

Do not we have another tricky way to get all possible result type instead of exposing PSObject to the user?

cagyirey commented 7 years ago

If there is another way, it might have to be a very clever fix. PS doesn't decide what kind of output you get until the last minute:

image

Not great for type inference. Because it's just 2 branches of the same function, you can't tell from the CommandInfo it does anything special. Besides looking at the disassembly, I haven't found any good indicators for which cmdlets do this.

Firgeis commented 7 years ago

Ok, here is the problem with this case and others like it. The Type Provider is inferring return types from the powershell OutputType documentation, however Microsoft says that "The OutputType attribute value is only a documentation note. It is not derived from the function code or compared to the actual function output. As such, the value might be inaccurate." as you can see in the following link: outputType docs.

In this particular case, you can see the informed return type: psgetexecreturn.

This means that the type provider will infer that this function will always return a ExecutionPolicy type, but in this case, the "list=true" parameter is changing the output type from ExecutionPoliciy to an Object[] and therefore produces this error.

One solution I can think of is to add a "default case type" to the inferred types in order to account for this behaviour. Since this type list is constructed when the type provider "reads" the types, I don't think we can change the return at runtime but another option would be to use a kind of dynamic type which would also be a design compromise (since we would lose the static typing).