Add Type Inference for $_ / $PSItem in catch{ } blocks (#8020)

This commit is contained in:
Joel Sallow 2018-10-18 00:42:16 -04:00 committed by Ilya
parent 64c1ca8926
commit 33f2f0faaf
3 changed files with 119 additions and 1 deletions

View file

@ -1670,6 +1670,19 @@ namespace System.Management.Automation
} // class ErrorRecord
/// <summary>
/// Dummy generic class for type inference purposes on typed catch blocks.
/// </summary>
/// <typeparam name="TException">Anything that inherits Exception.</typeparam>
internal class ErrorRecord<TException> : ErrorRecord where TException : Exception
{
public new TException Exception { get; }
public ErrorRecord(Exception exception, string errorId, ErrorCategory errorCategory, object targetObject) : base(exception, errorId, errorCategory, targetObject)
{
}
}
/// <summary>
/// Implemented by exception classes which contain additional
/// <see cref="System.Management.Automation.ErrorRecord"/>

View file

@ -1803,7 +1803,7 @@ namespace System.Management.Automation
// $_ is special, see if we're used in a script block in some pipeline.
while (parent != null)
{
if (parent is ScriptBlockExpressionAst)
if (parent is ScriptBlockExpressionAst || parent is CatchClauseAst)
{
break;
}
@ -1832,6 +1832,27 @@ namespace System.Management.Automation
parent = parent.Parent;
}
if (parent is CatchClauseAst catchBlock)
{
if (catchBlock.CatchTypes.Count > 0)
{
foreach (TypeConstraintAst catchType in catchBlock.CatchTypes)
{
Type exceptionType = catchType.TypeName.GetReflectionType();
if (exceptionType != null && typeof(Exception).IsAssignableFrom(exceptionType))
{
inferredTypes.Add(new PSTypeName(typeof(ErrorRecord<>).MakeGenericType(exceptionType)));
}
}
}
else
{
inferredTypes.Add(new PSTypeName(typeof(ErrorRecord)));
}
return;
}
if (parent.Parent is CommandAst commandAst)
{
// We found a command, see if there is a previous command in the pipeline.

View file

@ -926,6 +926,90 @@ Describe "Type inference Tests" -tags "CI" {
$res.Name | Should -Be System.Int32
}
It 'Infers type of variable $_ in catch block' {
$variableAst = { try {} catch { $_ } }.Ast.Find({ param($a) $a -is [System.Management.Automation.Language.VariableExpressionAst] }, $true)
$res = [AstTypeInference]::InferTypeOf($variableAst)
$res | Should -HaveCount 1
$res.Name | Should -Be System.Management.Automation.ErrorRecord
}
It 'Infers type of untyped $_.Exception in catch block' {
$memberAst = { try {} catch { $_.Exception } }.Ast.Find({ param($a) $a -is [System.Management.Automation.Language.MemberExpressionAst] }, $true)
$res = [AstTypeInference]::InferTypeOf($memberAst)
$res | Should -HaveCount 1
$res.Name | Should -Be System.Exception
}
$catchClauseTypes = @(
@{ Type = 'System.ArgumentException' }
@{ Type = 'System.ArgumentNullException' }
@{ Type = 'System.ArgumentOutOfRangeException' }
@{ Type = 'System.Collections.Generic.KeyNotFoundException' }
@{ Type = 'System.DivideByZeroException' }
@{ Type = 'System.FormatException' }
@{ Type = 'System.IndexOutOfRangeException' }
@{ Type = 'System.InvalidOperationException' }
@{ Type = 'System.IO.DirectoryNotFoundException' }
@{ Type = 'System.IO.DriveNotFoundException' }
@{ Type = 'System.IO.FileNotFoundException' }
@{ Type = 'System.IO.PathTooLongException' }
@{ Type = 'System.Management.Automation.CommandNotFoundException' }
@{ Type = 'System.Management.Automation.JobFailedException' }
@{ Type = 'System.Management.Automation.RuntimeException' }
@{ Type = 'System.Management.Automation.ValidationMetadataException' }
@{ Type = 'System.NotImplementedException' }
@{ Type = 'System.NotSupportedException' }
@{ Type = 'System.ObjectDisposedException' }
@{ Type = 'System.OverflowException' }
@{ Type = 'System.PlatformNotSupportedException' }
@{ Type = 'System.RankException' }
@{ Type = 'System.TimeoutException' }
@{ Type = 'System.UriFormatException' }
)
It 'Infers type of $_.Exception in [<Type>] typed catch block' -TestCases $catchClauseTypes {
param($Type)
$memberAst = [scriptblock]::Create("try {} catch [$Type] { `$_.Exception }").Ast.Find(
{ param($a) $a -is [System.Management.Automation.Language.MemberExpressionAst] },
$true
)
$res = [AstTypeInference]::InferTypeOf($memberAst)
$res | Should -HaveCount 1
$res.Name | Should -Be $Type
}
It 'Infers possible types of $_.Exception in multi-typed catch block' {
$memberAst = { try {} catch [System.ArgumentException], [System.NotImplementedException] { $_.Exception } }.Ast.Find(
{ param($a) $a -is [System.Management.Automation.Language.MemberExpressionAst] },
$true
)
$res = [AstTypeInference]::InferTypeOf($memberAst)
$res | Should -HaveCount 2
$res[0].Name | Should -Be System.ArgumentException
$res[1].Name | Should -Be System.NotImplementedException
}
It 'Infers type of $_.Exception in each successive catch block' {
$memberAst = {
try {}
catch [System.ArgumentException] { $_.Exception }
catch { $_.Exception }
}.Ast.FindAll(
{ param($a) $a -is [System.Management.Automation.Language.MemberExpressionAst] },
$true
)
$res = foreach ($item in $memberAst) { [AstTypeInference]::InferTypeOf($item) }
$res | Should -HaveCount 2
$res[0].Name | Should -Be System.ArgumentException
$res[1].Name | Should -Be System.Exception
}
It 'Infers type of function member' {
$res = [AstTypeInference]::InferTypeOf( {
class X {