Improve the type inference for binary expressions (#25030)

This commit is contained in:
MartinGC94
2025-02-20 10:39:29 +05:00
committed by GitHub
parent 1b03005d1a
commit f47b14e7ff
3 changed files with 220 additions and 6 deletions
@@ -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 {