Skip to content

Commit 6c0131b

Browse files
authored
Introduce RecordOf to get record type from a table type (#2739)
Introduce RecordOf syntax to get record type from a table type. eg: ``` Account := Type(RecordOf(Accounts)); Points := Type([{x : Number, y : Number}]); Point := Type(RecordOf(Points)); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2)); ``` `RecordOf` works only within a TypeLiteral . Lays foundations for future type helpers (eg: `Union` multiple record types ... ) that we may have. * This ensures that we can avoid reserving `RecordOf` keyword in functions * Avoids ambiguity when we move Named formula syntax to use `:=` for example , ``` Account := RecordOf(Accounts); ``` can mean type vs an UDF
1 parent 6a92a52 commit 6c0131b

File tree

14 files changed

+254
-21
lines changed

14 files changed

+254
-21
lines changed

src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4325,6 +4325,10 @@ public override bool PreVisit(CallNode node)
43254325
if (BuiltinFunctionsCore.OtherKnownFunctions.Contains(node.Head.Name.Value, StringComparer.OrdinalIgnoreCase))
43264326
{
43274327
_txb.ErrorContainer.Error(node, TexlStrings.ErrUnimplementedFunction, node.Head.Name.Value);
4328+
}
4329+
else if (BuiltinFunctionsCore.TypeHelperFunctions.Contains(node.Head.Name.Value, StringComparer.OrdinalIgnoreCase))
4330+
{
4331+
_txb.ErrorContainer.Error(node, TexlStrings.ErrKnownTypeHelperFunction, node.Head.Name.Value);
43284332
}
43294333
else
43304334
{

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ internal static class TexlStrings
639639
public static ErrorResourceKey ErrInvalidDot = new ErrorResourceKey("ErrInvalidDot");
640640
public static ErrorResourceKey ErrUnknownFunction = new ErrorResourceKey("ErrUnknownFunction");
641641
public static ErrorResourceKey ErrUnimplementedFunction = new ErrorResourceKey("ErrUnimplementedFunction");
642+
public static ErrorResourceKey ErrKnownTypeHelperFunction = new ErrorResourceKey("ErrKnownTypeHelperFunction");
642643
public static ErrorResourceKey ErrUnknownNamespaceFunction = new ErrorResourceKey("ErrUnknownNamespaceFunction");
643644
public static ErrorResourceKey ErrBadArity = new ErrorResourceKey("ErrBadArity");
644645
public static ErrorResourceKey ErrBadArityRange = new ErrorResourceKey("ErrBadArityRange");

src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,15 @@ internal TexlFunctionSet ApplyCreateUserDefinedFunctions()
166166
{
167167
_userDefinedFunctions = new TexlFunctionSet();
168168

169-
var partialUDFs = UserDefinedFunction.CreateFunctions(_parse.UDFs.Where(udf => udf.IsParseValid), _symbols, out var errors);
169+
var composedSymbols = ReadOnlySymbolTable.Compose(_localSymbolTable, _symbols);
170+
171+
var partialUDFs = UserDefinedFunction.CreateFunctions(_parse.UDFs.Where(udf => udf.IsParseValid), composedSymbols, out var errors);
170172

171173
if (errors.Any())
172174
{
173175
_errors.AddRange(ExpressionError.New(errors, _defaultErrorCulture));
174176
}
175177

176-
var composedSymbols = ReadOnlySymbolTable.Compose(_localSymbolTable, _symbols);
177178
foreach (var udf in partialUDFs)
178179
{
179180
var config = new BindingConfig(allowsSideEffects: _parserOptions.AllowsSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false, userDefinitionsMode: true);

src/libraries/Microsoft.PowerFx.Core/Syntax/Nodes/TypeLiteralNode.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal TypeLiteralNode(ref int idNext, Token firstToken, TexlNode type, Source
3131
: base(ref idNext, firstToken, sources)
3232
{
3333
TypeRoot = type;
34+
TypeRoot.Parent = this;
3435
}
3536

3637
internal override TexlNode Clone(ref int idNext, Span ts)
@@ -41,7 +42,7 @@ internal override TexlNode Clone(ref int idNext, Span ts)
4142
{ TypeRoot, typeRoot }
4243
};
4344

44-
return new TypeLiteralNode(ref idNext, Token.Clone(ts).As<Token>(), TypeRoot, this.SourceList.Clone(ts, newNodes));
45+
return new TypeLiteralNode(ref idNext, Token.Clone(ts).As<Token>(), typeRoot, this.SourceList.Clone(ts, newNodes));
4546
}
4647

4748
/// <inheritdoc />
@@ -113,6 +114,28 @@ public override void PostVisit(TableNode node)
113114
{
114115
}
115116

117+
public override bool PreVisit(CallNode node)
118+
{
119+
if (ValidRecordOfNode(node))
120+
{
121+
return true;
122+
}
123+
124+
_errors.Add(new TexlError(node, DocumentErrorSeverity.Severe, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString()));
125+
return false;
126+
}
127+
128+
public override bool PreVisit(ListNode node)
129+
{
130+
if (node.Parent is CallNode cn && ValidRecordOfNode(cn))
131+
{
132+
return true;
133+
}
134+
135+
_errors.Add(new TexlError(node, DocumentErrorSeverity.Severe, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString()));
136+
return false;
137+
}
138+
116139
// Invalid nodes
117140
public override void Visit(ErrorNode node)
118141
{
@@ -189,18 +212,6 @@ public override bool PreVisit(VariadicOpNode node)
189212
return false;
190213
}
191214

192-
public override bool PreVisit(CallNode node)
193-
{
194-
_errors.Add(new TexlError(node, DocumentErrorSeverity.Severe, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString()));
195-
return false;
196-
}
197-
198-
public override bool PreVisit(ListNode node)
199-
{
200-
_errors.Add(new TexlError(node, DocumentErrorSeverity.Severe, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString()));
201-
return false;
202-
}
203-
204215
public override bool PreVisit(AsNode node)
205216
{
206217
_errors.Add(new TexlError(node, DocumentErrorSeverity.Severe, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString()));
@@ -240,5 +251,16 @@ public override void PostVisit(AsNode node)
240251
{
241252
}
242253
}
254+
255+
internal static bool ValidRecordOfNode(CallNode node)
256+
{
257+
Contracts.AssertValue(node);
258+
Contracts.AssertValue(node.Args);
259+
Contracts.AssertAllValues(node.Args.ChildNodes);
260+
261+
return node.Head.Token.Name == LanguageConstants.RecordOfInvariantName &&
262+
node.Args.Count == 1 &&
263+
node.Args.ChildNodes.Single().AsFirstName() != null;
264+
}
243265
}
244266
}

src/libraries/Microsoft.PowerFx.Core/Syntax/TexlPretty.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ public override LazyList<string> Visit(TypeLiteralNode node, Precedence parentPr
414414
.With(
415415
LanguageConstants.TypeLiteralInvariantName,
416416
TexlLexer.PunctuatorParenOpen)
417-
.With(node.TypeRoot.Accept(this, Precedence.Atomic))
417+
.With(node.TypeRoot.Accept(this, Precedence.None))
418418
.With(TexlLexer.PunctuatorParenClose);
419419

420420
return result;

src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/DTypeVisitor.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,29 @@ public override DType Visit(TableNode node, INameResolver context)
107107

108108
return rowType.ToTable();
109109
}
110+
111+
public override DType Visit(CallNode node, INameResolver context)
112+
{
113+
Contracts.AssertValue(node);
114+
Contracts.AssertValue(context);
115+
Contracts.AssertValue(node.Args);
116+
Contracts.AssertAllValues(node.Args.ChildNodes);
117+
118+
if (!TypeLiteralNode.ValidRecordOfNode(node))
119+
{
120+
return DType.Invalid;
121+
}
122+
123+
Contracts.Assert(node.Args.ChildNodes.Count == 1);
124+
125+
var childType = node.Args.ChildNodes.Single().Accept(this, context);
126+
127+
if (!childType.IsTable)
128+
{
129+
return DType.Invalid;
130+
}
131+
132+
return childType.ToRecord();
133+
}
110134
}
111135
}

