Skip to content

Commit caa821f

Browse files
authored
Updated option set testing (#2446)
Fixes a bug in Canvas apps coercing an option set value to number. There were missing checks for pre-V1 semantics in DType.cs. But this bug, along with the other recent fixes, is because we were not testing with actual option sets, instead relying on enums since those can be built for all the backing kinds. In general, enums and option sets aren't that different in V1, but they are very different in pre-V1 which is what Canvas uses today. This has been corrected in this PR. We now run tests with the test enums registered as option sets instead, mimicing the semantics in Canvas for Dataverse number and Boolean option sets. These new typed option sets are not exposed generally, as we should look at that implementation more closely, today it translates between string values more than it needs to. By doing this testing, another bug was found in the interpeter, where we weren't back converting boolean values to the option set, for example in `Text( If( false, OptionSet.Value, true ) )`. C# now has the same semantics as Canvas for Booleans. Number and other backing kinds may also want this behavior long term, but since those aren't yet supported we have time to decide how to handle that scenario. Without any changes, a `CalculatedOptionSetValue` will be returned for the label if coerced to a string, but the value itself will be correct.
1 parent 0f75d9f commit caa821f

8 files changed

Lines changed: 2346 additions & 5 deletions

File tree

src/libraries/Microsoft.PowerFx.Core/Types/DType.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,7 +3414,7 @@ public virtual bool CoercesTo(
34143414
DateTime.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
34153415
Time.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
34163416
Date.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
3417-
(Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && OptionSetInfo.CanCoerceToBackingKind);
3417+
(Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules));
34183418
break;
34193419
case DKind.Currency:
34203420
// Ill-formatted strings coerce to null; unsafe.
@@ -3435,7 +3435,7 @@ public virtual bool CoercesTo(
34353435
DateTime.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
34363436
Date.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
34373437
Time.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) ||
3438-
(Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && OptionSetInfo.CanCoerceToBackingKind);
3438+
(Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules));
34393439
break;
34403440
case DKind.String:
34413441
doesCoerce = Kind != DKind.Color && Kind != DKind.Control && Kind != DKind.DataEntity && Kind != DKind.OptionSet && Kind != DKind.View && Kind != DKind.Polymorphic && Kind != DKind.File && Kind != DKind.LargeImage;
@@ -3518,7 +3518,7 @@ public virtual bool CoercesTo(
35183518

35193519
break;
35203520
case DKind.Color:
3521-
doesCoerce = Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Color && OptionSetInfo.CanCoerceToBackingKind;
3521+
doesCoerce = Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Color && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules);
35223522
break;
35233523
case DKind.Enum:
35243524
return CoercesTo(

src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUnary.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67
using Microsoft.PowerFx.Core.Errors;
78
using Microsoft.PowerFx.Core.IR;
89
using Microsoft.PowerFx.Core.IR.Nodes;
910
using Microsoft.PowerFx.Core.Localization;
11+
using Microsoft.PowerFx.Core.Utils;
1012
using Microsoft.PowerFx.Interpreter;
1113
using Microsoft.PowerFx.Interpreter.Exceptions;
1214
using Microsoft.PowerFx.Types;
@@ -1070,7 +1072,20 @@ public static OptionSetValue NumberToOptionSet(IRContext irContext, NumberValue[
10701072

10711073
public static OptionSetValue BooleanToOptionSet(IRContext irContext, BooleanValue[] args)
10721074
{
1073-
return new OptionSetValue(irContext, "CalculatedOptionSetValue", (OptionSetValueType)irContext.ResultType, (bool)args[0].Value);
1075+
var optionSetInfo = ((OptionSetValueType)irContext.ResultType)._type.OptionSetInfo;
1076+
1077+
if (optionSetInfo != null && optionSetInfo.BackingKind == Core.Types.DKind.Boolean && optionSetInfo.OptionNames.Count() == 2)
1078+
{
1079+
foreach (var option in optionSetInfo.OptionNames)
1080+
{
1081+
if (optionSetInfo.TryGetValue(option, out var value) && (bool)value.ExecutionValue == (bool)args[0].Value)
1082+
{
1083+
return value;
1084+
}
1085+
}
1086+
}
1087+
1088+
throw CommonExceptions.RuntimeMisMatch;
10741089
}
10751090

10761091
private static System.Drawing.Color ToColor(double doubValue)

src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,26 @@ Errors: Error 24-28: Invalid argument type (Enum (TestYeaNay)). Expecting a Enum
273273
>> TestXORYesNo( TestYesNo.No, TestYesNo.Yes )
274274
true
275275

276+
>> If( false, TestYeaNay.Yea, false )
277+
TestYeaNay.Nay
278+
279+
>> Text( If( false, TestYeaNay.Yea, false ) )
280+
"Nay"
281+
282+
// 10 is a part of the option set. In the future we may want to have this return .X instead.
283+
>> If( false, TestNumberCompareNumericCoerceFrom.V, 10 )
284+
TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue
285+
286+
>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) )
287+
"CalculatedOptionSetValue"
288+
289+
// 11 is not a part of the option set.
290+
>> If( false, TestNumberCompareNumericCoerceFrom.V, 11 )
291+
TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue
292+
293+
>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) )
294+
"CalculatedOptionSetValue"
295+
276296
//===========================================================================================================
277297
//
278298
// 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number
@@ -284,9 +304,33 @@ true
284304
>> Int( TestNumberCoerceTo.V )
285305
5
286306

307+
>> Power( TestNumberCoerceTo.V, 2 )
308+
25
309+
310+
>> TestSignNumber( TestNumberCoerceTo.V )
311+
1
312+
313+
>> TestSignDecimal( TestNumberCoerceTo.V )
314+
1
315+
287316
>> Power( TestNumberCoerceTo.V, TestNumberCoerceTo.V )
288317
3125
289318

319+
>> Int( TestNumberCompareNumeric.V )
320+
Errors: Error 0-3: The function 'Int' has some invalid arguments.|Error 29-31: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Decimal value instead.
321+
322+
>> Power( TestNumberCompareNumeric.V, 2 )
323+
Errors: Error 0-5: The function 'Power' has some invalid arguments.|Error 31-33: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead.
324+
325+
>> TestSignNumber( TestNumberCompareNumeric.V )
326+
Errors: Error 40-42: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead.|Error 0-14: The function 'TestSignNumber' has some invalid arguments.
327+
328+
>> TestSignDecimal( TestNumberCompareNumeric.V )
329+
Errors: Error 41-43: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Decimal value instead.|Error 0-15: The function 'TestSignDecimal' has some invalid arguments.
330+
331+
>> Power( TestNumberCompareNumeric.V, TestNumberCompareNumeric.V )
332+
Errors: Error 0-5: The function 'Power' has some invalid arguments.|Error 31-33: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead.|Error 59-61: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead.
333+
290334
// Unless there is a specific reason, CanCoerceToBackingKind is expected to be true for most Boolean option sets
291335

292336
>> TestYesNo.Yes && TestYeaNay.Yea
@@ -724,7 +768,10 @@ true
724768
false
725769

726770
>> If( false, TestYesNo.Yes, false )
727-
TestYesNo.CalculatedOptionSetValue
771+
TestYesNo.No
772+
773+
>> Text( If( false, TestYesNo.Yes, false ) )
774+
"No"
728775

729776
// Can be used as a Boolean (CanCoerceToBackingKind = true)
730777

0 commit comments

Comments
 (0)