Skip to content

Commit 0330bc1

Browse files
authored
Add Simple Expression constraint (#2980)
Some properties that depend on ThisItem scope will have significant performance costs to async operations. We've discussed and landed on restricting the set of things that can be accessed from those properties to just sync functions, the thisitem scope, enums, and operators. This PR is a first pass at this, but it won't be immediately exposed to customers, so we'll iterate on it.
1 parent 225787e commit 0330bc1

33 files changed

+283
-31
lines changed

src/libraries/Microsoft.PowerFx.Core/Binding/BindKind.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,16 @@ internal enum BindKind
8585

8686
Lim,
8787
}
88+
89+
internal static class BindKindExtensions
90+
{
91+
internal static bool IsValidInSimpleExpression(this BindKind kind)
92+
{
93+
// Starting simple, we could expand this set over time.
94+
return kind == BindKind.ThisItem ||
95+
kind == BindKind.Enum ||
96+
kind == BindKind.LambdaField ||
97+
kind == BindKind.LambdaFullRecord;
98+
}
99+
}
88100
}

src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,8 +1061,7 @@ internal bool TryGetDataSourceInfo(TexlNode node, out IExternalDataSource dataSo
10611061
return dataSourceInfo != null;
10621062

10631063
case NodeKind.DottedName:
1064-
IExpandInfo info;
1065-
if (TryGetEntityInfo(node.AsDottedName(), out info))
1064+
if (TryGetEntityInfo(node.AsDottedName(), out IExpandInfo info))
10661065
{
10671066
dataSourceInfo = info.ParentDataSource;
10681067
return dataSourceInfo != null;
@@ -2897,6 +2896,16 @@ public override void Visit(FirstNameNode node)
28972896
_txb.SetType(node, DType.Error);
28982897
_txb.SetInfo(node, FirstNameInfo.Create(node, default(NameLookupInfo)));
28992898
return;
2899+
}
2900+
2901+
// We have an allowlist of kinds permitted in simple expressions, all of which should be Sync.
2902+
// The IsAsync check is just to be sure we're not introducing async
2903+
// if things are added to the set of valid kinds in the future.
2904+
// As the main point of the "Simple Expression" constraint is to ensure certain expressions are sync
2905+
// but that's harder to communicate to low-code users.
2906+
if (_txb.BindingConfig.EnforceSimpleExpressionConstraint && (!lookupInfo.Kind.IsValidInSimpleExpression() || lookupInfo.IsAsync))
2907+
{
2908+
_txb.ErrorContainer.Error(node, TexlStrings.ErrViolatedSimpleConstraintAccess, node.Ident.Name.Value);
29002909
}
29012910

29022911
var isConstantNamedFormula = false;
@@ -4849,9 +4858,14 @@ private void FinalizeCall(CallNode node)
48494858
else if (func.IsTestOnly && _txb.Property != null && !_txb.Property.IsTestCaseProperty)
48504859
{
48514860
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrTestPropertyExpected);
4861+
}
4862+
else if ((!func.IsAllowedInSimpleExpressions || _txb.IsAsync(node)) && _txb.BindingConfig.EnforceSimpleExpressionConstraint)
4863+
{
4864+
// Functions that are not allowed in simple expressions cannot be used when the binding config restricts to simple expressions.
4865+
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrViolatedSimpleConstraintFunction, func.Name);
48524866
}
4853-
4854-
// Auto-refreshable functions cannot be used in behavior rules.
4867+
4868+
// Auto-refreshable functions cannot be used in behavior rules.
48554869
else if (func.IsAutoRefreshable && _txb.BindingConfig.AllowsSideEffects)
48564870
{
48574871
_txb.ErrorContainer.EnsureError(node, TexlStrings.ErrAutoRefreshNotAllowed);

src/libraries/Microsoft.PowerFx.Core/Binding/BindingConfig.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,31 @@ internal class BindingConfig
2525

2626
public bool MarkAsAsyncOnLazilyLoadedControlRef { get; } = false;
2727

28-
public bool UserDefinitionsMode { get; }
28+
public bool UserDefinitionsMode { get; }
29+
30+
/// <summary>
31+
/// Enforces the expression must be "Simple", i.e. no async, no global access, no impure functions
32+
/// This is mainly used for controls like combobox where the expression is used to populate a dropdown
33+
/// and must be FAST to run on large datasets.
34+
/// </summary>
35+
internal bool EnforceSimpleExpressionConstraint { get; }
2936

30-
public BindingConfig(bool allowsSideEffects = false, bool useThisRecordForRuleScope = false, bool numberIsFloat = false, bool analysisMode = false, bool markAsAsyncOnLazilyLoadedControlRef = false, bool userDefinitionsMode = false)
37+
public BindingConfig(
38+
bool allowsSideEffects = false,
39+
bool useThisRecordForRuleScope = false,
40+
bool numberIsFloat = false,
41+
bool analysisMode = false,
42+
bool markAsAsyncOnLazilyLoadedControlRef = false,
43+
bool userDefinitionsMode = false,
44+
bool enforceSimpleExpressions = false)
3145
{
3246
AllowsSideEffects = allowsSideEffects;
3347
UseThisRecordForRuleScope = useThisRecordForRuleScope;
3448
NumberIsFloat = numberIsFloat;
3549
AnalysisMode = analysisMode;
3650
MarkAsAsyncOnLazilyLoadedControlRef = markAsAsyncOnLazilyLoadedControlRef;
37-
UserDefinitionsMode = userDefinitionsMode;
51+
UserDefinitionsMode = userDefinitionsMode;
52+
EnforceSimpleExpressionConstraint = enforceSimpleExpressions;
3853
}
3954

4055
public BindingConfig Clone(bool allowsSideEffects)
@@ -45,7 +60,8 @@ public BindingConfig Clone(bool allowsSideEffects)
4560
numberIsFloat: this.NumberIsFloat,
4661
analysisMode: this.AnalysisMode,
4762
markAsAsyncOnLazilyLoadedControlRef: this.MarkAsAsyncOnLazilyLoadedControlRef,
48-
userDefinitionsMode: this.UserDefinitionsMode);
63+
userDefinitionsMode: this.UserDefinitionsMode,
64+
enforceSimpleExpressions: this.EnforceSimpleExpressionConstraint);
4965
}
5066
}
5167
}