src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using Microsoft.PowerFx.Core.Functions;
7-
using Microsoft.PowerFx.Core.Texl.Builtins;
7+
using Microsoft.PowerFx.Core.Texl.Builtins;
8+
using Microsoft.PowerFx.Core.Utils;
89

910
namespace Microsoft.PowerFx.Core.Texl
1011
{
@@ -23,6 +24,11 @@ internal class BuiltinFunctionsCore
2324
"RecordInfo", "Relate", "RemoveAll", "RemoveIf", "RequestHide", "Reset", "ResetForm", "Revert", "SaveData", "ScanBarcode", "Select", "SetFocus",
2425
"SetProperty", "ShowColumns", "State", "SubmitForm", "TraceValue", "Ungroup", "Unrelate", "Update", "UpdateContext", "UpdateIf", "User", "Validate", "ValidateRecord", "ViewForm",
2526
"Collect", "Clear", "Patch", "Remove", "ClearCollect", "Set"
27+
};
28+
29+
internal static readonly IReadOnlyCollection<string> TypeHelperFunctions = new HashSet<string>()
30+
{
31+
LanguageConstants.RecordOfInvariantName,
2632
};
2733

2834
// Functions in this list are shared and may show up in other hosts by default.
@@ -277,7 +283,10 @@ internal class BuiltinFunctionsCore
277283

