-
Notifications
You must be signed in to change notification settings - Fork 334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Aggregate types in UDF #2775
base: main
Are you sure you want to change the base?
Changes from all commits
3046a16
9a6f43c
a4aa143
b386621
7f5a525
422a9b9
00c51ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
using Microsoft.CodeAnalysis; | ||
using Microsoft.PowerFx.Core.App; | ||
using Microsoft.PowerFx.Core.App.Controls; | ||
using Microsoft.PowerFx.Core.App.ErrorContainers; | ||
using Microsoft.PowerFx.Core.Binding; | ||
using Microsoft.PowerFx.Core.Binding.BindInfo; | ||
using Microsoft.PowerFx.Core.Entities; | ||
|
@@ -57,6 +58,8 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) | |
|
||
public override bool SupportsParamCoercion => true; | ||
|
||
public override bool HasPreciseErrors => true; | ||
|
||
private const int MaxParameterCount = 30; | ||
|
||
public TexlNode UdfBody { get; } | ||
|
@@ -77,6 +80,27 @@ public override bool TryGetDataSource(CallNode callNode, TexlBinding binding, ou | |
|
||
public bool HasDelegationWarning => _binding?.ErrorContainer.GetErrors().Any(error => error.MessageKey.Contains("SuggestRemoteExecutionHint")) ?? false; | ||
|
||
public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap) | ||
{ | ||
if (!base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap)) | ||
{ | ||
return false; | ||
} | ||
|
||
for (int i = 0; i < argTypes.Length; i++) | ||
{ | ||
if ((argTypes[i].IsTableNonObjNull || argTypes[i].IsRecordNonObjNull) && | ||
!ParamTypes[i].Accepts(argTypes[i], exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules, true) && | ||
!argTypes[i].CoercesTo(ParamTypes[i], true, false, context.Features, true)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
errors.EnsureError(DocumentErrorSeverity.Severe, args[i], TexlStrings.ErrBadSchema_ExpectedType, ParamTypes[i].GetKindString()); | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="UserDefinedFunction"/> class. | ||
/// </summary> | ||
|
@@ -167,15 +191,27 @@ public void CheckTypesOnDeclaration(CheckTypesContext context, DType actualBodyR | |
Contracts.AssertValue(actualBodyReturnType); | ||
Contracts.AssertValue(binding); | ||
|
||
if (!ReturnType.Accepts(actualBodyReturnType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules)) | ||
if (!ReturnType.Accepts( | ||
actualBodyReturnType, | ||
exact: true, | ||
useLegacyDateTimeAccepts: false, | ||
usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules, | ||
restrictiveAggregateTypes: true)) | ||
{ | ||
if (actualBodyReturnType.CoercesTo(ReturnType, true, false, context.Features)) | ||
if (actualBodyReturnType.CoercesTo(ReturnType, true, false, context.Features, restrictiveAggregateTypes: true)) | ||
{ | ||
_binding.SetCoercedType(binding.Top, ReturnType); | ||
} | ||
else | ||
{ | ||
var node = UdfBody is VariadicOpNode variadicOpNode ? variadicOpNode.Children.Last() : UdfBody; | ||
|
||
if ((ReturnType.IsTable && actualBodyReturnType.IsTable) || (ReturnType.IsRecord && actualBodyReturnType.IsRecord)) | ||
{ | ||
binding.ErrorContainer.EnsureError(DocumentErrorSeverity.Severe, node, TexlStrings.ErrUDF_ReturnTypeSchemaDoesNotMatch, ReturnType.GetKindString()); | ||
return; | ||
} | ||
|
||
binding.ErrorContainer.EnsureError(DocumentErrorSeverity.Severe, node, TexlStrings.ErrUDF_ReturnTypeDoesNotMatch, ReturnType.GetKindString(), actualBodyReturnType.GetKindString()); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1851,12 +1851,13 @@ private bool AcceptsEntityType(DType type, bool usePowerFxV1CompatibilityRules) | |
/// <param name="useLegacyDateTimeAccepts">Legacy rules for accepting date/time types.</param> | ||
/// <param name="usePowerFxV1CompatibilityRules">Use PFx v1 compatibility rules if enabled (less | ||
/// permissive Accepts relationships).</param> | ||
/// <param name="restrictiveAggregateTypes">Flag to restrict using aggregate types with more fields than expected.</param> | ||
/// <returns> | ||
/// True if <see cref="DType"/> accepts <paramref name="type"/>, false otherwise. | ||
/// </returns> | ||
public bool Accepts(DType type, bool exact, bool useLegacyDateTimeAccepts, bool usePowerFxV1CompatibilityRules) | ||
public bool Accepts(DType type, bool exact, bool useLegacyDateTimeAccepts, bool usePowerFxV1CompatibilityRules, bool restrictiveAggregateTypes = false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
return Accepts(type, out _, out _, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules); | ||
return Accepts(type, out _, out _, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules, restrictiveAggregateTypes); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -1885,10 +1886,11 @@ public bool Accepts(DType type, bool exact, bool useLegacyDateTimeAccepts, bool | |
/// <param name="useLegacyDateTimeAccepts">Legacy rules for accepting date/time types.</param> | ||
/// <param name="usePowerFxV1CompatibilityRules">Use PFx v1 compatibility rules if enabled (less | ||
/// permissive Accepts relationships).</param> | ||
/// <param name="restrictiveAggregateTypes">Flag to restrict using aggregate types with more fields than expected.</param> | ||
/// <returns> | ||
/// True if <see cref="DType"/> accepts <paramref name="type"/>, false otherwise. | ||
/// </returns> | ||
public virtual bool Accepts(DType type, out KeyValuePair<string, DType> schemaDifference, out DType schemaDifferenceType, bool exact, bool useLegacyDateTimeAccepts, bool usePowerFxV1CompatibilityRules) | ||
public virtual bool Accepts(DType type, out KeyValuePair<string, DType> schemaDifference, out DType schemaDifferenceType, bool exact, bool useLegacyDateTimeAccepts, bool usePowerFxV1CompatibilityRules, bool restrictiveAggregateTypes = false) | ||
{ | ||
AssertValid(); | ||
type.AssertValid(); | ||
|
@@ -1941,7 +1943,7 @@ bool DefaultReturnValue(DType targetType) => | |
|
||
if (Kind == type.Kind) | ||
{ | ||
return TreeAccepts(this, TypeTree, type.TypeTree, out schemaDifference, out schemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules); | ||
return TreeAccepts(this, TypeTree, type.TypeTree, out schemaDifference, out schemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules, restrictiveAggregateTypes); | ||
} | ||
|
||
accepts = type.Kind == DKind.Unknown || type.Kind == DKind.Deferred; | ||
|
@@ -1955,7 +1957,7 @@ bool DefaultReturnValue(DType targetType) => | |
|
||
if (Kind == type.Kind || type.IsExpandEntity) | ||
{ | ||
return TreeAccepts(this, TypeTree, type.TypeTree, out schemaDifference, out schemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules); | ||
return TreeAccepts(this, TypeTree, type.TypeTree, out schemaDifference, out schemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules, restrictiveAggregateTypes); | ||
} | ||
|
||
accepts = (IsMultiSelectOptionSet() && TypeTree.GetPairs().First().Value.OptionSetInfo == type.OptionSetInfo) || type.Kind == DKind.Unknown || type.Kind == DKind.Deferred; | ||
|
@@ -2175,7 +2177,7 @@ bool DefaultReturnValue(DType targetType) => | |
} | ||
|
||
// Implements Accepts for Record and Table types. | ||
private static bool TreeAccepts(DType parentType, TypeTree treeDst, TypeTree treeSrc, out KeyValuePair<string, DType> schemaDifference, out DType treeSrcSchemaDifferenceType, bool exact = true, bool useLegacyDateTimeAccepts = false, bool usePowerFxV1CompatibilityRules = false) | ||
private static bool TreeAccepts(DType parentType, TypeTree treeDst, TypeTree treeSrc, out KeyValuePair<string, DType> schemaDifference, out DType treeSrcSchemaDifferenceType, bool exact = true, bool useLegacyDateTimeAccepts = false, bool usePowerFxV1CompatibilityRules = false, bool restrictiveAggregateTypes = false) | ||
{ | ||
treeDst.AssertValid(); | ||
treeSrc.AssertValid(); | ||
|
@@ -2215,7 +2217,7 @@ private static bool TreeAccepts(DType parentType, TypeTree treeDst, TypeTree tre | |
return false; | ||
} | ||
|
||
if (!pairDst.Value.Accepts(type, out var recursiveSchemaDifference, out var recursiveSchemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules)) | ||
if (!pairDst.Value.Accepts(type, out var recursiveSchemaDifference, out var recursiveSchemaDifferenceType, exact, useLegacyDateTimeAccepts, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules, restrictiveAggregateTypes)) | ||
{ | ||
if (!TryGetDisplayNameForColumn(parentType, pairDst.Key, out var colName)) | ||
{ | ||
|
@@ -2237,6 +2239,17 @@ private static bool TreeAccepts(DType parentType, TypeTree treeDst, TypeTree tre | |
} | ||
} | ||
|
||
if (restrictiveAggregateTypes) | ||
{ | ||
foreach (var pairSrc in treeSrc) | ||
{ | ||
if (!treeDst.Contains(pairSrc.Key)) | ||
{ | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
|
@@ -3141,17 +3154,17 @@ public bool ContainsControlType(DPath path) | |
(n.Type.IsAggregate && n.Type.ContainsControlType(DPath.Root))); | ||
} | ||
|
||
public bool CoercesTo(DType typeDest, bool aggregateCoercion, bool isTopLevelCoercion, Features features) | ||
public bool CoercesTo(DType typeDest, bool aggregateCoercion, bool isTopLevelCoercion, Features features, bool restrictiveAggregateTypes = false) | ||
{ | ||
return CoercesTo(typeDest, out _, aggregateCoercion, isTopLevelCoercion, features); | ||
return CoercesTo(typeDest, out _, aggregateCoercion, isTopLevelCoercion, features, restrictiveAggregateTypes); | ||
} | ||
|
||
public bool CoercesTo(DType typeDest, out bool isSafe, bool aggregateCoercion, bool isTopLevelCoercion, Features features) | ||
public bool CoercesTo(DType typeDest, out bool isSafe, bool aggregateCoercion, bool isTopLevelCoercion, Features features, bool restrictiveAggregateTypes = false) | ||
{ | ||
return CoercesTo(typeDest, out isSafe, out _, out _, out _, aggregateCoercion, isTopLevelCoercion, features); | ||
return CoercesTo(typeDest, out isSafe, out _, out _, out _, aggregateCoercion, isTopLevelCoercion, features, restrictiveAggregateTypes); | ||
} | ||
|
||
public bool AggregateCoercesTo(DType typeDest, out bool isSafe, out DType coercionType, out KeyValuePair<string, DType> schemaDifference, out DType schemaDifferenceType, Features features, bool aggregateCoercion = true) | ||
public bool AggregateCoercesTo(DType typeDest, out bool isSafe, out DType coercionType, out KeyValuePair<string, DType> schemaDifference, out DType schemaDifferenceType, Features features, bool aggregateCoercion = true, bool restrictiveAggregateTypes = false) | ||
{ | ||
Contracts.Assert(IsAggregate); | ||
|
||
|
@@ -3196,7 +3209,8 @@ public bool AggregateCoercesTo(DType typeDest, out bool isSafe, out DType coerci | |
out schemaDifferenceType, | ||
aggregateCoercion: true, | ||
isTopLevelCoercion: false, | ||
features); | ||
features, | ||
restrictiveAggregateTypes); | ||
} | ||
|
||
if (Kind != typeDest.Kind) | ||
|
@@ -3231,7 +3245,8 @@ public bool AggregateCoercesTo(DType typeDest, out bool isSafe, out DType coerci | |
out var fieldSchemaDifferenceType, | ||
aggregateCoercion, | ||
isTopLevelCoercion: false, | ||
features); | ||
features, | ||
restrictiveAggregateTypes); | ||
|
||
// This is the attempted coercion type. If we fail, we need to know this for error handling | ||
coercionType = coercionType.Add(typedName.Name, fieldCoercionType); | ||
|
@@ -3259,6 +3274,17 @@ public bool AggregateCoercesTo(DType typeDest, out bool isSafe, out DType coerci | |
isSafe &= fieldIsSafe; | ||
} | ||
|
||
if (restrictiveAggregateTypes) | ||
{ | ||
foreach (var typedName in GetNames(DPath.Root)) | ||
{ | ||
if (!typeDest.TryGetType(typedName.Name, out _)) | ||
{ | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return isValid; | ||
} | ||
|
||
|
@@ -3273,7 +3299,8 @@ public virtual bool CoercesTo( | |
out DType schemaDifferenceType, | ||
bool aggregateCoercion, | ||
bool isTopLevelCoercion, | ||
Features features) | ||
Features features, | ||
bool restrictiveAggregateTypes = false) | ||
{ | ||
AssertValid(); | ||
Contracts.Assert(typeDest.IsValid); | ||
|
@@ -3290,7 +3317,7 @@ public virtual bool CoercesTo( | |
return false; | ||
} | ||
|
||
if (typeDest.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules)) | ||
if (typeDest.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules, restrictiveAggregateTypes)) | ||
{ | ||
coercionType = typeDest; | ||
return true; | ||
|
@@ -3335,7 +3362,8 @@ public virtual bool CoercesTo( | |
out schemaDifference, | ||
out schemaDifferenceType, | ||
features, | ||
aggregateCoercion); | ||
aggregateCoercion, | ||
restrictiveAggregateTypes); | ||
} | ||
|
||
var subtypeCoerces = SubtypeCoercesTo( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4221,6 +4221,10 @@ | |
<value>The stated function return type '{0}' does not match the return type of the function body '{1}'.</value> | ||
<comment>This error message shows up when expected return type does not match with actual return type. The arguments '{0}' and '{1}' will be replaced with data types. For example, "The stated function return type 'Number' does not match the return type of the function body 'Table'"</comment> | ||
</data> | ||
<data name="ErrUDF_ReturnTypeSchemaDoesNotMatch" xml:space="preserve"> | ||
<value>The schema of stated function return type '{0}' does not match the schema of return type of the function body.</value> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This message is a little confusing. In the example from the test |
||
<comment>This error message shows up when expected return type schema does not match with schema of actual return type. The arguments '{0}' will be replaced with aggregate data types. For example, "The schema of stated function return type 'Table' does not match the schema of return type of the function body."</comment> | ||
</data> | ||
<data name="ErrUDF_TooManyParameters" xml:space="preserve"> | ||
<value>Function {0} has too many parameters. User-defined functions support up to {1} parameters.</value> | ||
<comment>This error message shows up when a user tries to define a function with too many parameters. {0} - the name of the user-defined function, {1} - the max number of parameters allowed.</comment> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use a named argument to make it easier to understand what this parameter means?