src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ internal abstract class TexlFunction : IFunction
115115
// abstract will force them to do so.
116116
public abstract bool IsSelfContained { get; }
117117

118+
/// <summary>
119+
/// Allowlist for functions that can be used in "simple" expressions.
120+
/// </summary>
121+
public virtual bool IsAllowedInSimpleExpressions => false;
122+
118123
// Return true if the function is stateless (same result for same input), or false otherwise.
119124
public virtual bool IsStateless => true;
120125

src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,5 +931,7 @@ internal static class TexlStrings
931931
public static ErrorResourceKey ErrJoinArgIsNotAsNode = new ErrorResourceKey("ErrJoinArgIsNotAsNode");
932932
public static ErrorResourceKey ErrJoinAtLeastOneRigthRecordField = new ErrorResourceKey("ErrJoinAtLeastOneRigthRecordField");
933933
public static ErrorResourceKey ErrJoinDottedNameleft = new ErrorResourceKey("ErrJoinDottedNameleft");
934+
public static ErrorResourceKey ErrViolatedSimpleConstraintAccess = new ErrorResourceKey("ErrViolatedSimpleConstraintAccess");
935+
public static ErrorResourceKey ErrViolatedSimpleConstraintFunction = new ErrorResourceKey("ErrViolatedSimpleConstraintFunction");
934936
}
935937
}

src/libraries/Microsoft.PowerFx.Core/Public/CheckResult.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,8 @@ public IEnumerable<string> GetFunctionNames(bool anonymizeUnknownPublicFunctions
766766
// for error reporting.
767767
internal interface IOperationStatus
768768
{
769-
public IEnumerable<ExpressionError> Errors { get; }
769+
IEnumerable<ExpressionError> Errors { get; }
770770

771-
public bool IsSuccess { get; }
771+
bool IsSuccess { get; }
772772
}
773773
}

src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Boolean.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ internal sealed class BooleanFunction : BuiltinFunction
2626

2727
public override bool IsSelfContained => true;
2828

29-
public override bool SupportsParamCoercion => false;
29+
public override bool SupportsParamCoercion => false;
30+
31+
public override bool IsAllowedInSimpleExpressions => true;
3032

