Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/System.Management.Automation/engine/ErrorPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,19 @@ public override string ToString()

} // 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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST
// $_ 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;
}
Expand Down Expand Up @@ -1832,6 +1832,27 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST
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.
Expand Down
84 changes: 84 additions & 0 deletions test/powershell/engine/Api/TypeInference.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down