From bd82be840aeb51c5caa1f6755337bc2472069b37 Mon Sep 17 00:00:00 2001 From: Alex Vallone Date: Tue, 25 Aug 2026 13:05:52 -0700 Subject: [PATCH 1/4] Allow defined tables without exact rows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 946bd35e-0c84-49f0-90ff-a15d522d34f8 --- src/powerquery-parser/language/type/type.ts | 2 +- .../language/type/typeUtils/factories.ts | 9 +++++---- .../language/type/typeUtils/isEqualType.ts | 6 +++--- .../language/typeUtils/isEqualType.test.ts | 16 ++++++++++++++++ .../language/typeUtils/typeUtils.test.ts | 6 ++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/powerquery-parser/language/type/type.ts b/src/powerquery-parser/language/type/type.ts index ad4745ce..322014f1 100644 --- a/src/powerquery-parser/language/type/type.ts +++ b/src/powerquery-parser/language/type/type.ts @@ -307,7 +307,7 @@ export type DefinedTable = IExtendedType & FieldSpecificationList & { readonly kind: TypeKind.Table; readonly extendedKind: ExtendedTypeKind.DefinedTable; - readonly rows: ReadonlyArray; + readonly rows: ReadonlyArray | undefined; }; export type FunctionType = IExtendedType & diff --git a/src/powerquery-parser/language/type/typeUtils/factories.ts b/src/powerquery-parser/language/type/typeUtils/factories.ts index 77d58d43..dfbb324f 100644 --- a/src/powerquery-parser/language/type/typeUtils/factories.ts +++ b/src/powerquery-parser/language/type/typeUtils/factories.ts @@ -97,19 +97,20 @@ export function definedRecord( } /** - * Creates a defined table with exact rows. + * Creates a defined table with known fields and optional exact rows. * - * Each row is asserted to contain exactly the declared fields and field types compatible with the table definition. + * Undefined rows indicate that the table's rows are unknown, while an empty array represents an exact empty table. + * Each supplied row is asserted to contain exactly the declared fields and field types compatible with the table definition. * @throws CommonError.InvariantError if a row does not satisfy those requirements. */ export function definedTable( isNullable: boolean, fields: Type.OrderedFields, - rows: ReadonlyArray, + rows: ReadonlyArray | undefined, ): Type.DefinedTable { const fieldNames: ReadonlyArray = [...fields.keys()]; - for (const [rowIndex, row] of rows.entries()) { + for (const [rowIndex, row] of rows?.entries() ?? []) { Assert.isTrue( MapUtils.hasKeys(row, fieldNames) && row.size === fields.size, `row fields do not match table fields`, diff --git a/src/powerquery-parser/language/type/typeUtils/isEqualType.ts b/src/powerquery-parser/language/type/typeUtils/isEqualType.ts index ddaccbbf..2f440e76 100644 --- a/src/powerquery-parser/language/type/typeUtils/isEqualType.ts +++ b/src/powerquery-parser/language/type/typeUtils/isEqualType.ts @@ -183,12 +183,12 @@ export function isEqualDefinedTable(left: Type.DefinedTable, right: Type.Defined } function isEqualDefinedTableRows( - left: ReadonlyArray, - right: ReadonlyArray, + left: ReadonlyArray | undefined, + right: ReadonlyArray | undefined, ): boolean { if (left === right) { return true; - } else if (left.length !== right.length) { + } else if (left === undefined || right === undefined || left.length !== right.length) { return false; } diff --git a/src/test/libraryTest/language/typeUtils/isEqualType.test.ts b/src/test/libraryTest/language/typeUtils/isEqualType.test.ts index 1a69e9c9..e5a7af56 100644 --- a/src/test/libraryTest/language/typeUtils/isEqualType.test.ts +++ b/src/test/libraryTest/language/typeUtils/isEqualType.test.ts @@ -99,5 +99,21 @@ describe(`TypeUtils.isEqualType`, () => { expected: false, }); }); + + it(`unknown rows equal unknown rows`, () => { + runTest({ + left: TypeUtils.definedTable(false, fields, undefined), + right: TypeUtils.definedTable(false, fields, undefined), + expected: true, + }); + }); + + it(`unknown rows differ from exact empty rows`, () => { + runTest({ + left: TypeUtils.definedTable(false, fields, undefined), + right: TypeUtils.definedTable(false, fields, []), + expected: false, + }); + }); }); }); diff --git a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts index 71726b71..90a674fd 100644 --- a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts @@ -247,6 +247,12 @@ describe(`TypeUtils`, () => { expect(type.rows).to.equal(rows); }); + it(`allows unknown rows`, () => { + const type: Type.DefinedTable = TypeUtils.definedTable(false, fields, undefined); + + expect(type.rows).to.be.undefined; + }); + it(`accepts null for a nullable column`, () => { const nullableFields: Type.OrderedFields = new OrderedMap([["Value", Type.NullableNumberInstance]]); From 73a7edea32884c669975495533ee334a0f5bb50f Mon Sep 17 00:00:00 2001 From: Alex Vallone Date: Tue, 25 Aug 2026 13:13:28 -0700 Subject: [PATCH 2/4] Make defined table rows optional Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 946bd35e-0c84-49f0-90ff-a15d522d34f8 --- src/powerquery-parser/language/type/type.ts | 2 +- src/powerquery-parser/language/type/typeUtils/factories.ts | 6 +++--- src/test/libraryTest/language/typeUtils/isEqualType.test.ts | 6 +++--- src/test/libraryTest/language/typeUtils/typeUtils.test.ts | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/powerquery-parser/language/type/type.ts b/src/powerquery-parser/language/type/type.ts index 322014f1..8e7899cc 100644 --- a/src/powerquery-parser/language/type/type.ts +++ b/src/powerquery-parser/language/type/type.ts @@ -307,7 +307,7 @@ export type DefinedTable = IExtendedType & FieldSpecificationList & { readonly kind: TypeKind.Table; readonly extendedKind: ExtendedTypeKind.DefinedTable; - readonly rows: ReadonlyArray | undefined; + readonly rows?: ReadonlyArray; }; export type FunctionType = IExtendedType & diff --git a/src/powerquery-parser/language/type/typeUtils/factories.ts b/src/powerquery-parser/language/type/typeUtils/factories.ts index dfbb324f..30639e2f 100644 --- a/src/powerquery-parser/language/type/typeUtils/factories.ts +++ b/src/powerquery-parser/language/type/typeUtils/factories.ts @@ -99,14 +99,14 @@ export function definedRecord( /** * Creates a defined table with known fields and optional exact rows. * - * Undefined rows indicate that the table's rows are unknown, while an empty array represents an exact empty table. + * Omitted rows indicate that the table's rows are unknown, while an empty array represents an exact empty table. * Each supplied row is asserted to contain exactly the declared fields and field types compatible with the table definition. * @throws CommonError.InvariantError if a row does not satisfy those requirements. */ export function definedTable( isNullable: boolean, fields: Type.OrderedFields, - rows: ReadonlyArray | undefined, + rows?: ReadonlyArray, ): Type.DefinedTable { const fieldNames: ReadonlyArray = [...fields.keys()]; @@ -143,7 +143,7 @@ export function definedTable( isNullable, fields, isOpen: false, - rows, + ...(rows === undefined ? {} : { rows }), }; } diff --git a/src/test/libraryTest/language/typeUtils/isEqualType.test.ts b/src/test/libraryTest/language/typeUtils/isEqualType.test.ts index e5a7af56..9e6c1811 100644 --- a/src/test/libraryTest/language/typeUtils/isEqualType.test.ts +++ b/src/test/libraryTest/language/typeUtils/isEqualType.test.ts @@ -102,15 +102,15 @@ describe(`TypeUtils.isEqualType`, () => { it(`unknown rows equal unknown rows`, () => { runTest({ - left: TypeUtils.definedTable(false, fields, undefined), - right: TypeUtils.definedTable(false, fields, undefined), + left: TypeUtils.definedTable(false, fields), + right: TypeUtils.definedTable(false, fields), expected: true, }); }); it(`unknown rows differ from exact empty rows`, () => { runTest({ - left: TypeUtils.definedTable(false, fields, undefined), + left: TypeUtils.definedTable(false, fields), right: TypeUtils.definedTable(false, fields, []), expected: false, }); diff --git a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts index 90a674fd..95e8dbf1 100644 --- a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts @@ -248,9 +248,10 @@ describe(`TypeUtils`, () => { }); it(`allows unknown rows`, () => { - const type: Type.DefinedTable = TypeUtils.definedTable(false, fields, undefined); + const type: Type.DefinedTable = TypeUtils.definedTable(false, fields); expect(type.rows).to.be.undefined; + expect(type).not.to.have.property(`rows`); }); it(`accepts null for a nullable column`, () => { From 8acc309e954e46ca10325ef4211ed1d3fffb5b66 Mon Sep 17 00:00:00 2001 From: Alex Vallone Date: Tue, 25 Aug 2026 13:18:55 -0700 Subject: [PATCH 3/4] Assign optional table rows directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 946bd35e-0c84-49f0-90ff-a15d522d34f8 --- src/powerquery-parser/language/type/typeUtils/factories.ts | 2 +- src/test/libraryTest/language/typeUtils/typeUtils.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/powerquery-parser/language/type/typeUtils/factories.ts b/src/powerquery-parser/language/type/typeUtils/factories.ts index 30639e2f..53a83f6b 100644 --- a/src/powerquery-parser/language/type/typeUtils/factories.ts +++ b/src/powerquery-parser/language/type/typeUtils/factories.ts @@ -143,7 +143,7 @@ export function definedTable( isNullable, fields, isOpen: false, - ...(rows === undefined ? {} : { rows }), + rows, }; } diff --git a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts index 95e8dbf1..940c94a8 100644 --- a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts @@ -251,7 +251,6 @@ describe(`TypeUtils`, () => { const type: Type.DefinedTable = TypeUtils.definedTable(false, fields); expect(type.rows).to.be.undefined; - expect(type).not.to.have.property(`rows`); }); it(`accepts null for a nullable column`, () => { From c7e80eff559852542b2932069740490caa1ed589 Mon Sep 17 00:00:00 2001 From: Alex Vallone Date: Tue, 25 Aug 2026 13:20:18 -0700 Subject: [PATCH 4/4] Bump parser version to 2.0.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 946bd35e-0c84-49f0-90ff-a15d522d34f8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6010dbc..0808ed45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-parser", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-parser", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "dependencies": { "grapheme-splitter": "^1.0.4", diff --git a/package.json b/package.json index 08b9df1e..198f675c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-parser", - "version": "1.0.0", + "version": "2.0.0", "description": "A parser for the Power Query/M formula language.", "author": "Microsoft", "license": "MIT",