diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs index fc22bfbfb0..37e4806f84 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs @@ -3414,7 +3414,7 @@ public virtual bool CoercesTo( DateTime.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || Time.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || Date.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || - (Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && OptionSetInfo.CanCoerceToBackingKind); + (Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules)); break; case DKind.Currency: // Ill-formatted strings coerce to null; unsafe. @@ -3435,7 +3435,7 @@ public virtual bool CoercesTo( DateTime.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || Date.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || Time.Accepts(this, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: usePowerFxV1CompatibilityRules) || - (Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && OptionSetInfo.CanCoerceToBackingKind); + (Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Number && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules)); break; case DKind.String: 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( break; case DKind.Color: - doesCoerce = Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Color && OptionSetInfo.CanCoerceToBackingKind; + doesCoerce = Kind == DKind.OptionSetValue && OptionSetInfo != null && OptionSetInfo.BackingKind == DKind.Color && (OptionSetInfo.CanCoerceToBackingKind || !usePowerFxV1CompatibilityRules); break; case DKind.Enum: return CoercesTo( diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUnary.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUnary.cs index e8bf412ab7..339e20422c 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUnary.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUnary.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.PowerFx.Core.Errors; using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Core.IR.Nodes; using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Interpreter; using Microsoft.PowerFx.Interpreter.Exceptions; using Microsoft.PowerFx.Types; @@ -1070,7 +1072,20 @@ public static OptionSetValue NumberToOptionSet(IRContext irContext, NumberValue[ public static OptionSetValue BooleanToOptionSet(IRContext irContext, BooleanValue[] args) { - return new OptionSetValue(irContext, "CalculatedOptionSetValue", (OptionSetValueType)irContext.ResultType, (bool)args[0].Value); + var optionSetInfo = ((OptionSetValueType)irContext.ResultType)._type.OptionSetInfo; + + if (optionSetInfo != null && optionSetInfo.BackingKind == Core.Types.DKind.Boolean && optionSetInfo.OptionNames.Count() == 2) + { + foreach (var option in optionSetInfo.OptionNames) + { + if (optionSetInfo.TryGetValue(option, out var value) && (bool)value.ExecutionValue == (bool)args[0].Value) + { + return value; + } + } + } + + throw CommonExceptions.RuntimeMisMatch; } private static System.Drawing.Color ToColor(double doubValue) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt index 869ff54a07..3a5dc0bd99 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt @@ -273,6 +273,26 @@ Errors: Error 24-28: Invalid argument type (Enum (TestYeaNay)). Expecting a Enum >> TestXORYesNo( TestYesNo.No, TestYesNo.Yes ) true +>> If( false, TestYeaNay.Yea, false ) +TestYeaNay.Nay + +>> Text( If( false, TestYeaNay.Yea, false ) ) +"Nay" + +// 10 is a part of the option set. In the future we may want to have this return .X instead. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) ) +"CalculatedOptionSetValue" + +// 11 is not a part of the option set. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) ) +"CalculatedOptionSetValue" + //=========================================================================================================== // // 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number @@ -284,9 +304,33 @@ true >> Int( TestNumberCoerceTo.V ) 5 +>> Power( TestNumberCoerceTo.V, 2 ) +25 + +>> TestSignNumber( TestNumberCoerceTo.V ) +1 + +>> TestSignDecimal( TestNumberCoerceTo.V ) +1 + >> Power( TestNumberCoerceTo.V, TestNumberCoerceTo.V ) 3125 +>> Int( TestNumberCompareNumeric.V ) +Errors: Error 0-3: The function 'Int' has some invalid arguments.|Error 29-31: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Decimal value instead. + +>> Power( TestNumberCompareNumeric.V, 2 ) +Errors: Error 0-5: The function 'Power' has some invalid arguments.|Error 31-33: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead. + +>> TestSignNumber( TestNumberCompareNumeric.V ) +Errors: Error 40-42: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Number value instead.|Error 0-14: The function 'TestSignNumber' has some invalid arguments. + +>> TestSignDecimal( TestNumberCompareNumeric.V ) +Errors: Error 41-43: Invalid argument type (Enum (TestNumberCompareNumeric)). Expecting a Decimal value instead.|Error 0-15: The function 'TestSignDecimal' has some invalid arguments. + +>> Power( TestNumberCompareNumeric.V, TestNumberCompareNumeric.V ) +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. + // Unless there is a specific reason, CanCoerceToBackingKind is expected to be true for most Boolean option sets >> TestYesNo.Yes && TestYeaNay.Yea @@ -724,7 +768,10 @@ true false >> If( false, TestYesNo.Yes, false ) -TestYesNo.CalculatedOptionSetValue +TestYesNo.No + +>> Text( If( false, TestYesNo.Yes, false ) ) +"No" // Can be used as a Boolean (CanCoerceToBackingKind = true) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets.txt new file mode 100644 index 0000000000..a0e4874350 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets.txt @@ -0,0 +1,855 @@ +#SETUP: StronglyTypedBuiltinEnums,PowerFxV1CompatibilityRules,AllEnumsPlusTestOptionSetsSetup,RegEx,EnableJsonFunctions + +// **** Using test enums AS OPTION SETS, V1 compat. **** +// Some enum varients are not represented in the Builtin set, including for example, any Boolean enums. +// The companion _BuiltInEnums version of this file uses only the built in enums. +// This version of the file usess Option Sets insetead of Enums, which helps test the use of host registered option sets, such as Dataverse option sets. + +// Strongly typed enums were strengthened to: +// 0. Strongly typed enum usage is the most common scenario and what Intellisense will suggest. +// 1. Avoid passing the wrong kind of enum to a function. For example JSON( [1,2,3], Match.IgnoreCase ) +// 2. Avoid passing an enum where a scalar was expected, except for text backed enums. For example Mid( "foo", StartOfWeek.Tuesday ) +// 3. Avoid passing a scalar where an enum was expected, excepf for text backed enums. For example Weekday( Now(), 12 ) +// +// Default operations with backing type +// 4. Equals/not equals between enum values of the same enum is always supported. For example, StartOfWeek.Tuesday = StartOfWeek.Monday +// 5. By default, Equals/not equals with the backing kind is not supported. For example, StartOfWeek.Tuesday = 12 +// 6. By default, Order comparisons between number based enums are not supported, by default. For example StartOfWeek.Tuesday < StartOfWeek.Monday +// 7. By default, math operations between number based enums are never supported. For example, StartOfWeek.Tuesday + StartOfWeek.Monday +// 8. By default, Boolean operations between Boolean based enums is not supported, but can be overriden with CanCoerceToBackingKind +// 9. If the underlying value is desired, the Text, Value, Decimal, Float, and Boolean functions can be called to get the backing value. +// +// In addition, there are flags for each option set that govern how it can be used. Default is no flags, used by Dataverse option sets. +// 10. CanCoerceFromBackingKind - For example, Match which allows a string in place of the enum +// 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number +// 12. CanConcatenateStronglyTyped (text only) - For example, JSONFormat which can concatenate different members together to create a new member +// 13. CanCompareNumeric (numbers only) - For example, ErrorKind can compare values +// 14. CanConcatenateStronglyTyped & CanCoerceFromBackingKind - An important combination, used by Match, allows strings and enums to be mixed +// +// Misc +// 15. Since there is no longer an Accepts relationship between enums and their backing kinds, more likely to get Void results +// 16. Everything coerces to string +// 17. Everything equals compares to Blank() (ObjNull), can order compare with numbers and CanCompareNumerics set +// +// Examples +// 18. TestYesNo - Standard DV Boolean backed option set, with CoerceToBackingKind and CoerceFromBackingKind + +//============================================================================================================ +// +// 0. Strongly typed enum usage is the most common scenario and what Intellisense will suggest. +// + +>> TestColorInvert( Color.Blue ) +RGBA(255,255,0,1) + +>> TestColorBlueRampInvert( TestBlueRamp.Blue25 ) +RGBA(64,64,0,1) + +>> TestXORYesNo( TestYesNo.Yes, TestYesNo.No ) +true + +//============================================================================================================ +// +// 1. Avoid passing the wrong kind of enum to a function. For example JSON( [1,2,3], Match.IgnoreCase ) +// + +>> TestXORYesNo( TestYeaNay.Yea, TestYeaNay.Nay ) +Errors: Error 24-28: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 40-44: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 0-12: The function 'TestXORYesNo' has some invalid arguments. + +>> TestColorBlueRampInvert( TestRedRamp.Red25 ) +Errors: Error 36-42: Invalid argument type (OptionSetValue (TestRedRamp)). Expecting a OptionSetValue (TestBlueRamp) value instead.|Error 0-23: The function 'TestColorBlueRampInvert' has some invalid arguments. + +>> TestColorBlueRampInvert( Color.Purple ) +Errors: Error 30-37: Invalid argument type (Enum (Color)). Expecting a OptionSetValue (TestBlueRamp) value instead.|Error 0-23: The function 'TestColorBlueRampInvert' has some invalid arguments. + +>> TestColorBlueRampInvert( ColorFade( Color.Yellow, 25% ) ) +Errors: Error 25-55: Invalid argument type (Color). Expecting a OptionSetValue (TestBlueRamp) value instead.|Error 0-23: The function 'TestColorBlueRampInvert' has some invalid arguments. + +>> ColorFade( TestRedRamp.Red25, 10% ) +Errors: Error 0-9: The function 'ColorFade' has some invalid arguments.|Error 22-28: Invalid argument type (OptionSetValue (TestRedRamp)). Expecting a Color value instead. + +>> TestColorInvert( TestRedRamp.Red50 ) +Errors: Error 28-34: Invalid argument type (OptionSetValue (TestRedRamp)). Expecting a Color value instead.|Error 0-15: The function 'TestColorInvert' has some invalid arguments. + +//=========================================================================================================== +// +// 2. Avoid passing an enum where a scalar was expected, except for text. For example Mid( "foo", StartOfWeek.Tuesday ). +// + +//=========================================================================================================== +// +// 3. Avoid passing a scalar where an enum was expected, excepf for text backed enums. For example Weekday( Now(), 12 ) +// + +>> TestColorBlueRampInvert( RGBA( 128, 128, 128, 100% ) ) +Errors: Error 25-52: Invalid argument type (Color). Expecting a OptionSetValue (TestBlueRamp) value instead.|Error 0-23: The function 'TestColorBlueRampInvert' has some invalid arguments. + +//=========================================================================================================== +// +// 4. Equals/not equals between enum values of the same enum is always supported. For example, StartOfWeek.Tuesday = StartOfWeek.Monday +// + +>> TestYesNo.Yes = TestYesNo.No +false + +>> TestYesNo.Yes = TestYesNo.Yes +true + +>> TestYesNo.Yes <> TestYesNo.No +true + +>> TestBooleanNoCoerce.SuperTrue = TestBooleanNoCoerce.SuperFalse +false + +>> TestBooleanNoCoerce.SuperTrue <> TestBooleanNoCoerce.SuperFalse +true + +// option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCoerceTo.V = TestNumberCoerceTo.V2 +// true + +// Not supported between different enums, even if the same backing kind or if CanCoerceToBackindKind is true + +>> TestNumberCoerceTo.V = ErrorKind.Div0 +Errors: Error 21-22: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCoerceTo), Enum (ErrorKind). + +>> TestYesNo.Yes = TestYeaNay.Nay +Errors: Error 14-15: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.Yes <> TestYeaNay.Nay +Errors: Error 14-16: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> Color.Red = TestRedRamp.Red100 +Errors: Error 10-11: Incompatible types for comparison. These types can't be compared: Enum (Color), OptionSetValue (TestRedRamp). + +//=========================================================================================================== +// +// 5. By default, Equals/not equals with the backing kind is not supported. For example, StartOfWeek.Tuesday = 12 +// + +>> TestRedRamp.Red25 = RGBA( 1,1,1,1 ) +Errors: Error 18-19: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestRedRamp), Color. + +>> TestBooleanNoCoerce.SuperTrue = true +Errors: Error 30-31: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestBooleanNoCoerce), Boolean. + +//=========================================================================================================== +// +// 6. By default, Order comparisons between number based enums are not supported by default, for example StartOfWeek.Tuesday < StartOfWeek.Monday +// + +//=========================================================================================================== +// +// 7. By default, math operations between number based enums are never supported. For example, StartOfWeek.Tuesday + StartOfWeek.Monday +// + +// Booleans cannot be used in math expressions, even if they support coercion to backing kind + +>> TestBooleanNoCoerce.SuperFalse + 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse * 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse / 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse ^ 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Number, Decimal, Text, Boolean, UntypedObject. + +>> TestYesNo.Yes + 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.No * 2 +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.Yes / 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.Yes ^ 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Number, Decimal, Text, Boolean, UntypedObject. + +//=========================================================================================================== +// +// 8. By default, Boolean operations between Boolean based enums is not supported, but can be overriden with CanCoerceToBackingKind +// + +>> TestYesNo.Yes && TestBooleanNoCoerce.SuperTrue +Errors: Error 36-46: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> TestBooleanNoCoerce.SuperTrue && TestBooleanNoCoerce.SuperFalse +Errors: Error 19-29: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject.|Error 52-63: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> TestBooleanNoCoerce.SuperTrue && false +Errors: Error 19-29: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> !TestBooleanNoCoerce.SuperTrue +Errors: Error 20-30: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> TestYesNo.Yes Or TestBooleanNoCoerce.SuperTrue +Errors: Error 36-46: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> Not TestBooleanNoCoerce.SuperTrue And Not TestBooleanNoCoerce.SuperFalse +Errors: Error 23-33: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject.|Error 61-72: Invalid argument type. Expecting one of the following: Boolean, Number, Decimal, Text, UntypedObject. + +>> TestBooleanNoCoerce.SuperTrue +TestBooleanNoCoerce.'1' + +>> And( TestBooleanNoCoerce.SuperTrue, TestBooleanNoCoerce.SuperTrue ) +Errors: Error 24-34: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 55-65: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 0-3: The function 'And' has some invalid arguments. + +>> Not( TestBooleanNoCoerce.SuperTrue ) +Errors: Error 24-34: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 0-3: The function 'Not' has some invalid arguments. + +>> Or( TestBooleanNoCoerce.SuperTrue, TestBooleanNoCoerce.SuperFalse ) +Errors: Error 23-33: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 54-65: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 0-2: The function 'Or' has some invalid arguments. + +>> Or(Not(TestBooleanNoCoerce.SuperFalse), Not(TestBooleanNoCoerce.SuperTrue)) +Errors: Error 26-37: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 3-6: The function 'Not' has some invalid arguments.|Error 64-74: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a Boolean value instead.|Error 41-44: The function 'Not' has some invalid arguments. + +//=========================================================================================================== +// +// 9. If the underlying value is desired, the Text, Value, Decimal, Float, and Boolean functions can be called to get the backing value. +// + +// Text can be called on all option set values + +>> Text( TestYesNo.Yes ) +"Yes" + +>> Value( TestYesNo.Yes ) +Errors: Error 0-5: The function 'Value' has some invalid arguments.|Error 16-20: Expected text or number. We expect text or a number at this point in the formula. + +>> Float( TestYesNo.Yes ) +Errors: Error 0-5: The function 'Float' has some invalid arguments.|Error 16-20: Expected text or number. We expect text or a number at this point in the formula. + +>> Decimal( TestYesNo.Yes ) +Errors: Error 0-7: The function 'Decimal' has some invalid arguments.|Error 18-22: Expected text or number. We expect text or a number at this point in the formula. + +>> Boolean( TestYesNo.No ) +false + +>> Boolean( TestYesNo.Yes ) +true + +>> Boolean( TestYeaNay.Nay ) +false + +>> Boolean( TestYeaNay.Yea ) +true + +>> Boolean( TestBooleanNoCoerce.SuperTrue ) +true + +>> Boolean( TestBooleanNoCoerce.SuperFalse ) +false + +// no constructor for Color values + +//=========================================================================================================== +// +// 10. CanCoerceFromBackingKind - For example, Match which allows a string in place of the enum +// + +>> TestXORNoCoerce( true, false ) +Errors: Error 17-21: Invalid argument type (Boolean). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 23-28: Invalid argument type (Boolean). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 0-15: The function 'TestXORNoCoerce' has some invalid arguments. + +>> TestXORNoCoerce( TestBooleanNoCoerce.SuperFalse, TestBooleanNoCoerce.SuperTrue ) +true + +>> TestXORNoCoerce( TestYeaNay.Yea, TestYeaNay.Nay ) +Errors: Error 27-31: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 43-47: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 0-15: The function 'TestXORNoCoerce' has some invalid arguments. + +>> TestXORNoCoerce( TestYesNo.No, TestYesNo.Yes ) +Errors: Error 26-29: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 40-44: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a OptionSetValue (TestBooleanNoCoerce) value instead.|Error 0-15: The function 'TestXORNoCoerce' has some invalid arguments. + +>> TestXORYesNo( true, false ) +true + +>> TestXORYesNo( TestBooleanNoCoerce.SuperFalse, TestBooleanNoCoerce.SuperTrue ) +Errors: Error 33-44: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 65-75: Invalid argument type (OptionSetValue (TestBooleanNoCoerce)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 0-12: The function 'TestXORYesNo' has some invalid arguments. + +>> TestXORYesNo( TestYeaNay.Nay, TestYeaNay.Yea ) +Errors: Error 24-28: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 40-44: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 0-12: The function 'TestXORYesNo' has some invalid arguments. + +>> TestXORYesNo( TestYesNo.No, TestYesNo.Yes ) +true + +>> TestXORYesNo( TestYesNo.No, true ) +true + +>> TestXORYesNo( TestYesNo.No, false ) +false + +>> If( false, TestYeaNay.Yea, false ) +TestYeaNay.'0' + +>> Text( If( false, TestYeaNay.Yea, false ) ) +"Nay" + +>> If( false, TestYeaNay.Yea, Blank() ) +Blank() + +>> Text( If( false, TestYeaNay.Yea, Blank() ) ) +Blank() + +// 10 is a part of the option set. In the future we may want to have this return .X instead. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) ) +"CalculatedOptionSetValue" + +// 11 is not a part of the option set. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) ) +"CalculatedOptionSetValue" + +>> If( false, TestNumberCompareNumericCoerceFrom.V, Blank() ) +Blank() + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, Blank() ) ) +Blank() + +>> If( false, TestBlueRamp.Blue50, Blank() ) +Blank() + +>> Text( If( false, TestBlueRamp.Blue50, Blank() ) ) +Blank() + +//=========================================================================================================== +// +// 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number +// + +>> TestNumberCoerceTo.V + TestNumberCoerceTo.X +15 + +>> Int( TestNumberCoerceTo.V ) +5 + +>> Power( TestNumberCoerceTo.V, 2 ) +25 + +>> TestSignNumber( TestNumberCoerceTo.V ) +1 + +>> TestSignDecimal( TestNumberCoerceTo.V ) +1 + +>> Power( TestNumberCoerceTo.V, TestNumberCoerceTo.V ) +3125 + +>> Int( TestNumberCompareNumeric.V ) +Errors: Error 0-3: The function 'Int' has some invalid arguments.|Error 29-31: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Decimal value instead. + +>> Power( TestNumberCompareNumeric.V, 2 ) +Errors: Error 0-5: The function 'Power' has some invalid arguments.|Error 31-33: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Number value instead. + +>> TestSignNumber( TestNumberCompareNumeric.V ) +Errors: Error 40-42: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Number value instead.|Error 0-14: The function 'TestSignNumber' has some invalid arguments. + +>> TestSignDecimal( TestNumberCompareNumeric.V ) +Errors: Error 41-43: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Decimal value instead.|Error 0-15: The function 'TestSignDecimal' has some invalid arguments. + +>> Power( TestNumberCompareNumeric.V, TestNumberCompareNumeric.V ) +Errors: Error 0-5: The function 'Power' has some invalid arguments.|Error 31-33: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Number value instead.|Error 59-61: Invalid argument type (OptionSetValue (TestNumberCompareNumeric)). Expecting a Number value instead. + +// Unless there is a specific reason, CanCoerceToBackingKind is expected to be true for most Boolean option sets + +>> TestYesNo.Yes && TestYeaNay.Yea +true + +>> !TestYesNo.Yes +false + +>> TestYesNo.Yes || TestYeaNay.Nay +true + +>> !TestYesNo.Yes || !TestYeaNay.Yea +false + +>> TestYesNo.Yes And TestYeaNay.Yea +true + +>> Not TestYesNo.Yes +false + +>> TestYesNo.Yes Or TestYeaNay.Nay +true + +>> Not TestYesNo.Yes And Not TestYeaNay.Yea +false + +>> TestYesNo.No +TestYesNo.'0' + +>> And( TestYesNo.Yes, TestYeaNay.Yea ) +true + +>> Not( TestYesNo.Yes ) +false + +>> Or( TestYesNo.Yes, TestYeaNay.Nay ) +true + +>> Or(Not(TestYesNo.Yes), Not(TestYeaNay.Yea)) +false + +// Equals/not equals comparisons + +>> TestYesNo.Yes = false +false + +>> TestYesNo.Yes <> true +false + +>> TestYesNo.Yes = true +true + +>> TestYesNo.Yes <> false +true + +//=========================================================================================================== +// +// 12. CanConcatenateStronglyTyped (text only) - For example, JSONFormat which can concatenate different members together to create a new member +// + +//=========================================================================================================== +// +// 13. CanCompareNumeric (numbers only) - For example, ErrorKind can compare values +// + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 < TestNumberCompareNumeric.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 <= TestNumberCompareNumeric.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 >= TestNumberCompareNumeric.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 > TestNumberCompareNumeric.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 < TestNumberCompareNumericCoerceFrom.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 <= TestNumberCompareNumericCoerceFrom.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 >= TestNumberCompareNumericCoerceFrom.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 > TestNumberCompareNumericCoerceFrom.V +// false + +// Paired with CoerceFromBackingKind, can compare with backing kind + +>> TestNumberCompareNumeric.V < 5 +Errors: Error 27-28: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), Decimal. + +// option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 < 5 +// Errors: Error 28-29: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), Decimal. + +>> TestNumberCompareNumericCoerceFrom.V < 5 +false + +// option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 < 5 +// false + +//=========================================================================================================== +// +// 14. CanConcatenateStronglyTyped & CanCoerceFromBackingKind - An important combination, used by Match, allows strings and enums to be mixed +// + +//=========================================================================================================== +// +// 15. Since there is no longer an Accepts relationship between enums and their backing kinds, more likely to get Void results +// + +//=========================================================================================================== +// +// 16. Everything coerces to string +// + +>> Text( TestYesNo.No ) +"No" + +>> "Label:" & TestYesNo.No +"Label:No" + +>> Concatenate( "Label:", TestYesNo.No ) +"Label:No" + +>> TestYesNo.No & "Way" +"NoWay" + +>> Concatenate( TestYesNo.No, "Way" ) +"NoWay" + +>> Text( TestYeaNay.Nay ) +"Nay" + +>> "Label:" & TestYeaNay.Nay +"Label:Nay" + +>> Text( TestBooleanNoCoerce.SuperTrue ) +"SuperTrue" + +>> "Label:" & TestBooleanNoCoerce.SuperTrue +"Label:SuperTrue" + +>> Text( TestYeaNay.Yea ) +"Yea" + +>> "Label:" & TestYeaNay.Yea +"Label:Yea" + +>> Text( TestBlueRamp.Blue50 ) +"Blue50" + +>> "Label:" & TestBlueRamp.Blue50 +"Label:Blue50" + +>> Concatenate( "Label:", TestBlueRamp.Blue50 ) +"Label:Blue50" + +>> TestBlueRamp.Blue50 & " is the sky" +"Blue50 is the sky" + +>> Concatenate( TestBlueRamp.Blue50, " is the sky") +"Blue50 is the sky" + +>> "The sky is so very " & TestBlueRamp.Blue50 & " !!!" +"The sky is so very Blue50 !!!" + +>> Concatenate( "The sky is so very ", TestBlueRamp.Blue50, " !!!") +"The sky is so very Blue50 !!!" + +>> Mid( TestBlueRamp.Blue50, 2 ) +"lue50" + +>> Len( TestBlueRamp.Blue50 ) +6 + +>> Text( TestRedRamp.Red25 ) +"Red25" + +>> "Label:" & TestRedRamp.Red25 +"Label:Red25" + +>> Mid( TestRedRamp.Red25, 2 ) +"ed25" + +>> Len( TestRedRamp.Red25 ) +5 + +>> true & TestRedRamp.Red25 +"trueRed25" + +>> 1 & TestRedRamp.Red25 +"1Red25" + +>> "hi" & TestRedRamp.Red25 +"hiRed25" + +>> TestRedRamp.Red25 & true +"Red25true" + +>> TestRedRamp.Red25 & 1 +"Red251" + +>> TestRedRamp.Red25 & "hi" +"Red25hi" + +>> Concatenate( true, TestRedRamp.Red25 ) +"trueRed25" + +>> Concatenate( 1, TestRedRamp.Red25 ) +"1Red25" + +>> Concatenate( "hi", TestRedRamp.Red25 ) +"hiRed25" + +>> true & TestYeaNay.Yea +"trueYea" + +>> 1 & TestYeaNay.Yea +"1Yea" + +>> "hi" & TestYeaNay.Yea +"hiYea" + +>> TestYeaNay.Yea & true +"Yeatrue" + +>> TestYeaNay.Yea & 1 +"Yea1" + +>> TestYeaNay.Yea & "hi" +"Yeahi" + +>> Concatenate( true, TestYeaNay.Yea ) +"trueYea" + +>> Concatenate( 1, TestYeaNay.Yea ) +"1Yea" + +>> Concatenate( "hi", TestYeaNay.Yea ) +"hiYea" + +//=========================================================================================================== +// +// 17. Everything equals compares to Blank() (ObjNull), can order compare with numbers and CanCompareNumerics set +// + +>> TestYesNo.Yes = Blank() +false + +>> TestYesNo.No = Blank() +false + +>> TestYesNo.Yes <> Blank() +true + +>> TestYesNo.No <> Blank() +true + +>> Blank() = TestYesNo.Yes +false + +>> Blank() = TestYesNo.No +false + +>> Blank() <> TestYesNo.Yes +true + +>> Blank() <> TestYesNo.No +true + +>> TestYesNo.No >= Blank() +Errors: Error 13-15: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), ObjNull. + +>> TestYesNo.No < Blank() +Errors: Error 13-14: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), ObjNull. + +>> Blank() < TestYesNo.Yes +Errors: Error 8-9: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestYesNo). + +>> Blank() <= TestYesNo.Yes +Errors: Error 8-10: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestYesNo). + +>> TestNumberCompareNumeric.V < Blank() +Errors: Error 27-28: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), ObjNull. + +>> TestNumberCompareNumeric.V <= Blank() +Errors: Error 27-29: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), ObjNull. + +>> TestNumberCompareNumeric.V >= Blank() +Errors: Error 27-29: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), ObjNull. + +>> TestNumberCompareNumeric.V > Blank() +Errors: Error 27-28: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCompareNumeric), ObjNull. + +>> TestNumberCompareNumeric.V = Blank() +false + +>> TestNumberCompareNumeric.V <> Blank() +true + +>> Blank() < TestNumberCompareNumeric.V +Errors: Error 8-9: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestNumberCompareNumeric). + +>> Blank() <= TestNumberCompareNumeric.V +Errors: Error 8-10: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestNumberCompareNumeric). + +>> Blank() >= TestNumberCompareNumeric.V +Errors: Error 8-10: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestNumberCompareNumeric). + +>> Blank() > TestNumberCompareNumeric.V +Errors: Error 8-9: Incompatible types for comparison. These types can't be compared: ObjNull, OptionSetValue (TestNumberCompareNumeric). + +>> Blank() = TestNumberCompareNumeric.V +false + +>> Blank() <> TestNumberCompareNumeric.V +true + +>> TestNumberCoerceTo.V > Blank() +Errors: Error 21-22: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestNumberCoerceTo), ObjNull. + +>> TestNumberCoerceTo.V = Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V < Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V <= Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V >= Blank() +true + +>> TestNumberCompareNumericCoerceFrom.V > Blank() +true + +>> TestNumberCompareNumericCoerceFrom.V = Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V <> Blank() +true + +>> Blank() < TestNumberCompareNumericCoerceFrom.V +true + +>> Blank() <= TestNumberCompareNumericCoerceFrom.V +true + +>> Blank() >= TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() > TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() = TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() <> TestNumberCompareNumericCoerceFrom.V +true + +//=========================================================================================================== +// +// 18. Examples - TestYesNo - Standard DV Boolean backed option set, with CoerceToBackingKind and CoerceFromBackingKind +// + +// Standard usage + +>> TestXORYesNo( TestYesNo.Yes, TestYesNo.Yes ) +false + +>> TestYesNo.Yes=TestYesNo.No +false + +>> TestYesNo.Yes<>TestYesNo.No +true + +>> TestYesNo.Yes=TestYesNo.Yes +true + +>> Boolean(TestYesNo.No) +false + +// Coerces to string + +>> Text(TestYesNo.No) +"No" + +>> "Severity: "&TestYesNo.No +"Severity: No" + +>> Len(TestYesNo.No) +2 + +>> Left(TestYesNo.No,3) +"No" + +// Standard error cases for number backed enums + +>> Value( TestYesNo.No ) +Errors: Error 0-5: The function 'Value' has some invalid arguments.|Error 16-19: Expected text or number. We expect text or a number at this point in the formula. + +>> Decimal( TestYesNo.No ) +Errors: Error 0-7: The function 'Decimal' has some invalid arguments.|Error 18-21: Expected text or number. We expect text or a number at this point in the formula. + +>> Float( TestYesNo.No ) +Errors: Error 0-5: The function 'Float' has some invalid arguments.|Error 16-19: Expected text or number. We expect text or a number at this point in the formula. + +>> TestYesNo.No = TestYeaNay.Yea +Errors: Error 13-14: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.No = TestYeaNay.Nay +Errors: Error 13-14: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.No <> TestYeaNay.Yea +Errors: Errors: Error 13-15: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestXORYesNo( TestYeaNay.Nay, TestYeaNay.Nay ) +Errors: Error 24-28: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 40-44: Invalid argument type (OptionSetValue (TestYeaNay)). Expecting a OptionSetValue (TestYesNo) value instead.|Error 0-12: The function 'TestXORYesNo' has some invalid arguments. + +// Boolean can be used in place (CanCoerceFromBackingKind = true) + +>> TestXORYesNo( false, false ) +false + +>> TestYesNo.No = true +false + +>> TestYesNo.No = false +true + +>> TestYesNo.No <> false +false + +>> If( false, TestYesNo.Yes, false ) +TestYesNo.'0' + +>> Text( If( false, TestYesNo.Yes, false ) ) +"No" + +// Can be used as a Boolean (CanCoerceToBackingKind = true) + +>> Sqrt( TestYesNo.No ) +Errors: Error 0-4: The function 'Sqrt' has some invalid arguments.|Error 15-18: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a Number value instead. + +>> Mod( TestYesNo.No, 2 ) +Errors: Error 0-3: The function 'Mod' has some invalid arguments.|Error 14-17: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a Decimal value instead. + +>> TestYesNo.No + 3 +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.No + TestYesNo.Yes +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject.|Error 24-28: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> If( false, true, TestYesNo.No ) // first type rule +false + +>> Not( TestYesNo.No ) +true + +>> Not TestYesNo.No +true + +>> TestYesNo.No Or TestYesNo.No Or TestYesNo.Yes +true + +>> TestYesNo.Yes And TestYesNo.Yes And TestYesNo.No +false + +// Can't compare numerically (CanCompareNumeric = false) + +>> TestYesNo.Yes < TestYesNo.No +Errors: Error 14-15: Unable to compare values of type OptionSetValue (TestYesNo). + +>> TestYesNo.Yes >= TestYesNo.No +Errors: Error 14-16: Unable to compare values of type OptionSetValue (TestYesNo). + +// Can't concatenate strongly typed (CanConcatenateStronglyTyped = false) + +>> TestYesNo.Yes & TestYesNo.Yes & TestYesNo.No +"YesYesNo" + +>> TestXORYesNo( TestYesNo.Yes & TestYesNo.No, true ) +Errors: Error 28-29: Invalid argument type (Text). Expecting a OptionSetValue (TestYesNo) value instead.|Error 0-12: The function 'TestXORYesNo' has some invalid arguments. diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets_PreV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets_PreV1.txt new file mode 100644 index 0000000000..bb23b1a7fc --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_AsOptionSets_PreV1.txt @@ -0,0 +1,850 @@ +#SETUP: disable:StronglyTypedBuiltinEnums,disable:PowerFxV1CompatibilityRules,AllEnumsPlusTestOptionSetsSetup,RegEx,EnableJsonFunctions + +// **** Using test enums AS OPTION SETS, Pre V1. **** +// Some enum varients are not represented in the Builtin set, including for example, any Boolean enums. +// The companion _BuiltInEnums version of this file uses only the built in enums. +// This version of the file usess Option Sets insetead of Enums, which helps test the use of host registered option sets, such as Dataverse option sets. + +// Expected resutls in this file were captured before the April 2024 changes for testing compatibilty with the old settings. + +// Strongly typed enums were strengthened to: +// 0. Strongly typed enum usage is the most common scenario and what Intellisense will suggest. +// 1. Avoid passing the wrong kind of enum to a function. For example JSON( [1,2,3], Match.IgnoreCase ) +// 2. Avoid passing an enum where a scalar was expected, except for text backed enums. For example Mid( "foo", StartOfWeek.Tuesday ) +// 3. Avoid passing a scalar where an enum was expected, excepf for text backed enums. For example Weekday( Now(), 12 ) +// +// Default operations with backing type +// 4. Equals/not equals between enum values of the same enum is always supported. For example, StartOfWeek.Tuesday = StartOfWeek.Monday +// 5. By default, Equals/not equals with the backing kind is not supported. For example, StartOfWeek.Tuesday = 12 +// 6. By default, Order comparisons between number based enums are not supported, by default. For example StartOfWeek.Tuesday < StartOfWeek.Monday +// 7. By default, math operations between number based enums are never supported. For example, StartOfWeek.Tuesday + StartOfWeek.Monday +// 8. By default, Boolean operations between Boolean based enums is not supported, but can be overriden with CanCoerceToBackingKind +// 9. If the underlying value is desired, the Text, Value, Decimal, Float, and Boolean functions can be called to get the backing value. +// +// In addition, there are flags for each option set that govern how it can be used. Default is no flags, used by Dataverse option sets. +// 10. CanCoerceFromBackingKind - For example, Match which allows a string in place of the enum +// 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number +// 12. CanConcatenateStronglyTyped (text only) - For example, JSONFormat which can concatenate different members together to create a new member +// 13. CanCompareNumeric (numbers only) - For example, ErrorKind can compare values +// 14. CanConcatenateStronglyTyped & CanCoerceFromBackingKind - An important combination, used by Match, allows strings and enums to be mixed +// +// Misc +// 15. Since there is no longer an Accepts relationship between enums and their backing kinds, more likely to get Void results +// 16. Everything coerces to string +// 17. Everything equals compares to Blank() (ObjNull), can order compare with numbers and CanCompareNumerics set + +//============================================================================================================ +// +// 0. Strongly typed enum usage is the most common scenario and what Intellisense will suggest. +// + +>> TestXORYesNo( TestYesNo.Yes, TestYesNo.No ) +true + +>> TestColorInvert( Color.Blue ) +RGBA(255,255,0,1) + +>> TestColorBlueRampInvert( TestBlueRamp.Blue25 ) +RGBA(64,64,0,1) + +//============================================================================================================ +// +// 1. Avoid passing the wrong kind of enum to a function. For example JSON( [1,2,3], Match.IgnoreCase ) +// + +>> TestXORYesNo( TestYeaNay.Yea, TestYeaNay.Nay ) +true + +>> TestColorBlueRampInvert( TestRedRamp.Red25 ) +RGBA(0,64,64,1) + +>> TestColorBlueRampInvert( Color.Purple ) +RGBA(127,255,127,1) + +>> TestColorBlueRampInvert( ColorFade( Color.Yellow, 25% ) ) +RGBA(0,0,192,1) + +>> ColorFade( TestRedRamp.Red25, 10% ) +RGBA(255,197,197,1) + +>> TestColorInvert( TestRedRamp.Red50 ) +RGBA(0,128,128,1) + +//=========================================================================================================== +// +// 2. Avoid passing an enum where a scalar was expected, except for text. For example Mid( "foo", StartOfWeek.Tuesday ). +// + +//=========================================================================================================== +// +// 3. Avoid passing a scalar where an enum was expected, excepf for text backed enums. For example Weekday( Now(), 12 ) +// + +>> TestColorBlueRampInvert( RGBA( 128, 128, 128, 100% ) ) +RGBA(127,127,127,1) + +//=========================================================================================================== +// +// 4. Equals/not equals between enum values of the same enum is always supported. For example, StartOfWeek.Tuesday = StartOfWeek.Monday +// + +>> TestYesNo.Yes = TestYesNo.No +false + +>> TestYesNo.Yes = TestYesNo.Yes +true + +>> TestYesNo.Yes <> TestYesNo.No +true + +>> TestBooleanNoCoerce.SuperTrue = TestBooleanNoCoerce.SuperFalse +false + +>> TestBooleanNoCoerce.SuperTrue <> TestBooleanNoCoerce.SuperFalse +true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCoerceTo.V = TestNumberCoerceTo.V2 +// true + +// Not supported between different enums, even if the same backing kind or if CanCoerceToBackindKind is true + +>> TestNumberCoerceTo.V = ErrorKind.Div0 +false + +>> TestYesNo.Yes = TestYeaNay.Nay +Errors: Error 14-15: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.Yes <> TestYeaNay.Nay +Errors: Error 14-16: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> Color.Red = TestRedRamp.Red100 +true + +//=========================================================================================================== +// +// 5. By default, Equals/not equals with the backing kind is not supported. For example, StartOfWeek.Tuesday = 12 +// + +>> TestRedRamp.Red25 = RGBA( 1,1,1,1 ) +false + +>> TestBooleanNoCoerce.SuperTrue = true +true + +//=========================================================================================================== +// +// 6. By default, Order comparisons between number based enums are not supported by default, for example StartOfWeek.Tuesday < StartOfWeek.Monday +// + +//=========================================================================================================== +// +// 7. By default, math operations between number based enums are never supported. For example, StartOfWeek.Tuesday + StartOfWeek.Monday +// + +// Booleans cannot be used in math expressions, even if they support coercion to backing kind + +>> TestBooleanNoCoerce.SuperFalse + 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse * 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse / 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestBooleanNoCoerce.SuperFalse ^ 2 +Errors: Error 19-30: Invalid argument type. Expecting one of the following: Number, Decimal, Text, Boolean, UntypedObject. + +>> TestYesNo.Yes + 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.No * 2 +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.Yes / 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.Yes ^ 2 +Errors: Error 9-13: Invalid argument type. Expecting one of the following: Number, Decimal, Text, Boolean, UntypedObject. + +//=========================================================================================================== +// +// 8. By default, Boolean operations between Boolean based enums is not supported, but can be overriden with CanCoerceToBackingKind +// + +>> TestYesNo.Yes && TestBooleanNoCoerce.SuperTrue +true + +>> TestBooleanNoCoerce.SuperTrue && TestBooleanNoCoerce.SuperFalse +false + +>> TestBooleanNoCoerce.SuperTrue && false +false + +>> !TestBooleanNoCoerce.SuperTrue +false + +>> TestYesNo.Yes Or TestBooleanNoCoerce.SuperTrue +true + +>> Not TestBooleanNoCoerce.SuperTrue And Not TestBooleanNoCoerce.SuperFalse +false + +>> TestBooleanNoCoerce.SuperTrue +TestBooleanNoCoerce.'1' + +>> And( TestBooleanNoCoerce.SuperTrue, TestBooleanNoCoerce.SuperTrue ) +true + +>> Not( TestBooleanNoCoerce.SuperTrue ) +false + +>> Or( TestBooleanNoCoerce.SuperTrue, TestBooleanNoCoerce.SuperFalse ) +true + +>> Or(Not(TestBooleanNoCoerce.SuperFalse), Not(TestBooleanNoCoerce.SuperTrue)) +true + +//=========================================================================================================== +// +// 9. If the underlying value is desired, the Text, Value, Float, and Boolean functions can be called to get the backing value. +// + +// Text can be called on all option set values + +>> Text( TestYesNo.Yes ) +"Yes" + +>> Value( TestYesNo.Yes ) +Errors: Error 0-5: The function 'Value' has some invalid arguments.|Error 16-20: Expected text or number. We expect text or a number at this point in the formula. + +>> Float( TestYesNo.Yes ) +Errors: Error 0-5: The function 'Float' has some invalid arguments.|Error 16-20: Expected text or number. We expect text or a number at this point in the formula. + +>> Boolean( TestYesNo.No ) +false + +>> Boolean( TestYesNo.Yes ) +true + +>> Boolean( TestYeaNay.Nay ) +false + +>> Boolean( TestYeaNay.Yea ) +true + +>> Boolean( TestBooleanNoCoerce.SuperTrue ) +true + +>> Boolean( TestBooleanNoCoerce.SuperFalse ) +false + +// no constructor for Color values + +//=========================================================================================================== +// +// 10. CanCoerceFromBackingKind - For example, Match which allows a string in place of the enum +// + +>> TestXORNoCoerce( true, false ) +true + +>> TestXORNoCoerce( TestBooleanNoCoerce.SuperFalse, TestBooleanNoCoerce.SuperTrue ) +true + +>> TestXORNoCoerce( TestYeaNay.Yea, TestYeaNay.Nay ) +true + +>> TestXORNoCoerce( TestYesNo.No, TestYesNo.Yes ) +true + +>> TestXORYesNo( true, false ) +true + +>> TestXORYesNo( TestBooleanNoCoerce.SuperFalse, TestBooleanNoCoerce.SuperTrue ) +true + +>> TestXORYesNo( TestYeaNay.Nay, TestYeaNay.Yea ) +true + +>> TestXORYesNo( TestYesNo.No, TestYesNo.Yes ) +true + +>> TestXORYesNo( TestYesNo.No, true ) +true + +>> TestXORYesNo( TestYesNo.No, false ) +false + +>> If( false, TestYeaNay.Yea, false ) +TestYeaNay.'0' + +>> Text( If( false, TestYeaNay.Yea, false ) ) +"Nay" + +>> If( false, TestYeaNay.Yea, Blank() ) +Blank() + +>> Text( If( false, TestYeaNay.Yea, Blank() ) ) +Blank() + +// 10 is a part of the option set. In the future we may want to have this return .X instead. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) ) +"CalculatedOptionSetValue" + +// 11 is not a part of the option set. +>> If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) +TestNumberCompareNumericCoerceFrom.CalculatedOptionSetValue + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) ) +"CalculatedOptionSetValue" + +>> If( false, TestNumberCompareNumericCoerceFrom.V, Blank() ) +Blank() + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, Blank() ) ) +Blank() + +>> If( false, TestBlueRamp.Blue50, Blank() ) +Blank() + +>> Text( If( false, TestBlueRamp.Blue50, Blank() ) ) +Blank() + +//=========================================================================================================== +// +// 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number +// + +>> TestNumberCoerceTo.V + TestNumberCoerceTo.X +15 + +>> Int( TestNumberCoerceTo.V ) +5 + +>> Power( TestNumberCoerceTo.V, 2 ) +25 + +>> TestSignNumber( TestNumberCoerceTo.V ) +1 + +>> TestSignDecimal( TestNumberCoerceTo.V ) +1 + +>> Power( TestNumberCoerceTo.V, TestNumberCoerceTo.V ) +3125 + +>> Int( TestNumberCompareNumeric.V ) +5 + +>> Power( TestNumberCompareNumeric.V, 2 ) +25 + +>> TestSignNumber( TestNumberCompareNumeric.V ) +1 + +>> TestSignDecimal( TestNumberCompareNumeric.V ) +1 + +>> Power( TestNumberCompareNumeric.V, TestNumberCompareNumeric.V ) +3125 + +// Unless there is a specific reason, CanCoerceToBackingKind is expected to be true for most Boolean option sets + +>> TestYesNo.Yes && TestYeaNay.Yea +true + +>> !TestYesNo.Yes +false + +>> TestYesNo.Yes || TestYeaNay.Nay +true + +>> !TestYesNo.Yes || !TestYeaNay.Yea +false + +>> TestYesNo.Yes And TestYeaNay.Yea +true + +>> Not TestYesNo.Yes +false + +>> TestYesNo.Yes Or TestYeaNay.Nay +true + +>> Not TestYesNo.Yes And Not TestYeaNay.Yea +false + +>> TestYesNo.No +TestYesNo.'0' + +>> And( TestYesNo.Yes, TestYeaNay.Yea ) +true + +>> Not( TestYesNo.Yes ) +false + +>> Or( TestYesNo.Yes, TestYeaNay.Nay ) +true + +>> Or(Not(TestYesNo.Yes), Not(TestYeaNay.Yea)) +false + +// Equals/not equals comparisons + +>> TestYesNo.Yes = false +false + +>> TestYesNo.Yes <> true +false + +>> TestYesNo.Yes = true +true + +>> TestYesNo.Yes <> false +true + +//=========================================================================================================== +// +// 12. CanConcatenateStronglyTyped (text only) - For example, JSONFormat which can concatenate different members together to create a new member +// + +//=========================================================================================================== +// +// 13. CanCompareNumeric (numbers only) - For example, ErrorKind can compare values +// + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 < TestNumberCompareNumeric.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 <= TestNumberCompareNumeric.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 >= TestNumberCompareNumeric.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 > TestNumberCompareNumeric.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 < TestNumberCompareNumericCoerceFrom.V +// false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 <= TestNumberCompareNumericCoerceFrom.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 >= TestNumberCompareNumericCoerceFrom.V +// true + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 > TestNumberCompareNumericCoerceFrom.V +// false + +// Paired with CoerceFromBackingKind, can compare with backing kind + +>> TestNumberCompareNumeric.V < 5 +false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumeric.V2 < 5 +// false + +>> TestNumberCompareNumericCoerceFrom.V < 5 +false + +// unlike enums, option sets can't have the same value for two options; .V2 tests can't run as option sets +// >> TestNumberCompareNumericCoerceFrom.V2 < 5 +// false + +// Most other number backeded enums can not, see section 5 above + +//=========================================================================================================== +// +// 14. CanConcatenateStronglyTyped & CanCoerceFromBackingKind - An important combination, used by Match, allows strings and enums to be mixed +// + +//=========================================================================================================== +// +// 15. Since there is no longer an Accepts relationship between enums and their backing kinds, more likely to get Void results +// + +//=========================================================================================================== +// +// 16. Everything coerces to string +// + +>> Text( TestYesNo.No ) +"No" + +>> "Label:" & TestYesNo.No +"Label:No" + +>> Concatenate( "Label:", TestYesNo.No ) +"Label:No" + +>> TestYesNo.No & "Way" +"NoWay" + +>> Concatenate( TestYesNo.No, "Way" ) +"NoWay" + +>> Text( TestYeaNay.Nay ) +"Nay" + +>> "Label:" & TestYeaNay.Nay +"Label:Nay" + +>> Text( TestBooleanNoCoerce.SuperTrue ) +"SuperTrue" + +>> "Label:" & TestBooleanNoCoerce.SuperTrue +"Label:SuperTrue" + +>> Text( TestYeaNay.Yea ) +"Yea" + +>> "Label:" & TestYeaNay.Yea +"Label:Yea" + +>> Text( TestBlueRamp.Blue50 ) +"Blue50" + +>> "Label:" & TestBlueRamp.Blue50 +"Label:Blue50" + +>> Concatenate( "Label:", TestBlueRamp.Blue50 ) +"Label:Blue50" + +>> TestBlueRamp.Blue50 & " is the sky" +"Blue50 is the sky" + +>> Concatenate( TestBlueRamp.Blue50, " is the sky") +"Blue50 is the sky" + +>> "The sky is so very " & TestBlueRamp.Blue50 & " !!!" +"The sky is so very Blue50 !!!" + +>> Concatenate( "The sky is so very ", TestBlueRamp.Blue50, " !!!") +"The sky is so very Blue50 !!!" + +>> Mid( TestBlueRamp.Blue50, 2 ) +"lue50" + +>> Len( TestBlueRamp.Blue50 ) +6 + +>> Text( TestRedRamp.Red25 ) +"Red25" + +>> "Label:" & TestRedRamp.Red25 +"Label:Red25" + +>> Mid( TestRedRamp.Red25, 2 ) +"ed25" + +>> Len( TestRedRamp.Red25 ) +5 + +>> true & TestRedRamp.Red25 +"trueRed25" + +>> 1 & TestRedRamp.Red25 +"1Red25" + +>> "hi" & TestRedRamp.Red25 +"hiRed25" + +>> TestRedRamp.Red25 & true +"Red25true" + +>> TestRedRamp.Red25 & 1 +"Red251" + +>> TestRedRamp.Red25 & "hi" +"Red25hi" + +>> Concatenate( true, TestRedRamp.Red25 ) +"trueRed25" + +>> Concatenate( 1, TestRedRamp.Red25 ) +"1Red25" + +>> Concatenate( "hi", TestRedRamp.Red25 ) +"hiRed25" + +>> true & TestYeaNay.Yea +"trueYea" + +>> 1 & TestYeaNay.Yea +"1Yea" + +>> "hi" & TestYeaNay.Yea +"hiYea" + +>> TestYeaNay.Yea & true +"Yeatrue" + +>> TestYeaNay.Yea & 1 +"Yea1" + +>> TestYeaNay.Yea & "hi" +"Yeahi" + +>> Concatenate( true, TestYeaNay.Yea ) +"trueYea" + +>> Concatenate( 1, TestYeaNay.Yea ) +"1Yea" + +>> Concatenate( "hi", TestYeaNay.Yea ) +"hiYea" + +//=========================================================================================================== +// +// 17. Everything equals compares to Blank() (ObjNull), can order compare with numbers and CanCompareNumerics set +// + +>> TestYesNo.Yes = Blank() +false + +>> TestYesNo.No = Blank() +false + +>> TestYesNo.Yes <> Blank() +true + +>> TestYesNo.No <> Blank() +true + +>> Blank() = TestYesNo.Yes +false + +>> Blank() = TestYesNo.No +false + +>> Blank() <> TestYesNo.Yes +true + +>> Blank() <> TestYesNo.No +true + +>> TestYesNo.No >= Blank() +Errors: Error 13-15: Unable to compare values of type OptionSetValue (TestYesNo). + +>> TestYesNo.No < Blank() +Errors: Error 13-14: Unable to compare values of type OptionSetValue (TestYesNo). + +>> Blank() < TestYesNo.Yes +Errors: Error 8-9: Unable to compare values of type OptionSetValue (TestYesNo). + +>> Blank() <= TestYesNo.Yes +Errors: Error 8-10: Unable to compare values of type OptionSetValue (TestYesNo). + +>> TestNumberCompareNumeric.V < Blank() +false + +>> TestNumberCompareNumeric.V <= Blank() +false + +>> TestNumberCompareNumeric.V >= Blank() +true + +>> TestNumberCompareNumeric.V > Blank() +true + +>> TestNumberCompareNumeric.V = Blank() +false + +>> TestNumberCompareNumeric.V <> Blank() +true + +>> Blank() < TestNumberCompareNumeric.V +true + +>> Blank() <= TestNumberCompareNumeric.V +true + +>> Blank() >= TestNumberCompareNumeric.V +false + +>> Blank() > TestNumberCompareNumeric.V +false + +>> Blank() = TestNumberCompareNumeric.V +false + +>> Blank() <> TestNumberCompareNumeric.V +true + +>> TestNumberCoerceTo.V > Blank() +true + +>> TestNumberCoerceTo.V = Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V < Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V <= Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V >= Blank() +true + +>> TestNumberCompareNumericCoerceFrom.V > Blank() +true + +>> TestNumberCompareNumericCoerceFrom.V = Blank() +false + +>> TestNumberCompareNumericCoerceFrom.V <> Blank() +true + +>> Blank() < TestNumberCompareNumericCoerceFrom.V +true + +>> Blank() <= TestNumberCompareNumericCoerceFrom.V +true + +>> Blank() >= TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() > TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() = TestNumberCompareNumericCoerceFrom.V +false + +>> Blank() <> TestNumberCompareNumericCoerceFrom.V +true + +//=========================================================================================================== +// +// 18. Examples - TestYesNo - Standard DV Boolean backed option set, with CoerceToBackingKind and CoerceFromBackingKind +// + +// Standard usage + +>> TestXORYesNo( TestYesNo.Yes, TestYesNo.Yes ) +false + +>> TestYesNo.Yes=TestYesNo.No +false + +>> TestYesNo.Yes<>TestYesNo.No +true + +>> TestYesNo.Yes=TestYesNo.Yes +true + +>> Boolean(TestYesNo.No) +false + +// Coerces to string + +>> Text(TestYesNo.No) +"No" + +>> "Severity: "&TestYesNo.No +"Severity: No" + +>> Len(TestYesNo.No) +2 + +>> Left(TestYesNo.No,3) +"No" + +// Standard error cases for number backed enums + +>> Value( TestYesNo.No ) +Errors: Error 0-5: The function 'Value' has some invalid arguments.|Error 16-19: Expected text or number. We expect text or a number at this point in the formula. + +>> Float( TestYesNo.No ) +Errors: Error 0-5: The function 'Float' has some invalid arguments.|Error 16-19: Expected text or number. We expect text or a number at this point in the formula. + +>> TestYesNo.No = TestYeaNay.Yea +Errors: Error 13-14: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.No = TestYeaNay.Nay +Errors: Error 13-14: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestYesNo.No <> TestYeaNay.Yea +Errors: Error 13-15: Incompatible types for comparison. These types can't be compared: OptionSetValue (TestYesNo), OptionSetValue (TestYeaNay). + +>> TestXORYesNo( TestYeaNay.Nay, TestYeaNay.Nay ) +false + +// Boolean can be used in place (CanCoerceFromBackingKind = true) + +>> TestXORYesNo( false, false ) +false + +>> TestYesNo.No = true +false + +>> TestYesNo.No = false +true + +>> TestYesNo.No <> false +false + +>> If( false, TestYesNo.Yes, false ) +TestYesNo.'0' + +>> Text( If( false, TestYesNo.Yes, false ) ) +"No" + +// Can be used as a Boolean (CanCoerceToBackingKind = true) + +>> Sqrt( TestYesNo.No ) +Errors: Error 0-4: The function 'Sqrt' has some invalid arguments.|Error 15-18: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a Number value instead. + +>> Mod( TestYesNo.No, 2 ) +Errors: Error 0-3: The function 'Mod' has some invalid arguments.|Error 14-17: Invalid argument type (OptionSetValue (TestYesNo)). Expecting a Decimal value instead. + +>> TestYesNo.No + 3 +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> TestYesNo.No + TestYesNo.Yes +Errors: Error 9-12: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject.|Error 24-28: Invalid argument type. Expecting one of the following: Decimal, Number, Text, Boolean, Date, Time, DateTimeNoTimeZone, DateTime, UntypedObject. + +>> If( false, true, TestYesNo.No ) // first type rule +false + +>> Not( TestYesNo.No ) +true + +>> Not TestYesNo.No +true + +>> TestYesNo.No Or TestYesNo.No Or TestYesNo.Yes +true + +>> TestYesNo.Yes And TestYesNo.Yes And TestYesNo.No +false + +// Can't compare numerically (CanCompareNumeric = false) + +>> TestYesNo.Yes < TestYesNo.No +Errors: Error 14-15: Unable to compare values of type OptionSetValue (TestYesNo). + +>> TestYesNo.Yes >= TestYesNo.No +Errors: Error 14-16: Unable to compare values of type OptionSetValue (TestYesNo). + +// Can't concatenate strongly typed (CanConcatenateStronglyTyped = false) + +>> TestYesNo.Yes & TestYesNo.Yes & TestYesNo.No +"YesYesNo" + +>> TestXORYesNo( TestYesNo.Yes & TestYesNo.No, true ) +Error({Kind:ErrorKind.InvalidArgument}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt index fb18f17671..f1c871cc73 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt @@ -269,6 +269,24 @@ true >> TestXORYesNo( TestYesNo.No, TestYesNo.Yes ) true +>> If( false, TestYeaNay.Yea, false ) +false + +>> Text( If( false, TestYeaNay.Yea, false ) ) +"false" + +>> If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) +10 + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 10 ) ) +"10" + +>> If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) +11 + +>> Text( If( false, TestNumberCompareNumericCoerceFrom.V, 11 ) ) +"11" + //=========================================================================================================== // // 11. CanCoerceToBackingKind - For example, ErrorKind that can be used as a number @@ -280,9 +298,33 @@ true >> Int( TestNumberCoerceTo.V ) 5 +>> Power( TestNumberCoerceTo.V, 2 ) +25 + +>> TestSignNumber( TestNumberCoerceTo.V ) +1 + +>> TestSignDecimal( TestNumberCoerceTo.V ) +1 + >> Power( TestNumberCoerceTo.V, TestNumberCoerceTo.V ) 3125 +>> Int( TestNumberCompareNumeric.V ) +5 + +>> Power( TestNumberCompareNumeric.V, 2 ) +25 + +>> TestSignNumber( TestNumberCompareNumeric.V ) +1 + +>> TestSignDecimal( TestNumberCompareNumeric.V ) +1 + +>> Power( TestNumberCompareNumeric.V, TestNumberCompareNumeric.V ) +3125 + // Unless there is a specific reason, CanCoerceToBackingKind is expected to be true for most Boolean option sets >> TestYesNo.Yes && TestYeaNay.Yea @@ -721,6 +763,9 @@ false >> If( false, TestYesNo.Yes, false ) false +>> Text( If( false, TestYesNo.Yes, false ) ) +"false" + // Can be used as a Boolean (CanCoerceToBackingKind = true) >> Sqrt( TestYesNo.No ) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunner.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunner.cs index cd1fac967b..476c6ec25e 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunner.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunner.cs @@ -88,6 +88,7 @@ public static Dictionary ParseSetupString(string setup) possible.Add("AllEnumsSetup"); possible.Add("AllEnumsPlusTestEnumsSetup"); + possible.Add("AllEnumsPlusTestOptionSetsSetup"); possible.Add("AsyncTestSetup"); possible.Add("Blob"); possible.Add("DecimalSupport"); diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs index 465bbe2a49..a70ab27f8e 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Drawing; using System.Globalization; using System.Linq; @@ -10,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Functions; using Microsoft.PowerFx.Core.Tests; using Microsoft.PowerFx.Core.Types; @@ -41,6 +43,7 @@ public class ExpressionEvaluationTests : PowerFxTest { { "AllEnumsSetup", (AllEnumsSetup, null, null, null) }, { "AllEnumsPlusTestEnumsSetup", (AllEnumsPlusTestEnumsSetup, null, null, null) }, + { "AllEnumsPlusTestOptionSetsSetup", (AllEnumsPlusTestOptionSetsSetup, null, null, null) }, { "Blob", (null, BlobSetup, null, null) }, { "DecimalSupport", (null, null, null, null) }, // Decimal is enabled in the C# interpreter { "EnableJsonFunctions", (null, EnableJsonFunctions, null, null) }, @@ -97,6 +100,114 @@ private static PowerFxConfig AllEnumsSetup(PowerFxConfig config) return newConfig; } + private static PowerFxConfig AllEnumsPlusTestOptionSetsSetup(PowerFxConfig config) + { + var store = new EnumStoreBuilder().WithDefaultEnums(); + var newConfig = PowerFxConfig.BuildWithEnumStore(store, new TexlFunctionSet(), config.Features); + + // There are no built in enums with boolean values and only one with colors. Adding these for testing purposes. + newConfig.AddEntity(_testYesNo_OptionSet, _testYesNo_OptionSet.EntityName); + newConfig.AddEntity(_testYeaNay_OptionSet, _testYeaNay_OptionSet.EntityName); + newConfig.AddEntity(_testBooleanNoCoerce_OptionSet, _testBooleanNoCoerce_OptionSet.EntityName); + newConfig.AddEntity(_testNumberCoerceTo_OptionSet, _testNumberCoerceTo_OptionSet.EntityName); + newConfig.AddEntity(_testNumberCompareNumeric_OptionSet, _testNumberCompareNumeric_OptionSet.EntityName); + newConfig.AddEntity(_testNumberCompareNumericCoerceFrom_OptionSet, _testNumberCompareNumericCoerceFrom_OptionSet.EntityName); + newConfig.AddEntity(_testBlueRampColors_OptionSet, _testBlueRampColors_OptionSet.EntityName); + newConfig.AddEntity(_testRedRampColors_OptionSet, _testRedRampColors_OptionSet.EntityName); + + // There are likewise no built in functions that take Boolean backed option sets as parameters + newConfig.AddFunction(new TestXORBooleanFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORYesNo_OptionSetFunction() : new Boolean_TestXORYesNoFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORNoCoerce_OptionSetFunction() : new Boolean_TestXORNoCoerceFunction()); + newConfig.AddFunction(new TestColorInvertFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvert_OptionSetFunction() : new Color_TestColorBlueRampInvertFunction()); + newConfig.AddFunction(new TestSignDecimalFunction()); + newConfig.AddFunction(new TestSignNumberFunction()); + + return newConfig; + } + + private static readonly BooleanOptionSet _testYesNo_OptionSet = new BooleanOptionSet( + "TestYesNo", + new Dictionary() + { + { true, new DName("Yes") }, + { false, new DName("No") } + }.ToImmutableDictionary(), + canCoerceFromBackingKind: true, + canCoerceToBackingKind: true); + + private static readonly BooleanOptionSet _testYeaNay_OptionSet = new BooleanOptionSet( + "TestYeaNay", + new Dictionary() + { + { true, new DName("Yea") }, + { false, new DName("Nay") } + }.ToImmutableDictionary(), + canCoerceFromBackingKind: true, + canCoerceToBackingKind: true); + + private static readonly BooleanOptionSet _testBooleanNoCoerce_OptionSet = new BooleanOptionSet( + "TestBooleanNoCoerce", + new Dictionary() + { + { true, new DName("SuperTrue") }, + { false, new DName("SuperFalse") } + }.ToImmutableDictionary()); + + private static readonly NumberOptionSet _testNumberCoerceTo_OptionSet = new NumberOptionSet( + "TestNumberCoerceTo", + new Dictionary() + { + { 10D, new DName("X") }, + { 5D, new DName("V") }, + { 1D, new DName("I") } + }.ToImmutableDictionary(), + canCoerceToBackingKind: true); + + private static readonly NumberOptionSet _testNumberCompareNumeric_OptionSet = new NumberOptionSet( + "TestNumberCompareNumeric", + new Dictionary() + { + { 10D, new DName("X") }, + { 5D, new DName("V") }, + { 1D, new DName("I") } + }.ToImmutableDictionary(), + canCompareNumeric: true); + + private static readonly NumberOptionSet _testNumberCompareNumericCoerceFrom_OptionSet = new NumberOptionSet( + "TestNumberCompareNumericCoerceFrom", + new Dictionary() + { + { 10D, new DName("X") }, + { 5D, new DName("V") }, + { 1D, new DName("I") } + }.ToImmutableDictionary(), + canCompareNumeric: true, + canCoerceFromBackingKind: true); + + private static readonly ColorOptionSet _testBlueRampColors_OptionSet = new ColorOptionSet( + "TestBlueRamp", + new Dictionary() + { + { (double)0xFF0000FFU, new DName("Blue100") }, + { (double)0xFF3F3FFFU, new DName("Blue75") }, + { (double)0xFF7F7FFFU, new DName("Blue50") }, + { (double)0xFFBFBFFFU, new DName("Blue25") }, + { (double)0xFFFFFFFFU, new DName("Blue0") } + }.ToImmutableDictionary()); + + private static readonly ColorOptionSet _testRedRampColors_OptionSet = new ColorOptionSet( + "TestRedRamp", + new Dictionary() + { + { (double)0xFFFF0000U, new DName("Red100") }, + { (double)0xFFFF3F3FU, new DName("Red75") }, + { (double)0xFFFF7F7FU, new DName("Red50") }, + { (double)0xFFFFBFBFU, new DName("Red25") }, + { (double)0xFFFFFFFFU, new DName("Red0") } + }.ToImmutableDictionary()); + private static PowerFxConfig AllEnumsPlusTestEnumsSetup(PowerFxConfig config) { var store = new EnumStoreBuilder().WithDefaultEnums(); @@ -119,6 +230,8 @@ private static PowerFxConfig AllEnumsPlusTestEnumsSetup(PowerFxConfig config) newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORNoCoerceFunction() : new Boolean_TestXORNoCoerceFunction()); newConfig.AddFunction(new TestColorInvertFunction()); newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvertFunction() : new Color_TestColorBlueRampInvertFunction()); + newConfig.AddFunction(new TestSignDecimalFunction()); + newConfig.AddFunction(new TestSignNumberFunction()); return newConfig; } @@ -228,6 +341,32 @@ public FormulaValue Execute(BooleanValue x, BooleanValue y) } } + private class TestSignNumberFunction : ReflectionFunction + { + public TestSignNumberFunction() + : base("TestSignNumber", FormulaType.Number, new[] { FormulaType.Number }) + { + } + + public FormulaValue Execute(NumberValue x) + { + return NumberValue.New((double)Math.Sign(x.Value)); + } + } + + private class TestSignDecimalFunction : ReflectionFunction + { + public TestSignDecimalFunction() + : base("TestSignDecimal", FormulaType.Decimal, new[] { FormulaType.Decimal }) + { + } + + public FormulaValue Execute(DecimalValue x) + { + return DecimalValue.New((decimal)Math.Sign(x.Value)); + } + } + private class TestColorInvertFunction : ReflectionFunction { public TestColorInvertFunction() @@ -260,6 +399,25 @@ public FormulaValue Execute(OptionSetValue x) } } + private class STE_TestColorBlueRampInvert_OptionSetFunction : ReflectionFunction + { + public STE_TestColorBlueRampInvert_OptionSetFunction() + : base("TestColorBlueRampInvert", FormulaType.Color, new[] { _testBlueRampColors_OptionSet.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x) + { + var value = Convert.ToUInt32((double)x.ExecutionValue); + var c = Color.FromArgb( + (byte)((value >> 24) & 0xFF), + (byte)((value >> 16) & 0xFF), + (byte)((value >> 8) & 0xFF), + (byte)(value & 0xFF)); + return ColorValue.New(Color.FromArgb(c.A, c.R ^ 0xff, c.G ^ 0xff, c.B ^ 0xff)); + } + } + private class Color_TestColorBlueRampInvertFunction : ReflectionFunction { public Color_TestColorBlueRampInvertFunction() @@ -286,6 +444,19 @@ public FormulaValue Execute(OptionSetValue x, OptionSetValue y) } } + private class STE_TestXORYesNo_OptionSetFunction : ReflectionFunction + { + public STE_TestXORYesNo_OptionSetFunction() + : base("TestXORYesNo", FormulaType.Boolean, new[] { _testYesNo_OptionSet.FormulaType, _testYesNo_OptionSet.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x, OptionSetValue y) + { + return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); + } + } + // Reflection functions don't know how to coerce an enum to a Boolean, if STE is turned off private class Boolean_TestXORYesNoFunction : ReflectionFunction { @@ -313,6 +484,19 @@ public FormulaValue Execute(OptionSetValue x, OptionSetValue y) } } + private class STE_TestXORNoCoerce_OptionSetFunction : ReflectionFunction + { + public STE_TestXORNoCoerce_OptionSetFunction() + : base("TestXORNoCoerce", FormulaType.Boolean, new[] { _testBooleanNoCoerce_OptionSet.FormulaType, _testBooleanNoCoerce_OptionSet.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x, OptionSetValue y) + { + return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); + } + } + private class Boolean_TestXORNoCoerceFunction : ReflectionFunction { public Boolean_TestXORNoCoerceFunction() @@ -851,6 +1035,350 @@ protected override async Task RunAsyncInternal(string expr, string se return new RunResult(result); } } + + // These option set classes are internal because they aren't final. + // They rely on converting back and forth from strings which isn't very efficient. + // They are heare for testing purposes only, matching behavior for hosts that expose Dataverse option sets. + + private class NumberOptionSet : IExternalOptionSet + { + private readonly DisplayNameProvider _displayNameProvider; + private readonly DType _type; + + private readonly bool _canCoerceFromBackingKind; + private readonly bool _canCoerceToBackingKind; + private readonly bool _canCompareNumeric; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The members of the option set. Enumerable of pairs of logical name to display name. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// NameCollisionException is thrown if display and logical names for options are not unique. + /// + public NumberOptionSet(string name, ImmutableDictionary options, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false, bool canCompareNumeric = false) + : this(name, IntDisplayNameProvider(options), canCoerceFromBackingKind, canCoerceToBackingKind, canCompareNumeric) + { + } + + private static DisplayNameProvider IntDisplayNameProvider(ImmutableDictionary optionSetValues) + { + return DisplayNameUtility.MakeUnique(optionSetValues.Select(kvp => new KeyValuePair(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value))); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The DisplayNameProvider for the members of the OptionSet. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// Consider using to generate + /// the DisplayNameProvider. + /// + public NumberOptionSet(string name, DisplayNameProvider displayNameProvider, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false, bool canCompareNumeric = false) + { + EntityName = new DName(name); + Options = displayNameProvider.LogicalToDisplayPairs; + + _canCoerceFromBackingKind = canCoerceFromBackingKind; + _canCoerceToBackingKind = canCoerceToBackingKind; + _canCompareNumeric = canCompareNumeric; + + _displayNameProvider = displayNameProvider; + FormulaType = new OptionSetValueType(this); + _type = DType.CreateOptionSetType(this); + } + + /// + /// Name of the option set, referenceable from expressions. + /// + public DName EntityName { get; } + + /// + /// Contains the members of the option set. + /// Key is logical/invariant name, value is display name. + /// + public IEnumerable> Options { get; } + + /// + /// Formula Type corresponding to this option set. + /// Use in record/table contexts to define the type of fields using this option set. + /// + public OptionSetValueType FormulaType { get; } + + public bool TryGetValue(DName fieldName, out OptionSetValue optionSetValue) + { + if (!Options.Any(option => option.Key == fieldName)) + { + optionSetValue = null; + return false; + } + + var osft = new OptionSetValueType(_type.OptionSetInfo); + optionSetValue = new OptionSetValue(fieldName.Value, osft, double.Parse(fieldName.Value, CultureInfo.InvariantCulture)); + return true; + } + + IEnumerable IExternalOptionSet.OptionNames => Options.Select(option => option.Key); + + DisplayNameProvider IExternalOptionSet.DisplayNameProvider => _displayNameProvider; + + bool IExternalOptionSet.IsConvertingDisplayNameMapping => false; + + DType IExternalEntity.Type => _type; + + DKind IExternalOptionSet.BackingKind => DKind.Number; + + bool IExternalOptionSet.CanCoerceFromBackingKind => _canCoerceFromBackingKind; + + bool IExternalOptionSet.CanCoerceToBackingKind => _canCoerceToBackingKind; + + bool IExternalOptionSet.CanCompareNumeric => _canCompareNumeric; + + bool IExternalOptionSet.CanConcatenateStronglyTyped => false; + + public override bool Equals(object obj) + { + return obj is NumberOptionSet other && + EntityName == other.EntityName && + this._type == other._type; + } + + public override int GetHashCode() + { + return Hashing.CombineHash(EntityName.GetHashCode(), this._type.GetHashCode()); + } + } + + private class BooleanOptionSet : IExternalOptionSet + { + private readonly DisplayNameProvider _displayNameProvider; + private readonly DType _type; + + private readonly bool _canCoerceFromBackingKind; + private readonly bool _canCoerceToBackingKind; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The members of the option set. Enumerable of pairs of logical name to display name. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// NameCollisionException is thrown if display and logical names for options are not unique. + /// + public BooleanOptionSet(string name, ImmutableDictionary options, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false) + : this(name, IntDisplayNameProvider(options), canCoerceFromBackingKind, canCoerceToBackingKind) + { + } + + private static DisplayNameProvider IntDisplayNameProvider(ImmutableDictionary optionSetValues) + { + return DisplayNameUtility.MakeUnique(optionSetValues.Select(kvp => new KeyValuePair(kvp.Key ? "1" : "0", kvp.Value))); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The DisplayNameProvider for the members of the OptionSet. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// Consider using to generate + /// the DisplayNameProvider. + /// + public BooleanOptionSet(string name, DisplayNameProvider displayNameProvider, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false) + { + EntityName = new DName(name); + Options = displayNameProvider.LogicalToDisplayPairs; + + Contracts.Assert(Options.Count() == 2); + Contracts.Assert((Options.First().Key == "0" && Options.Last().Key == "1") || (Options.First().Key == "1" && Options.Last().Key == "0")); + + _canCoerceFromBackingKind = canCoerceFromBackingKind; + _canCoerceToBackingKind = canCoerceToBackingKind; + + _displayNameProvider = displayNameProvider; + FormulaType = new OptionSetValueType(this); + _type = DType.CreateOptionSetType(this); + } + + /// + /// Name of the option set, referenceable from expressions. + /// + public DName EntityName { get; } + + /// + /// Contains the members of the option set. + /// Key is logical/invariant name, value is display name. + /// + public IEnumerable> Options { get; } + + /// + /// Formula Type corresponding to this option set. + /// Use in record/table contexts to define the type of fields using this option set. + /// + public OptionSetValueType FormulaType { get; } + + public bool TryGetValue(DName fieldName, out OptionSetValue optionSetValue) + { + if (!Options.Any(option => option.Key == fieldName)) + { + optionSetValue = null; + return false; + } + + var osft = new OptionSetValueType(_type.OptionSetInfo); + optionSetValue = new OptionSetValue(fieldName.Value, osft, fieldName.Value != "0"); + return true; + } + + IEnumerable IExternalOptionSet.OptionNames => Options.Select(option => option.Key); + + DisplayNameProvider IExternalOptionSet.DisplayNameProvider => _displayNameProvider; + + bool IExternalOptionSet.IsConvertingDisplayNameMapping => false; + + DType IExternalEntity.Type => _type; + + DKind IExternalOptionSet.BackingKind => DKind.Boolean; + + bool IExternalOptionSet.CanCoerceFromBackingKind => _canCoerceFromBackingKind; + + bool IExternalOptionSet.CanCoerceToBackingKind => _canCoerceToBackingKind; + + bool IExternalOptionSet.CanCompareNumeric => false; + + bool IExternalOptionSet.CanConcatenateStronglyTyped => false; + + public override bool Equals(object obj) + { + return obj is BooleanOptionSet other && + EntityName == other.EntityName && + this._type == other._type; + } + + public override int GetHashCode() + { + return Hashing.CombineHash(EntityName.GetHashCode(), this._type.GetHashCode()); + } + } + + private class ColorOptionSet : IExternalOptionSet + { + private readonly DisplayNameProvider _displayNameProvider; + private readonly DType _type; + + private readonly bool _canCoerceFromBackingKind; + private readonly bool _canCoerceToBackingKind; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The members of the option set. Enumerable of pairs of logical name to display name. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// NameCollisionException is thrown if display and logical names for options are not unique. + /// + public ColorOptionSet(string name, ImmutableDictionary options, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false) + : this(name, IntDisplayNameProvider(options), canCoerceFromBackingKind, canCoerceToBackingKind) + { + } + + private static DisplayNameProvider IntDisplayNameProvider(ImmutableDictionary optionSetValues) + { + return DisplayNameUtility.MakeUnique(optionSetValues.Select(kvp => new KeyValuePair(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value))); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The DisplayNameProvider for the members of the OptionSet. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// The name of the option set. Will be available as a global name in Power Fx expressions. + /// Consider using to generate + /// the DisplayNameProvider. + /// + public ColorOptionSet(string name, DisplayNameProvider displayNameProvider, bool canCoerceFromBackingKind = false, bool canCoerceToBackingKind = false) + { + EntityName = new DName(name); + Options = displayNameProvider.LogicalToDisplayPairs; + + _canCoerceFromBackingKind = canCoerceFromBackingKind; + _canCoerceToBackingKind = canCoerceToBackingKind; + + _displayNameProvider = displayNameProvider; + FormulaType = new OptionSetValueType(this); + _type = DType.CreateOptionSetType(this); + } + + /// + /// Name of the option set, referenceable from expressions. + /// + public DName EntityName { get; } + + /// + /// Contains the members of the option set. + /// Key is logical/invariant name, value is display name. + /// + public IEnumerable> Options { get; } + + /// + /// Formula Type corresponding to this option set. + /// Use in record/table contexts to define the type of fields using this option set. + /// + public OptionSetValueType FormulaType { get; } + + public bool TryGetValue(DName fieldName, out OptionSetValue optionSetValue) + { + if (!Options.Any(option => option.Key == fieldName)) + { + optionSetValue = null; + return false; + } + + var osft = new OptionSetValueType(_type.OptionSetInfo); + optionSetValue = new OptionSetValue(fieldName.Value, osft, double.Parse(fieldName.Value, CultureInfo.InvariantCulture)); + return true; + } + + IEnumerable IExternalOptionSet.OptionNames => Options.Select(option => option.Key); + + DisplayNameProvider IExternalOptionSet.DisplayNameProvider => _displayNameProvider; + + bool IExternalOptionSet.IsConvertingDisplayNameMapping => false; + + DType IExternalEntity.Type => _type; + + DKind IExternalOptionSet.BackingKind => DKind.Color; + + bool IExternalOptionSet.CanCoerceFromBackingKind => _canCoerceFromBackingKind; + + bool IExternalOptionSet.CanCoerceToBackingKind => _canCoerceToBackingKind; + + bool IExternalOptionSet.CanCompareNumeric => false; + + bool IExternalOptionSet.CanConcatenateStronglyTyped => false; + + public override bool Equals(object obj) + { + return obj is ColorOptionSet other && + EntityName == other.EntityName && + this._type == other._type; + } + + public override int GetHashCode() + { + return Hashing.CombineHash(EntityName.GetHashCode(), this._type.GetHashCode()); + } + } } public static class UserInfoTestSetup