Skip to content

Commit ecec364

Browse files
committed
add tests, improve error message , impl
1 parent 3e3baa5 commit ecec364

File tree

8 files changed

+118
-60
lines changed

8 files changed

+118
-60
lines changed

src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs

+3
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ internal static class TexlStrings
626626
public static ErrorResourceKey ErrInvalidSchemaNeedTypeCol_Col = new ErrorResourceKey("ErrInvalidSchemaNeedTypeCol_Col");
627627
public static ErrorResourceKey ErrInvalidSchemaNeedCol = new ErrorResourceKey("ErrInvalidSchemaNeedCol");
628628
public static ErrorResourceKey ErrNeedRecord = new ErrorResourceKey("ErrNeedRecord");
629+
public static ErrorResourceKey ErrNeedRecordOrTable = new ErrorResourceKey("ErrNeedRecordOrTable");
629630
public static ErrorResourceKey ErrAutoRefreshNotAllowed = new ErrorResourceKey("ErrAutoRefreshNotAllowed");
630631
public static ErrorResourceKey ErrIncompatibleRecord = new ErrorResourceKey("ErrIncompatibleRecord");
631632
public static ErrorResourceKey ErrNeedRecord_Func = new ErrorResourceKey("ErrNeedRecord_Func");
@@ -788,5 +789,7 @@ internal static class TexlStrings
788789
public static ErrorResourceKey ErrOnlyPartialAttribute = new ErrorResourceKey("ErrOnlyPartialAttribute");
789790
public static ErrorResourceKey ErrOperationDoesntMatch = new ErrorResourceKey("ErrOperationDoesntMatch");
790791
public static ErrorResourceKey ErrUnknownPartialOp = new ErrorResourceKey("ErrUnknownPartialOp");
792+
793+
public static ErrorResourceKey ErrTruncatedArgWarning = new ErrorResourceKey("ErrTruncatedArgWarning");
791794
}
792795
}

