Anderson Silva 2024-07-15 23:07:30 -05:00 коммит произвёл GitHub
Родитель 07a17a998f
Коммит d193162359
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
13 изменённых файлов: 482 добавлений и 180 удалений

Просмотреть файл

@ -122,6 +122,13 @@ namespace Microsoft.PowerFx.Types
public static ErrorValue NewError(IEnumerable<ExpressionError> error, FormulaType type) public static ErrorValue NewError(IEnumerable<ExpressionError> error, FormulaType type)
{ {
return new ErrorValue(IRContext.NotInSource(type), error.ToList()); 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) public static UntypedObjectValue New(IUntypedObject untypedObject)

Просмотреть файл

@ -79,5 +79,39 @@ namespace Microsoft.PowerFx.Types
// Not supported for the time being. // Not supported for the time being.
throw new NotImplementedException("UntypedObjectValue cannot be serialized."); 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. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using Microsoft.PowerFx.Core.Functions; using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.Types;
@ -35,6 +33,8 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
public override bool IsSelfContained => true; public override bool IsSelfContained => true;
public override bool PropagatesMutability => true;
public IndexFunction_UO() public IndexFunction_UO()
: base(IndexInvariantFunctionName, TexlStrings.AboutIndex, FunctionCategories.Table, DType.UntypedObject, 0, 2, 2, DType.UntypedObject, DType.Number) : 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.Interpreter.Exceptions;
using Microsoft.PowerFx.Types; using Microsoft.PowerFx.Types;
using static Microsoft.PowerFx.Functions.Library; using static Microsoft.PowerFx.Functions.Library;
using static Microsoft.PowerFx.Syntax.PrettyPrintVisitor;
namespace Microsoft.PowerFx namespace Microsoft.PowerFx
{ {
@ -154,28 +155,58 @@ namespace Microsoft.PowerFx
// Set is unique because it has an l-value for the first arg. // Set is unique because it has an l-value for the first arg.
// Async params can't have out-params. // Async params can't have out-params.
// Return null if not handled. Else non-null if handled. // Return null if not handled. Else non-null if handled.
private async Task<FormulaValue> TryHandleSet(CallNode node, EvalVisitorContext context) private async Task<FormulaValue> TryHandleSet(CallNode node, EvalVisitorContext context)
{ {
// Special case Set() calls because they take an LValue. // Special case Set() calls because they take an LValue.
if (node.Function.GetType() != typeof(RecalcEngineSetFunction)) if (node.Function.GetType() != typeof(RecalcEngineSetFunction))
{ {
return null; return null;
} }
var arg0 = node.Args[0]; var arg0 = node.Args[0];
var arg1 = node.Args[1]; var arg1 = node.Args[1];
var newValue = await arg1.Accept(this, context).ConfigureAwait(false); 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 (arg0 is RecordFieldAccessNode rfan)
if (arg0value is RecordValue rv)
{ {
rv.ShallowCopyFieldInPlace(rfan.Field); var arg0value = await rfan.From.Accept(this, context).ConfigureAwait(false);
rv.UpdateField(rfan.Field, newValue);
return node.IRContext.ResultType._type.Kind == DKind.Boolean ? FormulaValue.New(true) : FormulaValue.NewVoid(); 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 else
{ {
@ -184,9 +215,9 @@ namespace Microsoft.PowerFx
} }
// Binder has already ensured this is a first name node as well as mutable symbol. // Binder has already ensured this is a first name node as well as mutable symbol.
if (arg0 is ResolvedObjectNode obj) if (arg0 is ResolvedObjectNode obj)
{ {
if (obj.Value is ISymbolSlot sym) if (obj.Value is ISymbolSlot sym)
{ {
if (_symbolValues != null) 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. // This may happen if the runtime symbols are missing a value and we failed to update.
} }
} }
// Fail? // Fail?
return CommonErrors.UnreachableCodeError(node.IRContext); return CommonErrors.UnreachableCodeError(node.IRContext);
} }
// Handle invoke SetProperty(source.Prop, newValue) // Handle invoke SetProperty(source.Prop, newValue)

Просмотреть файл

@ -55,6 +55,12 @@ namespace Microsoft.PowerFx.Interpreter
nodeToCoercedTypeMap = null; nodeToCoercedTypeMap = null;
returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : DType.Boolean; 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); var isValid = CheckType(context, args[1], argTypes[1], argTypes[0], errors, ref nodeToCoercedTypeMap);
return isValid; return isValid;
@ -64,43 +70,63 @@ namespace Microsoft.PowerFx.Interpreter
public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors) public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
{ {
base.CheckSemantics(binding, args, argTypes, errors); base.CheckSemantics(binding, args, argTypes, errors);
Contracts.AssertValue(args); Contracts.AssertValue(args);
Contracts.AssertAllValues(args); Contracts.AssertAllValues(args);
Contracts.AssertValue(argTypes); Contracts.AssertValue(argTypes);
Contracts.AssertAllValid(argTypes); Contracts.AssertAllValid(argTypes);
Contracts.Assert(args.Length == argTypes.Length); Contracts.Assert(args.Length == argTypes.Length);
Contracts.AssertValue(errors); Contracts.AssertValue(errors);
Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity); Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity);
var arg0 = argTypes[0]; var arg0 = argTypes[0];
var arg1 = argTypes[1]; var arg1 = argTypes[1];
// Type check // Type check
if (!(arg0.Accepts(arg1, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: binding.Features.PowerFxV1CompatibilityRules) || if (arg0.IsUntypedObject)
(arg0.IsNumeric && arg1.IsNumeric)))
{ {
errors.EnsureError(DocumentErrorSeverity.Critical, args[1], ErrBadType_ExpectedType_ProvidedType, arg0.GetKindString(), arg1.GetKindString()); if (CheckMutability(binding, args, argTypes, errors))
return; {
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()) errors.EnsureError(DocumentErrorSeverity.Severe, args[0], TexlStrings.ErrNeedValidVariableName_Arg, Name, args[0]);
{ return;
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();
private bool CheckMutability(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
{
var firstName = args[0].AsFirstName();
if (firstName != null) if (firstName != null)
{ {
// Variable reference assignment, for example Set( x, 3 ) // 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) if (info.Data is NameSymbol nameSymbol && nameSymbol.Props.CanSet)
{ {
// We have a variable, success // We have a variable, success
return; return true;
} }
} }
else if (binding.Features.PowerFxV1CompatibilityRules) else if (binding.Features.PowerFxV1CompatibilityRules)
{ {
// Deep mutation, for example Set( x.a, 4 ) // Deep mutation, for example Set( x.a, 4 )
base.ValidateArgumentIsSetMutable(binding, args[0], errors); base.ValidateArgumentIsSetMutable(binding, args[0], errors);
return; return true;
} }
errors.EnsureError(DocumentErrorSeverity.Severe, args[0], TexlStrings.ErrNeedValidVariableName_Arg, Name, args[0]); return false;
return;
} }
} }
} }

Просмотреть файл

@ -9,27 +9,27 @@ using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Functions namespace Microsoft.PowerFx.Functions
{ {
internal class JsonUntypedObject : IUntypedObject internal class JsonUntypedObject : UntypedObjectBase
{ {
internal readonly JsonElement _element; internal readonly JsonElement _element;
public JsonUntypedObject(JsonElement element) public JsonUntypedObject(JsonElement element)
{ {
_element = element; _element = element;
} }
public FormulaType Type public override FormulaType Type
{ {
get get
{ {
switch (_element.ValueKind) switch (_element.ValueKind)
{ {
case JsonValueKind.Object: case JsonValueKind.Object:
return ExternalType.ObjectType; return ExternalType.ObjectType;
case JsonValueKind.Array: case JsonValueKind.Array:
return ExternalType.ArrayType; return ExternalType.ArrayType;
case JsonValueKind.String: case JsonValueKind.String:
return FormulaType.String; return FormulaType.String;
case JsonValueKind.Number: case JsonValueKind.Number:
// Do not be tempted to use FormulaType.Number here. JSON numbers can be interpreted as either // 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. // 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 // 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 // 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 // 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; return ExternalType.UntypedNumber;
case JsonValueKind.True: case JsonValueKind.True:
case JsonValueKind.False: case JsonValueKind.False:
return FormulaType.Boolean; return FormulaType.Boolean;
} }
return FormulaType.Blank; 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();
} }
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(); return _element.GetDecimal();
} }
public string GetUntypedNumber() public override string GetUntypedNumber()
{ {
if (Type == ExternalType.UntypedNumber) if (Type == ExternalType.UntypedNumber)
{ {
@ -89,25 +89,25 @@ namespace Microsoft.PowerFx.Functions
{ {
throw new FormatException(); 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) if (_element.ValueKind != JsonValueKind.Object)
{ {
result = null; result = null;
return false; return false;
} }
result = _element.EnumerateObject().Select(x => x.Name); result = _element.EnumerateObject().Select(x => x.Name);
return true; return true;
} }
} }
} }

Просмотреть файл

@ -141,6 +141,7 @@ namespace Microsoft.PowerFx.Core.Tests
"Microsoft.PowerFx.Types.IDelegatableTableValue", "Microsoft.PowerFx.Types.IDelegatableTableValue",
"Microsoft.PowerFx.Types.ITypeVisitor", "Microsoft.PowerFx.Types.ITypeVisitor",
"Microsoft.PowerFx.Types.IUntypedObject", "Microsoft.PowerFx.Types.IUntypedObject",
"Microsoft.PowerFx.Types.UntypedObjectBase",
"Microsoft.PowerFx.Types.IValueVisitor", "Microsoft.PowerFx.Types.IValueVisitor",
"Microsoft.PowerFx.Types.NamedFormulaType", "Microsoft.PowerFx.Types.NamedFormulaType",
"Microsoft.PowerFx.Types.NamedValue", "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; private readonly FormulaValue _value;
@ -303,46 +303,46 @@ namespace Microsoft.PowerFx.Interpreter.Tests
_value = value; _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(); throw new NotImplementedException();
} }
public bool GetBoolean() public override bool GetBoolean()
{ {
return ((BooleanValue)_value).Value; return ((BooleanValue)_value).Value;
} }
public double GetDouble() public override double GetDouble()
{ {
return ((NumberValue)_value).Value; return ((NumberValue)_value).Value;
} }
public decimal GetDecimal() public override decimal GetDecimal()
{ {
return ((DecimalValue)_value).Value; return ((DecimalValue)_value).Value;
} }
public string GetUntypedNumber() public override string GetUntypedNumber()
{ {
return ((StringValue)_value).Value; return ((StringValue)_value).Value;
} }
public string GetString() public override string GetString()
{ {
return ((StringValue)_value).Value; return ((StringValue)_value).Value;
} }
public bool TryGetProperty(string value, out IUntypedObject result) public override bool TryGetProperty(string value, out IUntypedObject result)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public bool TryGetPropertyNames(out IEnumerable<string> result) public override bool TryGetPropertyNames(out IEnumerable<string> result)
{ {
result = null; result = null;
return false; return false;

Просмотреть файл

@ -339,4 +339,8 @@ Errors: Error 45-52: The value passed to the 'Set' function cannot be changed.
>> rwt1_copy3 >> 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})}} {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.Equal(FormulaType.UntypedObject, fv3.Type);
Assert.True(fv3 is BlankValue); 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 DataTable DataTable;
public DataRow DataRow; public DataRow DataRow;
@ -74,8 +124,6 @@ namespace Microsoft.PowerFx.Interpreter.Tests
Cell = cell; Cell = cell;
} }
public IUntypedObject this[int index] => Index(index);
private IUntypedObject Index(int index) private IUntypedObject Index(int index)
{ {
return (DataTable != null) return (DataTable != null)
@ -85,7 +133,9 @@ namespace Microsoft.PowerFx.Interpreter.Tests
: throw new NotImplementedException(); : throw new NotImplementedException();
} }
public FormulaType Type => GetFormulaType(); public override FormulaType Type => GetFormulaType();
public override IUntypedObject this[int index] => Index(index);
private FormulaType GetFormulaType() private FormulaType GetFormulaType()
{ {
@ -102,7 +152,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
: ExternalType.ArrayAndObject; : ExternalType.ArrayAndObject;
} }
public int GetArrayLength() public override int GetArrayLength()
{ {
return (DataTable != null) return (DataTable != null)
? DataTable.Rows.Count ? DataTable.Rows.Count
@ -111,12 +161,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
: throw new NotImplementedException(); : throw new NotImplementedException();
} }
public bool GetBoolean() public override bool GetBoolean()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public double GetDouble() public override double GetDouble()
{ {
return Cell switch return Cell switch
{ {
@ -127,12 +177,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
}; };
} }
public decimal GetDecimal() public override decimal GetDecimal()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string GetUntypedNumber() public override string GetUntypedNumber()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -142,12 +192,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string GetString() public override string GetString()
{ {
return Cell.ToString(); return Cell.ToString();
} }
public bool TryGetProperty(string propertyName, out IUntypedObject result) public override bool TryGetProperty(string propertyName, out IUntypedObject result)
{ {
if (DataTable != null) if (DataTable != null)
{ {
@ -165,7 +215,154 @@ namespace Microsoft.PowerFx.Interpreter.Tests
return true; 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; result = null;
return false; return false;

Просмотреть файл

@ -799,6 +799,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests
var t_bsType = TableType.Empty().Add("b", FormulaType.String); var t_bsType = TableType.Empty().Add("b", FormulaType.String);
engine.UpdateVariable("t_bs1", FormulaValue.NewTable(t_bsType.ToRecord())); engine.UpdateVariable("t_bs1", FormulaValue.NewTable(t_bsType.ToRecord()));
engine.UpdateVariable("t_bs2", 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) private static void TraceSetup(RecalcEngine engine, bool numberIsFloat)

Просмотреть файл

@ -14,7 +14,8 @@ namespace Microsoft.PowerFx.Tests
// Wrap a .net object as an UntypedObject. // Wrap a .net object as an UntypedObject.
// This will lazily marshal through the object as it's accessed. // This will lazily marshal through the object as it's accessed.
[DebuggerDisplay("{_source}")] [DebuggerDisplay("{_source}")]
public class PrimitiveWrapperAsUnknownObject : IUntypedObject [DebuggerDisplay("{_source}")]
public class PrimitiveWrapperAsUnknownObject : UntypedObjectBase
{ {
public readonly object _source; public readonly object _source;
@ -28,7 +29,7 @@ namespace Microsoft.PowerFx.Tests
return FormulaValue.New(new PrimitiveWrapperAsUnknownObject(source)); return FormulaValue.New(new PrimitiveWrapperAsUnknownObject(source));
} }
public FormulaType Type public override FormulaType Type
{ {
get get
{ {
@ -61,7 +62,7 @@ namespace Microsoft.PowerFx.Tests
} }
} }
public IUntypedObject this[int index] public override IUntypedObject this[int index]
{ {
get get
{ {
@ -81,13 +82,13 @@ namespace Microsoft.PowerFx.Tests
} }
} }
public int GetArrayLength() public override int GetArrayLength()
{ {
var a = (Array)_source; var a = (Array)_source;
return a.Length; return a.Length;
} }
public bool GetBoolean() public override bool GetBoolean()
{ {
Assert.True(Type == FormulaType.Boolean); Assert.True(Type == FormulaType.Boolean);
@ -99,7 +100,7 @@ namespace Microsoft.PowerFx.Tests
throw new InvalidOperationException($"Not a boolean type"); throw new InvalidOperationException($"Not a boolean type");
} }
public double GetDouble() public override double GetDouble()
{ {
// Fx will only call this helper for numbers. // Fx will only call this helper for numbers.
Assert.True(Type == FormulaType.Number); Assert.True(Type == FormulaType.Number);
@ -122,7 +123,7 @@ namespace Microsoft.PowerFx.Tests
throw new InvalidOperationException($"Not a number type"); throw new InvalidOperationException($"Not a number type");
} }
public decimal GetDecimal() public override decimal GetDecimal()
{ {
// Fx will only call this helper for decimals. // Fx will only call this helper for decimals.
Assert.True(Type == FormulaType.Decimal); Assert.True(Type == FormulaType.Decimal);
@ -145,12 +146,12 @@ namespace Microsoft.PowerFx.Tests
throw new InvalidOperationException($"Not a decimal type"); throw new InvalidOperationException($"Not a decimal type");
} }
public string GetUntypedNumber() public override string GetUntypedNumber()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string GetString() public override string GetString()
{ {
Assert.True(Type == FormulaType.String); Assert.True(Type == FormulaType.String);
@ -162,7 +163,7 @@ namespace Microsoft.PowerFx.Tests
throw new InvalidOperationException($"Not a string type"); 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); Assert.True(Type == ExternalType.ObjectType);
@ -191,7 +192,7 @@ namespace Microsoft.PowerFx.Tests
return true; return true;
} }
public bool TryGetPropertyNames(out IEnumerable<string> result) public override bool TryGetPropertyNames(out IEnumerable<string> result)
{ {
result = null; result = null;
return false; return false;

Просмотреть файл

@ -13,7 +13,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
{ {
// Demonstrate mutation example using IUntypedObject // Demonstrate mutation example using IUntypedObject
public class ScenarioMutation : PowerFxTest public class ScenarioMutation : PowerFxTest
{ {
[Fact] [Fact]
public void MutabilityTest() public void MutabilityTest()
{ {
@ -26,7 +26,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
{ {
["prop"] = FormulaValue.New(123) ["prop"] = FormulaValue.New(123)
}; };
var obj = MutableObject.New(d); var obj = MutableObject.New(d);
engine.UpdateVariable("obj", obj); engine.UpdateVariable("obj", obj);
@ -45,15 +45,15 @@ namespace Microsoft.PowerFx.Interpreter.Tests
[Theory] [Theory]
[InlineData("Assert2(obj.prop, 123); Set2(obj, \"prop\", 456); Assert2(obj.prop, 456)")] [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) public void MutabilityTest_Chain(string expr)
{ {
var config = new PowerFxConfig(); var config = new PowerFxConfig();
config.AddFunction(new Assert2Function()); config.AddFunction(new Assert2Function());
config.AddFunction(new Set2Function()); config.AddFunction(new Set2Function());
config.AddFunction(new Set3Function()); config.AddFunction(new Set3Function());
var engine = new RecalcEngine(config); var engine = new RecalcEngine(config);
var d = new Dictionary<string, FormulaValue> var d = new Dictionary<string, FormulaValue>
{ {
["prop"] = FormulaValue.New(123), ["prop"] = FormulaValue.New(123),
@ -72,7 +72,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
Assert.IsType<DecimalValue>(x); Assert.IsType<DecimalValue>(x);
Assert.Equal(456, ((DecimalValue)x).Value); Assert.Equal(456, ((DecimalValue)x).Value);
Assert.Equal(456, ((DecimalValue)d["prop"]).Value); Assert.Equal(456, ((DecimalValue)d["prop"]).Value);
} }
[Fact] [Fact]
@ -133,7 +133,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
{ {
var impl = (MutableObject)obj.Impl; var impl = (MutableObject)obj.Impl;
impl.TryGetProperty(propName2.Value, out var propValue); 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)); 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>(); private Dictionary<string, FormulaValue> _values = new Dictionary<string, FormulaValue>();
@ -212,41 +212,41 @@ namespace Microsoft.PowerFx.Interpreter.Tests
return FormulaValue.New(x); 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(); throw new NotImplementedException();
} }
public bool GetBoolean() public override bool GetBoolean()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public double GetDouble() public override double GetDouble()
{
throw new NotImplementedException();
}
public decimal GetDecimal()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string GetUntypedNumber() public override decimal GetDecimal()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public string GetString() public override string GetUntypedNumber()
{ {
throw new NotImplementedException(); 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)) if (_values.TryGetValue(value, out var x))
{ {
@ -258,7 +258,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests
return false; return false;
} }
public bool TryGetPropertyNames(out IEnumerable<string> result) public override bool TryGetPropertyNames(out IEnumerable<string> result)
{ {
result = null; result = null;
return false; return false;