Private/GetInferredType.ps1
using namespace System.Reflection function GetInferredType { <# .SYNOPSIS Attempts to determine the type of a variable within a script file. .DESCRIPTION This function first attempts to infer type from command completion context. Failing that it will enumerate the scopes of any modules contained in the workspace as well as the global scope. If the variable is found in one of the scopes, the type of it's value will be returned. If the type cannot be inferred from command completion and the variable is defined in a function (or other child scope) this method will not work. The variable needs to be in a scope that exists at the time this function is ran. A workaround is to set a breakpoint right after the variable is defined. .INPUTS None .OUTPUTS System.Type Returns the inferred type if it was determined. This function does not have output otherwise. .EXAMPLE PS C:\> GetInferredType -Ast $memberExpressionAst.Expression Determines the type of the variable used in a member expression. #> [CmdletBinding()] [OutputType([type])] param( # Specifies the current context of the editor. [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [Microsoft.PowerShell.EditorServices.Extensions.EditorContext] $Context = $psEditor.GetEditorContext(), # Specifies the ast to analyze. [Parameter(Position=1, Mandatory)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Language.Ast] $Ast ) begin { function GetInferredTypeImpl { # Return cached inferred type if it's our custom MemberExpressionAst if ($Ast.InferredType -and $Ast.InferredType -ne [object]) { return $Ast.InferredType } if ($Ast -is [System.Management.Automation.Language.TypeExpressionAst]) { return GetType -TypeName $Ast.TypeName } if ($Ast.StaticType -and $Ast.StaticType -ne [object]) { return $Ast.StaticType } $PSCmdlet.WriteDebug("TYPEINF: Starting engine inference") try { $flags = [BindingFlags]'Instance, NonPublic' $mappedInput = [System.Management.Automation.CommandCompletion]:: MapStringInputToParsedInput( $Ast.Extent.StartScriptPosition.GetFullScript(), $Ast.Extent.EndOffset) # If anyone knows a public way to go about getting the type inference from the engine # give me a shout. $analysis = [ref]. Assembly. GetType('System.Management.Automation.CompletionAnalysis'). InvokeMember( <# name: #> $null, <# invokeAttr: #> $flags -bor [BindingFlags]::CreateInstance, <# binder: #> $null, <# target: #> $null, <# args: #> @( <# ast: #> $mappedInput.Item1, <# tokens: #> $mappedInput.Item2, <# cursorPosition: #> $mappedInput.Item3, <# options: #> @{})) $engineContext = $ExecutionContext.GetType(). GetField('_context', $flags). GetValue($ExecutionContext) $completionContext = $analysis.GetType(). GetMethod('CreateCompletionContext', $flags). Invoke($analysis, @($engineContext)) $type = $Ast.GetType(). GetMethod('GetInferredType', $flags). Invoke($Ast, @($completionContext)). Where({ $null -ne $PSItem.Type -and $PSItem.Type -ne [object]}, 'First')[0]. Type if ($type) { return $type } } catch { $PSCmdlet.WriteDebug('TYPEINF: Engine failed with error ID "{0}"' -f $Error[0].FullyQualifiedErrorId) } if ($Ast -is [System.Management.Automation.Language.MemberExpressionAst]) { $PSCmdlet.WriteDebug('TYPEINF: Starting member inference') if ($member = GetInferredMember -Ast $Ast) { return ( $member.ReturnType, $member.PropertyType, $member.FieldType ).Where({ $PSItem -is [type] }, 'First')[0] } } if ($Ast -is [System.Management.Automation.Language.VariableExpressionAst]) { $PSCmdlet.WriteDebug('TYPEINF: Starting module state inference') $inferredManifest = GetInferredManifest -ErrorAction Ignore $moduleVariable = Get-Module | Where-Object Guid -eq $inferredManifest.GUID | ForEach-Object { $PSItem.SessionState.PSVariable.GetValue($Ast.VariablePath.UserPath) } | Where-Object { $null -ne $PSItem } if ($moduleVariable) { return $moduleVariable.Where({ $null -ne $PSItem }, 'First')[0].GetType() } $PSCmdlet.WriteDebug('TYPEINF: Starting global state inference') $foundInGlobal = $ExecutionContext. SessionState. Module. GetVariableFromCallersModule( $Ast.VariablePath.UserPath) if ($foundInGlobal -and $null -ne $foundInGlobal.Value) { return $foundInGlobal.Value.GetType() } } } } end { $type = GetInferredTypeImpl if (-not $type) { ThrowError -Exception ([InvalidOperationException]::new($Strings.CannotInferType -f $Ast)) ` -Id CannotInferType ` -Category InvalidOperation ` -Target $Ast return } $type } } |