src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Table.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace Microsoft.PowerFx.Core.Texl.Builtins
1717
{
18-
// Table(rec, rec, ...)
18+
// Table(rec/table, rec/table, ...)
1919
internal class TableFunction : BuiltinFunction
2020
{
2121
public override bool IsSelfContained => true;
@@ -62,12 +62,12 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp
6262
for (var i = 0; i < argTypes.Length; i++)
6363
{
6464
var argType = argTypes[i];
65-
var argTypeRecord = argType.IsTable ? argType.ToRecord() : argType;
65+
var argTypeRecord = argType.IsTableNonObjNull ? argType.ToRecord() : argType;
6666
var isChildTypeAllowedInTable = !argType.IsDeferred && !argType.IsVoid;
6767

6868
if (!argTypeRecord.IsRecord)
6969
{
70-
errors.EnsureError(DocumentErrorSeverity.Severe, args[i], TexlStrings.ErrNeedRecord);
70+
errors.EnsureError(DocumentErrorSeverity.Severe, args[i], TexlStrings.ErrNeedRecordOrTable);
7171
isValid = false;
7272
}
7373
else if (!isChildTypeAllowedInTable)
@@ -118,7 +118,7 @@ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[
118118

119119
if (ads is IExternalDataSource tDsInfo && tDsInfo is IExternalTabularDataSource)
120120
{
121-
errors.EnsureError(DocumentErrorSeverity.Warning, args[i], TexlStrings.SuggestRemoteExecutionHint, args[i].ToString());
121+
errors.EnsureError(DocumentErrorSeverity.Warning, args[i], TexlStrings.ErrTruncatedArgWarning, args[i].ToString(), Name);
122122
continue;
123123
}
124124
}

src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs

+25-11
Original file line numberDiff line numberDiff line change
@@ -2125,20 +2125,34 @@ public static IEnumerable<DValue<RecordValue>> StandardTableNodeRecords(IRContex
21252125

21262126
public static FormulaValue Table(IRContext irContext, FormulaValue[] args)
21272127
{
2128-
// Table literal - change to for loop
2129-
var tables = Array.ConvertAll(
2130-
args,
2131-
arg => arg switch
2128+
// Table literal
2129+
var table = new List<DValue<RecordValue>>();
2130+
2131+
for (var i = 0; i < args.Length; i++)
2132+
{
2133+
switch (args[i])
21322134
{
2133-
TableValue r => r.Rows,
2134-
RecordValue r => new List<DValue<RecordValue>> { DValue<RecordValue>.Of(r) },
2135-
BlankValue b when b.Type._type.IsRecord => new List<DValue<RecordValue>> { DValue<RecordValue>.Of(b) },
2136-
BlankValue b => new List<DValue<RecordValue>>(),
2137-
_ => new List<DValue<RecordValue>> { DValue<RecordValue>.Of((ErrorValue)arg) },
2138-
});
2135+
case TableValue t:
2136+
table.AddRange(t.Rows);
2137+
break;
2138+
case RecordValue r:
2139+
table.Add(DValue<RecordValue>.Of(r));
2140+
break;
2141+
case BlankValue b when b.Type._type.IsTableNonObjNull:
2142+
break;
2143+
case BlankValue b:
2144+
table.Add(DValue<RecordValue>.Of(b));
2145+
break;
2146+
case ErrorValue e when e.Type._type.IsTableNonObjNull:
2147+
return e;
2148+
default:
2149+
table.Add(DValue<RecordValue>.Of((ErrorValue)args[i]));
2150+
break;
2151+
}
2152+
}
21392153

21402154
// Returning List to ensure that the returned table is mutable
2141-
return new InMemoryTableValue(irContext, tables.SelectMany(x => x));
2155+
return new InMemoryTableValue(irContext, table);
21422156
}
21432157

21442158
public static ValueTask<FormulaValue> Blank(EvalVisitor runner, EvalVisitorContext context, IRContext irContext, FormulaValue[] args)

src/strings/PowerFxResources.en-US.resx

+13-5
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,10 @@
19621962
<value>Cannot use a non-record value in this context.</value>
19631963
<comment>Error Message.</comment>
19641964
</data>
1965+
<data name="ErrNeedRecordOrTable" xml:space="preserve">
1966+
<value>In this context, only record or table values can be used.</value>
1967+
<comment>Error Message. If a record or table is expected.</comment>
1968+
</data>
19651969
<data name="ErrIncompatibleRecord" xml:space="preserve">
19661970
<value>Cannot use this record. It may contain colliding fields of incompatible types.</value>
19671971
<comment>Error Message.</comment>
@@ -2883,15 +2887,15 @@
28832887
<value>Language code of the supplied text.</value>
28842888
</data>
28852889
<data name="AboutTable" xml:space="preserve">
2886-
<value>Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)</value>
2890+
<value>Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)</value>
28872891
<comment>Description of 'Table' function.</comment>
28882892
</data>
28892893
<data name="TableArg1" xml:space="preserve">
2890-
<value>record</value>
2891-
<comment>function_parameter - Argument of the Table function - a record that will become a row in the resulting table.</comment>
2894+
<value>record/table</value>
2895+
<comment>function_parameter - Argument of the Table function - a record/table that will be part of the the resulting table.</comment>
28922896
</data>
2893-
<data name="AboutTable_record" xml:space="preserve">
2894-
<value>A record that will become a row in the resulting table.</value>
2897+
<data name="AboutTable_record/table" xml:space="preserve">
2898+
<value>A record/table that will be part of the the resulting table.</value>
28952899
</data>
28962900
<data name="AboutShowColumns" xml:space="preserve">
28972901
<value>Returns a table with all columns removed from the 'source' table except the specified columns.</value>
@@ -4508,4 +4512,8 @@
45084512
A partial operator is the 2nd part of a statement `[Partial Op]` and can be one of "And", "Or", "Table" or "Record".
45094513
It's used to determine how to combine multiple expressions with the same name and operator.</comment>
45104514
</data>
4515+
<data name="ErrTruncatedArgWarning" xml:space="preserve">
4516+
<value>Delegation warning. The result of this argument '{0}' may be truncated for large data sets before being passed to the '{1}' function.</value>
4517+
<comment>Error message when an argument to non-delegable function has possible delegation and resulting rows may be truncated</comment>
4518+
</data>
45114519
</root>

src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableConcatenate.txt

+38-8
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ Table({a:0,b:Blank(),c:Blank()},{a:Blank(),b:true,c:Blank()},{a:Blank(),b:Blank(
1515
>> Table([{a:0}], [{b:true}], [{c:"Hello", d: {x: "World"}}])
1616
Table({a:0,b:Blank(),c:Blank(),d:Blank()},{a:Blank(),b:true,c:Blank(),d:Blank()},{a:Blank(),b:Blank(),c:"Hello",d:{x:"World"}})
1717

18+
// Typed blank should be treated as blank table
1819
>> CountRows(Table(If(1<0,[1,2,3],Blank())))
1920
0
2021

22+
// untyped blank should be treated as blank record
23+
>> CountRows(Table([{a:0, b:"hello"}], Blank()))
24+
2
25+
2126
>> CountRows(Table(Sequence(3000)))
2227
3000
2328

24-
>> If(CountRows(Table([{a:0, b:"hello"}], Blank())) = 2, "As Expected")
25-
"As Expected"
26-
2729
// Mixing - record and table
2830
>> Table({c:"Hello", d: {x: "World"}}, [{c:"PowerFx", d: {x: "Cool"}}])
2931
Table({c:"Hello",d:{x:"World"}},{c:"PowerFx",d:{x:"Cool"}})
@@ -36,28 +38,56 @@ Errors: Error 0-63: The function 'Table' has some invalid arguments.|Error 28-62
3638
>> Table([1, 2], If(1<0, Table({Value:{a:2}})))
3739
Errors: Error 0-44: The function 'Table' has some invalid arguments.|Error 14-43: Incompatible type. The item you are trying to put into a table has a type that is not compatible with the table.
3840

41+
// No arg
3942
>> Table()
4043
Table()
4144

45+
// Single argument with table
46+
>> Table([{a:0, b:false, c:"Hello"}])
47+
Table({a:0,b:false,c:"Hello"})
48+
49+
// Single argument with record
50+
>> Table({a:0, b:false, c:"Hello"})
51+
Table({a:0,b:false,c:"Hello"})
52+
4253
// Blank inputs
4354
>> Table(Blank(), Blank())
4455
Table(Blank(),Blank())
4556

57+
>> Table(If(1<0,Blank()))
58+
Table(Blank())
59+
4660
>> Table([1, 2], Blank(), [4, 5], Blank(), [7, 8])
4761
Table({Value:1},{Value:2},Blank(),{Value:4},{Value:5},Blank(),{Value:7},{Value:8})
4862

49-
// Runtime Error inputs
50-
>> Table([1,2], If(1/0<2,[3,4]), [5,6])
51-
Table({Value:1},{Value:2},Error({Kind:ErrorKind.Div0}),{Value:5},{Value:6})
52-
63+
// Tables containing runtime errors
5364
>> Table([1, 2, 3/0, 4], [5, Sqrt(-1), 7, 8])
5465
Table({Value:1},{Value:2},{Value:Error({Kind:ErrorKind.Div0})},{Value:4},{Value:5},{Value:Error({Kind:ErrorKind.Numeric})},{Value:7},{Value:8})
5566

5667
>> Table(Filter([2,1,0,-1,-2], 1/Value>0), Filter([-2,-1,0,1,2], Log(Value)>0))
5768
Table({Value:2},{Value:1},Error({Kind:ErrorKind.Div0}),Error({Kind:ErrorKind.Numeric}),Error({Kind:ErrorKind.Numeric}),Error({Kind:ErrorKind.Numeric}),{Value:2})
5869

70+
// coercion failures
5971
>> Table([42],["everything"])
6072
Table({Value:42},{Value:Error({Kind:ErrorKind.InvalidArgument})})
6173

6274
>> Table(["everything"], [42])
63-
Table({Value:"everything"},{Value:"42"})
75+
Table({Value:"everything"},{Value:"42"})
76+
77+
// Error function has type ObjNull, which can is both a record and a table; we treat it as a record
78+
>> Table([{a:1}], Error({Kind:ErrorKind.Div0, Message:"Please don't divide by zero"}), {a:3}, [{a:4}])
79+
Table({a:1},Error({Kind:ErrorKind.Div0}),{a:3},{a:4})
80+
81+
>> Table([{a:1}], 1/0, {a:3}, [{a:4}])
82+
Errors: Error 0-35: The function 'Table' has some invalid arguments.|Error 16-17: In this context, only record or table values can be used.
83+
84+
// The second argument is a record, no ambiguity
85+
>> Table([{a:1}], If(1/0<2,{a:2}), {a:3}, [{a:4}])
86+
Table({a:1},Error({Kind:ErrorKind.Div0}),{a:3},{a:4})
87+
88+
// The second argument is a table. With an error table is passed in, the result is an error
89+
>> Table([{a:1}], If(1/0<2,[{a:2}]), {a:3}, [{a:4}])
90+
Error({Kind:ErrorKind.Div0})
91+
92+
>> Table([{a:1}], {a:Error({Kind:ErrorKind.Custom})}, {a:3}, [{a:4}])
93+
Table({a:1},{a:Error({Kind:ErrorKind.Custom})},{a:3},{a:4})

src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/16.json

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
11
{
22
"Signatures": [
33
{
4-
"Label": "Table(record, ...)",
5-
"Documentation": "Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
4+
"Label": "Table(record/table, ...)",
5+
"Documentation": "Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
66
"Parameters": [
77
{
8-
"Label": "record",
9-
"Documentation": "A record that will become a row in the resulting table."
8+
"Label": "record/table",
9+
"Documentation": "A record/table that will be part of the the resulting table."
1010
}
1111
]
1212
},
1313
{
14-
"Label": "Table(record, record, ...)",
15-
"Documentation": "Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
14+
"Label": "Table(record/table, record/table, ...)",
15+
"Documentation": "Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
1616
"Parameters": [
1717
{
18-
"Label": "record",
19-
"Documentation": "A record that will become a row in the resulting table."
18+
"Label": "record/table",
19+
"Documentation": "A record/table that will be part of the the resulting table."
2020
},
2121
{
22-
"Label": "record",
23-
"Documentation": "A record that will become a row in the resulting table."
22+
"Label": "record/table",
23+
"Documentation": "A record/table that will be part of the the resulting table."
2424
}
2525
]
2626
},
2727
{
28-
"Label": "Table(record, record, record, ...)",
29-
"Documentation": "Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
28+
"Label": "Table(record/table, record/table, record/table, ...)",
29+
"Documentation": "Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
3030
"Parameters": [
3131
{
32-
"Label": "record",
33-
"Documentation": "A record that will become a row in the resulting table."
32+
"Label": "record/table",
33+
"Documentation": "A record/table that will be part of the the resulting table."
3434
},
3535
{
36-
"Label": "record",
37-
"Documentation": "A record that will become a row in the resulting table."
36+
"Label": "record/table",
37+
"Documentation": "A record/table that will be part of the the resulting table."
3838
},
3939
{
40-
"Label": "record",
41-
"Documentation": "A record that will become a row in the resulting table."
40+
"Label": "record/table",
41+
"Documentation": "A record/table that will be part of the the resulting table."
4242
}
4343
]
4444
},
4545
{
46-
"Label": "Table(record)",
47-
"Documentation": "Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
46+
"Label": "Table(record/table)",
47+
"Documentation": "Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
4848
"Parameters": [
4949
{
50-
"Label": "record",
51-
"Documentation": "A record that will become a row in the resulting table."
50+
"Label": "record/table",
51+
"Documentation": "A record/table that will be part of the the resulting table."
5252
}
5353
]
5454
}

src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/17.json

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
{
22
"Signatures": [
33
{
4-
"Label": "Table(record, record, record, ...)",
5-
"Documentation": "Creates a table from the specified records, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
4+
"Label": "Table(record/table, record/table, record/table, ...)",
5+
"Documentation": "Creates a table from the specified records and tables, with as many columns as there are unique record fields. For example: Table({key1: val1, key2: val2, ...}, ...)",
66
"Parameters": [
77
{
8-
"Label": "record",
9-
"Documentation": "A record that will become a row in the resulting table."
8+
"Label": "record/table",
9+
"Documentation": "A record/table that will be part of the the resulting table."
1010
},
1111
{
12-
"Label": "record",
13-
"Documentation": "A record that will become a row in the resulting table."
12+
"Label": "record/table",
13+
"Documentation": "A record/table that will be part of the the resulting table."
1414
},
1515
{
16-
"Label": "record",
17-
"Documentation": "A record that will become a row in the resulting table."
16+
"Label": "record/table",
17+
"Documentation": "A record/table that will be part of the the resulting table."
1818
}
1919
]
2020
}

src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -4144,7 +4144,9 @@ public void TestBlobFunction(string expression, string expectedType)
41444144
[InlineData("Table(Table(T1, T2), T3)", "*[a:n, b:n, c:n, d:n]")]
41454145
[InlineData("Table(T1, Table(T2, T4))", "*[a:n, b:n, c:n]")]
41464146
[InlineData("Table(Table(T1, T2), T4)", "*[a:n, b:n, c:n]")]
4147-
[InlineData("Table([1,2], If(1/0<2,[3,4]), [5,6])", "*[Value: n]")]
4147+
[InlineData("Table([1,2], If(1/0<2,[3,4]), [5,6])", "*[Value: n]")]
4148+
[InlineData("Table(Sequence(20000))", "*[Value: n]")]
4149+
[InlineData("Table(Filter(T1, b = 5))", "*[a:n, b:n, c:n]")]
41484150
public void TexlFunctionTypeSemanticsTableConcatenate(string script, string expectedType)
41494151
{
41504152
var symbol = new SymbolTable();
@@ -4162,7 +4164,8 @@ public void TexlFunctionTypeSemanticsTableConcatenate(string script, string expe
41624164

41634165
[Theory]
41644166
[InlineData("Table(T1, T2)", "*[V: n]")]
4165-
[InlineData("Table([{a:Date(2024,1,1)}], [{a:GUID(\"some-guid-value-1234\")}])", "*[a: D]")]
4167+
[InlineData("Table([{a:Date(2024,1,1)}], [{a:GUID(\"some-guid-value-1234\")}])", "*[a: D]")]
4168+
[InlineData("Table([{a:1}], 1/0, {a:3}, [{a:4}])", "*[a: n]")]
41664169
public void TexlFunctionTypeSemanticsTableConcatenate_Negative(string script, string expectedType)
41674170
{
41684171
var symbol = new SymbolTable();

0 commit comments

Comments
 (0)