Classes/Expressions.ps1

using namespace System.Reflection
using namespace System.Collections.ObjectModel
using namespace System.Management.Automation.Language

class TypeExpressionHelper {
    [type] $Type;

    hidden [bool] $encloseWithBrackets;
    hidden [bool] $needsProxy;

    TypeExpressionHelper ([type] $type) {
        $this.Type = $Type
    }
    static [string] Create ([type] $type) {
        return [TypeExpressionHelper]::Create($type, $true)
    }
    static [string] Create ([type] $type, [bool] $encloseWithBrackets) {
        $helper = [TypeExpressionHelper]::new($type)
        $helper.encloseWithBrackets = $encloseWithBrackets
        return $helper.Create()
    }
   [string] Create () {
        # Non public types can't be retrieved with a type literal expression and need to be retrieved
        # from their assembly directly. The easiest way is to get a type from the same assembly and
        # get the assembly from that. The goal here is to build it as short as possible, hopefully
        # retaining some semblance of readability.
        if (-not $this.Type.IsPublic -or $this.Type.GenericTypeArguments.IsPublic -contains $false) {
            $this.needsProxy = $true
            return $this.CreateProxy()
        }
        else {
            return $this.CreateLiteral()
        }
    }
    hidden [string] CreateProxy () {
        $builder = [System.Text.StringBuilder]::new('[')
        $assembly = $this.Type.Assembly

        # First check if there are any type accelerators in the same assembly.
        $choices = $this.GetAccelerators().GetEnumerator().Where{ $PSItem.Value.Assembly -eq $assembly }.Key

        if (-not $choices) {
            # Then as a last resort pull every type from the assembly. This takes a extra second or
            # two the first time.
            $choices = $assembly.GetTypes().ToString
        }

        $builder.
            Append(($choices | Sort-Object Length)[0]).
            Append('].Assembly.GetType(''')

        if ($this.Type.GenericTypeArguments) {
            # Using the GetType method on the full name doesn't work for every type/combination, so
            # we use the MakeGenericType method.
            return $builder.AppendFormat('{0}.{1}'').MakeGenericType(', $this.Type.Namespace, $this.Type.Name).
                Append($this.GetGenericArguments()).
                Append(')').
                ToString()
        }
        else {
            return $builder.
                AppendFormat('{0}'')', $this.Type.ToString()).
                ToString()
        }
    }
    hidden [string] CreateLiteral () {
        $builder = [System.Text.StringBuilder]::new()
        # If we are building the type name as a generic type argument in a type literal we don't want
        # to enclose it with brackets.
        if ($this.encloseWithBrackets) { $builder.Append('[') }

        if ($this.Type.GenericTypeArguments) {
            $builder.
                AppendFormat('{0}.{1}', $this.Type.Namespace, $this.Type.Name).
                Append('[').
                Append($this.GetGenericArguments()).
                Append(']')
        }
        else {
            $name = $this.GetAccelerators().
                GetEnumerator().
                Where{ $PSItem.Value -eq $this.Type }.
                Key |
                Sort-Object Length

            if (-not $name) { $name = ($this.Type.Name -as [type]).Name }
            if (-not $name) { $name = $this.Type.ToString() }

            if ($name.Count -gt 1) { $name = $name[0] }

            $builder.Append($name)
        }

        if ($this.encloseWithBrackets) { $builder.Append(']') }

        return $builder.ToString()
    }
    hidden [string] GetGenericArguments () {
        $typeArguments = $this.Type.GenericTypeArguments

        $enclose = $false
        if ($this.needsProxy) { $enclose = $true }

        return $typeArguments.ForEach{
            [TypeExpressionHelper]::Create($PSItem, $enclose)
        } -join ', '
    }
    hidden [System.Collections.Generic.Dictionary[string, type]] GetAccelerators () {
       return [ref].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Get
    }
}

class ExtendedMemberExpressionAst : MemberExpressionAst {
    [type] $InferredType;
    [MemberInfo] $InferredMember;
    [BindingFlags] $BindingFlags;
    [ReadOnlyCollection[ExpressionAst]] $Arguments;

    ExtendedMemberExpressionAst ([IScriptExtent] $extent,
                                 [ExpressionAst] $expression,
                                 [CommandElementAst] $member,
                                 [bool] $static,
                                 [ReadOnlyCollection[ExpressionAst]] $arguments) :
                                 base($extent, $expression, $member, $static) {

        try {
            $this.Arguments      = $arguments
            $this.InferredMember = GetInferredMember -Ast $this
            $this.InferredType   = ($this.InferredMember.ReturnType,
                                    $this.InferredMember.PropertyType,
                                    $this.InferredMember.FieldType).
                                    Where({ $PSItem }, 'First')[0]

            $this.BindingFlags   = $this.InferredMember.GetType().
                GetProperty('BindingFlags', [BindingFlags]'Instance, NonPublic').
                GetValue($this.InferredMember)
        } catch {
            $this.InferredType = [object]
        }
    }
    static [ExtendedMemberExpressionAst] op_Implicit ([MemberExpressionAst] $ast) {

        $expression = $ast.Expression.Copy()
        if ($expression -is [MemberExpressionAst]) {
            $expression = [ExtendedMemberExpressionAst]$expression
        }
        $newAst = [ExtendedMemberExpressionAst]::new(
            $ast.Extent,
            $expression,
            $ast.Member.Copy(),
            $ast.Static,
            $ast.Arguments
        )

        if ($ast.Parent) {
            $ast.Parent.GetType().
                GetMethod('SetParent', [BindingFlags]'Instance, NonPublic').
                Invoke($ast.Parent, $newAst)
        }

        return $newAst
    }
}