@@ -1840,82 +1840,101 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST
1840
1840
( SpecialVariables . IsUnderbar ( astVariablePath . UserPath )
1841
1841
|| astVariablePath . UserPath . EqualsOrdinalIgnoreCase ( SpecialVariables . PSItem ) ) )
1842
1842
{
1843
- // $_ is special, see if we're used in a script block in some pipeline.
1844
- while ( parent != null )
1845
- {
1846
- if ( parent is ScriptBlockExpressionAst || parent is CatchClauseAst )
1843
+ // The automatic variable $_ is assigned a value in scriptblocks, Switch loops and Catch/Trap statements
1844
+ // This loop will find whichever Ast that determines the value of $_
1845
+ // The value in scriptblocks is determined by the parents of that scriptblock, the only interesting scenarios are:
1846
+ // 1: MemberInvocation like: $Collection.Where({$_})
1847
+ // 2: Command pipelines like: dir | where {$_}
1848
+ // The value in a Switch loop is whichever item is in the condition part of the statement.
1849
+ // The value in Catch/Trap statements is always an error record.
1850
+ bool hasSeenScriptBlock = false ;
1851
+ while ( parent is not null )
1852
+ {
1853
+ if ( parent is CatchClauseAst or TrapStatementAst )
1847
1854
{
1848
1855
break ;
1849
1856
}
1850
-
1851
- parent = parent . Parent ;
1852
- }
1853
-
1854
- if ( parent != null )
1855
- {
1856
- if ( parent . Parent is CommandExpressionAst && parent . Parent . Parent is PipelineAst )
1857
+ else if ( parent is SwitchStatementAst switchStatement )
1857
1858
{
1858
- // Script block in a hash table, could be something like:
1859
- // dir | ft @{ Expression = { $_ } }
1860
-
1861
- if ( parent . Parent . Parent . Parent is HashtableAst )
1862
- {
1863
- parent = parent . Parent . Parent . Parent ;
1864
- }
1865
- else if ( parent . Parent . Parent . Parent is ArrayLiteralAst && parent . Parent . Parent . Parent . Parent is HashtableAst )
1859
+ parent = switchStatement . Condition ;
1860
+ break ;
1861
+ }
1862
+ else if ( parent is ErrorStatementAst switchErrorStatement && switchErrorStatement . Kind ? . Kind == TokenKind . Switch )
1863
+ {
1864
+ if ( switchErrorStatement . Conditions ? . Count > 0 )
1866
1865
{
1867
- parent = parent . Parent . Parent . Parent . Parent ;
1866
+ parent = switchErrorStatement . Conditions [ 0 ] ;
1868
1867
}
1868
+ break ;
1869
1869
}
1870
-
1871
- if ( parent . Parent is CommandParameterAst )
1870
+ else if ( parent is ScriptBlockExpressionAst )
1872
1871
{
1873
- parent = parent . Parent ;
1872
+ hasSeenScriptBlock = true ;
1874
1873
}
1875
-
1876
- if ( parent is CatchClauseAst catchBlock )
1874
+ else if ( hasSeenScriptBlock )
1877
1875
{
1878
- if ( catchBlock . CatchTypes . Count > 0 )
1876
+ if ( parent is InvokeMemberExpressionAst invokeMember )
1879
1877
{
1880
- foreach ( TypeConstraintAst catchType in catchBlock . CatchTypes )
1878
+ parent = invokeMember . Expression ;
1879
+ break ;
1880
+ }
1881
+ else if ( parent is CommandAst cmdAst && cmdAst . Parent is PipelineAst pipeline && pipeline . PipelineElements . Count > 1 )
1882
+ {
1883
+ // We've found a pipeline with multiple commands, now we need to determine what command came before the command with the scriptblock:
1884
+ // eg Get-Partition in this example: Get-Disk | Get-Partition | Where {$_}
1885
+ var indexOfPreviousCommand = pipeline . PipelineElements . IndexOf ( cmdAst ) - 1 ;
1886
+ if ( indexOfPreviousCommand >= 0 )
1881
1887
{
1882
- Type exceptionType = catchType . TypeName . GetReflectionType ( ) ;
1883
- if ( exceptionType != null && typeof ( Exception ) . IsAssignableFrom ( exceptionType ) )
1884
- {
1885
- inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord < > ) . MakeGenericType ( exceptionType ) ) ) ;
1886
- }
1888
+ parent = pipeline . PipelineElements [ indexOfPreviousCommand ] ;
1889
+ break ;
1887
1890
}
1888
1891
}
1889
- else
1892
+ }
1893
+
1894
+ parent = parent . Parent ;
1895
+ }
1896
+
1897
+ if ( parent is CatchClauseAst catchBlock )
1898
+ {
1899
+ if ( catchBlock . CatchTypes . Count > 0 )
1900
+ {
1901
+ foreach ( TypeConstraintAst catchType in catchBlock . CatchTypes )
1890
1902
{
1891
- inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord ) ) ) ;
1903
+ Type exceptionType = catchType . TypeName . GetReflectionType ( ) ;
1904
+ if ( typeof ( Exception ) . IsAssignableFrom ( exceptionType ) )
1905
+ {
1906
+ inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord < > ) . MakeGenericType ( exceptionType ) ) ) ;
1907
+ }
1892
1908
}
1893
-
1894
- return ;
1895
1909
}
1896
1910
1897
- if ( parent . Parent is CommandAst commandAst )
1911
+ // Either no type constraint was specified, or all the specified catch types were unavailable but we still know it's an error record.
1912
+ if ( inferredTypes . Count == 0 )
1913
+ {
1914
+ inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord ) ) ) ;
1915
+ }
1916
+ }
1917
+ else if ( parent is TrapStatementAst trap )
1918
+ {
1919
+ if ( trap . TrapType is not null )
1898
1920
{
1899
- // We found a command, see if there is a previous command in the pipeline.
1900
- PipelineAst pipelineAst = ( PipelineAst ) commandAst . Parent ;
1901
- var previousCommandIndex = pipelineAst . PipelineElements . IndexOf ( commandAst ) - 1 ;
1902
- if ( previousCommandIndex < 0 )
1921
+ Type exceptionType = trap . TrapType . TypeName . GetReflectionType ( ) ;
1922
+ if ( typeof ( Exception ) . IsAssignableFrom ( exceptionType ) )
1903
1923
{
1904
- return ;
1924
+ inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord < > ) . MakeGenericType ( exceptionType ) ) ) ;
1905
1925
}
1906
-
1907
- AddInferredTypesForDollarUnderbar ( pipelineAst . PipelineElements [ 0 ] , inferredTypes ) ;
1908
-
1909
- return ;
1910
1926
}
1911
-
1912
- if ( parent . Parent is InvokeMemberExpressionAst memberExpression )
1927
+ if ( inferredTypes . Count == 0 )
1913
1928
{
1914
- AddInferredTypesForDollarUnderbar ( memberExpression . Expression , inferredTypes ) ;
1915
-
1916
- return ;
1929
+ inferredTypes . Add ( new PSTypeName ( typeof ( ErrorRecord ) ) ) ;
1917
1930
}
1918
1931
}
1932
+ else if ( parent is not null )
1933
+ {
1934
+ AddInferredTypesForDollarUnderbar ( parent , inferredTypes ) ;
1935
+ }
1936
+
1937
+ return ;
1919
1938
}
1920
1939
1921
1940
// For certain variables, we always know their type, well at least we can assume we know.
@@ -2072,7 +2091,7 @@ private void AddInferredTypesForDollarUnderbar(Ast parentExpression, List<PSType
2072
2091
continue ;
2073
2092
}
2074
2093
2075
- if ( typeof ( IEnumerable ) . IsAssignableFrom ( result . Type ) )
2094
+ if ( result . Type != typeof ( string ) && typeof ( IEnumerable ) . IsAssignableFrom ( result . Type ) )
2076
2095
{
2077
2096
// We can't deduce much from IEnumerable, but we can if it's generic.
2078
2097
var enumerableInterfaces = result . Type . GetInterfaces ( ) ;
0 commit comments