diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
index d4fe0a140a..e99c23e5e1 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
@@ -132,8 +132,8 @@ internal sealed partial class TexlBinding
///
/// The maximum number of selects in a table that will be included in data call.
///
- public const int MaxSelectsToInclude = 100;
-
+ public const int MaxSelectsToInclude = 100;
+
///
/// Default name used to access a Lambda scope.
///
@@ -350,7 +350,7 @@ private TexlBinding(
entityName: EntityName,
propertyName: Property?.InvariantName ?? string.Empty,
allowsSideEffects: bindingConfig.AllowsSideEffects,
- numberIsFloat: bindingConfig.NumberIsFloat,
+ numberIsFloat: bindingConfig.NumberIsFloat,
analysisMode: bindingConfig.AnalysisMode);
}
@@ -431,14 +431,14 @@ public static TexlBinding Run(
var txb = new TexlBinding(glue, scopeResolver, queryOptionsMap, node, resolver, bindingConfig, ruleScope, updateDisplayNames, forceUpdateDisplayNames, rule: rule, features: features, delegationHintProvider: delegationHintProvider);
var vis = new Visitor(txb, resolver, ruleScope, bindingConfig.UseThisRecordForRuleScope, features);
- vis.Run();
-
- // If the expression is dataflow only Ie non-side effecting, void doesn't have any practical use and should be converted to errors.
- if (!bindingConfig.AllowsSideEffects)
- {
- var v2e = new VoidToErrorTexlVisitor(txb);
- v2e.Run();
- }
+ vis.Run();
+
+ // If the expression is dataflow only Ie non-side effecting, void doesn't have any practical use and should be converted to errors.
+ if (!bindingConfig.AllowsSideEffects)
+ {
+ var v2e = new VoidToErrorTexlVisitor(txb);
+ v2e.Run();
+ }
// Determine if a rename has occured at the top level
if (txb.Top is AsNode asNode)
@@ -767,7 +767,7 @@ private bool TryGetEntityInfo(CallNode node, out IExpandInfo info)
// It is possible for function to be null here if it referred to
// a service function from a service we are in the process of
- // deregistering.
+ // deregistering.
// GetInfo on a callNode may return null hence need null conditional operator and it short circuits if null.
return GetInfo(callNode)?.VerifyValue().Function?.TryGetEntityInfo(node, this, out info) ?? false;
}
@@ -2581,43 +2581,43 @@ public override void Visit(BlankNode node)
_txb.SetSelfContainedConstant(node, true);
_txb.SetType(node, DType.ObjNull);
}
-
- // Binding TypeLiteralNode from anywhere other than valid type context should be an error.
- // This ensures that binding of unintended use of TypeLiteralNode eg: "If(Type(Boolean), 1, 2)" will result in an error.
+
+ // Binding TypeLiteralNode from anywhere other than valid type context should be an error.
+ // This ensures that binding of unintended use of TypeLiteralNode eg: "If(Type(Boolean), 1, 2)" will result in an error.
// VisitType method is used to resolve the type of TypeLiteralNode from valid context.
public override void Visit(TypeLiteralNode node)
{
AssertValid();
- Contracts.AssertValue(node);
-
- _txb.SetType(node, DType.Error);
+ Contracts.AssertValue(node);
+
+ _txb.SetType(node, DType.Error);
_txb.ErrorContainer.Error(node, TexlStrings.ErrTypeFunction_UnsupportedUsage);
- }
-
- // Method to bind TypeLiteralNode from valid context where a type is expected.
- // Binding TypeLiteralNode in an expression where a type is not expected invokes the Visit method
- // from normal visitor pattern and results in error.
+ }
+
+ // Method to bind TypeLiteralNode from valid context where a type is expected.
+ // Binding TypeLiteralNode in an expression where a type is not expected invokes the Visit method
+ // from normal visitor pattern and results in error.
private void VisitType(TypeLiteralNode node)
{
AssertValid();
- Contracts.AssertValue(node);
-
- if (_nameResolver == null)
- {
- _txb.SetType(node, DType.Unknown);
- return;
- }
-
- var type = DTypeVisitor.Run(node.TypeRoot, _nameResolver);
-
- if (type.IsValid)
- {
- _txb.SetType(node, type);
- }
- else
- {
- _txb.SetType(node, DType.Error);
- _txb.ErrorContainer.Error(node, TexlStrings.ErrTypeFunction_InvalidTypeExpression, node.ToString());
+ Contracts.AssertValue(node);
+
+ if (_nameResolver == null)
+ {
+ _txb.SetType(node, DType.Unknown);
+ return;
+ }
+
+ var type = DTypeVisitor.Run(node.TypeRoot, _nameResolver);
+
+ if (type.IsValid)
+ {
+ _txb.SetType(node, type);
+ }
+ else
+ {
+ _txb.SetType(node, DType.Error);
+ _txb.ErrorContainer.Error(node, TexlStrings.ErrTypeFunction_InvalidTypeExpression, node.ToString());
}
}
@@ -2834,20 +2834,20 @@ public override void Visit(FirstNameNode node)
return;
}
- DType nodeType = null;
-
- if (scope.ScopeIdentifiers == null || scope.ScopeIdentifiers.Length == 1)
- {
- nodeType = scope.Type;
- }
- else
- {
- // If scope.ScopeIdentifier.Length > 1, it meant the function creates more than 1 scope and the scope types are contained within a record.
- // Example: Join(t1, t2, LeftRecord.a = RightRecord.a, ...)
- // The expression above will create LeftRecord and RightRecord scopes. The scope type will be ![LeftRecord:![...],RightRecord:![...]]
- // Example: Join(t1 As X1, t2 As X2, X1.a = X2.a, ...)
- // The expression above will create LeftRecord and RightRecord scopes. The scope type will be ![X1:![...],X2:![...]]
- nodeType = scope.Type.GetType(nodeName);
+ DType nodeType = null;
+
+ if (scope.ScopeIdentifiers == null || scope.ScopeIdentifiers.Length == 1)
+ {
+ nodeType = scope.Type;
+ }
+ else
+ {
+ // If scope.ScopeIdentifier.Length > 1, it meant the function creates more than 1 scope and the scope types are contained within a record.
+ // Example: Join(t1, t2, LeftRecord.a = RightRecord.a, ...)
+ // The expression above will create LeftRecord and RightRecord scopes. The scope type will be ![LeftRecord:![...],RightRecord:![...]]
+ // Example: Join(t1 As X1, t2 As X2, X1.a = X2.a, ...)
+ // The expression above will create LeftRecord and RightRecord scopes. The scope type will be ![X1:![...],X2:![...]]
+ nodeType = scope.Type.GetType(nodeName);
}
if (!isWholeScope)
@@ -2896,16 +2896,16 @@ public override void Visit(FirstNameNode node)
_txb.SetType(node, DType.Error);
_txb.SetInfo(node, FirstNameInfo.Create(node, default(NameLookupInfo)));
return;
- }
-
- // We have an allowlist of kinds permitted in simple expressions, all of which should be Sync.
- // The IsAsync check is just to be sure we're not introducing async
- // if things are added to the set of valid kinds in the future.
- // As the main point of the "Simple Expression" constraint is to ensure certain expressions are sync
- // but that's harder to communicate to low-code users.
- if (_txb.BindingConfig.EnforceSimpleExpressionConstraint && (!lookupInfo.Kind.IsValidInSimpleExpression() || lookupInfo.IsAsync))
- {
- _txb.ErrorContainer.Error(node, TexlStrings.ErrViolatedSimpleConstraintAccess, node.Ident.Name.Value);
+ }
+
+ // We have an allowlist of kinds permitted in simple expressions, all of which should be Sync.
+ // The IsAsync check is just to be sure we're not introducing async
+ // if things are added to the set of valid kinds in the future.
+ // As the main point of the "Simple Expression" constraint is to ensure certain expressions are sync
+ // but that's harder to communicate to low-code users.
+ if (_txb.BindingConfig.EnforceSimpleExpressionConstraint && (!lookupInfo.Kind.IsValidInSimpleExpression() || lookupInfo.IsAsync))
+ {
+ _txb.ErrorContainer.Error(node, TexlStrings.ErrViolatedSimpleConstraintAccess, node.Ident.Name.Value);
}
var isConstantNamedFormula = false;
@@ -2916,29 +2916,29 @@ public override void Visit(FirstNameNode node)
_txb.SetSetMutable(node, nameSymbol?.Props.CanSetMutate ?? false);
if (lookupInfo.Data is IExternalNamedFormula formula)
{
- isConstantNamedFormula = formula.IsConstant;
-
- // If the definition of the named formula has a delegation warning, every use should also inherit this warning
- if (formula.HasDelegationWarning)
- {
- _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_NF, node.Ident.Name);
+ isConstantNamedFormula = formula.IsConstant;
+
+ // If the definition of the named formula has a delegation warning, every use should also inherit this warning
+ if (formula.HasDelegationWarning)
+ {
+ _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_NF, node.Ident.Name);
}
}
}
- else if (lookupInfo.Kind == BindKind.Data)
- {
- if (lookupInfo.Data is IExternalCdsDataSource or IExternalTabularDataSource)
- {
- _txb.SetMutable(node, true);
- }
- else if (lookupInfo.Data is IExternalDataSource ds)
- {
- _txb.SetMutable(node, ds.IsWritable);
- }
- }
- else if (lookupInfo.Kind == BindKind.ScopeCollection)
- {
- _txb.SetMutable(node, true);
+ else if (lookupInfo.Kind == BindKind.Data)
+ {
+ if (lookupInfo.Data is IExternalCdsDataSource or IExternalTabularDataSource)
+ {
+ _txb.SetMutable(node, true);
+ }
+ else if (lookupInfo.Data is IExternalDataSource ds)
+ {
+ _txb.SetMutable(node, ds.IsWritable);
+ }
+ }
+ else if (lookupInfo.Kind == BindKind.ScopeCollection)
+ {
+ _txb.SetMutable(node, true);
}
else if (lookupInfo.Kind == BindKind.ScopeCollection)
{
@@ -3053,14 +3053,14 @@ public override void Visit(FirstNameNode node)
{
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrInvalidControlReference);
}
- }
-
- if (_txb.BindingConfig.MarkAsAsyncOnLazilyLoadedControlRef &&
- lookupType.IsControl &&
- lookupInfo.Data is IExternalControl control &&
- !control.IsAppGlobalControl)
- {
- _txb.FlagPathAsAsync(_txb.Top);
+ }
+
+ if (_txb.BindingConfig.MarkAsAsyncOnLazilyLoadedControlRef &&
+ lookupType.IsControl &&
+ lookupInfo.Data is IExternalControl control &&
+ !control.IsAppGlobalControl)
+ {
+ _txb.FlagPathAsAsync(_txb.Top);
}
// Update _usesGlobals, _usesResources, etc.
@@ -3594,7 +3594,7 @@ public override void PostVisit(DottedNameNode node)
// If the reference is to Control.Property and the rule for that Property is a constant,
// we need to mark the node as constant, and save the control info so we may look up the
// rule later.
- if (controlInfo?.GetRule(property.InvariantName) is IExternalRule rule &&
+ if (controlInfo?.GetRule(property.InvariantName) is IExternalRule rule &&
rule.IsInvariantExpression)
{
value = controlInfo;
@@ -3853,16 +3853,16 @@ protected DType GetExpandedEntityType(DType expandEntityType, string relatedEnti
// Update the datasource and relatedEntity path.
type.ExpandInfo.UpdateEntityInfo(expandEntityInfo.ParentDataSource, relatedEntityPath);
entityTypes.Add(expandEntityInfo.ExpandPath, type);
- }
- else if (!type.ExpandInfo.ExpandPath.IsReachedFromPath(relatedEntityPath))
- {
- // Expands reached via a different path should have a different relatedentitypath.
- // If we found an expand in the cache but it's not accessed via the same relationship
- // we need to create a different expand info but with the same type.
- // DType.Clone doesn't clone expand info, so we force that with CopyExpandInfo,
- // because that sadly mutates expand info on what should otherwise be an immutable dtype.
+ }
+ else if (!type.ExpandInfo.ExpandPath.IsReachedFromPath(relatedEntityPath))
+ {
+ // Expands reached via a different path should have a different relatedentitypath.
+ // If we found an expand in the cache but it's not accessed via the same relationship
+ // we need to create a different expand info but with the same type.
+ // DType.Clone doesn't clone expand info, so we force that with CopyExpandInfo,
+ // because that sadly mutates expand info on what should otherwise be an immutable dtype.
type = DType.CopyExpandInfo(type.Clone(), type);
- type.ExpandInfo.UpdateEntityInfo(expandEntityInfo.ParentDataSource, relatedEntityPath);
+ type.ExpandInfo.UpdateEntityInfo(expandEntityInfo.ParentDataSource, relatedEntityPath);
}
return type;
@@ -4140,13 +4140,13 @@ private void SetCallNodePurity(CallNode node, CallInfo info)
var infoTexlFunction = info.Function;
if (_txb._glue.IsComponentScopedPropertyFunction(infoTexlFunction))
- {
- // Behavior only component properties should be treated as stateful.
- hasSideEffects |= infoTexlFunction.IsBehaviorOnly;
-
- // At the moment, we're going to treat all invocations of component scoped property functions as stateful.
- // This ensures that we don't lift these function invocations in loops, and that they are re-evaluated every time they are called,
- // which is always correct, although less efficient in some cases.
+ {
+ // Behavior only component properties should be treated as stateful.
+ hasSideEffects |= infoTexlFunction.IsBehaviorOnly;
+
+ // At the moment, we're going to treat all invocations of component scoped property functions as stateful.
+ // This ensures that we don't lift these function invocations in loops, and that they are re-evaluated every time they are called,
+ // which is always correct, although less efficient in some cases.
isStateFul |= true;
}
else
@@ -4312,40 +4312,40 @@ private void UntypedObjectScopeError(CallNode node, TexlFunction maybeFunc, Texl
_txb.SetInfo(node, new CallInfo(maybeFunc, node, null, default, false, _currentScope.Nest));
_txb.SetType(node, maybeFunc.ReturnType);
- }
-
- // Checks if the call node best matches function overloads with UntypedObject/JSON,
- // or the typed overload for the Copilot function
- private bool MatchOverloadWithUntypedOrJSONConversionOrCopilotFunctions(CallNode node, TexlFunction maybeFunc)
- {
- Contracts.AssertValue(node);
- Contracts.AssertValue(maybeFunc);
- Contracts.Assert(maybeFunc.HasTypeArgs);
-
- if (maybeFunc.Name == AsTypeFunction.AsTypeInvariantFunctionName &&
- _txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
- {
- return true;
- }
-
- if (maybeFunc.Name == IsTypeFunction_UO.IsTypeInvariantFunctionName &&
- _txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
- {
- return true;
- }
-
- if (maybeFunc.Name == ParseJSONFunction.ParseJSONInvariantFunctionName &&
- node.Args.Count > 1)
- {
- return true;
- }
-
- if (maybeFunc is CopilotFunction && node.Args.Count > 2)
- {
- return true;
- }
-
- return false;
+ }
+
+ // Checks if the call node best matches function overloads with UntypedObject/JSON,
+ // or the typed overload for the Copilot function
+ private bool MatchOverloadWithUntypedOrJSONConversionOrCopilotFunctions(CallNode node, TexlFunction maybeFunc)
+ {
+ Contracts.AssertValue(node);
+ Contracts.AssertValue(maybeFunc);
+ Contracts.Assert(maybeFunc.HasTypeArgs);
+
+ if (maybeFunc.Name == AsTypeFunction.AsTypeInvariantFunctionName &&
+ _txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
+ {
+ return true;
+ }
+
+ if (maybeFunc.Name == IsTypeFunction_UO.IsTypeInvariantFunctionName &&
+ _txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
+ {
+ return true;
+ }
+
+ if (maybeFunc.Name == ParseJSONFunction.ParseJSONInvariantFunctionName &&
+ node.Args.Count > 1)
+ {
+ return true;
+ }
+
+ if (maybeFunc is CopilotFunction && node.Args.Count > 2)
+ {
+ return true;
+ }
+
+ return false;
}
public override bool PreVisit(CallNode node)
@@ -4366,7 +4366,7 @@ public override bool PreVisit(CallNode node)
if (BuiltinFunctionsCore.OtherKnownFunctions.Contains(node.Head.Name.Value, StringComparer.OrdinalIgnoreCase))
{
_txb.ErrorContainer.Error(node, TexlStrings.ErrUnimplementedFunction, node.Head.Name.Value);
- }
+ }
else if (BuiltinFunctionsCore.TypeHelperFunctions.Contains(node.Head.Name.Value, StringComparer.OrdinalIgnoreCase))
{
_txb.ErrorContainer.Error(node, TexlStrings.ErrKnownTypeHelperFunction, node.Head.Name.Value);
@@ -4400,9 +4400,9 @@ public override bool PreVisit(CallNode node)
// If there are no overloads with lambdas or identifiers, we can continue the visitation and
// yield to the normal overload resolution.
- var overloadsWithLambdasOrIdentifiers = overloads.Where(func => func.HasLambdas || func.HasColumnIdentifiers);
-
- var overloadsWithTypeArgs = overloads.Where(func => func.HasTypeArgs);
+ var overloadsWithLambdasOrIdentifiers = overloads.Where(func => func.HasLambdas || func.HasColumnIdentifiers);
+
+ var overloadsWithTypeArgs = overloads.Where(func => func.HasTypeArgs);
if (!overloadsWithLambdasOrIdentifiers.Any())
{
@@ -4436,23 +4436,23 @@ public override bool PreVisit(CallNode node)
if (overloadsWithTypeArgs.Any() && node.Args.Count > 1)
{
- Contracts.Assert(overloadsWithTypeArgs.Count() == 1);
-
- // Evaluates the first argument; required for the call to
- // MatchOverloadWithUntypedOrJSONConversionFunctions
- var firstArg = node.Args.Children[0];
- firstArg.Accept(this);
-
- var functionWithTypeArg = overloadsWithTypeArgs.First();
-
- // Either one of the untyped JSON conversion functions, or the overload
+ Contracts.Assert(overloadsWithTypeArgs.Count() == 1);
+
+ // Evaluates the first argument; required for the call to
+ // MatchOverloadWithUntypedOrJSONConversionFunctions
+ var firstArg = node.Args.Children[0];
+ firstArg.Accept(this);
+
+ var functionWithTypeArg = overloadsWithTypeArgs.First();
+
+ // Either one of the untyped JSON conversion functions, or the overload
// for the Copilot function that takes type args
- if (MatchOverloadWithUntypedOrJSONConversionOrCopilotFunctions(node, functionWithTypeArg))
+ if (MatchOverloadWithUntypedOrJSONConversionOrCopilotFunctions(node, functionWithTypeArg))
{
- PreVisitTypeArgAndProccesCallNode(node, functionWithTypeArg);
- FinalizeCall(node);
+ PreVisitTypeArgAndProccesCallNode(node, functionWithTypeArg);
+ FinalizeCall(node);
return false;
- }
+ }
startArg++;
}
@@ -4517,16 +4517,16 @@ public override bool PreVisit(CallNode node)
scopeNew = new Scope(node, _currentScope, scopeInfo.ScopeType, skipForInlineRecords: maybeFunc.SkipScopeForInlineRecords);
}
else if (carg > 0)
- {
- // Gets the lesser number between the CallNode chidl args and func.ScopeArgs.
- // There reason why is that the intellisense can visit this code and provide a number of args less than the func.ScopeArgs.
- // Example: Join(|
- var argsCount = Math.Min(node.Args.Children.Count, maybeFunc.ScopeArgs);
- var types = new DType[argsCount];
-
- for (int i = 0; i < argsCount; i++)
- {
- node.Args.Children[i].Accept(this);
+ {
+ // Gets the lesser number between the CallNode chidl args and func.ScopeArgs.
+ // There reason why is that the intellisense can visit this code and provide a number of args less than the func.ScopeArgs.
+ // Example: Join(|
+ var argsCount = Math.Min(node.Args.Children.Count, maybeFunc.ScopeArgs);
+ var types = new DType[argsCount];
+
+ for (int i = 0; i < argsCount; i++)
+ {
+ node.Args.Children[i].Accept(this);
}
// At this point we know the type of the first argument, so we can check for untyped objects
@@ -4536,7 +4536,7 @@ public override bool PreVisit(CallNode node)
scopeInfo = maybeFunc.ScopeInfo;
}
- // Determine the Scope Identifier using the func.ScopeArgs arg
+ // Determine the Scope Identifier using the func.ScopeArgs arg
required = scopeInfo.GetScopeIdent(node.Args.Children.ToArray(), out scopeIdentifiers);
if (scopeInfo.CheckInput(_txb.Features, node, node.Args.Children.ToArray(), out scope, GetScopeArgsTypes(node.Args.Children, argsCount)))
@@ -4614,14 +4614,14 @@ public override bool PreVisit(CallNode node)
{
_currentScopeDsNodeId = dsNode.Id;
}
-
- argTypes[0] = _txb.GetType(nodeInput);
+
+ argTypes[0] = _txb.GetType(nodeInput);
// Get the cursor type for this arg. Note we're not adding document errors at this point.
DType typeScope;
DName[] scopeIdent = default;
var identRequired = false;
- var fArgsValid = true;
+ var fArgsValid = true;
if (scopeInfo.ScopeType != null)
{
@@ -4630,17 +4630,17 @@ public override bool PreVisit(CallNode node)
// For functions with a Scope Type, there is no ScopeIdent needed
}
else
- {
- // Starting from 1 since 0 was visited above.
- for (int i = 1; i < maybeFunc.ScopeArgs; i++)
- {
- _txb.AddVolatileVariables(node, _txb.GetVolatileVariables(args[i]));
- args[i].Accept(this);
- }
-
+ {
+ // Starting from 1 since 0 was visited above.
+ for (int i = 1; i < maybeFunc.ScopeArgs; i++)
+ {
+ _txb.AddVolatileVariables(node, _txb.GetVolatileVariables(args[i]));
+ args[i].Accept(this);
+ }
+
fArgsValid = scopeInfo.CheckInput(_txb.Features, node, args, out typeScope, GetScopeArgsTypes(node.Args.Children, maybeFunc.ScopeArgs));
- // Determine the scope identifier using the first node for lambda params
+ // Determine the scope identifier using the first node for lambda params
identRequired = scopeInfo.GetScopeIdent(args, out scopeIdent);
}
@@ -4713,15 +4713,15 @@ public override bool PreVisit(CallNode node)
_currentScope = (isIdentifier || isLambdaArg) ? scopeNew : scopeNew.Parent;
if (!isIdentifier || maybeFunc.GetIdentifierParamStatus(args[i], _features, i) == TexlFunction.ParamIdentifierStatus.PossiblyIdentifier)
- {
- if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
- {
- args[i].Accept(this);
- _txb.AddVolatileVariables(node, _txb.GetVolatileVariables(args[i]));
- }
-
- argTypes[i] = _txb.GetType(args[i]);
-
+ {
+ if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
+ {
+ args[i].Accept(this);
+ _txb.AddVolatileVariables(node, _txb.GetVolatileVariables(args[i]));
+ }
+
+ argTypes[i] = _txb.GetType(args[i]);
+
Contracts.Assert(argTypes[i].IsValid);
}
else
@@ -4788,17 +4788,17 @@ public override bool PreVisit(CallNode node)
// We fully processed the call, so don't visit children or call PostVisit.
return false;
- }
-
- ///
- /// Get all DType used to compose the scope of the function (func.ScopeArgs).
- ///
- /// Call child nodes.
- /// TexlFunction ScopeArgs property.
- /// DType array.
- private DType[] GetScopeArgsTypes(IReadOnlyList args, int scopeArgs)
- {
- return args.Take(scopeArgs).Select(node => _txb.GetType(node)).ToArray();
+ }
+
+ ///
+ /// Get all DType used to compose the scope of the function (func.ScopeArgs).
+ ///
+ /// Call child nodes.
+ /// TexlFunction ScopeArgs property.
+ /// DType array.
+ private DType[] GetScopeArgsTypes(IReadOnlyList args, int scopeArgs)
+ {
+ return args.Take(scopeArgs).Select(node => _txb.GetType(node)).ToArray();
}
private void FinalizeCall(CallNode node)
@@ -4853,14 +4853,14 @@ private void FinalizeCall(CallNode node)
// Invalid datasources always result in error
if (func.IsBehaviorOnly && !_txb.BindingConfig.AllowsSideEffects)
- {
- if (_txb.BindingConfig.UserDefinitionsMode)
- {
- _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorFunctionInDataUDF);
- }
- else
- {
- _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorPropertyExpected);
+ {
+ if (_txb.BindingConfig.UserDefinitionsMode)
+ {
+ _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorFunctionInDataUDF);
+ }
+ else
+ {
+ _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorPropertyExpected);
}
}
@@ -4868,14 +4868,14 @@ private void FinalizeCall(CallNode node)
else if (func.IsTestOnly && _txb.Property != null && !_txb.Property.IsTestCaseProperty)
{
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrTestPropertyExpected);
- }
- else if ((!func.IsAllowedInSimpleExpressions || _txb.IsAsync(node)) && _txb.BindingConfig.EnforceSimpleExpressionConstraint)
- {
- // Functions that are not allowed in simple expressions cannot be used when the binding config restricts to simple expressions.
- _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrViolatedSimpleConstraintFunction, func.Name);
- }
-
- // Auto-refreshable functions cannot be used in behavior rules.
+ }
+ else if ((!func.IsAllowedInSimpleExpressions || _txb.IsAsync(node)) && _txb.BindingConfig.EnforceSimpleExpressionConstraint)
+ {
+ // Functions that are not allowed in simple expressions cannot be used when the binding config restricts to simple expressions.
+ _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrViolatedSimpleConstraintFunction, func.Name);
+ }
+
+ // Auto-refreshable functions cannot be used in behavior rules.
else if (func.IsAutoRefreshable && _txb.BindingConfig.AllowsSideEffects)
{
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrAutoRefreshNotAllowed);
@@ -4903,12 +4903,12 @@ private void FinalizeCall(CallNode node)
{
_txb.ErrorContainer.EnsureError(node, errorKey, badAncestor.Head.Name);
}
- }
-
- // If the definition of the user-defined function has a delegation warning, every usage should also inherit this warning
- if (func is UserDefinedFunction udf && udf.HasDelegationWarning)
- {
- _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_UDF, udf.Name);
+ }
+
+ // If the definition of the user-defined function has a delegation warning, every usage should also inherit this warning
+ if (func is UserDefinedFunction udf && udf.HasDelegationWarning)
+ {
+ _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_UDF, udf.Name);
}
_txb.CheckAndMarkAsDelegatable(node);
@@ -4973,9 +4973,20 @@ private bool IsIncorrectlySideEffectful(CallNode node, out ErrorResourceKey erro
var ancestorScopeInfo = ancestorCall.Function?.ScopeInfo;
// Check for bad scope modification
- if (ancestorFunc != null && ancestorScopeInfo != null && ds != null && ancestorScopeInfo.IteratesOverScope)
+ if (ancestorFunc != null && ancestorScopeInfo != null && ancestorScopeInfo.IteratesOverScope)
{
- if (ancestorFunc.TryGetDataSource(ancestorScope.Call, _txb, out var ancestorDs) && ancestorDs == ds)
+ if (ds != null && ancestorFunc.TryGetDataSource(ancestorScope.Call, _txb, out var ancestorDs) && ancestorDs == ds)
+ {
+ errorKey = TexlStrings.ErrScopeModificationLambda;
+ badAncestor = ancestorScope.Call;
+ return true;
+ }
+
+ // Also check for global table variables (not external data sources)
+ if (_txb.Features.EnhancedIterationChecks &&
+ TryGetFirstArgGlobalTableVariable(node, out var modifiedVar) &&
+ TryGetFirstArgGlobalTableVariable(ancestorScope.Call, out var iteratedVar) &&
+ modifiedVar == iteratedVar)
{
errorKey = TexlStrings.ErrScopeModificationLambda;
badAncestor = ancestorScope.Call;
@@ -5002,6 +5013,41 @@ private bool IsIncorrectlySideEffectful(CallNode node, out ErrorResourceKey erro
return false;
}
+ // Returns true if the first argument of callNode is a global table variable, and outputs its name.
+ // Used for self modifying variable detection for ForAll and other iterator functions.
+ //
+ // This function will dive deep into the first argument of callNodes. So, it will extract the first argument of:
+ //
+ // top level: ForAll( DS, Set( DS, [...] ) ), lambda: Set( DS, [ ...] ) - match found, self modifying
+ // Self modifying and error produced.
+ //
+ // top level: ForAll( DS, Set( First(DS).Value, ... ) ), lambda: Set( First(DS).Value, ... ) - match NOT found due to lambda (two deep)
+ // We consider this OK and not self modifying as we are modifying a record of DS, not the structure of DS itself which would mess up the iteration.
+ //
+ // top level: ForAll( Filter(DS, Value > 0), Set( DS, [...] ) ), lambda: Set( DS, [...] ) - match NOT found due to top level (two deep)
+ // We consider this OK and not self modifying because DS is not delegable, so a copy is made for Filter to iterate over.
+ private bool TryGetFirstArgGlobalTableVariable(CallNode callNode, out DName variableName)
+ {
+ variableName = default;
+
+ if (callNode.Args.Count < 1)
+ {
+ return false;
+ }
+
+ if (callNode.Args.Children[0] is FirstNameNode firstName)
+ {
+ var info = _txb.GetInfo(firstName);
+ if (info?.Kind == BindKind.PowerFxResolvedObject && _txb.GetType(firstName).IsTable)
+ {
+ variableName = firstName.Ident.Name;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void PostVisit(CallNode node)
{
Contracts.Assert(false, "Should never get here");
@@ -5113,9 +5159,9 @@ private void PreVisitMetadataArg(CallNode node, TexlFunction func)
}
else
{
- if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
- {
- args[i].Accept(this);
+ if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
+ {
+ args[i].Accept(this);
}
}
@@ -5166,78 +5212,78 @@ private void PreVisitMetadataArg(CallNode node, TexlFunction func)
}
_txb.SetType(node, returnType);
- }
-
- // Method to previsit type arg of callnode if it is determined as untyped/json conversion function
- private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func)
- {
+ }
+
+ // Method to previsit type arg of callnode if it is determined as untyped/json conversion function
+ private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func)
+ {
AssertValid();
Contracts.AssertValue(node);
- Contracts.AssertValue(func);
- Contracts.Assert(func.HasTypeArgs);
- Contracts.Assert(node.Args.Count != func.MaxArity || func.ArgIsType(node.Args.Count - 1)); // Type argument must be the last one for valid invocations
-
- var args = node.Args.Children.ToArray();
- var argCount = args.Count();
-
- Contracts.AssertValue(_txb.GetType(args[0]));
-
+ Contracts.AssertValue(func);
+ Contracts.Assert(func.HasTypeArgs);
+ Contracts.Assert(node.Args.Count != func.MaxArity || func.ArgIsType(node.Args.Count - 1)); // Type argument must be the last one for valid invocations
+
+ var args = node.Args.Children.ToArray();
+ var argCount = args.Count();
+
+ Contracts.AssertValue(_txb.GetType(args[0]));
+
if (argCount < func.MinArity || argCount > func.MaxArity)
{
ArityError(func.MinArity, func.MaxArity, node, argCount, _txb.ErrorContainer);
_txb.SetInfo(node, new CallInfo(func, node));
_txb.SetType(node, DType.Error);
return;
- }
-
+ }
+
if (!_features.IsUserDefinedTypesEnabled)
{
_txb.ErrorContainer.Error(node, TexlStrings.ErrUserDefinedTypesDisabled);
_txb.SetInfo(node, new CallInfo(func, node));
_txb.SetType(node, DType.Error);
return;
- }
-
- Contracts.Assert(argCount > 1);
- var typeArg = args[args.Length - 1];
- Contracts.AssertValue(typeArg);
-
- if (typeArg is FirstNameNode typeName)
- {
- if (_nameResolver.LookupType(typeName.Ident.Name, out var typeArgType))
- {
- _txb.SetType(typeName, typeArgType._type);
- _txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, typeArgType._type, DPath.Root, 0)));
- }
- else
- {
- _txb.ErrorContainer.Error(typeArg, TexlStrings.ErrInvalidName, typeName.Ident.Name.Value);
- _txb.SetType(typeArg, DType.Error);
- _txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, DType.Error, DPath.Root, 0)));
-
- _txb.ErrorContainer.Error(node, TexlStrings.ErrInvalidArgumentExpectedType, typeName.Ident.Name.Value);
- _txb.SetInfo(node, new CallInfo(func, node));
- _txb.SetType(node, DType.Error);
- }
- }
- else if (typeArg is TypeLiteralNode typeLiteral)
- {
- VisitType(typeLiteral);
- }
- else
- {
- _txb.ErrorContainer.Error(typeArg, TexlStrings.ErrInvalidArgumentExpectedType, typeArg);
- _txb.SetType(typeArg, DType.Error);
- }
-
- // Process other non-type arguments
- for (var i = 1; i < args.Length - 1; i++)
- {
- args[i].Accept(this);
- }
-
- PostVisit(node.Args);
-
+ }
+
+ Contracts.Assert(argCount > 1);
+ var typeArg = args[args.Length - 1];
+ Contracts.AssertValue(typeArg);
+
+ if (typeArg is FirstNameNode typeName)
+ {
+ if (_nameResolver.LookupType(typeName.Ident.Name, out var typeArgType))
+ {
+ _txb.SetType(typeName, typeArgType._type);
+ _txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, typeArgType._type, DPath.Root, 0)));
+ }
+ else
+ {
+ _txb.ErrorContainer.Error(typeArg, TexlStrings.ErrInvalidName, typeName.Ident.Name.Value);
+ _txb.SetType(typeArg, DType.Error);
+ _txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, DType.Error, DPath.Root, 0)));
+
+ _txb.ErrorContainer.Error(node, TexlStrings.ErrInvalidArgumentExpectedType, typeName.Ident.Name.Value);
+ _txb.SetInfo(node, new CallInfo(func, node));
+ _txb.SetType(node, DType.Error);
+ }
+ }
+ else if (typeArg is TypeLiteralNode typeLiteral)
+ {
+ VisitType(typeLiteral);
+ }
+ else
+ {
+ _txb.ErrorContainer.Error(typeArg, TexlStrings.ErrInvalidArgumentExpectedType, typeArg);
+ _txb.SetType(typeArg, DType.Error);
+ }
+
+ // Process other non-type arguments
+ for (var i = 1; i < args.Length - 1; i++)
+ {
+ args[i].Accept(this);
+ }
+
+ PostVisit(node.Args);
+
var info = _txb.GetInfo(node);
// If PreVisit resulted in errors for the node (and a non-null CallInfo),
@@ -5251,27 +5297,27 @@ private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func)
Contracts.AssertNull(info);
- _txb.SetInfo(node, new CallInfo(func, node));
-
- var returnType = func.ReturnType;
+ _txb.SetInfo(node, new CallInfo(func, node));
+
+ var returnType = func.ReturnType;
var argTypes = args.Select(_txb.GetType).ToArray();
bool fArgsValid;
var checkErrorContainer = new ErrorContainer();
// Typecheck the invocation and infer the return type.
fArgsValid = func.HandleCheckInvocation(_txb, args, argTypes, checkErrorContainer, out returnType, out var _);
-
- if (checkErrorContainer?.HasErrors() == true)
- {
- _txb.ErrorContainer.MergeErrors(checkErrorContainer.GetErrors());
- }
+
+ if (checkErrorContainer?.HasErrors() == true)
+ {
+ _txb.ErrorContainer.MergeErrors(checkErrorContainer.GetErrors());
+ }
if (!fArgsValid)
{
_txb.ErrorContainer.Error(DocumentErrorSeverity.Severe, node.Head.Token, TexlStrings.ErrInvalidArgs_Func, func.Name);
}
- _txb.SetType(node, returnType);
+ _txb.SetType(node, returnType);
}
private void PreVisitBottomUp(CallNode node, int argCountVisited, Scope scopeNew = null)
@@ -5336,10 +5382,10 @@ private void PreVisitBottomUp(CallNode node, int argCountVisited, Scope scopeNew
{
_txb.AddVolatileVariables(args[i], volatileVariables);
}
-
- if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
- {
- args[i].Accept(this);
+
+ if (_txb.GetTypeAllowInvalid(args[i]) != null && !_txb.GetTypeAllowInvalid(args[i]).IsValid)
+ {
+ args[i].Accept(this);
}
// In case weight was added during visitation
@@ -5779,8 +5825,8 @@ public override void PostVisit(TableNode node)
}
}
}
-
- // Tables that are created from sealed records become sealed
+
+ // Tables that are created from sealed records become sealed
// Among other things, this avoids someone writing First( Table( SealedRecord ) ) to remove the seal
DType tableType = exprType.IsValid
? (_features.TableSyntaxDoesntWrapRecords && exprType.IsRecord
diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs
index b8db9e2ce4..0987419b1a 100644
--- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs
+++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs
@@ -4640,33 +4640,33 @@ public void TexlFunctionTypeSemanticsTable_PageableInputs(string script, string
// self modifying, direct to DS delegable DS, enhanced , non-del DS, enhanced , variable DS, enhanced
- [InlineData("ForAll(DS, Collect(DS, {Id:1})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, Refresh(DS)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, Patch(DS,First(DS),{Id:2})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, If(ThisRecord.Id>1, Patch(DS,First(DS),{Id:2}))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, If(Id>1, Patch(DS,First(DS),{Id:2}))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, Collect(DS, {Id:1}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, If(ThisRecord.Id>1,Collect(DS, {Id:1}); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, If(Id>1,Collect(DS, {Id:1}); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Concat(DS, Collect(DS, {Id:1}); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Distinct(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Sum(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Average(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Min(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Max(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("VarP(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("StdevP(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, ClearCollect(DS, {Id:1})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("ForAll(DS, Clear(DS)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("AddColumns(DS, Num, ClearCollect(DS, {Id:1}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Concat(DS, Clear(DS); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Distinct(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Sum(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Average(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Min(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("Max(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("VarP(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
- [InlineData("StdevP(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " Unordered")]
+ [InlineData("ForAll(DS, Collect(DS, {Id:1})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, Refresh(DS)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, Patch(DS,First(DS),{Id:2})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, If(ThisRecord.Id>1, Patch(DS,First(DS),{Id:2}))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, If(Id>1, Patch(DS,First(DS),{Id:2}))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, Collect(DS, {Id:1}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, If(ThisRecord.Id>1,Collect(DS, {Id:1}); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, If(Id>1,Collect(DS, {Id:1}); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Concat(DS, Collect(DS, {Id:1}); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Distinct(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Sum(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Average(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Min(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Max(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("VarP(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("StdevP(DS, Collect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, ClearCollect(DS, {Id:1})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("ForAll(DS, Clear(DS)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, ClearCollect(DS, {Id:1}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Concat(DS, Clear(DS); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Distinct(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Sum(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Average(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Min(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("Max(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("VarP(DS, Clear(DS); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
+ [InlineData("StdevP(DS, ClearCollect(DS, {Id:1}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Unordered", " SelfMod")]
// self modifying, with Filter delegable DS, enhanced , non-del DS, enhanced , variable DS, enhanced
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs
index 22343098fe..2313749d25 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs
@@ -240,8 +240,13 @@ public void RunOneMatchCompare()
[Theory]
[ReplFileSimpleList("MutationScripts")]
public void RunMutationTests_V1(string file)
- {
- RunMutationTestFile(file, Features.PowerFxV1, "PowerFxV1");
+ {
+ var features = new Features(Features.PowerFxV1)
+ {
+ EnhancedIterationChecks = true,
+ };
+
+ RunMutationTestFile(file, features, "PowerFxV1");
}
[Theory]
@@ -252,7 +257,8 @@ public void RunMutationTests_Canvas(string file)
{
TableSyntaxDoesntWrapRecords = true,
ConsistentOneColumnTableResult = true,
- IsUserDefinedTypesEnabled = true,
+ IsUserDefinedTypesEnabled = true,
+ EnhancedIterationChecks = true,
};
// disable:PowerFxV1CompatibilityRules will force the tests specifically for those behaviors to be excluded from this run.
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs
index d263b33535..0dcb897cbb 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs
@@ -636,22 +636,22 @@ public void MutationSuggestionTests(string expression, bool allowSideEffects, pa
// Set self modifying, direct DS delegable DS, enhanced , non-del DS, enhanced , variable DS, enhanced
- [InlineData("ForAll(DS, Set(DS, [{Id:1,Name:\"a\"}])) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
+ [InlineData("ForAll(DS, Set(DS, [{Id:1,Name:\"a\"}])) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
[InlineData("ForAll(DS, Set(First(DS).Id, 3)) ", " Immutable", " Immutable", " Immutable", " Immutable", " Ok", " Ok")]
- [InlineData("ForAll(DS, If(ThisRecord.Id>1, Set(DS, [{Id:1,Name:\"a\"}]))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("ForAll(DS, If(Id>1, Set(DS, [{Id:1,Name:\"a\"}]))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, Set(DS, [{Id:1,Name:\"a\"}]); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
+ [InlineData("ForAll(DS, If(ThisRecord.Id>1, Set(DS, [{Id:1,Name:\"a\"}]))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("ForAll(DS, If(Id>1, Set(DS, [{Id:1,Name:\"a\"}]))) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, Set(DS, [{Id:1,Name:\"a\"}]); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
[InlineData("AddColumns(DS, Num, Set(First(DS).Id, 3); 2) ", " Immutable", " Immutable", " Immutable", " Immutable", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, If(ThisRecord.Id>1,Set(DS, [{Id:1,Name:\"a\"}]); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, If(Id>1,Set(DS, [{Id:1,Name:\"a\"}]); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Concat(DS, Set(DS, [{Id:1,Name:\"a\"}]); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Distinct(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Sum(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Average(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Min(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Max(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("VarP(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("StdevP(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
+ [InlineData("AddColumns(DS, Num, If(ThisRecord.Id>1,Set(DS, [{Id:1,Name:\"a\"}]); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, If(Id>1,Set(DS, [{Id:1,Name:\"a\"}]); 2)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Concat(DS, Set(DS, [{Id:1,Name:\"a\"}]); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Distinct(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Sum(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Average(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Min(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Max(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("VarP(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("StdevP(DS, Set(DS, [{Id:1,Name:\"a\"}]); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
[InlineData("Concat(DS, Set(First(DS).Id, 3); Text(Id)) ", " Immutable", " Immutable", " Immutable", " Immutable", " Ok", " Ok")]
[InlineData("Distinct(DS, Set(First(DS).Id, 3); Id) ", " Immutable", " Immutable", " Immutable", " Immutable", " Ok", " Ok")]
[InlineData("Sum(DS, Set(First(DS).Id, 3); Id) ", " Immutable", " Immutable", " Immutable", " Immutable", " Ok", " Ok")]
@@ -791,16 +791,16 @@ public void MutationSuggestionTests(string expression, bool allowSideEffects, pa
// Remove self modifying, direct DS delegable DS, enhanced , non-del DS, enhanced , variable DS, enhanced
- [InlineData("ForAll(DS, Remove(DS, {})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("AddColumns(DS, Num, Remove(DS, {}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Concat(DS, Remove(DS, {}); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Distinct(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Sum(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Average(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Min(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("Max(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("VarP(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
- [InlineData("StdevP(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " Ok")]
+ [InlineData("ForAll(DS, Remove(DS, {})) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("AddColumns(DS, Num, Remove(DS, {}); 2) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Concat(DS, Remove(DS, {}); Text(Id)) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Distinct(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Sum(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Average(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Min(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("Max(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("VarP(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
+ [InlineData("StdevP(DS, Remove(DS, {}); Id) ", " SelfMod", " SelfMod", " SelfMod", " SelfMod", " Ok", " SelfMod")]
// Remove self modifying, with Sort delegable DS, enhanced , non-del DS, enhanced , variable DS, enhanced
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt
index e50f43b564..89fb147e4a 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt
@@ -36,7 +36,7 @@ Table({Value:2},{Value:4},{Value:6},{Value:8},{Value:10})
Blank()
>> ForAll(t3,Patch(t3,ThisRecord,{Value:4}))
-Blank()
+Errors: Error 10-40: This function cannot operate on the same data source that is used in ForAll.
>> 1;t3
Blank()
@@ -55,7 +55,7 @@ Table()
// Should do nothing as t4 is empty
>> ForAll(t4,Patch(t4,ThisRecord,{Value:4}))
-Table()
+Errors: Error 10-40: This function cannot operate on the same data source that is used in ForAll.
>> 2;t4
Table()