diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Values/UntypedObjectValue.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Values/UntypedObjectValue.cs
index c922f5714..e62fdccfd 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Values/UntypedObjectValue.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Values/UntypedObjectValue.cs
@@ -101,6 +101,31 @@ namespace Microsoft.PowerFx.Types
public abstract bool TryGetProperty(string value, out IUntypedObject result);
+ ///
+ /// Get the property value of the untyped object.
+ /// Hosts should override this method in case a different return value is needed. For example, a host may want to return a ErrorValue in case of a missing property.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual FormulaValue GetProperty(string value, FormulaType returnType)
+ {
+ if (TryGetProperty(value, out var res))
+ {
+ if (res.Type == FormulaType.Blank)
+ {
+ return new BlankValue(IRContext.NotInSource(returnType));
+ }
+
+ return new UntypedObjectValue(IRContext.NotInSource(returnType), res);
+ }
+ else
+ {
+ return new BlankValue(IRContext.NotInSource(returnType));
+ }
+ }
+
public abstract bool TryGetPropertyNames(out IEnumerable propertyNames);
///
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/EvalVisitor.cs b/src/libraries/Microsoft.PowerFx.Interpreter/EvalVisitor.cs
index 53f3e7936..258bbbcca 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/EvalVisitor.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/EvalVisitor.cs
@@ -558,7 +558,12 @@ namespace Microsoft.PowerFx
if (arg1 is UntypedObjectValue cov && arg2 is StringValue sv)
{
if (cov.Impl.Type is ExternalType et && (et.Kind == ExternalTypeKind.Object || et.Kind == ExternalTypeKind.ArrayAndObject))
- {
+ {
+ if (cov.Impl is UntypedObjectBase untypedObjectBase)
+ {
+ return untypedObjectBase.GetProperty(sv.Value, node.IRContext.ResultType);
+ }
+
if (cov.Impl.TryGetProperty(sv.Value, out var res))
{
if (res.Type == FormulaType.Blank)
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs
index edfe0905b..fbdcc52a6 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.PowerFx.Core.IR;
+using Microsoft.PowerFx.Syntax;
using Microsoft.PowerFx.Types;
using Xunit;
@@ -138,6 +139,30 @@ namespace Microsoft.PowerFx.Interpreter.Tests
Assert.Equal(97m, result.ToObject());
}
+ [Fact]
+ public void PadUntypedObjectMissingProperty()
+ {
+ var uo1 = new PadUntypedObject(GetDataTable());
+ var uo2 = new PadUntypedObject2(GetDataTable());
+
+ var uov1 = new UntypedObjectValue(IRContext.NotInSource(FormulaType.UntypedObject), uo1);
+ var uov2 = new UntypedObjectValue(IRContext.NotInSource(FormulaType.UntypedObject), uo2);
+
+ RecalcEngine engine = new RecalcEngine();
+
+ engine.Config.SymbolTable.EnableMutationFunctions();
+ engine.UpdateVariable("padTable1", uov1);
+ engine.UpdateVariable("padTable2", uov2);
+
+ // PadUntypedObject does not override GetProperty. Returns blank if property is missing.
+ var result1 = engine.Eval(@"Index(padTable1, 1).Missing");
+ Assert.IsType(result1);
+
+ // PadUntypedObject2 overrides GetProperty. Returns error if property is missing.
+ var result2 = engine.Eval(@"Index(padTable2, 1).Missing");
+ Assert.IsType(result2);
+ }
+
private DataTable GetDataTable()
{
var dt = new DataTable("someTable");
@@ -562,6 +587,11 @@ namespace Microsoft.PowerFx.Interpreter.Tests
throw new CustomFunctionErrorException("Something went wrong.", ErrorKind.InvalidArgument);
}
+
+ public override FormulaValue GetProperty(string value, FormulaType returnType)
+ {
+ return new ErrorValue(IRContext.NotInSource(returnType), new ExpressionError() { Kind = ErrorKind.InvalidArgument });
+ }
}
#endregion
}