зеркало из https://github.com/microsoft/Power-Fx.git
UO set property (#2547)
Issue https://github.com/microsoft/Power-Fx/issues/2501.
This commit is contained in:
Родитель
07a17a998f
Коммит
d193162359
|
@ -122,6 +122,13 @@ namespace Microsoft.PowerFx.Types
|
|||
public static ErrorValue NewError(IEnumerable<ExpressionError> error, FormulaType type)
|
||||
{
|
||||
return new ErrorValue(IRContext.NotInSource(type), error.ToList());
|
||||
}
|
||||
|
||||
public static UntypedObjectValue New(UntypedObjectBase untypedObject)
|
||||
{
|
||||
return new UntypedObjectValue(
|
||||
IRContext.NotInSource(new UntypedObjectType()),
|
||||
untypedObject);
|
||||
}
|
||||
|
||||
public static UntypedObjectValue New(IUntypedObject untypedObject)
|
||||
|
|
|
@ -79,5 +79,39 @@ namespace Microsoft.PowerFx.Types
|
|||
// Not supported for the time being.
|
||||
throw new NotImplementedException("UntypedObjectValue cannot be serialized.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class UntypedObjectBase : IUntypedObject
|
||||
{
|
||||
public abstract IUntypedObject this[int index] { get; }
|
||||
|
||||
public abstract FormulaType Type { get; }
|
||||
|
||||
public abstract int GetArrayLength();
|
||||
|
||||
public abstract bool GetBoolean();
|
||||
|
||||
public abstract decimal GetDecimal();
|
||||
|
||||
public abstract double GetDouble();
|
||||
|
||||
public abstract string GetString();
|
||||
|
||||
public abstract string GetUntypedNumber();
|
||||
|
||||
public abstract bool TryGetProperty(string value, out IUntypedObject result);
|
||||
|
||||
public abstract bool TryGetPropertyNames(out IEnumerable<string> propertyNames);
|
||||
|
||||
/// <summary>
|
||||
/// Set a property on the object.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="value">FormulaValue to be set.</param>
|
||||
public virtual void SetProperty(string propertyName, FormulaValue value)
|
||||
{
|
||||
// In case of unwanted behavior, throw an CustomFunctionErrorException exception.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
|
@ -35,6 +33,8 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
|
||||
public override bool IsSelfContained => true;
|
||||
|
||||
public override bool PropagatesMutability => true;
|
||||
|
||||
public IndexFunction_UO()
|
||||
: base(IndexInvariantFunctionName, TexlStrings.AboutIndex, FunctionCategories.Table, DType.UntypedObject, 0, 2, 2, DType.UntypedObject, DType.Number)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ using Microsoft.PowerFx.Interpreter;
|
|||
using Microsoft.PowerFx.Interpreter.Exceptions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using static Microsoft.PowerFx.Functions.Library;
|
||||
using static Microsoft.PowerFx.Syntax.PrettyPrintVisitor;
|
||||
|
||||
namespace Microsoft.PowerFx
|
||||
{
|
||||
|
@ -154,28 +155,58 @@ namespace Microsoft.PowerFx
|
|||
// Set is unique because it has an l-value for the first arg.
|
||||
// Async params can't have out-params.
|
||||
// Return null if not handled. Else non-null if handled.
|
||||
private async Task<FormulaValue> TryHandleSet(CallNode node, EvalVisitorContext context)
|
||||
{
|
||||
// Special case Set() calls because they take an LValue.
|
||||
if (node.Function.GetType() != typeof(RecalcEngineSetFunction))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var arg0 = node.Args[0];
|
||||
var arg1 = node.Args[1];
|
||||
|
||||
private async Task<FormulaValue> TryHandleSet(CallNode node, EvalVisitorContext context)
|
||||
{
|
||||
// Special case Set() calls because they take an LValue.
|
||||
if (node.Function.GetType() != typeof(RecalcEngineSetFunction))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var arg0 = node.Args[0];
|
||||
var arg1 = node.Args[1];
|
||||
|
||||
var newValue = await arg1.Accept(this, context).ConfigureAwait(false);
|
||||
|
||||
if (arg0.IRContext.IsMutation && arg0 is RecordFieldAccessNode rfan)
|
||||
if (arg0.IRContext.IsMutation)
|
||||
{
|
||||
var arg0value = await rfan.From.Accept(this, context).ConfigureAwait(false);
|
||||
|
||||
if (arg0value is RecordValue rv)
|
||||
if (arg0 is RecordFieldAccessNode rfan)
|
||||
{
|
||||
rv.ShallowCopyFieldInPlace(rfan.Field);
|
||||
rv.UpdateField(rfan.Field, newValue);
|
||||
return node.IRContext.ResultType._type.Kind == DKind.Boolean ? FormulaValue.New(true) : FormulaValue.NewVoid();
|
||||
var arg0value = await rfan.From.Accept(this, context).ConfigureAwait(false);
|
||||
|
||||
if (arg0value is RecordValue rv)
|
||||
{
|
||||
rv.ShallowCopyFieldInPlace(rfan.Field);
|
||||
rv.UpdateField(rfan.Field, newValue);
|
||||
return node.IRContext.ResultType._type.Kind == DKind.Boolean ? FormulaValue.New(true) : FormulaValue.NewVoid();
|
||||
}
|
||||
else
|
||||
{
|
||||
return CommonErrors.UnreachableCodeError(node.IRContext);
|
||||
}
|
||||
}
|
||||
else if (arg0 is BinaryOpNode bon && bon.Op == BinaryOpKind.DynamicGetField)
|
||||
{
|
||||
var arg0value = await bon.Left.Accept(this, context).ConfigureAwait(false);
|
||||
|
||||
if (arg0value is UntypedObjectValue uov && uov.Impl is UntypedObjectBase impl)
|
||||
{
|
||||
TextLiteralNode textLiteralNode = (TextLiteralNode)bon.Right;
|
||||
|
||||
try
|
||||
{
|
||||
impl.SetProperty(textLiteralNode.LiteralValue, newValue);
|
||||
return node.IRContext.ResultType._type.Kind == DKind.Boolean ? FormulaValue.New(true) : FormulaValue.NewVoid();
|
||||
}
|
||||
catch (CustomFunctionErrorException ex)
|
||||
{
|
||||
return new ErrorValue(node.IRContext, new ExpressionError() { Message = ex.Message, Span = node.IRContext.SourceContext, Kind = ex.ErrorKind });
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
return CommonErrors.NotYetImplementedError(node.IRContext, $"Class {impl.GetType()} does not implement 'SetProperty'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -184,9 +215,9 @@ namespace Microsoft.PowerFx
|
|||
}
|
||||
|
||||
// Binder has already ensured this is a first name node as well as mutable symbol.
|
||||
if (arg0 is ResolvedObjectNode obj)
|
||||
{
|
||||
if (obj.Value is ISymbolSlot sym)
|
||||
if (arg0 is ResolvedObjectNode obj)
|
||||
{
|
||||
if (obj.Value is ISymbolSlot sym)
|
||||
{
|
||||
if (_symbolValues != null)
|
||||
{
|
||||
|
@ -197,9 +228,9 @@ namespace Microsoft.PowerFx
|
|||
// This may happen if the runtime symbols are missing a value and we failed to update.
|
||||
}
|
||||
}
|
||||
|
||||
// Fail?
|
||||
return CommonErrors.UnreachableCodeError(node.IRContext);
|
||||
|
||||
// Fail?
|
||||
return CommonErrors.UnreachableCodeError(node.IRContext);
|
||||
}
|
||||
|
||||
// Handle invoke SetProperty(source.Prop, newValue)
|
||||
|
|
|
@ -55,6 +55,12 @@ namespace Microsoft.PowerFx.Interpreter
|
|||
nodeToCoercedTypeMap = null;
|
||||
returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : DType.Boolean;
|
||||
|
||||
if (argTypes[0].IsUntypedObject)
|
||||
{
|
||||
// if arg0 is untyped object, the host implementation will handle arg1.
|
||||
return true;
|
||||
}
|
||||
|
||||
var isValid = CheckType(context, args[1], argTypes[1], argTypes[0], errors, ref nodeToCoercedTypeMap);
|
||||
|
||||
return isValid;
|
||||
|
@ -64,43 +70,63 @@ namespace Microsoft.PowerFx.Interpreter
|
|||
public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
|
||||
{
|
||||
base.CheckSemantics(binding, args, argTypes, errors);
|
||||
|
||||
Contracts.AssertValue(args);
|
||||
Contracts.AssertAllValues(args);
|
||||
Contracts.AssertValue(argTypes);
|
||||
Contracts.AssertAllValid(argTypes);
|
||||
Contracts.Assert(args.Length == argTypes.Length);
|
||||
Contracts.AssertValue(errors);
|
||||
Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity);
|
||||
|
||||
|
||||
Contracts.AssertValue(args);
|
||||
Contracts.AssertAllValues(args);
|
||||
Contracts.AssertValue(argTypes);
|
||||
Contracts.AssertAllValid(argTypes);
|
||||
Contracts.Assert(args.Length == argTypes.Length);
|
||||
Contracts.AssertValue(errors);
|
||||
Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity);
|
||||
|
||||
var arg0 = argTypes[0];
|
||||
var arg1 = argTypes[1];
|
||||
|
||||
// Type check
|
||||
if (!(arg0.Accepts(arg1, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: binding.Features.PowerFxV1CompatibilityRules) ||
|
||||
(arg0.IsNumeric && arg1.IsNumeric)))
|
||||
if (arg0.IsUntypedObject)
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrBadType_ExpectedType_ProvidedType, arg0.GetKindString(), arg1.GetKindString());
|
||||
return;
|
||||
if (CheckMutability(binding, args, argTypes, errors))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(arg0.Accepts(arg1, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: binding.Features.PowerFxV1CompatibilityRules) ||
|
||||
(arg0.IsNumeric && arg1.IsNumeric)))
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrBadType_ExpectedType_ProvidedType, arg0.GetKindString(), arg1.GetKindString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg1.AggregateHasExpandedType())
|
||||
{
|
||||
if (arg1.IsTable)
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrSetVariableWithRelationshipNotAllowTable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg1.IsRecord)
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrSetVariableWithRelationshipNotAllowRecord);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckMutability(binding, args, argTypes, errors))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg1.AggregateHasExpandedType())
|
||||
{
|
||||
if (arg1.IsTable)
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrSetVariableWithRelationshipNotAllowTable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg1.IsRecord)
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrSetVariableWithRelationshipNotAllowRecord);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var firstName = args[0].AsFirstName();
|
||||
errors.EnsureError(DocumentErrorSeverity.Severe, args[0], TexlStrings.ErrNeedValidVariableName_Arg, Name, args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
private bool CheckMutability(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
|
||||
{
|
||||
var firstName = args[0].AsFirstName();
|
||||
if (firstName != null)
|
||||
{
|
||||
// Variable reference assignment, for example Set( x, 3 )
|
||||
|
@ -108,18 +134,17 @@ namespace Microsoft.PowerFx.Interpreter
|
|||
if (info.Data is NameSymbol nameSymbol && nameSymbol.Props.CanSet)
|
||||
{
|
||||
// We have a variable, success
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (binding.Features.PowerFxV1CompatibilityRules)
|
||||
{
|
||||
// Deep mutation, for example Set( x.a, 4 )
|
||||
base.ValidateArgumentIsSetMutable(binding, args[0], errors);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
errors.EnsureError(DocumentErrorSeverity.Severe, args[0], TexlStrings.ErrNeedValidVariableName_Arg, Name, args[0]);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,27 +9,27 @@ using Microsoft.PowerFx.Types;
|
|||
|
||||
namespace Microsoft.PowerFx.Functions
|
||||
{
|
||||
internal class JsonUntypedObject : IUntypedObject
|
||||
{
|
||||
internal readonly JsonElement _element;
|
||||
|
||||
public JsonUntypedObject(JsonElement element)
|
||||
{
|
||||
_element = element;
|
||||
}
|
||||
|
||||
public FormulaType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
return ExternalType.ObjectType;
|
||||
case JsonValueKind.Array:
|
||||
return ExternalType.ArrayType;
|
||||
case JsonValueKind.String:
|
||||
return FormulaType.String;
|
||||
internal class JsonUntypedObject : UntypedObjectBase
|
||||
{
|
||||
internal readonly JsonElement _element;
|
||||
|
||||
public JsonUntypedObject(JsonElement element)
|
||||
{
|
||||
_element = element;
|
||||
}
|
||||
|
||||
public override FormulaType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
return ExternalType.ObjectType;
|
||||
case JsonValueKind.Array:
|
||||
return ExternalType.ArrayType;
|
||||
case JsonValueKind.String:
|
||||
return FormulaType.String;
|
||||
case JsonValueKind.Number:
|
||||
// Do not be tempted to use FormulaType.Number here. JSON numbers can be interpreted as either
|
||||
// a float or a decimal and connectors take advantage of this to interop with decimals in databases.
|
||||
|
@ -41,45 +41,45 @@ namespace Microsoft.PowerFx.Functions
|
|||
// number types of various capacities and complements, fixed or floating, binary or decimal.That can make
|
||||
// interchange between different programming languages difficult. JSON instead offers only the representation of
|
||||
// numbers that humans use: a sequence of digits. All programming languages know how to make sense of digit
|
||||
// sequences even if they disagree on internal representations. That is enough to allow interchange
|
||||
// sequences even if they disagree on internal representations. That is enough to allow interchange
|
||||
return ExternalType.UntypedNumber;
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
return FormulaType.Boolean;
|
||||
}
|
||||
|
||||
return FormulaType.Blank;
|
||||
}
|
||||
}
|
||||
|
||||
public IUntypedObject this[int index] => new JsonUntypedObject(_element[index]);
|
||||
|
||||
public int GetArrayLength()
|
||||
{
|
||||
return _element.GetArrayLength();
|
||||
}
|
||||
|
||||
public double GetDouble()
|
||||
{
|
||||
return _element.GetDouble();
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
{
|
||||
return _element.GetString();
|
||||
}
|
||||
|
||||
public bool GetBoolean()
|
||||
{
|
||||
return _element.GetBoolean();
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
return FormulaType.Boolean;
|
||||
}
|
||||
|
||||
return FormulaType.Blank;
|
||||
}
|
||||
}
|
||||
|
||||
public decimal GetDecimal()
|
||||
public override IUntypedObject this[int index] => new JsonUntypedObject(_element[index]);
|
||||
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
return _element.GetArrayLength();
|
||||
}
|
||||
|
||||
public override double GetDouble()
|
||||
{
|
||||
return _element.GetDouble();
|
||||
}
|
||||
|
||||
public override string GetString()
|
||||
{
|
||||
return _element.GetString();
|
||||
}
|
||||
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
return _element.GetBoolean();
|
||||
}
|
||||
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
return _element.GetDecimal();
|
||||
}
|
||||
|
||||
public string GetUntypedNumber()
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
if (Type == ExternalType.UntypedNumber)
|
||||
{
|
||||
|
@ -89,25 +89,25 @@ namespace Microsoft.PowerFx.Functions
|
|||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetProperty(string value, out IUntypedObject result)
|
||||
{
|
||||
var res = _element.TryGetProperty(value, out var je);
|
||||
result = new JsonUntypedObject(je);
|
||||
return res;
|
||||
}
|
||||
|
||||
public bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
public override bool TryGetProperty(string value, out IUntypedObject result)
|
||||
{
|
||||
var res = _element.TryGetProperty(value, out var je);
|
||||
result = new JsonUntypedObject(je);
|
||||
return res;
|
||||
}
|
||||
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
if (_element.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
result = _element.EnumerateObject().Select(x => x.Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
"Microsoft.PowerFx.Types.IDelegatableTableValue",
|
||||
"Microsoft.PowerFx.Types.ITypeVisitor",
|
||||
"Microsoft.PowerFx.Types.IUntypedObject",
|
||||
"Microsoft.PowerFx.Types.UntypedObjectBase",
|
||||
"Microsoft.PowerFx.Types.IValueVisitor",
|
||||
"Microsoft.PowerFx.Types.NamedFormulaType",
|
||||
"Microsoft.PowerFx.Types.NamedValue",
|
||||
|
|
|
@ -294,7 +294,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private class SimpleObject : IUntypedObject
|
||||
private class SimpleObject : UntypedObjectBase
|
||||
{
|
||||
private readonly FormulaValue _value;
|
||||
|
||||
|
@ -303,46 +303,46 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
_value = value;
|
||||
}
|
||||
|
||||
public IUntypedObject this[int index] => throw new NotImplementedException();
|
||||
public override IUntypedObject this[int index] => throw new NotImplementedException();
|
||||
|
||||
public FormulaType Type => _value.Type;
|
||||
public override FormulaType Type => _value.Type;
|
||||
|
||||
public int GetArrayLength()
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool GetBoolean()
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
return ((BooleanValue)_value).Value;
|
||||
}
|
||||
|
||||
public double GetDouble()
|
||||
public override double GetDouble()
|
||||
{
|
||||
return ((NumberValue)_value).Value;
|
||||
}
|
||||
|
||||
public decimal GetDecimal()
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
return ((DecimalValue)_value).Value;
|
||||
}
|
||||
|
||||
public string GetUntypedNumber()
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
return ((StringValue)_value).Value;
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
public override string GetString()
|
||||
{
|
||||
return ((StringValue)_value).Value;
|
||||
}
|
||||
|
||||
public bool TryGetProperty(string value, out IUntypedObject result)
|
||||
public override bool TryGetProperty(string value, out IUntypedObject result)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
|
|
|
@ -339,4 +339,8 @@ Errors: Error 45-52: The value passed to the 'Set' function cannot be changed.
|
|||
>> rwt1_copy3
|
||||
{Field1:3,Field2:{Field2_1:321,Field2_2:"2_2",Field2_4:Table({Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true})}}
|
||||
|
||||
// Untyped object
|
||||
>> IsError(Set(ParseJSON("{""x"":5}").x, 99))
|
||||
Errors: Error 34-36: The value passed to the 'Set' function cannot be changed.
|
||||
|
||||
|
||||
|
|
|
@ -45,9 +45,59 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
Assert.Equal(FormulaType.UntypedObject, fv3.Type);
|
||||
Assert.True(fv3 is BlankValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PadUntypedObjectMutationTest()
|
||||
{
|
||||
DataTable dt = new DataTable("someTable");
|
||||
dt.Columns.Add("Id", typeof(int));
|
||||
dt.Columns.Add("Column1", typeof(string));
|
||||
dt.Columns.Add("Column2", typeof(string));
|
||||
dt.Rows.Add(1, "data1", "data2");
|
||||
dt.Rows.Add(2, "data3", "data4");
|
||||
|
||||
PadUntypedObject uo = new PadUntypedObject(dt);
|
||||
PadUntypedObject uoCell = new PadUntypedObject(99);
|
||||
NotImplementedUntypedObject notImplementedUO = new NotImplementedUntypedObject(dt);
|
||||
|
||||
UntypedObjectValue uov = new UntypedObjectValue(IRContext.NotInSource(FormulaType.UntypedObject), uo);
|
||||
UntypedObjectValue uovCell = new UntypedObjectValue(IRContext.NotInSource(FormulaType.UntypedObject), uoCell);
|
||||
UntypedObjectValue notImplementedValue = new UntypedObjectValue(IRContext.NotInSource(FormulaType.UntypedObject), notImplementedUO);
|
||||
|
||||
PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1);
|
||||
RecalcEngine engine = new RecalcEngine(config);
|
||||
|
||||
engine.Config.SymbolTable.EnableMutationFunctions();
|
||||
engine.UpdateVariable("padTable", uov, new SymbolProperties() { CanMutate = true, CanSetMutate = true });
|
||||
engine.UpdateVariable("notImplementedUO", notImplementedValue, new SymbolProperties() { CanMutate = true, CanSetMutate = true });
|
||||
engine.UpdateVariable("padCell", uovCell);
|
||||
|
||||
// Setting an untyped object (padCell).
|
||||
DecimalValue result = (DecimalValue)engine.Eval(@"Set(Index(padTable, 1).Id, padCell);Index(padTable, 1).Id+1", options: new ParserOptions() { AllowsSideEffects = true });
|
||||
Assert.Equal(100m, result.ToObject());
|
||||
|
||||
// Setting a strongly typed object (99).
|
||||
result = (DecimalValue)engine.Eval(@"Set(Index(padTable, 1).Id, 99);Index(padTable, 1).Id+1", options: new ParserOptions() { AllowsSideEffects = true });
|
||||
Assert.Equal(100m, result.ToObject());
|
||||
|
||||
// Property does not exist.
|
||||
ErrorValue errorValue = (ErrorValue)engine.Eval(@"Set(Index(padTable, 1).DoesNotExist, 99)", options: new ParserOptions() { AllowsSideEffects = true });
|
||||
Assert.IsType<ErrorValue>(errorValue);
|
||||
Assert.Equal(ErrorKind.InvalidArgument, errorValue.Errors.First().Kind);
|
||||
|
||||
// Type not supported.
|
||||
errorValue = (ErrorValue)engine.Eval(@"Set(Index(padTable, 1).Column2, GUID())", options: new ParserOptions() { AllowsSideEffects = true });
|
||||
Assert.IsType<ErrorValue>(errorValue);
|
||||
Assert.Equal(ErrorKind.InvalidArgument, errorValue.Errors.First().Kind);
|
||||
|
||||
// 'SetProperty' not implemented.
|
||||
errorValue = (ErrorValue)engine.Eval(@"Set(Index(notImplementedUO, 1).Id, 1)", options: new ParserOptions() { AllowsSideEffects = true });
|
||||
Assert.IsType<ErrorValue>(errorValue);
|
||||
Assert.Equal(ErrorKind.NotSupported, errorValue.Errors.First().Kind);
|
||||
}
|
||||
}
|
||||
|
||||
public class PadUntypedObject : IUntypedObject
|
||||
public class PadUntypedObject : UntypedObjectBase
|
||||
{
|
||||
public DataTable DataTable;
|
||||
public DataRow DataRow;
|
||||
|
@ -74,8 +124,6 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
Cell = cell;
|
||||
}
|
||||
|
||||
public IUntypedObject this[int index] => Index(index);
|
||||
|
||||
private IUntypedObject Index(int index)
|
||||
{
|
||||
return (DataTable != null)
|
||||
|
@ -85,7 +133,9 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FormulaType Type => GetFormulaType();
|
||||
public override FormulaType Type => GetFormulaType();
|
||||
|
||||
public override IUntypedObject this[int index] => Index(index);
|
||||
|
||||
private FormulaType GetFormulaType()
|
||||
{
|
||||
|
@ -102,7 +152,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
: ExternalType.ArrayAndObject;
|
||||
}
|
||||
|
||||
public int GetArrayLength()
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
return (DataTable != null)
|
||||
? DataTable.Rows.Count
|
||||
|
@ -111,12 +161,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool GetBoolean()
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public double GetDouble()
|
||||
public override double GetDouble()
|
||||
{
|
||||
return Cell switch
|
||||
{
|
||||
|
@ -127,12 +177,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
};
|
||||
}
|
||||
|
||||
public decimal GetDecimal()
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetUntypedNumber()
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -142,12 +192,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
public override string GetString()
|
||||
{
|
||||
return Cell.ToString();
|
||||
}
|
||||
|
||||
public bool TryGetProperty(string propertyName, out IUntypedObject result)
|
||||
public override bool TryGetProperty(string propertyName, out IUntypedObject result)
|
||||
{
|
||||
if (DataTable != null)
|
||||
{
|
||||
|
@ -165,7 +215,154 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void SetProperty(string propertyName, FormulaValue value)
|
||||
{
|
||||
if (DataTable != null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (!DataRow.Table.Columns.Contains(propertyName))
|
||||
{
|
||||
value = default;
|
||||
throw new CustomFunctionErrorException($"Property '{propertyName}' does not exist.", ErrorKind.InvalidArgument);
|
||||
}
|
||||
|
||||
if (value is DecimalValue dv)
|
||||
{
|
||||
DataRow[propertyName] = dv.Value;
|
||||
}
|
||||
else if (value is GuidValue)
|
||||
{
|
||||
throw new CustomFunctionErrorException($"Type '{value.Type.ToString()}' is not supported.", ErrorKind.InvalidArgument);
|
||||
}
|
||||
else if (value is UntypedObjectValue uov)
|
||||
{
|
||||
if (DataRow[propertyName].GetType() == typeof(string))
|
||||
{
|
||||
DataRow[propertyName] = uov.Impl.GetString();
|
||||
}
|
||||
else if (DataRow[propertyName].GetType() == typeof(int))
|
||||
{
|
||||
DataRow[propertyName] = uov.Impl.GetDouble();
|
||||
}
|
||||
else if (DataRow[propertyName].GetType() == typeof(bool))
|
||||
{
|
||||
DataRow[propertyName] = uov.Impl.GetBoolean();
|
||||
}
|
||||
else if (DataRow[propertyName].GetType() == typeof(decimal))
|
||||
{
|
||||
DataRow[propertyName] = uov.Impl.GetDecimal();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CustomFunctionErrorException($"Type '{DataRow[propertyName].GetType()}' is not supported.", ErrorKind.InvalidArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NotImplementedUntypedObject : UntypedObjectBase
|
||||
{
|
||||
public DataTable DataTable;
|
||||
public DataRow DataRow;
|
||||
public object Cell;
|
||||
|
||||
public NotImplementedUntypedObject(DataTable dt)
|
||||
{
|
||||
DataTable = dt;
|
||||
DataRow = null;
|
||||
Cell = null;
|
||||
}
|
||||
|
||||
public NotImplementedUntypedObject(DataRow dr)
|
||||
{
|
||||
DataTable = null;
|
||||
DataRow = dr;
|
||||
Cell = null;
|
||||
}
|
||||
|
||||
public NotImplementedUntypedObject(object cell)
|
||||
{
|
||||
DataTable = null;
|
||||
DataRow = null;
|
||||
Cell = cell;
|
||||
}
|
||||
|
||||
private IUntypedObject Index(int index)
|
||||
{
|
||||
return new NotImplementedUntypedObject(DataTable.Rows[index]);
|
||||
}
|
||||
|
||||
public override FormulaType Type => GetFormulaType();
|
||||
|
||||
public override IUntypedObject this[int index] => Index(index);
|
||||
|
||||
private FormulaType GetFormulaType()
|
||||
{
|
||||
return ExternalType.ArrayAndObject;
|
||||
}
|
||||
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override double GetDouble()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string[] GetPropertyNames()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetString()
|
||||
{
|
||||
return Cell.ToString();
|
||||
}
|
||||
|
||||
public override bool TryGetProperty(string propertyName, out IUntypedObject result)
|
||||
{
|
||||
if (DataTable != null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (!DataRow.Table.Columns.Contains(propertyName))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cell = DataRow[propertyName];
|
||||
result = new NotImplementedUntypedObject(cell);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
|
|
|
@ -799,6 +799,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
var t_bsType = TableType.Empty().Add("b", FormulaType.String);
|
||||
engine.UpdateVariable("t_bs1", FormulaValue.NewTable(t_bsType.ToRecord()));
|
||||
engine.UpdateVariable("t_bs2", FormulaValue.NewTable(t_bsType.ToRecord()));
|
||||
|
||||
engine.Config.EnableJsonFunctions();
|
||||
}
|
||||
|
||||
private static void TraceSetup(RecalcEngine engine, bool numberIsFloat)
|
||||
|
|
|
@ -14,7 +14,8 @@ namespace Microsoft.PowerFx.Tests
|
|||
// Wrap a .net object as an UntypedObject.
|
||||
// This will lazily marshal through the object as it's accessed.
|
||||
[DebuggerDisplay("{_source}")]
|
||||
public class PrimitiveWrapperAsUnknownObject : IUntypedObject
|
||||
[DebuggerDisplay("{_source}")]
|
||||
public class PrimitiveWrapperAsUnknownObject : UntypedObjectBase
|
||||
{
|
||||
public readonly object _source;
|
||||
|
||||
|
@ -28,7 +29,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
return FormulaValue.New(new PrimitiveWrapperAsUnknownObject(source));
|
||||
}
|
||||
|
||||
public FormulaType Type
|
||||
public override FormulaType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -61,7 +62,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
}
|
||||
}
|
||||
|
||||
public IUntypedObject this[int index]
|
||||
public override IUntypedObject this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -81,13 +82,13 @@ namespace Microsoft.PowerFx.Tests
|
|||
}
|
||||
}
|
||||
|
||||
public int GetArrayLength()
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
var a = (Array)_source;
|
||||
return a.Length;
|
||||
}
|
||||
|
||||
public bool GetBoolean()
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
Assert.True(Type == FormulaType.Boolean);
|
||||
|
||||
|
@ -99,7 +100,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
throw new InvalidOperationException($"Not a boolean type");
|
||||
}
|
||||
|
||||
public double GetDouble()
|
||||
public override double GetDouble()
|
||||
{
|
||||
// Fx will only call this helper for numbers.
|
||||
Assert.True(Type == FormulaType.Number);
|
||||
|
@ -122,7 +123,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
throw new InvalidOperationException($"Not a number type");
|
||||
}
|
||||
|
||||
public decimal GetDecimal()
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
// Fx will only call this helper for decimals.
|
||||
Assert.True(Type == FormulaType.Decimal);
|
||||
|
@ -145,12 +146,12 @@ namespace Microsoft.PowerFx.Tests
|
|||
throw new InvalidOperationException($"Not a decimal type");
|
||||
}
|
||||
|
||||
public string GetUntypedNumber()
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
public override string GetString()
|
||||
{
|
||||
Assert.True(Type == FormulaType.String);
|
||||
|
||||
|
@ -162,7 +163,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
throw new InvalidOperationException($"Not a string type");
|
||||
}
|
||||
|
||||
public bool TryGetProperty(string value, out IUntypedObject result)
|
||||
public override bool TryGetProperty(string value, out IUntypedObject result)
|
||||
{
|
||||
Assert.True(Type == ExternalType.ObjectType);
|
||||
|
||||
|
@ -191,7 +192,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
{
|
||||
// Demonstrate mutation example using IUntypedObject
|
||||
public class ScenarioMutation : PowerFxTest
|
||||
{
|
||||
{
|
||||
[Fact]
|
||||
public void MutabilityTest()
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
{
|
||||
["prop"] = FormulaValue.New(123)
|
||||
};
|
||||
|
||||
|
||||
var obj = MutableObject.New(d);
|
||||
engine.UpdateVariable("obj", obj);
|
||||
|
||||
|
@ -45,15 +45,15 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
|
||||
[Theory]
|
||||
[InlineData("Assert2(obj.prop, 123); Set2(obj, \"prop\", 456); Assert2(obj.prop, 456)")]
|
||||
[InlineData("Assert2(obj.prop, 123); Set3(obj, \"prop\", \"prop2\"); Assert2(obj.prop, 456)")]
|
||||
[InlineData("Assert2(obj.prop, 123); Set3(obj, \"prop\", \"prop2\"); Assert2(obj.prop, 456)")]
|
||||
public void MutabilityTest_Chain(string expr)
|
||||
{
|
||||
var config = new PowerFxConfig();
|
||||
var config = new PowerFxConfig();
|
||||
config.AddFunction(new Assert2Function());
|
||||
config.AddFunction(new Set2Function());
|
||||
config.AddFunction(new Set3Function());
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
var d = new Dictionary<string, FormulaValue>
|
||||
{
|
||||
["prop"] = FormulaValue.New(123),
|
||||
|
@ -72,7 +72,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
|
||||
Assert.IsType<DecimalValue>(x);
|
||||
Assert.Equal(456, ((DecimalValue)x).Value);
|
||||
Assert.Equal(456, ((DecimalValue)d["prop"]).Value);
|
||||
Assert.Equal(456, ((DecimalValue)d["prop"]).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -133,7 +133,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
{
|
||||
var impl = (MutableObject)obj.Impl;
|
||||
impl.TryGetProperty(propName2.Value, out var propValue);
|
||||
var val = propValue.GetDecimal();
|
||||
var val = propValue.GetDecimal();
|
||||
impl.Set(propName.Value, new DecimalValue(IRContext.NotInSource(FormulaType.Decimal), val));
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private class MutableObject : IUntypedObject
|
||||
private class MutableObject : UntypedObjectBase
|
||||
{
|
||||
private Dictionary<string, FormulaValue> _values = new Dictionary<string, FormulaValue>();
|
||||
|
||||
|
@ -212,41 +212,41 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
return FormulaValue.New(x);
|
||||
}
|
||||
|
||||
public IUntypedObject this[int index] => throw new NotImplementedException();
|
||||
public override IUntypedObject this[int index] => throw new NotImplementedException();
|
||||
|
||||
public FormulaType Type => ExternalType.ObjectType;
|
||||
public override FormulaType Type => ExternalType.ObjectType;
|
||||
|
||||
public int GetArrayLength()
|
||||
public override int GetArrayLength()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool GetBoolean()
|
||||
public override bool GetBoolean()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public double GetDouble()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public decimal GetDecimal()
|
||||
public override double GetDouble()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetUntypedNumber()
|
||||
public override decimal GetDecimal()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
public override string GetUntypedNumber()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryGetProperty(string value, out IUntypedObject result)
|
||||
public override string GetString()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool TryGetProperty(string value, out IUntypedObject result)
|
||||
{
|
||||
if (_values.TryGetValue(value, out var x))
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
|
|||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
public override bool TryGetPropertyNames(out IEnumerable<string> result)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
|
|
Загрузка…
Ссылка в новой задаче