3133
public BooleanFunction()
3234
: base(BooleanInvariantFunctionName, TexlStrings.AboutBoolean, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.String)
@@ -86,7 +88,9 @@ internal sealed class BooleanNFunction : BuiltinFunction
8688
{
8789
public override bool IsSelfContained => true;
8890

89-
public override bool SupportsParamCoercion => false;
91+
public override bool SupportsParamCoercion => false;
92+
93+
public override bool IsAllowedInSimpleExpressions => true;
9094

9195
public BooleanNFunction()
9296
: base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanN, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.Number)
@@ -153,7 +157,9 @@ internal sealed class BooleanWFunction : BuiltinFunction
153157
{
154158
public override bool IsSelfContained => true;
155159

156-
public override bool SupportsParamCoercion => false;
160+
public override bool SupportsParamCoercion => false;
161+
162+
public override bool IsAllowedInSimpleExpressions => true;
157163

158164
// Reusing BooleanN strings as they are generic for numbers
159165
public BooleanWFunction()
@@ -225,7 +231,9 @@ internal sealed class BooleanBFunction : BuiltinFunction
225231
{
226232
public override bool IsSelfContained => true;
227233

228-
public override bool SupportsParamCoercion => false;
234+
public override bool SupportsParamCoercion => false;
235+
236+
public override bool IsAllowedInSimpleExpressions => true;
229237

230238
public BooleanBFunction()
231239
: base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanB, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.Boolean)
@@ -326,7 +334,9 @@ internal sealed class BooleanLFunction : BuiltinFunction
326334
{
327335
public override bool IsSelfContained => true;
328336

329-
public override bool SupportsParamCoercion => false;
337+
public override bool SupportsParamCoercion => false;
338+
339+
public override bool IsAllowedInSimpleExpressions => true;
330340

331341
public BooleanLFunction()
332342
: base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanL, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.OptionSetValue)
@@ -411,7 +421,9 @@ internal sealed class BooleanFunction_UO : BuiltinFunction
411421
{
412422
public override bool IsSelfContained => true;
413423

414-
public override bool SupportsParamCoercion => false;
424+
public override bool SupportsParamCoercion => false;
425+
426+
public override bool IsAllowedInSimpleExpressions => true;
415427

416428
public BooleanFunction_UO()
417429
: base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBoolean, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.UntypedObject)

src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Char.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public override ArgPreprocessor GetArgPreprocessor(int index, int argCount)
2626

2727
public override bool IsSelfContained => true;
2828

29+
public override bool IsAllowedInSimpleExpressions => true;
30+
2931
public CharFunction()
3032
: base("Char", TexlStrings.AboutChar, FunctionCategories.Text, DType.String, 0, 1, 1, DType.Number)
3133
{

src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Concatenate.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ internal sealed class ConcatenateFunction : BuiltinFunction
2222
{
2323
public override bool IsSelfContained => true;
2424

25+
public override bool IsAllowedInSimpleExpressions => true;
26+
2527
public ConcatenateFunction()
2628
: base("Concatenate", TexlStrings.AboutConcatenate, FunctionCategories.Text, DType.String, 0, 1, int.MaxValue)
2729
{

src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/DateTime.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public override ArgPreprocessor GetArgPreprocessor(int index, int argCount)
2828

2929
public override bool IsSelfContained => true;
3030

31-
public override bool HasPreciseErrors => true;
31+
public override bool HasPreciseErrors => true;
32+
33+
public override bool IsAllowedInSimpleExpressions => true;
3234

3335
public DateFunction()
3436
: base("Date", TexlStrings.AboutDate, FunctionCategories.DateTime, DType.Date, 0, 3, 3, DType.Number, DType.Number, DType.Number)
@@ -46,7 +48,9 @@ internal abstract class ExtractDateTimeFunctionBase : BuiltinFunction
4648
{
4749
public override bool HasPreciseErrors => true;
4850

49-
public override bool IsSelfContained => true;
51+
public override bool IsSelfContained => true;
52+
53+
public override bool IsAllowedInSimpleExpressions => true;
5054

5155
public ExtractDateTimeFunctionBase(string name, TexlStrings.StringGetter description)
5256
: base(name, description, FunctionCategories.DateTime, DType.Number, 0, 1, 1, DType.DateTime)
@@ -86,7 +90,9 @@ public override ArgPreprocessor GetArgPreprocessor(int index, int argCount)
8690

8791
public override bool IsSelfContained => true;
8892

89-
public override bool HasPreciseErrors => true;
93+
public override bool HasPreciseErrors => true;
94+
95+
public override bool IsAllowedInSimpleExpressions => true;
9096

9197
public TimeFunction()
9298
: base("Time", TexlStrings.AboutTime, FunctionCategories.DateTime, DType.Time, 0, 3, 4, DType.Number, DType.Number, DType.Number, DType.Number)
@@ -110,7 +116,9 @@ public override ArgPreprocessor GetArgPreprocessor(int index, int argCount)
110116

111117
public override bool IsSelfContained => true;
112118

113-
public override bool HasPreciseErrors => true;
119+
public override bool HasPreciseErrors => true;
120+
121+
public override bool IsAllowedInSimpleExpressions => true;
114122

115123
public DateTimeFunction()
116124
: base("DateTime", TexlStrings.AboutDateTime, FunctionCategories.DateTime, DType.DateTime, 0, 6, 7, DType.Number, DType.Number, DType.Number, DType.Number, DType.Number, DType.Number, DType.Number)
@@ -232,7 +240,9 @@ internal sealed class WeekdayFunction : BuiltinFunction
232240
{
233241
public override bool IsSelfContained => true;
234242

235-
public override bool HasPreciseErrors => true;
243+
public override bool HasPreciseErrors => true;
244+
245+
public override bool IsAllowedInSimpleExpressions => true;
236246

237247
public WeekdayFunction()
238248
: base("Weekday", TexlStrings.AboutWeekday, FunctionCategories.DateTime, DType.Number, 0, 1, 2, DType.DateTime, BuiltInEnums.StartOfWeekEnum.FormulaType._type)
@@ -269,7 +279,9 @@ internal sealed class WeekNumFunction : BuiltinFunction
269279
{
270280
public override bool IsSelfContained => true;
271281

272-
public override bool HasPreciseErrors => true;
282+
public override bool HasPreciseErrors => true;
283+
284+
public override bool IsAllowedInSimpleExpressions => true;
273285

274286
public WeekNumFunction()
275287
: base("WeekNum", TexlStrings.AboutWeekNum, FunctionCategories.DateTime, DType.Number, 0, 1, 2, DType.DateTime, BuiltInEnums.StartOfWeekEnum.FormulaType._type)
@@ -367,7 +379,9 @@ internal abstract class DateTimeGenericFunction : BuiltinFunction
367379
{
368380
public override bool IsSelfContained => true;
369381

370-
public override bool HasPreciseErrors => true;
382+
public override bool HasPreciseErrors => true;
383+
384+
public override bool IsAllowedInSimpleExpressions => true;
371385

372386
protected DateTimeGenericFunction(string name, TexlStrings.StringGetter description, DType returnType)
373387
: base(name, description, FunctionCategories.DateTime, returnType, 0, 1, 2, DType.String, DType.String)
@@ -445,7 +459,9 @@ public DateTimeValueFunction()
445459
// DateAdd(timestamp: d, delta: n, [ unit: TimeUnits ]) : d
446460
internal sealed class DateAddFunction : BuiltinFunction
447461
{
448-
public override bool IsSelfContained => true;
462+
public override bool IsSelfContained => true;
463+
464+
public override bool IsAllowedInSimpleExpressions => true;
449465

450466
public DateAddFunction()
451467
: base("DateAdd", TexlStrings.AboutDateAdd, FunctionCategories.DateTime, DType.DateTime, 0, 2, 3, DType.DateTime, DType.Number, BuiltInEnums.TimeUnitEnum.FormulaType._type)
@@ -627,7 +643,9 @@ internal sealed class DateDiffFunction : BuiltinFunction
627643
{
628644
public override bool IsSelfContained => true;
629645

630-
public override bool HasPreciseErrors => true;
646+
public override bool HasPreciseErrors => true;
647+
648+
public override bool IsAllowedInSimpleExpressions => true;
631649

632650
public DateDiffFunction()
633651
: base("DateDiff", TexlStrings.AboutDateDiff, FunctionCategories.DateTime, DType.Number, 0, 2, 3, DType.DateTime, DType.DateTime, BuiltInEnums.TimeUnitEnum.FormulaType._type)
@@ -784,7 +802,9 @@ internal sealed class DateValueFunction_UO : BuiltinFunction
784802
{
785803
public override bool HasPreciseErrors => true;
786804

787-
public override bool IsSelfContained => true;
805+
public override bool IsSelfContained => true;
806+
807+
public override bool IsAllowedInSimpleExpressions => true;
788808

789809
public DateValueFunction_UO()
790810
: base(DateValueFunction.DateValueInvariantFunctionName, TexlStrings.AboutDateValue, FunctionCategories.DateTime, DType.Date, 0, 1, 1, DType.UntypedObject)
@@ -802,7 +822,9 @@ internal sealed class TimeValueFunction_UO : BuiltinFunction
802822
{
803823
public override bool HasPreciseErrors => true;
804824

805-
public override bool IsSelfContained => true;
825+
public override bool IsSelfContained => true;
826+
827+
public override bool IsAllowedInSimpleExpressions => true;
806828

807829
public TimeValueFunction_UO()
808830
: base(TimeValueFunction.TimeValueFunctionInvariantName, TexlStrings.AboutTimeValue, FunctionCategories.DateTime, DType.Time, 0, 1, 1, DType.UntypedObject)
@@ -820,7 +842,9 @@ internal sealed class DateTimeValueFunction_UO : BuiltinFunction
820842
{
821843
public override bool HasPreciseErrors => true;
822844

823-
public override bool IsSelfContained => true;
845+
public override bool IsSelfContained => true;
846+
847+
public override bool IsAllowedInSimpleExpressions => true;
824848

825849
public DateTimeValueFunction_UO()
826850
: base(DateTimeValueFunction.DateTimeValueInvariantFunctionName, TexlStrings.AboutDateTimeValue, FunctionCategories.DateTime, DType.DateTime, 0, 1, 1, DType.UntypedObject)

0 commit comments

Comments
 (0)