Add Type Inference for $_ / $PSItem in catch{ } blocks (#8020)
This commit is contained in:
parent
64c1ca8926
commit
33f2f0faaf
|
@ -1670,6 +1670,19 @@ namespace System.Management.Automation
|
||||||
|
|
||||||
} // class ErrorRecord
|
} // 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>
|
/// <summary>
|
||||||
/// Implemented by exception classes which contain additional
|
/// Implemented by exception classes which contain additional
|
||||||
/// <see cref="System.Management.Automation.ErrorRecord"/>
|
/// <see cref="System.Management.Automation.ErrorRecord"/>
|
||||||
|
|
|
@ -1803,7 +1803,7 @@ namespace System.Management.Automation
|
||||||
// $_ is special, see if we're used in a script block in some pipeline.
|
// $_ is special, see if we're used in a script block in some pipeline.
|
||||||
while (parent != null)
|
while (parent != null)
|
||||||
{
|
{
|
||||||
if (parent is ScriptBlockExpressionAst)
|
if (parent is ScriptBlockExpressionAst || parent is CatchClauseAst)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1832,6 +1832,27 @@ namespace System.Management.Automation
|
||||||
parent = parent.Parent;
|
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)
|
if (parent.Parent is CommandAst commandAst)
|
||||||
{
|
{
|
||||||
// We found a command, see if there is a previous command in the pipeline.
|
// We found a command, see if there is a previous command in the pipeline.
|
||||||
|
|
|
@ -926,6 +926,90 @@ Describe "Type inference Tests" -tags "CI" {
|
||||||
$res.Name | Should -Be System.Int32
|
$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' {
|
It 'Infers type of function member' {
|
||||||
$res = [AstTypeInference]::InferTypeOf( {
|
$res = [AstTypeInference]::InferTypeOf( {
|
||||||
class X {
|
class X {
|
||||||
|
|
Loading…
Reference in a new issue