From de16049af3981f14a2fd464641b6f76e74e5f50c Mon Sep 17 00:00:00 2001 From: Adithya Selvaprithiviraj Date: Fri, 27 Sep 2024 17:47:50 -0700 Subject: [PATCH] Change NamedType definition syntax (#2616) Changes NamedType definitions to use `:=` --- .../Microsoft.PowerFx.Core/Lexer/TexlLexer.cs | 6 +- .../Microsoft.PowerFx.Core/Lexer/TokKind.cs | 8 +- .../Localization/Strings.cs | 1 + .../Parser/TexlParser.cs | 47 +++++++++- src/strings/PowerFxResources.en-US.resx | 8 ++ .../NamedFormulasTests.cs | 6 +- .../UserDefinedTypeTests.cs | 94 ++++++++++--------- .../RecalcEngineTests.cs | 42 ++++----- .../AsTypeIsTypeParseJSONTests.cs | 6 +- 9 files changed, 140 insertions(+), 78 deletions(-) diff --git a/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs b/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs index fb6a05e1b5..e59d6713ef 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs @@ -81,7 +81,8 @@ public enum Flags public const string PunctuatorColon = ":"; public const string PunctuatorAt = "@"; public const char IdentifierDelimiter = '\''; - public const string PunctuatorDoubleBarrelArrow = "=>"; + public const string PunctuatorDoubleBarrelArrow = "=>"; + public const string PunctuatorColonEqual = ":="; // These puntuators are related to commenting in the formula bar public const string PunctuatorBlockComment = "/*"; @@ -305,7 +306,8 @@ private TexlLexer(string preferredDecimalSeparator) AddPunctuator(punctuators, PunctuatorAmpersand, TokKind.Ampersand); AddPunctuator(punctuators, PunctuatorPercent, TokKind.PercentSign); AddPunctuator(punctuators, PunctuatorAt, TokKind.At); - AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow); + AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow); + AddPunctuator(punctuators, PunctuatorColonEqual, TokKind.ColonEqual); // Commenting punctuators AddPunctuator(punctuators, PunctuatorBlockComment, TokKind.Comment); diff --git a/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs b/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs index a91473efe9..55ef86bd0b 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs @@ -303,6 +303,12 @@ public enum TokKind /// Start of body for user defined functions. /// => /// - DoubleBarrelArrow, + DoubleBarrelArrow, + + /// + /// Colon immediately followed by equal. + /// := + /// + ColonEqual, } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs index f1dd1963b3..63ce198629 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs @@ -763,6 +763,7 @@ internal static class TexlStrings public static ErrorResourceKey ErrNamedFormula_MissingSemicolon = new ErrorResourceKey("ErrNamedFormula_MissingSemicolon"); public static ErrorResourceKey ErrNamedFormula_MissingValue = new ErrorResourceKey("ErrNamedFormula_MissingValue"); + public static ErrorResourceKey ErrNamedType_MissingTypeLiteral = new ErrorResourceKey("ErrNamedType_MissingTypeLiteral"); public static ErrorResourceKey ErrUDF_MissingFunctionBody = new ErrorResourceKey("ErrUDF_MissingFunctionBody"); public static ErrorResourceKey ErrNamedFormula_AlreadyDefined = new ErrorResourceKey("ErrNamedFormula_AlreadyDefined"); public static ErrorResourceKey ErrorResource_NameConflict = new ErrorResourceKey("ErrorResource_NameConflict"); diff --git a/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs b/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs index 8e642a5bd1..8f95c9a50f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs @@ -300,7 +300,7 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse continue; } - if (_curs.TidCur == TokKind.Equ) + if (_curs.TidCur == TokKind.ColonEqual && _flagsMode.Peek().HasFlag(Flags.AllowTypeLiteral)) { var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart); _curs.TokMove(); @@ -308,7 +308,7 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse if (_curs.TidCur == TokKind.Semicolon) { - CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue); + CreateError(thisIdentifier, TexlStrings.ErrNamedType_MissingTypeLiteral); } // Extract expression @@ -334,6 +334,49 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse definitionBeforeTrivia = new List(); continue; } + else + { + CreateError(_curs.TokCur, TexlStrings.ErrNamedType_MissingTypeLiteral); + } + + // If the result was an error, keep moving cursor until end of named type expression + if (result.Kind == NodeKind.Error) + { + while (_curs.TidCur != TokKind.Semicolon && _curs.TidCur != TokKind.Eof) + { + _curs.TokMove(); + } + } + } + + declarationStart = _curs.TokCur.Span.Lim; + _curs.TokMove(); + ParseTrivia(); + } + else if (_curs.TidCur == TokKind.Equ) + { + var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart); + _curs.TokMove(); + definitionBeforeTrivia.Add(ParseTrivia()); + + if (_curs.TidCur == TokKind.Semicolon) + { + CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue); + } + + // Extract expression + while (_curs.TidCur != TokKind.Semicolon) + { + // Check if we're at EOF before a semicolon is found + if (_curs.TidCur == TokKind.Eof) + { + CreateError(_curs.TokCur, TexlStrings.ErrNamedFormula_MissingSemicolon); + break; + } + + // Parse expression + definitionBeforeTrivia.Add(ParseTrivia()); + var result = ParseExpr(Precedence.None); namedFormulas.Add(new NamedFormula(thisIdentifier.As(), new Formula(result.GetCompleteSpan().GetFragment(script), result), _startingIndex, attribute)); userDefinitionSourceInfos.Add(new UserDefinitionSourceInfo(index++, UserDefinitionType.NamedFormula, thisIdentifier.As(), declaration, new SourceList(definitionBeforeTrivia), GetExtraTriviaSourceList())); diff --git a/src/strings/PowerFxResources.en-US.resx b/src/strings/PowerFxResources.en-US.resx index 6ac8be115e..06c0828c9a 100644 --- a/src/strings/PowerFxResources.en-US.resx +++ b/src/strings/PowerFxResources.en-US.resx @@ -4115,6 +4115,14 @@ Named formula must be an expression. This error message shows up when Named formula is not an expression. For example, a = ; + + Named type must be a type literal. + + This error message shows up when Named type is not a type literal. A valid type literal expression is of syntax "Type(Expression)". + Some examples for valid named type declarations - "Point := Type({x: Number, y: Number});" , "T1 := Type(Number);" , "T2 := Type([Boolean]);". + Some examples for invalid named type declarations - "T1 := 5;" , "T2 := ;" , "T3 := [1, 2, 3];". + + User-defined function must have a body. This error message shows up when user-defined function does not have a body diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs index 9a81a045ac..400377e58d 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs @@ -17,7 +17,7 @@ public class NamedFormulasTests : PowerFxTest private readonly ParserOptions _parseOptions = new ParserOptions() { AllowsSideEffects = true }; [Theory] - [InlineData("Foo = Type(Number);")] + [InlineData("Foo := Type(Number);")] public void DefSimpleTypeTest(string script) { var parserOptions = new ParserOptions() @@ -33,7 +33,7 @@ public void DefSimpleTypeTest(string script) } [Theory] - [InlineData("Foo = Type({ Age: Number });")] + [InlineData("Foo := Type({ Age: Number });")] public void DefRecordTypeTest(string script) { var parserOptions = new ParserOptions() @@ -63,7 +63,7 @@ public void AsTypeTest(string script) } [Theory] - [InlineData("Foo = Type({Age: Number}; Bar(x: Number): Number = Abs(x);")] + [InlineData("Foo := Type({Age: Number}; Bar(x: Number): Number = Abs(x);")] public void FailParsingTest(string script) { var parserOptions = new ParserOptions() diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs index a9d10f5337..163e732dc1 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs @@ -22,24 +22,24 @@ public class UserDefinedTypeTests : PowerFxTest [Theory] // Check record, table types with primitive types - [InlineData("Point = Type({ x: Number, y: Number })", "![x:n,y:n]", true)] - [InlineData("Points = Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)] - [InlineData("Person = Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)] - [InlineData("People = Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)] - [InlineData("Heights = Type([Number])", "*[Value:n]", true)] - [InlineData("Palette = Type([Color])", "*[Value:c]", true)] + [InlineData("Point := Type({ x: Number, y: Number })", "![x:n,y:n]", true)] + [InlineData("Points := Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)] + [InlineData("Person := Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)] + [InlineData("People := Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)] + [InlineData("Heights := Type([Number])", "*[Value:n]", true)] + [InlineData("Palette := Type([Color])", "*[Value:c]", true)] // Type alias - [InlineData("DTNZ = Type(DateTimeTZInd)", "Z", true)] + [InlineData("DTNZ := Type(DateTimeTZInd)", "Z", true)] // Nested record types - [InlineData("Nested = Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)] + [InlineData("Nested := Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)] // Invalid types - [InlineData("Pics = Type([Image])", "*[Value:i]", false)] - [InlineData("A = Type(B)", "", false)] - [InlineData("A = Type([])", "", false)] - [InlineData("A = Type({})", "", false)] + [InlineData("Pics := Type([Image])", "*[Value:i]", false)] + [InlineData("A := Type(B)", "", false)] + [InlineData("A := Type([])", "", false)] + [InlineData("A := Type({})", "", false)] public void TestUserDefinedType(string typeDefinition, string expectedDefinedTypeString, bool isValid) { var parseOptions = new ParserOptions @@ -66,38 +66,38 @@ public void TestUserDefinedType(string typeDefinition, string expectedDefinedTyp } [Theory] - [InlineData("X = 5; Point = Type({ x: Number, y: Number })", 1)] - [InlineData("Point = Type({ x: Number, y: Number }); Points = Type([Point])", 2)] + [InlineData("X = 5; Point := Type({ x: Number, y: Number })", 1)] + [InlineData("Point := Type({ x: Number, y: Number }); Points := Type([Point])", 2)] // Mix named formula with named type - [InlineData("X = 5; Point = Type({ x: X, y: Number }); Points = Type([Point])", 0)] + [InlineData("X = 5; Point := Type({ x: X, y: Number }); Points := Type([Point])", 0)] // Have invalid type expression - [InlineData("WrongType = Type(5+5); WrongTypes = Type([WrongType]); People = Type([{ name: Text, age: Number }])", 1)] + [InlineData("WrongType := Type(5+5); WrongTypes := Type([WrongType]); People := Type([{ name: Text, age: Number }])", 1)] // Have incomplete expressions and parse errors - [InlineData("Point = Type({a:); Points = Type([Point]); People = Type([{ name: Text, age })", 0)] - [InlineData("Point = Type({a:; Points = Type([Point]); People = Type([{ name: Text, age: Number })", 1)] + [InlineData("Point := Type({a:); Points = Type([Point]); People := Type([{ name: Text, age })", 0)] + [InlineData("Point := Type({a:; Points = Type([Point]); People := Type([{ name: Text, age: Number })", 1)] // Redeclare type - [InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1)] + [InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1)] // Redeclare typed name in record - [InlineData("X= Type({ f:Number, f:Number});", 0)] + [InlineData("X:= Type({ f:Number, f:Number});", 0)] // Cyclic definition - [InlineData("B = Type({ x: A }); A = Type(B);", 0)] - [InlineData("B = Type(B);", 0)] + [InlineData("B := Type({ x: A }); A := Type(B);", 0)] + [InlineData("B := Type(B);", 0)] // Complex resolutions - [InlineData("C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type(Number);", 3)] - [InlineData("D = Type({nArray: [Number]}), C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", 1)] + [InlineData("C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type(Number);", 3)] + [InlineData("D := Type({nArray: [Number]}), C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type([C]);", 1)] // With Invalid types - [InlineData("A = Type(Blob); B = Type({x: Currency}); C = Type([DateTime]); D = Type(None)", 2)] + [InlineData("A := Type(Blob); B := Type({x: Currency}); C := Type([DateTime]); D := Type(None)", 2)] // Have named formulas and udf in the script - [InlineData("NAlias = Type(Number);X = 5; ADDX(n:Number): Number = n + X; SomeType = Type(UntypedObject)", 2)] + [InlineData("NAlias := Type(Number);X := 5; ADDX(n:Number): Number = n + X; SomeType := Type(UntypedObject)", 2)] public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCount) { var parseOptions = new ParserOptions @@ -118,11 +118,13 @@ public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCo [Theory] //To test DefinitionsCheckResult.ApplyErrors method and error messages - [InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")] - [InlineData("X= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")] - [InlineData("B = Type({ x: A }); A = Type(B);", 2, "ErrNamedType_InvalidCycles")] - [InlineData("B = Type(B);", 1, "ErrNamedType_InvalidCycles")] - [InlineData("Currency = Type({x: Text}); Record = Type([DateTime]); D = Type(None);", 2, "ErrNamedType_InvalidTypeName")] + [InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")] + [InlineData("X:= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")] + [InlineData("B := Type({ x: A }); A := Type(B);", 2, "ErrNamedType_InvalidCycles")] + [InlineData("B := Type(B);", 1, "ErrNamedType_InvalidCycles")] + [InlineData("Currency := Type({x: Text}); Record := Type([DateTime]); D := Type(None);", 2, "ErrNamedType_InvalidTypeName")] + [InlineData("A = 5;C :=; B := Type(Number);", 1, "ErrNamedType_MissingTypeLiteral")] + [InlineData("C := 5; D := [1,2,3];", 2, "ErrNamedType_MissingTypeLiteral")] public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string expectedMessageKey) { var parseOptions = new ParserOptions @@ -140,21 +142,21 @@ public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string } [Theory] - [InlineData("T = Type({ x: 5+5, y: -5 });", 2)] - [InlineData("T = Type(Type(Number));", 1)] - [InlineData("T = Type({+});", 1)] - [InlineData("T = Type({);", 1)] - [InlineData("T = Type({x: true, y: \"Number\"});", 2)] - [InlineData("T1 = Type({A: Number}); T2 = Type(T1.A);", 1)] - [InlineData("T = Type((1, 2));", 1)] - [InlineData("T1 = Type(UniChar(955)); T2 = Type([Table(Number)])", 2)] - [InlineData("T = Type((1; 2));", 1)] - [InlineData("T = Type(Self.T);", 1)] - [InlineData("T = Type(Parent.T);", 1)] - [InlineData("T = Type(Number As T1);", 1)] - [InlineData("T = Type(Text); T1 = Type(Not T);", 1)] - [InlineData("T1 = Type({V: Number}); T2 = Type(T1[@V]);", 1)] - [InlineData("T = Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)] + [InlineData("T := Type({ x: 5+5, y: -5 });", 2)] + [InlineData("T := Type(Type(Number));", 1)] + [InlineData("T := Type({+});", 1)] + [InlineData("T := Type({);", 1)] + [InlineData("T := Type({x: true, y: \"Number\"});", 2)] + [InlineData("T1 := Type({A: Number}); T2 := Type(T1.A);", 1)] + [InlineData("T := Type((1, 2));", 1)] + [InlineData("T1 := Type(UniChar(955)); T2 := Type([Table(Number)])", 2)] + [InlineData("T := Type((1; 2));", 1)] + [InlineData("T := Type(Self.T);", 1)] + [InlineData("T := Type(Parent.T);", 1)] + [InlineData("T := Type(Number As T1);", 1)] + [InlineData("T := Type(Text); T1 := Type(Not T);", 1)] + [InlineData("T1 := Type({V: Number}); T2 := Type(T1[@V]);", 1)] + [InlineData("T := Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)] public void TestUDTParseErrors(string typeDefinition, int expectedErrorCount) { var parseOptions = new ParserOptions diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs index b681966a19..0fee0cc1a1 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs @@ -617,7 +617,7 @@ public void ShadowingFunctionPrecedenceTest() result = check.GetEvaluator().Eval(); Assert.Equal(11111, result.AsDouble()); - engine.AddUserDefinitions("Test = Type({A: Number}); TestTable = Type([{A: Number}]);" + + engine.AddUserDefinitions("Test := Type({A: Number}); TestTable := Type([{A: Number}]);" + "Filter(X: TestTable):Test = First(X); ShowColumns(X: TestTable):TestTable = FirstN(X, 3);"); check = engine.Check("Filter([{A: 123}]).A"); @@ -1651,57 +1651,57 @@ public void LookupBuiltinOptionSets() [Theory] [InlineData( - "Point = Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", + "Point := Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", "distance({x: 0, y: 0}, {x: 0, y: 5})", true, 5.0)] // Table types are accepted [InlineData( - "People = Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "People := Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", "countMinors([{Id: 1, Age: 17}, {Id: 2, Age: 21}])", true, 1.0)] [InlineData( - "Numbers = Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", + "Numbers := Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", "countEven([1,2,3,4,5,6,7,8,9,10])", true, 5.0)] // Type Aliases are allowed [InlineData( - "CarYear = Type(Number); Car = Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", + "CarYear := Type(Number); Car := Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", "createCar(\"Model Y\", 2024).ModelYear", true, 2024.0)] // Type definitions order shouldn't matter [InlineData( - "Person = Type({Id: IdType, Age: Number}); IdType = Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", + "Person := Type({Id: IdType, Age: Number}); IdType := Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", "createUser(1, 42).Age", true, 42.0)] // Functions accept record with more/less fields [InlineData( - "People = Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "People := Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", "countMinors([{Name: \"Bob\", Age: 21, Title: \"Engineer\"}, {Name: \"Alice\", Age: 25, Title: \"Manager\"}])", true, 0.0)] [InlineData( - "Employee = Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", + "Employee := Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", "getAge({Name: \"Bob\", Age: 21})", true, 21.0)] [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); Employees = Type([Employee]); EmployeeNames = Type([{Name: Text}]); + @"Employee := Type({Name: Text, Age: Number, Title: Text}); Employees := Type([Employee]); EmployeeNames := Type([{Name: Text}]); getNames(e: Employees):EmployeeNames = ShowColumns(e, Name); getNamesCount(e: EmployeeNames):Number = CountRows(getNames(e));", "getNamesCount([{Name: \"Jim\", Age:25}, {Name: \"Tony\", Age:42}])", true, 2.0)] [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); + @"Employee := Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age; hasNoAge(e: Employee): Number = IsBlank(getAge(e));", "hasNoAge({Name: \"Bob\", Title: \"CEO\"})", @@ -1710,8 +1710,8 @@ public void LookupBuiltinOptionSets() // Types with UDF restricted primitive types resolve successfully [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); + @"Patient := Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients := Type([Patient]); Dummy():Number = CountRows([]);", "Dummy()", true, @@ -1719,45 +1719,45 @@ public void LookupBuiltinOptionSets() // Aggregate types with restricted types are not allowed in UDF [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); + @"Patient := Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients := Type([Patient]); getAnomaly(p: Patients): Patients = Filter(p, Weight < 0);", "", false)] [InlineData( - @"Patient = Type({Name: Text, Details: {h: Number, w:Decimal}}); + @"Patient := Type({Name: Text, Details: {h: Number, w:Decimal}}); getPatient(): Patient = {Name:""Alice"", Details: {h: 1, w: 2}};", "", false)] // Cycles not allowed [InlineData( - "Z = Type([{a: {b: Z}}]);", + "Z := Type([{a: {b: Z}}]);", "", false)] [InlineData( - "X = Type(Y); Y = Type(X);", + "X := Type(Y); Y := Type(X);", "", false)] [InlineData( - "C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", + "C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type([C]);", "", false)] // Redeclaration not allowed [InlineData( - "Number = Type(Text);", + "Number := Type(Text);", "", false)] [InlineData( - "Point = Type({x : Number, y : Number}); Point = Type({x : Number, y : Number, z: Number})", + "Point := Type({x : Number, y : Number}); Point := Type({x : Number, y : Number, z: Number})", "", false)] // UDFs with body errors should fail [InlineData( - "S = Type({x:Text}); f():S = ({);", + "S := Type({x:Text}); f():S = ({);", "", false)] diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs index 5b74f1c72e..c031431f43 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs @@ -39,7 +39,7 @@ public void PrimitivesTest() var engine = SetupEngine(); // custom-type type alias - engine.AddUserDefinitions("T = Type(Number);"); + engine.AddUserDefinitions("T := Type(Number);"); // Positive tests CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "Number", 42D); @@ -74,7 +74,7 @@ public void RecordsTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T = Type({a: Number});"); + engine.AddUserDefinitions("T := Type({a: Number});"); dynamic obj1 = new ExpandoObject(); obj1.a = 5D; @@ -103,7 +103,7 @@ public void TablesTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T = Type([{a: Number}]);"); + engine.AddUserDefinitions("T := Type([{a: Number}]);"); var t1 = new object[] { 5D }; var t2 = new object[] { 1m, 2m, 3m, 4m };