diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs index 3ea1b77240..72b45e9d47 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs @@ -325,7 +325,7 @@ private TexlBinding( HasParentItemReference = false; ContextScope = ruleScope; - BinderNodeMetadataArgTypeVisitor = new BinderNodesMetadataArgTypeVisitor(this, resolver, ruleScope, BindingConfig.UseThisRecordForRuleScope, features); + BinderNodeMetadataArgTypeVisitor = new BinderNodesMetadataArgTypeVisitor(this, resolver, ruleScope, BindingConfig.UseThisRecordForRuleScope, features, BindingConfig.NumberIsFloat); HasReferenceToAttachment = false; NodesToReplace = new List>(); UpdateDisplayNames = updateDisplayNames; @@ -430,7 +430,7 @@ public static TexlBinding Run( features ??= Features.None; var txb = new TexlBinding(glue, scopeResolver, queryOptionsMap, node, resolver, bindingConfig, ruleScope, updateDisplayNames, forceUpdateDisplayNames, rule: rule, features: features, delegationHintProvider: delegationHintProvider); - var vis = new Visitor(txb, resolver, ruleScope, bindingConfig.UseThisRecordForRuleScope, features); + var vis = new Visitor(txb, resolver, ruleScope, bindingConfig.UseThisRecordForRuleScope, features, bindingConfig.NumberIsFloat); vis.Run(); // If the expression is dataflow only Ie non-side effecting, void doesn't have any practical use and should be converted to errors. @@ -1061,8 +1061,7 @@ internal bool TryGetDataSourceInfo(TexlNode node, out IExternalDataSource dataSo return dataSourceInfo != null; case NodeKind.DottedName: - IExpandInfo info; - if (TryGetEntityInfo(node.AsDottedName(), out info)) + if (TryGetEntityInfo(node.AsDottedName(), out IExpandInfo info)) { dataSourceInfo = info.ParentDataSource; return dataSourceInfo != null; @@ -2507,16 +2506,18 @@ public Scope Up(int upCount) private readonly TexlBinding _txb; private Scope _currentScope; private int _currentScopeDsNodeId; - private readonly Features _features; + private readonly Features _features; + private readonly bool _numberIsFloat; - public Visitor(TexlBinding txb, INameResolver resolver, DType topScope, bool useThisRecordForRuleScope, Features features) + public Visitor(TexlBinding txb, INameResolver resolver, DType topScope, bool useThisRecordForRuleScope, Features features, bool numberIsFloat) { Contracts.AssertValue(txb); Contracts.AssertValueOrNull(resolver); _txb = txb; _nameResolver = resolver; - _features = features; + _features = features; + _numberIsFloat = numberIsFloat; _topScope = new Scope(null, null, topScope ?? DType.Error, useThisRecordForRuleScope ? new[] { ThisRecordDefaultName } : default); _currentScope = _topScope; @@ -2609,7 +2610,8 @@ private void VisitType(TypeLiteralNode node) return; } - var type = DTypeVisitor.Run(node.TypeRoot, _nameResolver); + var visitor = new DTypeVisitor(_numberIsFloat); + var type = visitor.Run(node.TypeRoot, _nameResolver); if (type.IsValid) { @@ -5178,7 +5180,8 @@ private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func) if (args[1] is FirstNameNode typeName) { - if (_nameResolver.LookupType(typeName.Ident.Name, out var typeArgType)) + var name = DType.MapNumber(typeName.Ident.Name, _numberIsFloat); + if (_nameResolver.LookupType(name, out var typeArgType)) { _txb.SetType(typeName, typeArgType._type); _txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, typeArgType._type, DPath.Root, 0))); diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/BinderNodesMetadataArgTypeVisitor.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/BinderNodesMetadataArgTypeVisitor.cs index 705e379d49..0adabc68d1 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Binding/BinderNodesMetadataArgTypeVisitor.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Binding/BinderNodesMetadataArgTypeVisitor.cs @@ -17,8 +17,8 @@ private sealed class BinderNodesMetadataArgTypeVisitor : Visitor { private readonly TexlBinding _txb; - public BinderNodesMetadataArgTypeVisitor(TexlBinding binding, INameResolver resolver, DType topScope, bool useThisRecordForRuleScope, Features features) - : base(binding, resolver, topScope, useThisRecordForRuleScope, features) + public BinderNodesMetadataArgTypeVisitor(TexlBinding binding, INameResolver resolver, DType topScope, bool useThisRecordForRuleScope, Features features, bool numberIsFloat) + : base(binding, resolver, topScope, useThisRecordForRuleScope, features, numberIsFloat) { Contracts.AssertValue(binding); diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs index 5a76538c50..cb11733ba4 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs @@ -350,8 +350,8 @@ public static IEnumerable CreateFunctions(IEnumerable continue; } - var parametersOk = CheckParameters(udf.Args, errors, nameResolver, out var parameterTypes); - var returnTypeOk = CheckReturnType(udf.ReturnType, errors, nameResolver, udf.IsImperative, out var returnType); + var parametersOk = CheckParameters(udf.Args, errors, nameResolver, udf.NumberIsFloat, out var parameterTypes); + var returnTypeOk = CheckReturnType(udf.ReturnType, errors, nameResolver, udf.IsImperative, udf.NumberIsFloat, out var returnType); if (!parametersOk || !returnTypeOk) { continue; @@ -401,8 +401,8 @@ internal static UserDefinedFunction CreatePartialFunction(UDF udf, INameResolver errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_TooManyParameters, udfName, MaxParameterCount)); } - var parametersOk = CheckParameters(udf.Args, errors, nameResolver, out var parameterTypes); - var returnTypeOk = CheckReturnType(udf.ReturnType, errors, nameResolver, udf.IsImperative, out var returnType); + var parametersOk = CheckParameters(udf.Args, errors, nameResolver, udf.NumberIsFloat, out var parameterTypes); + var returnTypeOk = CheckReturnType(udf.ReturnType, errors, nameResolver, udf.IsImperative, udf.NumberIsFloat, out var returnType); if (!returnTypeOk) { @@ -421,7 +421,7 @@ internal static UserDefinedFunction CreatePartialFunction(UDF udf, INameResolver return func; } - private static bool CheckParameters(ISet args, List errors, INameResolver nameResolver, out DType[] parameterTypes) + private static bool CheckParameters(ISet args, List errors, INameResolver nameResolver, bool numberIsFloat, out DType[] parameterTypes) { if (args.Count == 0) { @@ -447,7 +447,9 @@ private static bool CheckParameters(ISet args, List errors, I if (arg.TypeIdent != null) { - if (!nameResolver.LookupType(arg.TypeIdent.Name, out var parameterType)) + var name = DType.MapNumber(arg.TypeIdent.Name, numberIsFloat: numberIsFloat); + + if (!nameResolver.LookupType(name, out var parameterType)) { errors.Add(new TexlError(arg.TypeIdent, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_UnknownType, arg.TypeIdent.Name)); isParamCheckSuccessful = false; @@ -474,15 +476,17 @@ private static bool CheckParameters(ISet args, List errors, I return isParamCheckSuccessful; } - private static bool CheckReturnType(IdentToken returnTypeToken, List errors, INameResolver nameResolver, bool isImperative, out DType returnType) + private static bool CheckReturnType(IdentToken returnTypeToken, List errors, INameResolver nameResolver, bool isImperative, bool numberIsFloat, out DType returnType) { if (returnTypeToken == null) { returnType = DType.Unknown; return false; } + + var name = DType.MapNumber(returnTypeToken.Name, numberIsFloat); - if (!nameResolver.LookupType(returnTypeToken.Name, out var returnTypeFormulaType)) + if (!nameResolver.LookupType(name, out var returnTypeFormulaType)) { errors.Add(new TexlError(returnTypeToken, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_UnknownType, returnTypeToken.Name)); returnType = DType.Invalid; diff --git a/src/libraries/Microsoft.PowerFx.Core/Parser/ParserOptions.cs b/src/libraries/Microsoft.PowerFx.Core/Parser/ParserOptions.cs index f1c75de7d6..bf7389fdfe 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Parser/ParserOptions.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Parser/ParserOptions.cs @@ -39,7 +39,7 @@ public class ParserOptions /// This primarily determines numeric decimal separator character /// as well as chaining operator. /// - public CultureInfo Culture { get; set; } + public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// /// If greater than 0, enforces a maximum length on a single expression. diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs index fa3fa04f0a..d22caa1ac8 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs @@ -212,12 +212,13 @@ public void AddConstant(string name, FormulaValue data) _variables.Add(name, info); // can't exist } - internal static ParserOptions GetUDFParserOptions(CultureInfo culture, bool allowSideEffects) + internal static ParserOptions GetUDFParserOptions(CultureInfo culture, bool numberIsFloat, bool allowSideEffects) { return new ParserOptions() { AllowsSideEffects = allowSideEffects, Culture = culture ?? CultureInfo.InvariantCulture, + NumberIsFloat = numberIsFloat }; } @@ -225,14 +226,15 @@ internal static ParserOptions GetUDFParserOptions(CultureInfo culture, bool allo /// Adds user defined functions in the script. /// /// String representation of the user defined function. - /// CultureInfo to parse the script againts. Default is invariant. + /// CultureInfo to parse the script againts. Default is invariant. /// Extra symbols to bind UDF. Commonly coming from Engine. /// Additional symbols to bind UDF. /// Allow for curly brace parsing. - internal DefinitionsCheckResult AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, ReadOnlySymbolTable extraSymbolTable = null, bool allowSideEffects = false) + internal DefinitionsCheckResult AddUserDefinedFunction(string script, ParserOptions parserOptions = null, ReadOnlySymbolTable symbolTable = null, ReadOnlySymbolTable extraSymbolTable = null, bool allowSideEffects = false) { var composedSymbolTable = ReadOnlySymbolTable.Compose(this, symbolTable, extraSymbolTable); - var checkResult = GetDefinitionsCheckResult(script, parseCulture, composedSymbolTable, allowSideEffects); + var options = parserOptions ?? new ParserOptions(CultureInfo.InvariantCulture); + var checkResult = GetDefinitionsCheckResult(script, options, composedSymbolTable, allowSideEffects); var udfs = checkResult.ApplyCreateUserDefinedFunctions(); @@ -246,9 +248,9 @@ internal DefinitionsCheckResult AddUserDefinedFunction(string script, CultureInf return checkResult; } - internal static DefinitionsCheckResult GetDefinitionsCheckResult(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false) + internal static DefinitionsCheckResult GetDefinitionsCheckResult(string script, ParserOptions parserOptions, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false) { - var options = GetUDFParserOptions(parseCulture, allowSideEffects); + var options = GetUDFParserOptions(parserOptions.Culture, parserOptions.NumberIsFloat, allowSideEffects); var checkResult = new DefinitionsCheckResult(); return checkResult.SetText(script, options) .SetBindingInfo(symbolTable); diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs index 11a524d8b3..4540f2ea31 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs @@ -141,7 +141,7 @@ internal IReadOnlyDictionary ApplyResolveTypes() { if (_parse.DefinedTypes.Any()) { - this._resolvedTypes = DefinedTypeResolver.ResolveTypes(_parse.DefinedTypes.Where(dt => dt.IsParseValid), _symbols, out var errors); + this._resolvedTypes = DefinedTypeResolver.ResolveTypes(_parse.DefinedTypes.Where(dt => dt.IsParseValid), _symbols, _parserOptions.NumberIsFloat, out var errors); this._localSymbolTable.AddTypes(this._resolvedTypes); _errors.AddRange(ExpressionError.New(errors, _defaultErrorCulture)); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs index 480bab8594..02df4500a9 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs @@ -564,16 +564,18 @@ public string GetDisplayExpression(string expressionText, ReadOnlySymbolTable sy return ExpressionLocalizationHelper.ConvertExpression(expressionText, ruleScope, GetDefaultBindingConfig(), CreateResolverInternal(symbolTable), CreateBinderGlue(), culture, Config.Features, toDisplay: true); } - public DefinitionsCheckResult AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false) + public DefinitionsCheckResult AddUserDefinedFunction(string script, ParserOptions parserOptions = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false) { - return Config.SymbolTable.AddUserDefinedFunction(script, parseCulture, UDFDefaultBindingSymbols, symbolTable, allowSideEffects); + return Config.SymbolTable.AddUserDefinedFunction(script, parserOptions, UDFDefaultBindingSymbols, symbolTable, allowSideEffects); } +#if false public ReadOnlySymbolTable GetUserDefinedFunctionSymbol(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false) { var resultSymbols = new SymbolTable(); resultSymbols.AddUserDefinedFunction(script, parseCulture, UDFDefaultBindingSymbols, symbolTable, allowSideEffects); return resultSymbols; } +#endif } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Types/FormulaType.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Types/FormulaType.cs index 9ecc268047..48bb003ea6 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Types/FormulaType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Types/FormulaType.cs @@ -83,7 +83,7 @@ internal FormulaType(DType type) { new DName("DateTime"), DateTime }, { new DName("DateTimeTZInd"), DateTimeNoTimeZone }, { new DName("GUID"), Guid }, - { new DName("Number"), Number }, + { new DName("Float"), Number }, { new DName("Decimal"), Decimal }, { new DName("Text"), String }, { new DName("Hyperlink"), Hyperlink }, diff --git a/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/DTypeVisitor.cs b/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/DTypeVisitor.cs index 13e75c1015..44a3644f14 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/DTypeVisitor.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/DTypeVisitor.cs @@ -17,17 +17,20 @@ namespace Microsoft.PowerFx.Core.Syntax.Visitors // Visitor to resolve TypeLiteralNode.TypeRoot into DType. internal class DTypeVisitor : DefaultVisitor { - private DTypeVisitor() + private readonly bool _numberIsFloat; + + public DTypeVisitor(bool numberIsFloat) : base(DType.Invalid) { + _numberIsFloat = numberIsFloat; } - public static DType Run(TexlNode node, INameResolver context) + public DType Run(TexlNode node, INameResolver context) { Contracts.AssertValue(node); Contracts.AssertValue(context); - return node.Accept(new DTypeVisitor(), context); + return node.Accept(this, context); } public override DType Visit(FirstNameNode node, INameResolver context) @@ -35,7 +38,7 @@ public override DType Visit(FirstNameNode node, INameResolver context) Contracts.AssertValue(node); Contracts.AssertValue(context); - var name = node.Ident.Name; + var name = DType.MapNumber(node.Ident.Name, _numberIsFloat); if (context.LookupType(name, out FormulaType ft)) { return ft._type; diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/UntypedOrJSONConversionFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/UntypedOrJSONConversionFunction.cs index 44381bb084..e5ac4a71a8 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/UntypedOrJSONConversionFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/UntypedOrJSONConversionFunction.cs @@ -37,6 +37,9 @@ public override bool HasSuggestionsForParam(int argIndex) return argIndex == 1; } + // This list needs to be used with the intersection of Engine.PrimitiveTypes. The parser will effectively do this. + // Notably this list excludes ObjNull and Void, which make no sense to use in this context. + // It also excludes Color, as we have not implemented the coversion of "Red" and other color names. internal static readonly ISet SupportedJSONTypes = new HashSet { DType.Boolean, DType.Number, DType.Decimal, DType.Date, DType.DateTime, DType.DateTimeNoTimeZone, DType.Time, DType.String, DType.Guid, DType.Hyperlink, DType.UntypedObject }; public UntypedOrJSONConversionFunction(string name, TexlStrings.StringGetter description, DType returnType, int arityMax, params DType[] paramTypes) diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs index 42a6315f28..d7ab6771bb 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs @@ -4081,5 +4081,17 @@ internal static bool IsSupportedType(DType type, ISet supportedTypes, out return true; } + + internal static DName MapNumber(DName name, bool numberIsFloat) + { + if (name.Value == "Number") + { + return numberIsFloat ? new DName("Float") : new DName("Decimal"); + } + else + { + return name; + } + } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/DefinedTypeResolver.cs b/src/libraries/Microsoft.PowerFx.Core/Types/DefinedTypeResolver.cs index 8de232a595..9941cedc12 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Types/DefinedTypeResolver.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Types/DefinedTypeResolver.cs @@ -23,6 +23,7 @@ internal class DefinedTypeResolver { private readonly Dictionary _typesDict; private readonly ReadOnlySymbolTable _globalSymbols; + private readonly bool _numberIsFloat; private readonly IEnumerable _nodes; private readonly List> _edges; @@ -32,7 +33,7 @@ internal class DefinedTypeResolver // TODO: Add more future type names private static readonly ISet _restrictedTypeNames = new HashSet { "Record", "Table", "Currency" }; - private DefinedTypeResolver(IEnumerable definedTypes, ReadOnlySymbolTable globalSymbols) + private DefinedTypeResolver(IEnumerable definedTypes, ReadOnlySymbolTable globalSymbols, bool numberIsFloat) { Contracts.AssertValue(globalSymbols); Contracts.AssertValue(definedTypes); @@ -41,6 +42,7 @@ private DefinedTypeResolver(IEnumerable definedTypes, ReadOnlySymbo _errors = new List(); _typesDict = new Dictionary(); _globalSymbols = globalSymbols; + _numberIsFloat = numberIsFloat; foreach (var dt in definedTypes) { @@ -77,7 +79,7 @@ private bool CheckTypeName(DefinedType dt) { Contracts.AssertValue(dt); - var typeName = dt.Ident.Name; + var typeName = DType.MapNumber(dt.Ident.Name, _numberIsFloat); if (_restrictedTypeNames.Contains(typeName)) { @@ -108,7 +110,8 @@ private IReadOnlyDictionary ResolveTypes() { PopType(typeName, out var currentType); - var resolvedType = DTypeVisitor.Run(currentType.Type.TypeRoot, composedSymbols); + var visitor = new DTypeVisitor(_numberIsFloat); + var resolvedType = visitor.Run(currentType.Type.TypeRoot, composedSymbols); if (resolvedType == DType.Invalid) { @@ -153,13 +156,13 @@ private void PopType(string typeName, out DefinedType type) } // Resolve a given set of DefinedType ASTs to FormulaType. - public static IReadOnlyDictionary ResolveTypes(IEnumerable definedTypes, ReadOnlySymbolTable typeNameResolver, out List errors) + public static IReadOnlyDictionary ResolveTypes(IEnumerable definedTypes, ReadOnlySymbolTable typeNameResolver, bool numberIsFloat, out List errors) { Contracts.AssertValue(typeNameResolver); Contracts.AssertValue(definedTypes); Contracts.AssertAllValues(definedTypes); - var typeGraph = new DefinedTypeResolver(definedTypes, typeNameResolver); + var typeGraph = new DefinedTypeResolver(definedTypes, typeNameResolver, numberIsFloat); var resolvedTypes = typeGraph.ResolveTypes(); errors = typeGraph._errors; return resolvedTypes; diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs b/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs index f85f9ad6e4..8df5d4d2b7 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs @@ -43,6 +43,23 @@ public RecalcEngine() { } + internal static readonly ReadOnlySymbolTable _recalcPrimitiveTypes = + SymbolTable.NewDefaultTypes(ImmutableDictionary.CreateRange(new Dictionary() + { + { new DName("Boolean"), FormulaType.Boolean }, + { new DName("Color"), FormulaType.Color }, + { new DName("Date"), FormulaType.Date }, + { new DName("Time"), FormulaType.Time }, + { new DName("DateTime"), FormulaType.DateTime }, + { new DName("GUID"), FormulaType.Guid }, + { new DName("Float"), FormulaType.Number }, + { new DName("Decimal"), FormulaType.Decimal }, + { new DName("Text"), FormulaType.String }, + { new DName("Hyperlink"), FormulaType.Hyperlink }, + { new DName("Dynamic"), FormulaType.UntypedObject }, + { new DName("Void"), FormulaType.Void }, + })); + public RecalcEngine(PowerFxConfig powerFxConfig) : base(powerFxConfig) { @@ -392,14 +409,8 @@ public void UpdateSupportedFunctions(SymbolTable s) /// Script containing user defined functions and/or named formulas. /// Locale to parse user defined script. /// Function to be called when update is triggered. - public void AddUserDefinitions(string script, CultureInfo parseCulture = null, Action onUpdate = null) + public void AddUserDefinitions(string script, ParserOptions options, Action onUpdate = null) { - var options = new ParserOptions() - { - AllowsSideEffects = false, - Culture = parseCulture ?? CultureInfo.InvariantCulture - }; - var sb = new StringBuilder(); var checkResult = new DefinitionsCheckResult(this.Config.Features) diff --git a/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Public/UDFEditorContextScope.cs b/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Public/UDFEditorContextScope.cs index 90a073161e..1c01c6f24e 100644 --- a/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Public/UDFEditorContextScope.cs +++ b/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Public/UDFEditorContextScope.cs @@ -25,10 +25,11 @@ internal UDFEditorContextScope( Engine engine, CultureInfo cultureInfo, ReadOnlySymbolTable symbols = null, + bool numberIsFloat = false, bool allowSideEffects = false) : this( engine, - SymbolTable.GetUDFParserOptions(cultureInfo, allowSideEffects), + SymbolTable.GetUDFParserOptions(cultureInfo, numberIsFloat, allowSideEffects), ReadOnlySymbolTable.Compose(engine.UDFDefaultBindingSymbols, symbols)) { } diff --git a/src/libraries/Microsoft.PowerFx.Repl/Modules/ModuleLoadContext.cs b/src/libraries/Microsoft.PowerFx.Repl/Modules/ModuleLoadContext.cs index 7b522052d6..714681041c 100644 --- a/src/libraries/Microsoft.PowerFx.Repl/Modules/ModuleLoadContext.cs +++ b/src/libraries/Microsoft.PowerFx.Repl/Modules/ModuleLoadContext.cs @@ -170,7 +170,7 @@ private bool ResolveBody(ModulePoco poco, SymbolTable moduleExports, ReadOnlySym var definedTypes = parseResult.DefinedTypes; if (definedTypes != null && definedTypes.Any()) { - var resolvedTypes = DefinedTypeResolver.ResolveTypes(definedTypes, incomingSymbols, out var errors4); + var resolvedTypes = DefinedTypeResolver.ResolveTypes(definedTypes, incomingSymbols, false, out var errors4); errors.AddRange(ExpressionError.NewFragment(errors4, str, fragmentLocation)); if (errors.Any(x => !x.IsWarning)) { diff --git a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs index dc90498451..b462903eec 100644 --- a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs +++ b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs @@ -510,7 +510,7 @@ await this.Output.WriteLineAsync($"Error: Can't set '{name}' to a Void value.", if (this.AllowUserDefinedFunctions && definitionsCheckResult.IsSuccess && definitionsCheckResult.ContainsUDF) { - var defCheckResult = this.Engine.AddUserDefinedFunction(expression, this.ParserOptions.Culture, extraSymbolTable); + var defCheckResult = this.Engine.AddUserDefinedFunction(expression, this.ParserOptions, extraSymbolTable); if (!defCheckResult.IsSuccess) { diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs index ebdc59df63..ca0799755f 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs @@ -102,7 +102,7 @@ private void TestDelegableExpressions(Features features, string expression, bool var engine = new Engine(config); if (!string.IsNullOrWhiteSpace(udfScript)) { - engine.AddUserDefinedFunction(udfScript, CultureInfo.InvariantCulture); + engine.AddUserDefinedFunction(udfScript); } var result = engine.Check(expression); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UDFHasSameDefinitionTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UDFHasSameDefinitionTests.cs index 1cc25ce66c..470e7c3ee4 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UDFHasSameDefinitionTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UDFHasSameDefinitionTests.cs @@ -52,7 +52,8 @@ public void TestSimpleUDFSameness(string udfFormula1, string udfFormula2, bool a { var parserOptions = new ParserOptions() { - AllowsSideEffects = true + AllowsSideEffects = true, + NumberIsFloat = true }; var types = FormulaType.PrimitiveTypes.Union(new Dictionary() diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs index d30a9a14a5..600692c05b 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs @@ -84,7 +84,8 @@ public void TestCoercionWithUDFParams(string udfScript, string invocationScript, { var parserOptions = new ParserOptions() { - AllowsSideEffects = false + AllowsSideEffects = false, + NumberIsFloat = true }; var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library); @@ -136,7 +137,8 @@ public void TestCoercionWithUDFBody(string udfScript, string expectedIR) { var parserOptions = new ParserOptions() { - AllowsSideEffects = false + AllowsSideEffects = false, + NumberIsFloat = true, }; var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); @@ -403,8 +405,9 @@ public void TestUserDefinedFunctionValidity2() public void Basic() { var st1 = SymbolTable.WithPrimitiveTypes(); - st1.AddUserDefinedFunction("Foo1(x: Number): Number = x*2;"); - st1.AddUserDefinedFunction("Foo2(x: Number): Number = Foo1(x)+1;"); + var parserOptions = new ParserOptions() { NumberIsFloat = true }; + st1.AddUserDefinedFunction("Foo1(x: Number): Number = x*2;", parserOptions); + st1.AddUserDefinedFunction("Foo2(x: Number): Number = Foo1(x)+1;", parserOptions); var engine = new Engine(); var check = engine.Check("Foo2(3)", symbolTable: st1); @@ -435,8 +438,17 @@ public void BasicEngine() var extra = new SymbolTable(); extra.AddVariable("K1", FormulaType.Number); - var engine = new Engine(); - engine.AddUserDefinedFunction("Foo1(x: Number): Number = Abs(K1);", symbolTable: extra); + var engine = new Engine() + { + PrimitiveTypes = + SymbolTable.NewDefaultTypes(ImmutableDictionary.CreateRange(new Dictionary() + { + { new DName("Float"), FormulaType.Number }, + })) + }; + + var parserOptions = new ParserOptions() { NumberIsFloat = true }; + engine.AddUserDefinedFunction("Foo1(x: Number): Number = Abs(K1);", parserOptions, symbolTable: extra); var check = engine.Check("Foo1(3)"); Assert.True(check.IsSuccess); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs index 87fc368b52..18ca669d0b 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs @@ -43,8 +43,9 @@ public class UserDefinedTypeTests : PowerFxTest [InlineData("A := Type({})", "", false)] public void TestUserDefinedType(string typeDefinition, string expectedDefinedTypeString, bool isValid) { + var parserOptions = new ParserOptions() { NumberIsFloat = true }; var checkResult = new DefinitionsCheckResult() - .SetText(typeDefinition) + .SetText(typeDefinition, parserOptions) .SetBindingInfo(_primitiveTypes); checkResult.ApplyResolveTypes(); diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/UDFIntellisenseTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/UDFIntellisenseTests.cs index f1befc35df..95d0bf221e 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/UDFIntellisenseTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/UDFIntellisenseTests.cs @@ -16,9 +16,9 @@ public class UDFIntellisenseTests [Theory] [InlineData("AddNumbers(x: Number, y: Number): Number = x + y; |", "")] [InlineData("AddNumbers(x: Number, y: Number): Number = x + |", "")] - [InlineData("AddNumbers(x: Number, y: Number): Number = x + Su|", "ErrorKind.InsufficientMemory,ErrorKind.NotSupported,StartOfWeek.Sunday,Sum,Substitute,TraceOptions.IgnoreUnsupportedTypes")] - [InlineData("AddNumbers(x: Number, y: Number): |", "Boolean,Color,Date,DateTime,Dynamic,GUID,Hyperlink,Number,Text,Time,Void")] - [InlineData("AddNumbers(x: Number, y: |", "Boolean,Color,Date,DateTime,Dynamic,GUID,Hyperlink,Number,Text,Time")] + [InlineData("AddNumbers(x: Number, y: Number): Number = x + Su|", "ErrorKind.InsufficientMemory,ErrorKind.NotSupported,StartOfWeek.Sunday,Substitute,Sum,TraceOptions.IgnoreUnsupportedTypes")] + [InlineData("AddNumbers(x: Number, y: Number): |", "Boolean,Color,Date,DateTime,Decimal,Dynamic,GUID,Hyperlink,Number,Text,Time,Void")] + [InlineData("AddNumbers(x: Number, y: |", "Boolean,Color,Date,DateTime,Decimal,Dynamic,GUID,Hyperlink,Number,Text,Time")] // Suggest UDF names when calling one UDF from another [InlineData("AddNumbers(x: Number, y: Number): Number = x + y; AddNumbers2(x: Number, y: Text): Number = AddNum|", "AddNumbers")] diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs index 96f4c88e2e..4ca1e0f437 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs @@ -379,10 +379,11 @@ public void UserDefinitionOnUpdateTest() { var config = new PowerFxConfig(); config.EnableSetFunction(); - var engine = new RecalcEngine(config); + var engine = new RecalcEngine(config); + var options = new ParserOptions { AllowsSideEffects = true, NumberIsFloat = true }; engine.UpdateVariable("A", 1m); - engine.AddUserDefinitions("B=A*2;C=A*B;", onUpdate: OnUpdate); + engine.AddUserDefinitions("B=A*2;C=A*B;", options: options, onUpdate: OnUpdate); AssertUpdate("B-->2;C-->2;"); // Can't set formulas, they're read only @@ -549,7 +550,7 @@ public void UserDefinedFunctionTest(string udfExpression, string expression, boo try { - recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture); + recalcEngine.AddUserDefinedFunction(udfExpression); var check = recalcEngine.Check(expression); @@ -576,7 +577,7 @@ public void UserDefinedFunctionSymbolTableTest(string script, string expression, engine.UpdateVariable("myArg", FormulaValue.New(10)); - symbolTable.AddUserDefinedFunction(script, CultureInfo.InvariantCulture, engine.SupportedFunctions, engine.PrimitiveTypes); + symbolTable.AddUserDefinedFunction(script, symbolTable: engine.SupportedFunctions, extraSymbolTable: engine.PrimitiveTypes); var check = engine.Check(expression, symbolTable: symbolTable); var result = check.GetEvaluator().Eval(); @@ -592,7 +593,7 @@ public void DefinedFunctionsErrorsTest(string script) { var engine = new RecalcEngine(); - Assert.False(engine.AddUserDefinedFunction(script, CultureInfo.InvariantCulture).IsSuccess); + Assert.False(engine.AddUserDefinedFunction(script).IsSuccess); } // Overloads and conflict @@ -605,8 +606,9 @@ public void FunctionPrecedenceTest(string script, double expected) SymbolTable st = new SymbolTable { DebugName = "Extras" }; st.AddConstant("K1", FormulaValue.New(9999)); - var engine = new RecalcEngine(); - engine.AddUserDefinedFunction(script, symbolTable: st); + var engine = new RecalcEngine(); + var parserOptions = new ParserOptions { NumberIsFloat = true }; + engine.AddUserDefinedFunction(script, parserOptions: parserOptions, symbolTable: st); var check = engine.Check("foo(1)"); Assert.True(check.IsSuccess); @@ -619,36 +621,39 @@ public void FunctionPrecedenceTest(string script, double expected) [Fact] public void ShadowingFunctionPrecedenceTest() { - var engine = new RecalcEngine(); - engine.AddUserDefinedFunction("Concat(x:Text):Text = \"xyz\"; Average(x:Number):Number = 11111;"); + var engine = new RecalcEngine(); + var parserOptions = new ParserOptions { AllowsSideEffects = true, NumberIsFloat = true }; + engine.AddUserDefinedFunction("Concat(x:Text):Text = \"xyz\"; Average(x:Number):Number = 11111;", parserOptions: parserOptions); - var check = engine.Check("Concat(\"abc\")"); + var check = engine.Check("Concat(\"abc\")", parserOptions); Assert.True(check.IsSuccess); Assert.Equal(FormulaType.String, check.ReturnType); var result = check.GetEvaluator().Eval(); Assert.Equal("xyz", ((StringValue)result).Value); - check = engine.Check("Average(123)"); + check = engine.Check("Average(123)", parserOptions); Assert.True(check.IsSuccess); Assert.Equal(FormulaType.Number, check.ReturnType); result = check.GetEvaluator().Eval(); Assert.Equal(11111, result.AsDouble()); - engine.AddUserDefinitions("Test := Type({A: Number}); TestTable := Type([{A: Number}]);" + - "Filter(X: TestTable):Test = First(X); ShowColumns(X: TestTable):TestTable = FirstN(X, 3);"); + engine.AddUserDefinitions( + "Test := Type({A: Number}); TestTable := Type([{A: Number}]);" + + "Filter(X: TestTable):Test = First(X); ShowColumns(X: TestTable):TestTable = FirstN(X, 3);", + options: parserOptions); - check = engine.Check("Filter([{A: 123}]).A"); + check = engine.Check("Filter([{A: 123}]).A", options: parserOptions); Assert.True(check.IsSuccess); Assert.Equal(FormulaType.Number, check.ReturnType); result = check.GetEvaluator().Eval(); Assert.Equal(123, result.AsDouble()); - check = engine.Check("CountRows(ShowColumns([{A: 123}, {A: 124}, {A:125}, {A:126}, {A: 127}]))"); + check = engine.Check("CountRows(ShowColumns([{A: 123}, {A: 124}, {A:125}, {A:126}, {A: 127}]))", options: parserOptions); Assert.True(check.IsSuccess); - Assert.Equal(FormulaType.Decimal, check.ReturnType); + Assert.Equal(FormulaType.Number, check.ReturnType); result = check.GetEvaluator().Eval(); Assert.Equal(3, result.AsDouble()); @@ -677,7 +682,7 @@ public void BehaviorFunctionInImperativeUDF(string udfExpression, bool expectedE var engine = new RecalcEngine(config); engine.UpdateVariable("a", 1m); - var result = engine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); + var result = engine.AddUserDefinedFunction(udfExpression, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); Assert.True(expectedError ? result.Errors.Count() > 0 : result.Errors.Count() == 0); if (expectedError) @@ -711,7 +716,7 @@ public void ImperativeUserDefinedFunctionTest(string udfExpression, string expre var recalcEngine = new RecalcEngine(config); recalcEngine.UpdateVariable("a", 1m); - var definitionsCheckResult = recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); + var definitionsCheckResult = recalcEngine.AddUserDefinedFunction(udfExpression, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); if (!expectedError) { @@ -751,7 +756,7 @@ public void TestMismatchReturnType(string udfExpression, bool expectedError, boo var engine = new RecalcEngine(config); engine.UpdateVariable("x", 1m); - var result = engine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); + var result = engine.AddUserDefinedFunction(udfExpression, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); Assert.True(expectedError ? result.Errors.Count() > 0 : result.Errors.Count() == 0); if (expectedError) @@ -793,7 +798,7 @@ public void DelegableUDFTest() var recalcEngine = new RecalcEngine(config); - recalcEngine.AddUserDefinedFunction("A():MyDataSourceTableType = Filter(MyDataSource, Value > 10);C():MyDataSourceTableType = A(); B():MyDataSourceTableType = Filter(C(), Value > 11); D():MyDataSourceTableType = { Filter(B(), Value > 12); };", CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); + recalcEngine.AddUserDefinedFunction("A():MyDataSourceTableType = Filter(MyDataSource, Value > 10);C():MyDataSourceTableType = A(); B():MyDataSourceTableType = Filter(C(), Value > 11); D():MyDataSourceTableType = { Filter(B(), Value > 12); };", symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); var result = recalcEngine.Check("A()"); Assert.True(result.IsSuccess); var callNode = result.Binding.Top.AsCall(); @@ -829,7 +834,7 @@ public void DelegableUDFTest() Assert.False(callInfo.Function.IsServerDelegatable(callNode, result.Binding)); // Binding fails for recursive definitions and hence function is not added. - Assert.False(recalcEngine.AddUserDefinedFunction("E():Void = { E(); };", CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true).IsSuccess); + Assert.False(recalcEngine.AddUserDefinedFunction("E():Void = { E(); };", symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true).IsSuccess); } [Fact] @@ -860,7 +865,7 @@ public void PageableUDFTest() var recalcEngine = new RecalcEngine(config); - recalcEngine.AddUserDefinedFunction("A():MyDataSourceTableType = Filter(MyDataSource, Value > 10);C():MyDataSourceTableType = A(); CheckCountRowsOfUDF():Number = CountRows(C()); CheckCountRowsOfDS():Number = CountRows(MyDataSource);", CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); + recalcEngine.AddUserDefinedFunction("A():MyDataSourceTableType = Filter(MyDataSource, Value > 10);C():MyDataSourceTableType = A(); CheckCountRowsOfUDF():Number = CountRows(C()); CheckCountRowsOfDS():Number = CountRows(MyDataSource);", symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); var result = recalcEngine.Check("A()"); Assert.True(result.IsSuccess); var callNode = result.Binding.Top.AsCall(); @@ -922,12 +927,12 @@ public void TestInheritanceOfDelegationWarningsInUDFs() }; var engine = new RecalcEngine(config); - var result = engine.AddUserDefinedFunction("NonDelegatableUDF():MyDataSourceTableType = Filter(MyDataSource, Sqrt(Value) > 5);", CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + var result = engine.AddUserDefinedFunction("NonDelegatableUDF():MyDataSourceTableType = Filter(MyDataSource, Sqrt(Value) > 5);", symbolTable: engine.EngineSymbols, allowSideEffects: true); Assert.True(result.IsSuccess); var func = engine.Functions.WithName("NonDelegatableUDF").First() as UserDefinedFunction; Assert.True(func.HasDelegationWarning); - result = engine.AddUserDefinedFunction("NonDelegatableUDF2():MyDataSourceTableType = NonDelegatableUDF();", CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + result = engine.AddUserDefinedFunction("NonDelegatableUDF2():MyDataSourceTableType = NonDelegatableUDF();", symbolTable: engine.EngineSymbols, allowSideEffects: true); Assert.True(result.IsSuccess); func = engine.Functions.WithName("NonDelegatableUDF2").First() as UserDefinedFunction; Assert.True(func.HasDelegationWarning); @@ -2088,9 +2093,10 @@ public void UserDefinedTypeTest(string userDefinitions, string evalExpression, b { var config = new PowerFxConfig(); var recalcEngine = new RecalcEngine(config); - var parserOptions = new ParserOptions() + var parserOptions = new ParserOptions() { AllowsSideEffects = false, + NumberIsFloat = true }; var entityType = new Interpreter.Tests.DatabaseSimulationTests.TestEntityType(new Tests.BindingEngineTests.LazyRecursiveRecordType().ToTable()._type); @@ -2099,8 +2105,9 @@ public void UserDefinedTypeTest(string userDefinitions, string evalExpression, b recalcEngine._symbolValues.Add("Entity", entityValue); if (isValidDefinition) - { - recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); + { + var options = new ParserOptions { AllowsSideEffects = true, NumberIsFloat = true }; + recalcEngine.AddUserDefinitions(userDefinitions, options: options); if (isValidEval) { @@ -2121,7 +2128,8 @@ public void UserDefinedTypeTest(string userDefinitions, string evalExpression, b { Assert.Throws(() => { - recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); + var options = new ParserOptions { AllowsSideEffects = true, NumberIsFloat = true }; + recalcEngine.AddUserDefinitions(userDefinitions, options); }); } } @@ -2152,7 +2160,8 @@ public void UDFImperativeVsRecordAmbiguityTest(string udf, string evalExpression var recalcEngine = new RecalcEngine(config); var parserOptions = new ParserOptions() { - AllowsSideEffects = true, + AllowsSideEffects = true, + NumberIsFloat = true }; var extraSymbols = new SymbolTable(); @@ -2160,12 +2169,12 @@ public void UDFImperativeVsRecordAmbiguityTest(string udf, string evalExpression if (isValid) { - recalcEngine.AddUserDefinedFunction(udf, CultureInfo.InvariantCulture, extraSymbols, true); + recalcEngine.AddUserDefinedFunction(udf, parserOptions: parserOptions, symbolTable: extraSymbols, allowSideEffects: true); Assert.Equal(expectedResult, recalcEngine.Eval(evalExpression, options: parserOptions).ToObject()); } else { - Assert.False(recalcEngine.AddUserDefinedFunction(udf, CultureInfo.InvariantCulture, extraSymbols, true).IsSuccess); + Assert.False(recalcEngine.AddUserDefinedFunction(udf, parserOptions: parserOptions, symbolTable: extraSymbols, allowSideEffects: true).IsSuccess); } } @@ -2275,19 +2284,21 @@ public void RecordOfTests(string userDefinitions, string evalExpression, bool is { var config = new PowerFxConfig(); var recalcEngine = new RecalcEngine(config); - var parserOptions = new ParserOptions(); + var parserOptions = new ParserOptions() { NumberIsFloat = true }; recalcEngine.Config.SymbolTable.AddType(new DName("Accounts"), FormulaType.Build(TestUtils.DT("*[id: n, name:s, address:s]"))); recalcEngine.Config.SymbolTable.AddType(new DName("SomeRecord"), FormulaType.Build(TestUtils.DT("![id: n, name:s]"))); if (isValid) - { - recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); + { + recalcEngine.AddUserDefinitions(userDefinitions, options: parserOptions); + var r = recalcEngine.Eval(evalExpression, options: parserOptions).ToObject(); Assert.Equal(expectedResult, recalcEngine.Eval(evalExpression, options: parserOptions).ToObject()); } else - { - Assert.Throws(() => recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture)); + { + var options = new ParserOptions { AllowsSideEffects = true, NumberIsFloat = true }; + Assert.Throws(() => recalcEngine.AddUserDefinitions(userDefinitions, options: options)); } } @@ -2304,8 +2315,8 @@ public void UDFAggregateInputErrorMessage(string userDefinitions, string evalExp { var config = new PowerFxConfig(); var recalcEngine = new RecalcEngine(config); - var parserOptions = new ParserOptions(); - recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); + var parserOptions = new ParserOptions() { NumberIsFloat = true }; + recalcEngine.AddUserDefinitions(userDefinitions, parserOptions); var ex = Assert.Throws(() => { recalcEngine.Eval(evalExpression, options: parserOptions); diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs index 715e8cb96a..cbc1d0410b 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs @@ -29,9 +29,14 @@ public class UserDefinedTests [InlineData("myvar=Weekday(Date(2024,2,2)) > 1 And false;Bar(x: Number): Number = x + x;", "Bar(1) + myvar", 2d)] public void NamedFormulaEntryTest(string script, string expression, double expected) { - var engine = new RecalcEngine(); + var engine = new RecalcEngine(); + var options = new ParserOptions() + { + AllowsSideEffects = true, + NumberIsFloat = true, + }; - engine.AddUserDefinitions(script); + engine.AddUserDefinitions(script, options: options); var check = engine.Check(expression); Assert.True(check.IsSuccess); diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs index a679870987..85bb129310 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs @@ -38,7 +38,8 @@ public void PrimitivesTest() var engine = SetupEngine(); // custom-type type alias - engine.AddUserDefinitions("T := Type(Number);"); + var options = new ParserOptions() { NumberIsFloat = true }; + engine.AddUserDefinitions("T := Type(Number);", options); // Positive tests CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "Number", 42D); @@ -73,7 +74,8 @@ public void RecordsTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T := Type({a: Number});"); + var options = new ParserOptions() { NumberIsFloat = true }; + engine.AddUserDefinitions("T := Type({a: Number});", options); dynamic obj1 = new ExpandoObject(); obj1.a = 5D; @@ -104,7 +106,8 @@ public void TablesTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T := Type([{a: Number}]);"); + var options = new ParserOptions() { NumberIsFloat = true }; + engine.AddUserDefinitions("T := Type([{a: Number}]);", options); var t1 = new object[] { 5D }; var t2 = new object[] { 1m, 2m, 3m, 4m }; @@ -184,13 +187,15 @@ public void TestFunctionsWithTypeArgs() private void CheckIsTypeAsTypeParseJSON(RecalcEngine engine, string json, string type, object expectedValue, bool isValid = true, ParserOptions options = null) { - var result = engine.Eval($"AsType(ParseJSON({json}), {type})", options: options); + var parserOptions = options ?? new ParserOptions() { NumberIsFloat = true }; + + var result = engine.Eval($"AsType(ParseJSON({json}), {type})", options: parserOptions); CheckResult(expectedValue, result, isValid); - result = engine.Eval($"ParseJSON({json}, {type})", options: options); + result = engine.Eval($"ParseJSON({json}, {type})", options: parserOptions); CheckResult(expectedValue, result, isValid); - result = engine.Eval($"IsType(ParseJSON({json}), {type})", options: options); + result = engine.Eval($"IsType(ParseJSON({json}), {type})", options: parserOptions); Assert.Equal(isValid, result.ToObject()); } diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json index 150b1b0f31..cfc6e53b39 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json @@ -1,5 +1,5 @@ { "Type": { - "Name": "Number" + "Name": "Float" } } diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json index 87be95db9c..7fefb5e402 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json @@ -10,7 +10,7 @@ }, "Foo": { "Type": { - "Name": "Number" + "Name": "Float" } } } diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json index bb66f2c8ff..93879d546b 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json @@ -24,7 +24,7 @@ }, "Foo": { "Type": { - "Name": "Number" + "Name": "Float" } } } diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json index 9992ebd804..9b0ac0ef3d 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json @@ -26,7 +26,7 @@ }, "Foo": { "Type": { - "Name": "Number" + "Name": "Float" } } }