278284
public static bool IsKnownPublicFunction(string functionName)
279285
{
280-
if (_library.AnyWithName(functionName) || OtherKnownFunctions.Contains(functionName) || _featureGateFunctions.AnyWithName(functionName))
286+
if (_library.AnyWithName(functionName) ||
287+
OtherKnownFunctions.Contains(functionName) ||
288+
_featureGateFunctions.AnyWithName(functionName) ||
289+
TypeHelperFunctions.Contains(functionName))
281290
{
282291
return true;
283292
}

src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ internal class LanguageConstants
8181
/// </summary>
8282
public const string TypeLiteralInvariantName = "Type";
8383

84+
/// <summary>
85+
/// The string value representing the RecordOf keyword.
86+
/// </summary>
87+
public const string RecordOfInvariantName = "RecordOf";
88+
8489
/// <summary>
8590
/// The string value representing the join type literal.
8691
/// </summary>

src/strings/PowerFxResources.en-US.resx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,10 @@
19171917
<value>'{0}' is an unknown or unsupported function.</value>
19181918
<comment>Error Message.</comment>
19191919
</data>
1920+
<data name="ErrKnownTypeHelperFunction" xml:space="preserve">
1921+
<value>'{0}' is recognized as type helper function and it can only be used within the Type function.</value>
1922+
<comment>{Locked=Type} Error message shown when a type helper is used outside of Type function. Example of valid usage: "Type(RecordOf(Accounts))"</comment>
1923+
</data>
19201924
<data name="ErrUnknownNamespaceFunction" xml:space="preserve">
19211925
<value>'{0}' is an unknown or unsupported function in namespace '{1}'.</value>
19221926
<comment>Error Message.</comment>

src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public sealed class FormatterTests : PowerFxTest
3030
[InlineData(
3131
"ParseJSON(\"[{ \"\"Age\"\": 5}]\", Type([{Age: Number}]))",
3232
"ParseJSON(#$string$#, Type([ { #$fieldname$#:#$firstname$# } ]))")]
33+
[InlineData(
34+
"ParseJSON(\"{}\", Type(RecordOf(Accounts)))",
35+
"ParseJSON(#$string$#, Type(RecordOf(#$firstname$#)))")]
3336
public void TestStucturalPrint(string script, string expected)
3437
{
3538
var result = ParseScript(
@@ -262,6 +265,7 @@ public void TestMinificationWithCommentRemovalInTextFirst(string script, string
262265
[InlineData("$\"This is {{\"Another\"}} interpolated {{string}}\"", "$\"This is {{\"Another\"}} interpolated {{string}}\"")]
263266
[InlineData("ParseJSON(\"[]\", Type([{Age: Number}]))", "ParseJSON(\n \"[]\",\n Type([{Age: Number}])\n)")]
264267
[InlineData("Type([{Age: Number, Name: Text}])", "Type(\n [\n {\n Age: Number,\n Name: Text\n }\n ]\n)")]
268+
[InlineData("Type(RecordOf(Accounts))", "Type(RecordOf(Accounts))")]
265269
public void TestPrettyPrint(string script, string expected)
266270
{
267271
// Act & Assert
@@ -284,6 +288,7 @@ public void TestPrettyPrint(string script, string expected)
284288
[InlineData("T := Type(Number);", "T := Type(Number);")]
285289
[InlineData("N = 5; /*Type Dec*/ T := Type([{name: Text, age: Number}]);", "N = 5;\n/*Type Dec*/ T := Type([\n {\n name: Text,\n age: Number\n }\n]);")]
286290
[InlineData("T := Type/*com*/( /*com*/ Number /*com*/) /*com*/;", "T := Type/*com*/( /*com*/Number /*com*/)/*com*/;")]
291+
[InlineData("T := Type(/*RecordOf*/ RecordOf(Accounts));", "T := Type(/*RecordOf*/RecordOf(Accounts));")]
287292
public void TestUserDefinitionsPrettyPrint(string script, string expected)
288293
{
289294
var parserOptions = new ParserOptions()

0 commit comments

Comments
 (0)