mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Improve the type inference for binary expressions (#25030)
This commit is contained in:
@@ -718,14 +718,176 @@ namespace System.Management.Automation
|
||||
|
||||
object ICustomAstVisitor.VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
|
||||
{
|
||||
// TODO: Handle other kinds of expressions on the right side.
|
||||
if (binaryExpressionAst.Operator == TokenKind.As && binaryExpressionAst.Right is TypeExpressionAst typeExpression)
|
||||
switch (binaryExpressionAst.Operator)
|
||||
{
|
||||
var type = typeExpression.TypeName.GetReflectionType();
|
||||
var psTypeName = type != null ? new PSTypeName(type) : new PSTypeName(typeExpression.TypeName.FullName);
|
||||
return new[] { psTypeName };
|
||||
case TokenKind.And:
|
||||
case TokenKind.Ccontains:
|
||||
case TokenKind.Cin:
|
||||
case TokenKind.Cnotcontains:
|
||||
case TokenKind.Cnotin:
|
||||
case TokenKind.Icontains:
|
||||
case TokenKind.Iin:
|
||||
case TokenKind.Inotcontains:
|
||||
case TokenKind.Inotin:
|
||||
case TokenKind.Is:
|
||||
case TokenKind.IsNot:
|
||||
case TokenKind.Or:
|
||||
case TokenKind.Xor:
|
||||
// Always returns a bool
|
||||
return BinaryExpressionAst.BoolTypeNameArray;
|
||||
|
||||
case TokenKind.As:
|
||||
// TODO: Handle other kinds of expressions on the right side.
|
||||
if (binaryExpressionAst.Right is TypeExpressionAst typeExpression)
|
||||
{
|
||||
var type = typeExpression.TypeName.GetReflectionType();
|
||||
var psTypeName = type != null ? new PSTypeName(type) : new PSTypeName(typeExpression.TypeName.FullName);
|
||||
return new[] { psTypeName };
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenKind.Ceq:
|
||||
case TokenKind.Cge:
|
||||
case TokenKind.Cgt:
|
||||
case TokenKind.Cle:
|
||||
case TokenKind.Clike:
|
||||
case TokenKind.Clt:
|
||||
case TokenKind.Cmatch:
|
||||
case TokenKind.Cne:
|
||||
case TokenKind.Cnotlike:
|
||||
case TokenKind.Cnotmatch:
|
||||
case TokenKind.Ieq:
|
||||
case TokenKind.Ige:
|
||||
case TokenKind.Igt:
|
||||
case TokenKind.Ile:
|
||||
case TokenKind.Ilike:
|
||||
case TokenKind.Ilt:
|
||||
case TokenKind.Imatch:
|
||||
case TokenKind.Ine:
|
||||
case TokenKind.Inotlike:
|
||||
case TokenKind.Inotmatch:
|
||||
// Returns a bool or filtered output from the left hand side if it's enumerable
|
||||
var comparisonOutput = new List<PSTypeName>() { new(typeof(bool)) };
|
||||
comparisonOutput.AddRange(InferTypes(binaryExpressionAst.Left));
|
||||
return comparisonOutput;
|
||||
|
||||
case TokenKind.Creplace:
|
||||
case TokenKind.Format:
|
||||
case TokenKind.Ireplace:
|
||||
case TokenKind.Join:
|
||||
// Always returns a string
|
||||
return BinaryExpressionAst.StringTypeNameArray;
|
||||
|
||||
case TokenKind.Csplit:
|
||||
case TokenKind.Isplit:
|
||||
// Always returns a string array
|
||||
return BinaryExpressionAst.StringArrayTypeNameArray;
|
||||
|
||||
case TokenKind.QuestionQuestion:
|
||||
// Can return left or right hand side
|
||||
var nullCoalescingOutput = InferTypes(binaryExpressionAst.Left).ToList();
|
||||
nullCoalescingOutput.AddRange(InferTypes(binaryExpressionAst.Right));
|
||||
return nullCoalescingOutput.Distinct();
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return InferTypes(binaryExpressionAst.Left);
|
||||
|
||||
List<PSTypeName> lhsTypes = InferTypes(binaryExpressionAst.Left).ToList();
|
||||
if (lhsTypes.Count == 0)
|
||||
{
|
||||
return lhsTypes;
|
||||
}
|
||||
|
||||
string methodName;
|
||||
switch (binaryExpressionAst.Operator)
|
||||
{
|
||||
case TokenKind.Divide:
|
||||
methodName = "op_Division";
|
||||
break;
|
||||
|
||||
case TokenKind.Minus:
|
||||
methodName = "op_Subtraction";
|
||||
break;
|
||||
|
||||
case TokenKind.Multiply:
|
||||
methodName = "op_Multiply";
|
||||
break;
|
||||
|
||||
case TokenKind.Plus:
|
||||
methodName = "op_Addition";
|
||||
break;
|
||||
|
||||
case TokenKind.Rem:
|
||||
methodName = "op_Modulus";
|
||||
break;
|
||||
|
||||
case TokenKind.Shl:
|
||||
methodName = "op_LeftShift";
|
||||
break;
|
||||
|
||||
case TokenKind.Shr:
|
||||
methodName = "op_RightShift";
|
||||
break;
|
||||
|
||||
default:
|
||||
return lhsTypes;
|
||||
}
|
||||
|
||||
List<PSTypeName> rhsTypes = InferTypes(binaryExpressionAst.Right).ToList();
|
||||
HashSet<string> addedReturnTypes = new HashSet<string>();
|
||||
List<PSTypeName> result = new List<PSTypeName>();
|
||||
foreach (PSTypeName lType in lhsTypes)
|
||||
{
|
||||
if (lType.Type is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (MethodInfo method in lType.Type.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
if (!method.Name.Equals(methodName, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rhsTypes.Count == 0)
|
||||
{
|
||||
if (addedReturnTypes.Add(method.ReturnType.FullName))
|
||||
{
|
||||
result.Add(new PSTypeName(method.ReturnType));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ParameterInfo[] methodParams = method.GetParameters();
|
||||
if (methodParams.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (PSTypeName rType in rhsTypes)
|
||||
{
|
||||
if (rType.Type is not null && rType.Type.IsAssignableTo(methodParams[1].ParameterType))
|
||||
{
|
||||
if (addedReturnTypes.Add(method.ReturnType.FullName))
|
||||
{
|
||||
result.Add(new PSTypeName(method.ReturnType));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
result.AddRange(lhsTypes);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
object ICustomAstVisitor.VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
|
||||
|
||||
@@ -7591,6 +7591,8 @@ namespace System.Management.Automation.Language
|
||||
}
|
||||
|
||||
internal static readonly PSTypeName[] BoolTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(bool)) };
|
||||
internal static readonly PSTypeName[] StringTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(string)) };
|
||||
internal static readonly PSTypeName[] StringArrayTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(string[])) };
|
||||
|
||||
#region Visitors
|
||||
|
||||
|
||||
@@ -268,6 +268,56 @@ Describe "Type inference Tests" -tags "CI" {
|
||||
$res.Name | Should -Be 'System.Management.ManagementObject#root\cimv2\Win32_Process'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with a bool operator as bool" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
(1..10) -contains 5
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 1
|
||||
$res.Name | Should -Be 'System.Boolean'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with a string operator as string" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
(1..10) -join ','
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 1
|
||||
$res.Name | Should -Be 'System.String'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with a split operator as string array" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
"Test:Value" -split ':'
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 1
|
||||
$res.Name | Should -Be 'System.String[]'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with a comparison operator as bool + the left hand side type" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
("Hello", "World") -eq "Hello"
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 2
|
||||
$res.Name[0] | Should -Be 'System.Boolean'
|
||||
$res.Name[1] | Should -Be 'System.String[]'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with a null coalescing operator as left and right hand side types" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
"NotNull" ?? 10
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 2
|
||||
$res.Name[0] | Should -Be 'System.String'
|
||||
$res.Name[1] | Should -Be 'System.Int32'
|
||||
}
|
||||
|
||||
It "Infers type from binary expression with an overridden operator" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
(Get-Date) - (Get-Date)
|
||||
}.Ast)
|
||||
$res.Count | Should -Be 1
|
||||
$res.Name | Should -Be 'System.TimeSpan'
|
||||
}
|
||||
|
||||
It "Infers type from DATA statement" {
|
||||
$res = [AstTypeInference]::InferTypeOf( {
|
||||
DATA {
|
||||
|
||||
Reference in New Issue
Block a user